cross-rs / cross

“Zero setup” cross compilation and “cross testing” of Rust crates
Apache License 2.0
6.62k stars 369 forks source link

Cross v0.1.15 Breaks f32/f64 `to_bits` on PowerPC #313

Closed Alexhuszagh closed 5 years ago

Alexhuszagh commented 5 years ago

The following code worked for cross v0.1.14, so any issues were introduced with the latest release.

Targets

Failing Code

pub fn main() {
    let f: f32 = 1e-45;
    println!("to_bits={:#b}", f.to_bits());

    let p = &f as *const f32;
    let p = p as *const i32;
    unsafe {
        println!("transmuted as integer={:#b}", *p);
    }
}

Results

to_bits=0b100
transmuted as integer=0b1

The value of f.to_bits() is 4, rather than 1, as expected, since 1e-45 is the smallest denormal 32-bit floating point number. The direct type-pun through a pointer alias gets the correct value of 1. to_bits directly calls mem::transmute, which should just pun the bytes to a new type.

Explanation

The binary representation for the IEEE-754 single-precision floating point number 1e-45 is:

0 | 00000000 | 00000000000000000000001

The first group is the sign bit, the second group is the exponent (including the hidden bit), and the third group is the significant digits (mantissa). The endianness of floats should be the same as integers on the architecture. Therefore, f.to_bits() should return 1, instead, for some unknown reason, it returns 4.

Minimal Steps to Reproduce

  1. Create a repository (cargo new --bin sample).
  2. Copy the failing code into main.rs.
  3. Run the binary on a PowerPC architecture (cross run --target powerpc-unknown-linux-gnu).
reitermarkus commented 5 years ago

Is this the same QEMU bug as in https://github.com/rust-embedded/cross/issues/314?

Alexhuszagh commented 5 years ago

@reitermarkus I doubt it's the same Qemu bug, but it appears to be another bug in Qemu. I'm attempting to diagnose which versions are currently affected now.

Function

pub fn to_bits(f: f32) -> u32 {
    f.to_bits()
}

The x86 assembly is simple, just a move instruction, as we'd expect:

example::to_bits:
        mov     eax, dword ptr [esp + 4]
        ret

The PowerPC assembly is as follows:

example::to_bits:
        stwu 1, -16(1)
        stfs 1, 12(1)
        lwz 3, 12(1)
        addi 1, 1, 16
        blr

The PowerPC assembly for the type alias is identical:

example::to_bits_alias:
        stwu 1, -16(1)
        stfs 1, 12(1)
        lwz 3, 12(1)
        addi 1, 1, 16
        blr

Run Instructions


$ docker run \
    -t -i \
    -v ~/Desktop/sample/target:/target \
    docker.io/rustembedded/cross:powerpc-unknown-linux-gnu-0.1.15 \
    /bin/bash

# qemu-ppc --version
qemu-ppc version 4.1.0
Copyright (c) 2003-2019 Fabrice Bellard and the QEMU Project developers

# qemu-ppc /target/powerpc-unknown-linux-gnu/debug/sample
8
2

$ docker run \
    -t -i \
    -v ~/Desktop/sample/target:/target \
    docker.io/japaric/powerpc-unknown-linux-gnu:v0.1.14 \
    /bin/bash

# qemu-ppc --version
qemu-ppc version 2.10.0
Copyright (c) 2003-2017 Fabrice Bellard and the QEMU Project developers
# qemu-ppc /target/powerpc-unknown-linux-gnu/debug/sample
1
1

It seems like Qemu 4.10 is just spitting out garbage values.

Alexhuszagh commented 5 years ago

Versions:

I've tried other Qemu versions using:

# Install dependencies
apt install curl python zlib1g-dev libglib2.0-dev libpixman-1-dev -y

# Download the Qemu from version
version=...
curl -L https://download.qemu.org/qemu-$version.tar.xz --output qemu-$version.tar.xz
tar xvf qemu-$version.tar.xz

# Build from source
cd qemu-$version
mkdir build && cd build
../configure --disable-kvm --target-list="ppc-linux-user"
make -j 5

# Test the Version
ppc-linux-user/qemu-ppc /target/powerpc-unknown-linux-gnu/debug/sample

Results

2.12.1

# ppc-linux-user/qemu-ppc /target/powerpc-unknown-linux-gnu/debug/sample
1
1

3.0.1

# ppc-linux-user/qemu-ppc /target/powerpc-unknown-linux-gnu/debug/sample
1
1

3.1.0

# ppc-linux-user/qemu-ppc /target/powerpc-unknown-linux-gnu/debug/sample
8
2

3.1.1

# ppc-linux-user/qemu-ppc /target/powerpc-unknown-linux-gnu/debug/sample
8
2
Alexhuszagh commented 5 years ago

@reitermarkus I'll file another upstream bug, in the meantime, since such rudimentary code is failing on PowerPC (just loads and stores), it might be prudent to use Qemu version 3.0.1 on PowerPC.

reitermarkus commented 5 years ago

Thanks. Can you submit a PR downgrading QEMU with a comment pointing to the upstream issue?

Alexhuszagh commented 5 years ago

@reitermarkus I believe it's a duplicate of this bug, since on further inspection, it affects both conversions to-and-from single-precision floats, but not double-precision floats. It seems to occur due to an internal conversion to a double, and was introduced in the same version I reported. I'll send the patch now.

Code

fn f32_to_bits(f: f32) -> u32 {
    f.to_bits()
}

fn f32_to_bits_alias(f: f32) -> u32 {
    let p = &f as *const f32;
    let p = p as *const u32;
    unsafe {
        *p
    }
}

fn f64_to_bits(f: f64) -> u64 {
    f.to_bits()
}

fn f64_to_bits_alias(f: f64) -> u64 {
    let p = &f as *const f64;
    let p = p as *const u64;
    unsafe {
        *p
    }
}

fn f32_from_bits(dw: u32) -> f32 {
    f32::from_bits(dw)
}

fn f32_from_bits_alias(dw: u32) -> f32 {
    let p = &dw as *const u32;
    let p = p as *const f32;
    unsafe {
        *p
    }
}

fn f64_from_bits(dw: u64) -> f64 {
    f64::from_bits(dw)
}

fn f64_from_bits_alias(dw: u64) -> f64 {
    let p = &dw as *const u64;
    let p = p as *const f64;
    unsafe {
        *p
    }
}

pub fn main() {
    // Both have a single bit set, equivalent to 0x1.
    let f: f32 = 1e-45;
    let d: f64 = 5e-324;
    println!("f32_to_bits={:?}", f32_to_bits(f));
    println!("f32_to_bits_alias={:?}", f32_to_bits_alias(f));
    println!("f64_to_bits={:?}", f64_to_bits(d));
    println!("f64_to_bits_alias={:?}", f64_to_bits_alias(d));

    let dw: u32 = 0x1;
    let qw: u64 = 0x1;
    println!("f32_from_bits={:?}", f32_from_bits(dw));
    println!("f32_from_bits_alias={:?}", f32_from_bits_alias(dw));
    println!("f64_from_bits={:?}", f64_from_bits(qw));
    println!("f64_from_bits_alias={:?}", f64_from_bits_alias(qw));
}

Results

f32_to_bits=8
f32_to_bits_alias=2
f64_to_bits=1
f64_to_bits_alias=1
f32_from_bits=0.000000000000000000000000000000000000000000011
f32_from_bits_alias=0.000000000000000000000000000000000000000000003
f64_from_bits=0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005
f64_from_bits_alias=0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005