qemu-ppc (user) incorrectly translates float32 arithmetics

Bug #1821444 reported by Sergei Trofimovich
28
This bug affects 4 people
Affects Status Importance Assigned to Milestone
QEMU
Fix Released
Undecided
Unassigned

Bug Description

I'm using qemu-3.1.0 (Gentoo).

When I was running regression test suite via qemu-ppc for GHC I noticed a few uint32_t<->float32 failures I did not expect to encounter.

Here is an example

$ cat a.c
#include <stdio.h>
#include <stdint.h>

int main() {
    volatile uint32_t i = 1;
    printf("0x1 = %e\n", *(volatile float*)&i);
}

$ powerpc-unknown-linux-gnu-gcc -O2 a.c -Wall -o a -fno-strict-aliasing -fno-stack-protector -static && ./a
0x1 = 2.802597e-45

$ scp a timberdoodle.ppc64.dev.gentoo.org:~/
a 100% 826KB 102.0KB/s 00:08

$ ssh timberdoodle.ppc64.dev.gentoo.org ./a
0x1 = 1.401298e-45
$ qemu-ppc ./a
0x1 = 2.802597e-45

Looks like off-by-one bit somewhere. I'm not sure if it's FPU instruction or some internals of printf() that are emulated incorrectly.

Revision history for this message
Sergei Trofimovich (trofi) wrote :
Revision history for this message
Sergei Trofimovich (trofi) wrote :
Download full text (4.6 KiB)

My native system is x86_64-pc-linux-gnu with a few binfmt_misc handlers wired.
Checking other targets I have locally I get the following:

affected targets:
- powerpc
- powerpc64
- powerpc64le
unaffected targets:
- arm
- arm64
- hppa
- sparc
probably unaffected:
- alpha (maybe it's ok as alpha is not quite an IEEE754 platform)

Raw log:

$ for gcc in /usr/bin/*-gcc; do rm -f a; $gcc -O2 a.c -Wall -o a -fno-strict-aliasing -fno-stack-protector 2>/dev/null && ./a 2>/dev/null && echo -n "$gcc: " && file a; done | sort

0x1 = 1.401298e-45 : /usr/bin/aarch64-unknown-linux-gnu-gcc: a: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 3.7.0, not stripped
0x1 = 1.401298e-45 : /usr/bin/aarch64_be-unknown-linux-gnu-gcc: a: ELF 64-bit MSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64_be.so.1, for GNU/Linux 3.7.0, not stripped
0x1 = 1.401298e-45 : /usr/bin/afl-gcc: a: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, with debug_info, not stripped
0x1 = 1.401298e-45 : /usr/bin/armv6j-unknown-linux-gnueabihf-gcc: a: ELF 32-bit LSB pie executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, not stripped
0x1 = 1.401298e-45 : /usr/bin/armv7a-unknown-linux-gnueabihf-gcc: a: ELF 32-bit LSB pie executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, not stripped
0x1 = 1.401298e-45 : /usr/bin/hppa-unknown-linux-gnu-gcc: a: ELF 32-bit MSB pie executable, PA-RISC, 1.1 version 1 (GNU/Linux), dynamically linked, interpreter /lib/ld.so.1, for GNU/Linux 3.2.0, with debug_info, not stripped
0x1 = 1.401298e-45 : /usr/bin/hppa2.0-unknown-linux-gnu-gcc: a: ELF 32-bit MSB pie executable, PA-RISC, 1.1 version 1 (GNU/Linux), dynamically linked, interpreter /lib/ld.so.1, for GNU/Linux 3.2.0, with debug_info, not stripped
0x1 = 1.401298e-45 : /usr/bin/m68k-unknown-linux-gnu-gcc: a: ELF 32-bit MSB pie executable, Motorola m68k, 68020, version 1 (SYSV), dynamically linked, interpreter /lib/ld.so.1, for GNU/Linux 3.2.0, not stripped
0x1 = 1.401298e-45 : /usr/bin/mips64-unknown-linux-gnuabin64-gcc: a: ELF 64-bit MSB pie executable, MIPS, MIPS-III version 1 (SYSV), dynamically linked, interpreter /lib64/ld.so.1, for GNU/Linux 3.2.0, not stripped
0x1 = 1.401298e-45 : /usr/bin/riscv64-unknown-linux-gnu-gcc: a: ELF 64-bit LSB pie executable, UCB RISC-V, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-riscv64-lp64d.so.1, for GNU/Linux 4.15.0, not stripped
0x1 = 1.401298e-45 : /usr/bin/s390x-unknown-linux-gnu-gcc: a: ELF 64-bit MSB pie executable, IBM S/390, version 1 (SYSV), dynamically linked, interpreter /lib/ld64.so.1, for GNU/Linux 3.2.0, not stripped
0x1 = 1.401298e-45 : /usr/bin/sparc-unknown-linux-gnu-gcc: a: ELF 32-bit MSB pie executable, SPARC32PLUS, V8+ Required, total store ordering, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, not stripped
0x1 = 1.401298e-45 : /u...

Read more...

Revision history for this message
Sergei Trofimovich (trofi) wrote :

Shorter example without relying on printf() implementation. Looks like uint32_t<->float<->double transitions are enough.

$ cat a.c
#include <stdio.h>
#include <stdint.h>

int main() {
    volatile uint32_t i = 1;
    volatile float f;
    volatile double d;
    *(volatile uint32_t*)&f = i;
    d = f; f = d; // double conversion
    i = *(volatile uint32_t*)&f;
    printf("0x1 = %x\n", i);
}

$ powerpc-unknown-linux-gnu-gcc -O2 a.c -Wall -o a -fno-strict-aliasing -fno-stack-protector -static && qemu-ppc ./a
0x1 = 2

Revision history for this message
Sergei Trofimovich (trofi) wrote :

A bit more investigation:

It looks like the bug happens in float->double conversion direction.

$ cat a.c
#include <stdio.h>
#include <stdint.h>

int main() {
    volatile uint32_t i = 1;
    volatile float f;
    volatile double d;
    *(volatile uint32_t*)&f = i;
    d = f;
    printf("d = %#llx (expect 0x36a0000000000000)\n", *(volatile uint64_t*)&d);
}

$ powerpc-unknown-linux-gnu-gcc -O2 a.c -Wall -o a -fno-strict-aliasing -fno-stack-protector -static && qemu-ppc ./a
d = 0x36b0000000080000 (expect 0x36a0000000000000)

10000400 <main>:
10000404: 39 20 00 01 li r9,1
...
10000434: 91 21 00 10 stw r9,16(r1)
...
1000043c: c0 01 00 10 lfs f0,16(r1)
10000440: d8 01 00 08 stfd f0,8(r1)
...
10000448: 80 a1 00 08 lwz r5,8(r1)
1000044c: 80 c1 00 0c lwz r6,12(r1)
...
10000454: 48 02 01 ad bl 10020600 <___printf_chk>

This is just lfs/stfd conversion. qemu does translates that pair if instructions into:
$ ppc-linux-user/qemu-ppc -d in_asm,out_asm,op,op_opt /tmp/b/a
...
IN: main
...
0x1000043c: c0010010 lfs f0, 0x10(r1)
0x10000440: d8010008 stfd f0, 8(r1)
...
OP:
 ---- 1000043c
 movi_i32 tmp1,$0x10
 add_i32 tmp0,r1,tmp1
 qemu_ld_i32 tmp1,tmp0,beul,2
 call todouble,$0x5,$1,tmp2,tmp1
 st_i64 tmp2,env,$0x9198

'todouble' must be a 'uint64_t helper_todouble(uint32_t arg=0x1)' from:
    https://github.com/qemu/qemu/blob/master/target/ppc/fpu_helper.c#L55

Revision history for this message
Sergei Trofimovich (trofi) wrote :

Attaching the patch and sending for review as:
    https://lists.gnu.org/archive/html/qemu-devel/2019-03/msg06562.html

Revision history for this message
Sergei Trofimovich (trofi) wrote :

Alternatively 'uint64_t helper_todouble(uint32_t)' could be implemented via
    include/fpu/softfloat.h:float64 float32_to_float64(float32, float_status *status);

Revision history for this message
Carlo Marcelo Arenas Belón (carenasgm) wrote :

fix committed with c0e6616b6685ffdb4c5e091bc152e46e14703dd1 and released with 4.2.0

Changed in qemu:
status: New → Fix Released
To post a comment you must log in.
This report contains Public information  
Everyone can see this information.

Duplicates of this bug

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.