Skip to content

Commit 7297de2

Browse files
committed
Ensure floats are returned losslessly by the Rust ABI on 32-bit x86
1 parent 9782770 commit 7297de2

File tree

2 files changed

+91
-0
lines changed

2 files changed

+91
-0
lines changed

compiler/rustc_ty_utils/src/abi.rs

+34
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,40 @@ fn fn_abi_adjust_for_abi<'tcx>(
741741
return;
742742
}
743743

744+
// Avoid returning floats in x87 registers on x86 as loading and storing from x87
745+
// registers will quiet signalling NaNs.
746+
if cx.tcx.sess.target.arch == "x86"
747+
&& arg_idx.is_none()
748+
// Intrinsics themselves are not actual "real" functions, so theres no need to
749+
// change their ABIs.
750+
&& abi != SpecAbi::RustIntrinsic
751+
{
752+
match arg.layout.abi {
753+
// Handle similar to the way arguments with an `Abi::Aggregate` abi are handled
754+
// below, by returning arguments up to the size of a pointer (32 bits on x86)
755+
// cast to an appropriately sized integer.
756+
Abi::Scalar(s) if s.primitive() == F32 => {
757+
// Same size as a pointer, return in a register.
758+
arg.cast_to(Reg::i32());
759+
return;
760+
}
761+
Abi::Scalar(s) if s.primitive() == F64 => {
762+
// Larger than a pointer, return indirectly.
763+
arg.make_indirect();
764+
return;
765+
}
766+
Abi::ScalarPair(s1, s2)
767+
if matches!(s1.primitive(), F32 | F64)
768+
|| matches!(s2.primitive(), F32 | F64) =>
769+
{
770+
// Larger than a pointer, return indirectly.
771+
arg.make_indirect();
772+
return;
773+
}
774+
_ => {}
775+
};
776+
}
777+
744778
match arg.layout.abi {
745779
Abi::Aggregate { .. } => {}
746780

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//@ run-pass
2+
//@ compile-flags: -Copt-level=0
3+
4+
// Test that floats (in particular signalling NaNs) are losslessly returned from functions.
5+
6+
fn main() {
7+
let bits_f32 = std::hint::black_box([
8+
4.2_f32.to_bits(),
9+
f32::INFINITY.to_bits(),
10+
f32::NEG_INFINITY.to_bits(),
11+
f32::NAN.to_bits(),
12+
// These two masks cover all the mantissa bits. One of them is a signalling NaN, the other
13+
// is quiet.
14+
// Similar to the masks in `test_float_bits_conv` in library/std/src/f32/tests.rs
15+
f32::NAN.to_bits() ^ 0x002A_AAAA,
16+
f32::NAN.to_bits() ^ 0x0055_5555,
17+
// Same as above but with the sign bit flipped.
18+
f32::NAN.to_bits() ^ 0x802A_AAAA,
19+
f32::NAN.to_bits() ^ 0x8055_5555,
20+
]);
21+
for bits in bits_f32 {
22+
assert_eq!(identity(f32::from_bits(bits)).to_bits(), bits);
23+
// Test types that are returned as scalar pairs.
24+
assert_eq!(identity((f32::from_bits(bits), 42)).0.to_bits(), bits);
25+
assert_eq!(identity((42, f32::from_bits(bits))).1.to_bits(), bits);
26+
let (a, b) = identity((f32::from_bits(bits), f32::from_bits(bits)));
27+
assert_eq!((a.to_bits(), b.to_bits()), (bits, bits));
28+
}
29+
30+
let bits_f64 = std::hint::black_box([
31+
4.2_f64.to_bits(),
32+
f64::INFINITY.to_bits(),
33+
f64::NEG_INFINITY.to_bits(),
34+
f64::NAN.to_bits(),
35+
// These two masks cover all the mantissa bits. One of them is a signalling NaN, the other
36+
// is quiet.
37+
// Similar to the masks in `test_float_bits_conv` in library/std/src/f64/tests.rs
38+
f64::NAN.to_bits() ^ 0x000A_AAAA_AAAA_AAAA,
39+
f64::NAN.to_bits() ^ 0x0005_5555_5555_5555,
40+
// Same as above but with the sign bit flipped.
41+
f64::NAN.to_bits() ^ 0x800A_AAAA_AAAA_AAAA,
42+
f64::NAN.to_bits() ^ 0x8005_5555_5555_5555,
43+
]);
44+
for bits in bits_f64 {
45+
assert_eq!(identity(f64::from_bits(bits)).to_bits(), bits);
46+
// Test types that are returned as scalar pairs.
47+
assert_eq!(identity((f64::from_bits(bits), 42)).0.to_bits(), bits);
48+
assert_eq!(identity((42, f64::from_bits(bits))).1.to_bits(), bits);
49+
let (a, b) = identity((f64::from_bits(bits), f64::from_bits(bits)));
50+
assert_eq!((a.to_bits(), b.to_bits()), (bits, bits));
51+
}
52+
}
53+
54+
#[inline(never)]
55+
fn identity<T>(x: T) -> T {
56+
x
57+
}

0 commit comments

Comments
 (0)