Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
Alexhuszagh opened this issue Sep 8, 2019 · 6 comments · Fixed by #316
Closed

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

Alexhuszagh opened this issue Sep 8, 2019 · 6 comments · Fixed by #316
Labels
A-powerpc Area: PowerPC targets A-qemu Area: qemu runners bug

Comments

@Alexhuszagh
Copy link
Contributor

Alexhuszagh commented Sep 8, 2019

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

Targets

  • powerpc-unknown-linux-gnu
  • powerpc64-unknown-linux-gnu
  • powerpc64le-unknown-linux-gnu

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
Copy link
Member

Is this the same QEMU bug as in #314?

@Alexhuszagh
Copy link
Contributor Author

@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
Copy link
Contributor Author

Versions:

  • 2.12.1 (works)
  • 3.0.1 (works)
  • 3.1.0 (fails)
  • 3.1.1 (fails)

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
Copy link
Contributor Author

@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
Copy link
Member

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

@Alexhuszagh
Copy link
Contributor Author

@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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-powerpc Area: PowerPC targets A-qemu Area: qemu runners bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants