Skip to content

Commit c6727fc

Browse files
committedJul 12, 2024
Auto merge of #123351 - beetrees:x86-ret-snan-rust, r=nikic,workingjubilee
Ensure floats are returned losslessly by the Rust ABI on 32-bit x86 Solves #115567 for the (default) `"Rust"` ABI. When compiling for 32-bit x86, this PR changes the `"Rust"` ABI to return floats indirectly instead of in x87 registers (with the exception of single `f32`s, which this PR returns in general purpose registers as they are small enough to fit in one). No change is made to the `"C"` ABI as that ABI requires x87 register usage and therefore will need a different solution.
2 parents 62c068f + cae9d48 commit c6727fc

File tree

8 files changed

+461
-10
lines changed

8 files changed

+461
-10
lines changed
 

‎compiler/rustc_ty_utils/src/abi.rs

+34
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,40 @@ fn fn_abi_adjust_for_abi<'tcx>(
743743
return;
744744
}
745745

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

‎src/doc/rustc/src/platform-support.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,10 @@ target | notes
4141
`x86_64-pc-windows-msvc` | 64-bit MSVC (Windows 10+, Windows Server 2016+)
4242
`x86_64-unknown-linux-gnu` | 64-bit Linux (kernel 3.2+, glibc 2.17+)
4343

44-
[^x86_32-floats-return-ABI]: Due to limitations of the C ABI, floating-point support on `i686` targets is non-compliant: floating-point return values are passed via an x87 register, so NaN payload bits can be lost. See [issue #114479][x86-32-float-issue].
44+
[^x86_32-floats-return-ABI]: Due to limitations of the C ABI, floating-point support on `i686` targets is non-compliant: floating-point return values are passed via an x87 register, so NaN payload bits can be lost. Functions with the default Rust ABI are not affected. See [issue #115567][x86-32-float-return-issue].
4545

4646
[77071]: https://github.com/rust-lang/rust/issues/77071
47-
[x86-32-float-issue]: https://github.com/rust-lang/rust/issues/114479
47+
[x86-32-float-return-issue]: https://github.com/rust-lang/rust/issues/115567
4848

4949
## Tier 1
5050

@@ -209,6 +209,8 @@ target | std | notes
209209

210210
[^x86_32-floats-x87]: Floating-point support on `i586` targets is non-compliant: the `x87` registers and instructions used for these targets do not provide IEEE-754-compliant behavior, in particular when it comes to rounding and NaN payload bits. See [issue #114479][x86-32-float-issue].
211211

212+
[x86-32-float-issue]: https://github.com/rust-lang/rust/issues/114479
213+
212214
[wasi-rename]: https://github.com/rust-lang/compiler-team/issues/607
213215

214216
[Fortanix ABI]: https://edp.fortanix.com/

‎tests/assembly/x86-return-float.rs

+328
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
//@ assembly-output: emit-asm
2+
//@ only-x86
3+
// FIXME(#114479): LLVM miscompiles loading and storing `f32` and `f64` when SSE is disabled.
4+
// There's no compiletest directive to ignore a test on i586 only, so just always explicitly enable
5+
// SSE2.
6+
// Use the same target CPU as `i686` so that LLVM orders the instructions in the same order.
7+
//@ compile-flags: -Ctarget-feature=+sse2 -Ctarget-cpu=pentium4
8+
// Force frame pointers to make ASM more consistent between targets
9+
//@ compile-flags: -O -C force-frame-pointers
10+
//@ filecheck-flags: --implicit-check-not fld --implicit-check-not fst
11+
//@ revisions: unix windows
12+
//@[unix] ignore-windows
13+
//@[windows] only-windows
14+
15+
#![crate_type = "lib"]
16+
#![feature(f16, f128)]
17+
18+
// Tests that returning `f32` and `f64` with the "Rust" ABI on 32-bit x86 doesn't use the x87
19+
// floating point stack, as loading and storing `f32`s and `f64`s to and from the x87 stack quietens
20+
// signalling NaNs.
21+
22+
// Returning individual floats
23+
24+
// CHECK-LABEL: return_f32:
25+
#[no_mangle]
26+
pub fn return_f32(x: f32) -> f32 {
27+
// CHECK: movl {{.*}}(%ebp), %eax
28+
// CHECK-NOT: ax
29+
// CHECK: retl
30+
x
31+
}
32+
33+
// CHECK-LABEL: return_f64:
34+
#[no_mangle]
35+
pub fn return_f64(x: f64) -> f64 {
36+
// CHECK: movl [[#%d,OFFSET:]](%ebp), %[[PTR:.*]]
37+
// CHECK-NEXT: movsd [[#%d,OFFSET+4]](%ebp), %[[VAL:.*]]
38+
// CHECK-NEXT: movsd %[[VAL]], (%[[PTR]])
39+
// CHECK: retl
40+
x
41+
}
42+
43+
// Returning scalar pairs containing floats
44+
45+
// CHECK-LABEL: return_f32_f32:
46+
#[no_mangle]
47+
pub fn return_f32_f32(x: (f32, f32)) -> (f32, f32) {
48+
// CHECK: movl [[#%d,OFFSET:]](%ebp), %[[PTR:.*]]
49+
// CHECK-NEXT: movss [[#%d,OFFSET+4]](%ebp), %[[VAL1:.*]]
50+
// CHECK-NEXT: movss [[#%d,OFFSET+8]](%ebp), %[[VAL2:.*]]
51+
// CHECK-NEXT: movss %[[VAL1]], (%[[PTR]])
52+
// CHECK-NEXT: movss %[[VAL2]], 4(%[[PTR]])
53+
// CHECK: retl
54+
x
55+
}
56+
57+
// CHECK-LABEL: return_f64_f64:
58+
#[no_mangle]
59+
pub fn return_f64_f64(x: (f64, f64)) -> (f64, f64) {
60+
// CHECK: movl [[#%d,OFFSET:]](%ebp), %[[PTR:.*]]
61+
// CHECK-NEXT: movsd [[#%d,OFFSET+4]](%ebp), %[[VAL1:.*]]
62+
// CHECK-NEXT: movsd [[#%d,OFFSET+12]](%ebp), %[[VAL2:.*]]
63+
// CHECK-NEXT: movsd %[[VAL1]], (%[[PTR]])
64+
// CHECK-NEXT: movsd %[[VAL2]], 8(%[[PTR]])
65+
// CHECK: retl
66+
x
67+
}
68+
69+
// CHECK-LABEL: return_f32_f64:
70+
#[no_mangle]
71+
pub fn return_f32_f64(x: (f32, f64)) -> (f32, f64) {
72+
// CHECK: movl [[#%d,OFFSET:]](%ebp), %[[PTR:.*]]
73+
// CHECK-NEXT: movss [[#%d,OFFSET+4]](%ebp), %[[VAL1:.*]]
74+
// CHECK-NEXT: movsd [[#%d,OFFSET+8]](%ebp), %[[VAL2:.*]]
75+
// CHECK-NEXT: movss %[[VAL1]], (%[[PTR]])
76+
// CHECK-NEXT: movsd %[[VAL2]], {{4|8}}(%[[PTR]])
77+
// CHECK: retl
78+
x
79+
}
80+
81+
// CHECK-LABEL: return_f64_f32:
82+
#[no_mangle]
83+
pub fn return_f64_f32(x: (f64, f32)) -> (f64, f32) {
84+
// CHECK: movl [[#%d,OFFSET:]](%ebp), %[[PTR:.*]]
85+
// CHECK-NEXT: movsd [[#%d,OFFSET+4]](%ebp), %[[VAL1:.*]]
86+
// CHECK-NEXT: movss [[#%d,OFFSET+12]](%ebp), %[[VAL2:.*]]
87+
// CHECK-NEXT: movsd %[[VAL1]], (%[[PTR]])
88+
// CHECK-NEXT: movss %[[VAL2]], 8(%[[PTR]])
89+
// CHECK: retl
90+
x
91+
}
92+
93+
// CHECK-LABEL: return_f32_other:
94+
#[no_mangle]
95+
pub fn return_f32_other(x: (f32, usize)) -> (f32, usize) {
96+
// CHECK: movl [[#%d,OFFSET:]](%ebp), %[[PTR:.*]]
97+
// CHECK-NEXT: movss [[#%d,OFFSET+4]](%ebp), %[[VAL1:.*]]
98+
// CHECK-NEXT: movl [[#%d,OFFSET+8]](%ebp), %[[VAL2:.*]]
99+
// CHECK-NEXT: movss %[[VAL1]], (%[[PTR]])
100+
// CHECK-NEXT: movl %[[VAL2]], 4(%[[PTR]])
101+
// CHECK: retl
102+
x
103+
}
104+
105+
// CHECK-LABEL: return_f64_other:
106+
#[no_mangle]
107+
pub fn return_f64_other(x: (f64, usize)) -> (f64, usize) {
108+
// CHECK: movl [[#%d,OFFSET:]](%ebp), %[[PTR:.*]]
109+
// CHECK-NEXT: movsd [[#%d,OFFSET+4]](%ebp), %[[VAL1:.*]]
110+
// CHECK-NEXT: movl [[#%d,OFFSET+12]](%ebp), %[[VAL2:.*]]
111+
// CHECK-NEXT: movsd %[[VAL1]], (%[[PTR]])
112+
// CHECK-NEXT: movl %[[VAL2]], 8(%[[PTR]])
113+
// CHECK: retl
114+
x
115+
}
116+
117+
// CHECK-LABEL: return_other_f32:
118+
#[no_mangle]
119+
pub fn return_other_f32(x: (usize, f32)) -> (usize, f32) {
120+
// CHECK: movl [[#%d,OFFSET:]](%ebp), %[[PTR:.*]]
121+
// CHECK-NEXT: movl [[#%d,OFFSET+4]](%ebp), %[[VAL1:.*]]
122+
// CHECK-NEXT: movss [[#%d,OFFSET+8]](%ebp), %[[VAL2:.*]]
123+
// CHECK-NEXT: movl %[[VAL1]], (%[[PTR]])
124+
// CHECK-NEXT: movss %[[VAL2]], 4(%[[PTR]])
125+
// CHECK: retl
126+
x
127+
}
128+
129+
// CHECK-LABEL: return_other_f64:
130+
#[no_mangle]
131+
pub fn return_other_f64(x: (usize, f64)) -> (usize, f64) {
132+
// CHECK: movl [[#%d,OFFSET:]](%ebp), %[[PTR:.*]]
133+
// CHECK-NEXT: movl [[#%d,OFFSET+4]](%ebp), %[[VAL1:.*]]
134+
// CHECK-NEXT: movsd [[#%d,OFFSET+8]](%ebp), %[[VAL2:.*]]
135+
// CHECK-NEXT: movl %[[VAL1]], (%[[PTR]])
136+
// CHECK-NEXT: movsd %[[VAL2]], {{4|8}}(%[[PTR]])
137+
// CHECK: retl
138+
x
139+
}
140+
141+
// Calling functions returning floats
142+
143+
// CHECK-LABEL: call_f32:
144+
#[no_mangle]
145+
pub unsafe fn call_f32(x: &mut f32) {
146+
extern "Rust" {
147+
fn get_f32() -> f32;
148+
}
149+
// CHECK: movl {{.*}}(%ebp), %[[PTR:.*]]
150+
// CHECK: calll {{()|_}}get_f32
151+
// CHECK-NEXT: movl %eax, (%[[PTR]])
152+
*x = get_f32();
153+
}
154+
155+
// CHECK-LABEL: call_f64:
156+
#[no_mangle]
157+
pub unsafe fn call_f64(x: &mut f64) {
158+
extern "Rust" {
159+
fn get_f64() -> f64;
160+
}
161+
// CHECK: movl {{.*}}(%ebp), %[[PTR:.*]]
162+
// CHECK: calll {{()|_}}get_f64
163+
// CHECK: movsd {{.*}}(%{{ebp|esp}}), %[[VAL:.*]]
164+
// CHECK-NEXT: movsd %[[VAL:.*]], (%[[PTR]])
165+
*x = get_f64();
166+
}
167+
168+
// Calling functions returning scalar pairs containing floats
169+
170+
// CHECK-LABEL: call_f32_f32:
171+
#[no_mangle]
172+
pub unsafe fn call_f32_f32(x: &mut (f32, f32)) {
173+
extern "Rust" {
174+
fn get_f32_f32() -> (f32, f32);
175+
}
176+
// CHECK: movl {{.*}}(%ebp), %[[PTR:.*]]
177+
// CHECK: calll {{()|_}}get_f32_f32
178+
// CHECK: movss [[#%d,OFFSET:]](%ebp), %[[VAL1:.*]]
179+
// CHECK-NEXT: movss [[#%d,OFFSET+4]](%ebp), %[[VAL2:.*]]
180+
// CHECK-NEXT: movss %[[VAL1]], (%[[PTR]])
181+
// CHECK-NEXT: movss %[[VAL2]], 4(%[[PTR]])
182+
*x = get_f32_f32();
183+
}
184+
185+
// CHECK-LABEL: call_f64_f64:
186+
#[no_mangle]
187+
pub unsafe fn call_f64_f64(x: &mut (f64, f64)) {
188+
extern "Rust" {
189+
fn get_f64_f64() -> (f64, f64);
190+
}
191+
// CHECK: movl {{.*}}(%ebp), %[[PTR:.*]]
192+
// CHECK: calll {{()|_}}get_f64_f64
193+
// unix: movsd [[#%d,OFFSET:]](%ebp), %[[VAL1:.*]]
194+
// unix-NEXT: movsd [[#%d,OFFSET+8]](%ebp), %[[VAL2:.*]]
195+
// windows: movsd (%esp), %[[VAL1:.*]]
196+
// windows-NEXT: movsd 8(%esp), %[[VAL2:.*]]
197+
// CHECK-NEXT: movsd %[[VAL1]], (%[[PTR]])
198+
// CHECK-NEXT: movsd %[[VAL2]], 8(%[[PTR]])
199+
*x = get_f64_f64();
200+
}
201+
202+
// CHECK-LABEL: call_f32_f64:
203+
#[no_mangle]
204+
pub unsafe fn call_f32_f64(x: &mut (f32, f64)) {
205+
extern "Rust" {
206+
fn get_f32_f64() -> (f32, f64);
207+
}
208+
// CHECK: movl {{.*}}(%ebp), %[[PTR:.*]]
209+
// CHECK: calll {{()|_}}get_f32_f64
210+
// unix: movss [[#%d,OFFSET:]](%ebp), %[[VAL1:.*]]
211+
// unix-NEXT: movsd [[#%d,OFFSET+4]](%ebp), %[[VAL2:.*]]
212+
// windows: movss (%esp), %[[VAL1:.*]]
213+
// windows-NEXT: movsd 8(%esp), %[[VAL2:.*]]
214+
// CHECK-NEXT: movss %[[VAL1]], (%[[PTR]])
215+
// unix-NEXT: movsd %[[VAL2]], 4(%[[PTR]])
216+
// windows-NEXT: movsd %[[VAL2]], 8(%[[PTR]])
217+
*x = get_f32_f64();
218+
}
219+
220+
// CHECK-LABEL: call_f64_f32:
221+
#[no_mangle]
222+
pub unsafe fn call_f64_f32(x: &mut (f64, f32)) {
223+
extern "Rust" {
224+
fn get_f64_f32() -> (f64, f32);
225+
}
226+
// CHECK: movl {{.*}}(%ebp), %[[PTR:.*]]
227+
// CHECK: calll {{()|_}}get_f64_f32
228+
// unix: movsd [[#%d,OFFSET:]](%ebp), %[[VAL1:.*]]
229+
// unix-NEXT: movss [[#%d,OFFSET+8]](%ebp), %[[VAL2:.*]]
230+
// windows: movsd (%esp), %[[VAL1:.*]]
231+
// windows-NEXT: movss 8(%esp), %[[VAL2:.*]]
232+
// CHECK-NEXT: movsd %[[VAL1]], (%[[PTR]])
233+
// CHECK-NEXT: movss %[[VAL2]], 8(%[[PTR]])
234+
*x = get_f64_f32();
235+
}
236+
237+
// CHECK-LABEL: call_f32_other:
238+
#[no_mangle]
239+
pub unsafe fn call_f32_other(x: &mut (f32, usize)) {
240+
extern "Rust" {
241+
fn get_f32_other() -> (f32, usize);
242+
}
243+
// CHECK: movl {{.*}}(%ebp), %[[PTR:.*]]
244+
// CHECK: calll {{()|_}}get_f32_other
245+
// CHECK: movss [[#%d,OFFSET:]](%ebp), %[[VAL1:.*]]
246+
// CHECK-NEXT: movl [[#%d,OFFSET+4]](%ebp), %[[VAL2:.*]]
247+
// CHECK-NEXT: movss %[[VAL1]], (%[[PTR]])
248+
// CHECK-NEXT: movl %[[VAL2]], 4(%[[PTR]])
249+
*x = get_f32_other();
250+
}
251+
252+
// CHECK-LABEL: call_f64_other:
253+
#[no_mangle]
254+
pub unsafe fn call_f64_other(x: &mut (f64, usize)) {
255+
extern "Rust" {
256+
fn get_f64_other() -> (f64, usize);
257+
}
258+
// CHECK: movl {{.*}}(%ebp), %[[PTR:.*]]
259+
// CHECK: calll {{()|_}}get_f64_other
260+
// unix: movsd [[#%d,OFFSET:]](%ebp), %[[VAL1:.*]]
261+
// unix-NEXT: movl [[#%d,OFFSET+8]](%ebp), %[[VAL2:.*]]
262+
// windows: movsd (%esp), %[[VAL1:.*]]
263+
// windows-NEXT: movl 8(%esp), %[[VAL2:.*]]
264+
// CHECK-NEXT: movsd %[[VAL1]], (%[[PTR]])
265+
// CHECK-NEXT: movl %[[VAL2]], 8(%[[PTR]])
266+
*x = get_f64_other();
267+
}
268+
269+
// CHECK-LABEL: call_other_f32:
270+
#[no_mangle]
271+
pub unsafe fn call_other_f32(x: &mut (usize, f32)) {
272+
extern "Rust" {
273+
fn get_other_f32() -> (usize, f32);
274+
}
275+
// CHECK: movl {{.*}}(%ebp), %[[PTR:.*]]
276+
// CHECK: calll {{()|_}}get_other_f32
277+
// CHECK: movl [[#%d,OFFSET:]](%ebp), %[[VAL1:.*]]
278+
// CHECK-NEXT: movss [[#%d,OFFSET+4]](%ebp), %[[VAL2:.*]]
279+
// CHECK-NEXT: movl %[[VAL1]], (%[[PTR]])
280+
// CHECK-NEXT: movss %[[VAL2]], 4(%[[PTR]])
281+
*x = get_other_f32();
282+
}
283+
284+
// CHECK-LABEL: call_other_f64:
285+
#[no_mangle]
286+
pub unsafe fn call_other_f64(x: &mut (usize, f64)) {
287+
extern "Rust" {
288+
fn get_other_f64() -> (usize, f64);
289+
}
290+
// CHECK: movl {{.*}}(%ebp), %[[PTR:.*]]
291+
// CHECK: calll {{()|_}}get_other_f64
292+
// unix: movl [[#%d,OFFSET:]](%ebp), %[[VAL1:.*]]
293+
// unix-NEXT: movsd [[#%d,OFFSET+4]](%ebp), %[[VAL2:.*]]
294+
// windows: movl (%esp), %[[VAL1:.*]]
295+
// windows-NEXT: movsd 8(%esp), %[[VAL2:.*]]
296+
// CHECK-NEXT: movl %[[VAL1]], (%[[PTR]])
297+
// unix-NEXT: movsd %[[VAL2]], 4(%[[PTR]])
298+
// windows-NEXT: movsd %[[VAL2]], 8(%[[PTR]])
299+
*x = get_other_f64();
300+
}
301+
302+
// The "C" ABI for `f16` and `f128` on x86 has never used the x87 floating point stack. Do some
303+
// basic checks to ensure this remains the case for the "Rust" ABI.
304+
305+
// CHECK-LABEL: return_f16:
306+
#[no_mangle]
307+
pub fn return_f16(x: f16) -> f16 {
308+
// CHECK: pinsrw $0, {{.*}}(%ebp), %xmm0
309+
// CHECK-NOT: xmm0
310+
// CHECK: retl
311+
x
312+
}
313+
314+
// CHECK-LABEL: return_f128:
315+
#[no_mangle]
316+
pub fn return_f128(x: f128) -> f128 {
317+
// CHECK: movl [[#%d,OFFSET:]](%ebp), %[[PTR:.*]]
318+
// CHECK-NEXT: movl [[#%d,OFFSET+16]](%ebp), %[[VAL4:.*]]
319+
// CHECK-NEXT: movl [[#%d,OFFSET+4]](%ebp), %[[VAL1:.*]]
320+
// CHECK-NEXT: movl [[#%d,OFFSET+8]](%ebp), %[[VAL2:.*]]
321+
// CHECK-NEXT: movl [[#%d,OFFSET+12]](%ebp), %[[VAL3:.*]]
322+
// CHECK-NEXT: movl %[[VAL4:.*]] 12(%[[PTR]])
323+
// CHECK-NEXT: movl %[[VAL3:.*]] 8(%[[PTR]])
324+
// CHECK-NEXT: movl %[[VAL2:.*]] 4(%[[PTR]])
325+
// CHECK-NEXT: movl %[[VAL1:.*]] (%[[PTR]])
326+
// CHECK: retl
327+
x
328+
}

‎tests/codegen/float/f128.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
// 32-bit x86 returns `f32` and `f64` differently to avoid the x87 stack.
2+
//@ revisions: x86 other
3+
//@[x86] only-x86
4+
//@[other] ignore-x86
5+
16
// Verify that our intrinsics generate the correct LLVM calls for f128
27

38
#![crate_type = "lib"]
@@ -138,14 +143,16 @@ pub fn f128_as_f16(a: f128) -> f16 {
138143
a as f16
139144
}
140145

141-
// CHECK-LABEL: float @f128_as_f32(
146+
// other-LABEL: float @f128_as_f32(
147+
// x86-LABEL: i32 @f128_as_f32(
142148
#[no_mangle]
143149
pub fn f128_as_f32(a: f128) -> f32 {
144150
// CHECK: fptrunc fp128 %{{.+}} to float
145151
a as f32
146152
}
147153

148-
// CHECK-LABEL: double @f128_as_f64(
154+
// other-LABEL: double @f128_as_f64(
155+
// x86-LABEL: void @f128_as_f64(
149156
#[no_mangle]
150157
pub fn f128_as_f64(a: f128) -> f64 {
151158
// CHECK: fptrunc fp128 %{{.+}} to double

‎tests/codegen/float/f16.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
// 32-bit x86 returns `f32` and `f64` differently to avoid the x87 stack.
2+
//@ revisions: x86 other
3+
//@[x86] only-x86
4+
//@[other] ignore-x86
5+
16
// Verify that our intrinsics generate the correct LLVM calls for f16
27

38
#![crate_type = "lib"]
@@ -140,14 +145,16 @@ pub fn f16_as_self(a: f16) -> f16 {
140145
a as f16
141146
}
142147

143-
// CHECK-LABEL: float @f16_as_f32(
148+
// other-LABEL: float @f16_as_f32(
149+
// x86-LABEL: i32 @f16_as_f32(
144150
#[no_mangle]
145151
pub fn f16_as_f32(a: f16) -> f32 {
146152
// CHECK: fpext half %{{.+}} to float
147153
a as f32
148154
}
149155

150-
// CHECK-LABEL: double @f16_as_f64(
156+
// other-LABEL: double @f16_as_f64(
157+
// x86-LABEL: void @f16_as_f64(
151158
#[no_mangle]
152159
pub fn f16_as_f64(a: f16) -> f64 {
153160
// CHECK: fpext half %{{.+}} to double

‎tests/codegen/issues/issue-32031.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
//@ compile-flags: -C no-prepopulate-passes -Copt-level=0
2+
// 32-bit x86 returns `f32` and `f64` differently to avoid the x87 stack.
3+
//@ revisions: x86 other
4+
//@[x86] only-x86
5+
//@[other] ignore-x86
26

37
#![crate_type = "lib"]
48

59
#[no_mangle]
610
pub struct F32(f32);
711

8-
// CHECK: define{{.*}}float @add_newtype_f32(float %a, float %b)
12+
// other: define{{.*}}float @add_newtype_f32(float %a, float %b)
13+
// x86: define{{.*}}i32 @add_newtype_f32(float %a, float %b)
914
#[inline(never)]
1015
#[no_mangle]
1116
pub fn add_newtype_f32(a: F32, b: F32) -> F32 {
@@ -15,7 +20,8 @@ pub fn add_newtype_f32(a: F32, b: F32) -> F32 {
1520
#[no_mangle]
1621
pub struct F64(f64);
1722

18-
// CHECK: define{{.*}}double @add_newtype_f64(double %a, double %b)
23+
// other: define{{.*}}double @add_newtype_f64(double %a, double %b)
24+
// x86: define{{.*}}void @add_newtype_f64(ptr{{.*}}sret([8 x i8]){{.*}}%_0, double %a, double %b)
1925
#[inline(never)]
2026
#[no_mangle]
2127
pub fn add_newtype_f64(a: F64, b: F64) -> F64 {

‎tests/codegen/union-abi.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
//@ ignore-emscripten vectors passed directly
22
//@ compile-flags: -O -C no-prepopulate-passes
3+
// 32-bit x86 returns `f32` differently to avoid the x87 stack.
4+
//@ revisions: x86 other
5+
//@[x86] only-x86
6+
//@[other] ignore-x86
37

48
// This test that using union forward the abi of the inner type, as
59
// discussed in #54668
@@ -67,7 +71,8 @@ pub union UnionF32 {
6771
a: f32,
6872
}
6973

70-
// CHECK: define {{(dso_local )?}}float @test_UnionF32(float %_1)
74+
// other: define {{(dso_local )?}}float @test_UnionF32(float %_1)
75+
// x86: define {{(dso_local )?}}i32 @test_UnionF32(float %_1)
7176
#[no_mangle]
7277
pub fn test_UnionF32(_: UnionF32) -> UnionF32 {
7378
loop {}
@@ -78,7 +83,8 @@ pub union UnionF32F32 {
7883
b: f32,
7984
}
8085

81-
// CHECK: define {{(dso_local )?}}float @test_UnionF32F32(float %_1)
86+
// other: define {{(dso_local )?}}float @test_UnionF32F32(float %_1)
87+
// x86: define {{(dso_local )?}}i32 @test_UnionF32F32(float %_1)
8288
#[no_mangle]
8389
pub fn test_UnionF32F32(_: UnionF32F32) -> UnionF32F32 {
8490
loop {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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+
// FIXME(#114479): LLVM miscompiles loading and storing `f32` and `f64` when SSE is disabled on
8+
// x86.
9+
if cfg!(not(all(target_arch = "x86", not(target_feature = "sse2")))) {
10+
let bits_f32 = std::hint::black_box([
11+
4.2_f32.to_bits(),
12+
f32::INFINITY.to_bits(),
13+
f32::NEG_INFINITY.to_bits(),
14+
f32::NAN.to_bits(),
15+
// These two masks cover all the mantissa bits. One of them is a signalling NaN, the
16+
// other is quiet.
17+
// Similar to the masks in `test_float_bits_conv` in library/std/src/f32/tests.rs
18+
f32::NAN.to_bits() ^ 0x002A_AAAA,
19+
f32::NAN.to_bits() ^ 0x0055_5555,
20+
// Same as above but with the sign bit flipped.
21+
f32::NAN.to_bits() ^ 0x802A_AAAA,
22+
f32::NAN.to_bits() ^ 0x8055_5555,
23+
]);
24+
for bits in bits_f32 {
25+
assert_eq!(identity(f32::from_bits(bits)).to_bits(), bits);
26+
// Test types that are returned as scalar pairs.
27+
assert_eq!(identity((f32::from_bits(bits), 42)).0.to_bits(), bits);
28+
assert_eq!(identity((42, f32::from_bits(bits))).1.to_bits(), bits);
29+
let (a, b) = identity((f32::from_bits(bits), f32::from_bits(bits)));
30+
assert_eq!((a.to_bits(), b.to_bits()), (bits, bits));
31+
}
32+
33+
let bits_f64 = std::hint::black_box([
34+
4.2_f64.to_bits(),
35+
f64::INFINITY.to_bits(),
36+
f64::NEG_INFINITY.to_bits(),
37+
f64::NAN.to_bits(),
38+
// These two masks cover all the mantissa bits. One of them is a signalling NaN, the
39+
// other is quiet.
40+
// Similar to the masks in `test_float_bits_conv` in library/std/src/f64/tests.rs
41+
f64::NAN.to_bits() ^ 0x000A_AAAA_AAAA_AAAA,
42+
f64::NAN.to_bits() ^ 0x0005_5555_5555_5555,
43+
// Same as above but with the sign bit flipped.
44+
f64::NAN.to_bits() ^ 0x800A_AAAA_AAAA_AAAA,
45+
f64::NAN.to_bits() ^ 0x8005_5555_5555_5555,
46+
]);
47+
for bits in bits_f64 {
48+
assert_eq!(identity(f64::from_bits(bits)).to_bits(), bits);
49+
// Test types that are returned as scalar pairs.
50+
assert_eq!(identity((f64::from_bits(bits), 42)).0.to_bits(), bits);
51+
assert_eq!(identity((42, f64::from_bits(bits))).1.to_bits(), bits);
52+
let (a, b) = identity((f64::from_bits(bits), f64::from_bits(bits)));
53+
assert_eq!((a.to_bits(), b.to_bits()), (bits, bits));
54+
}
55+
}
56+
}
57+
58+
#[inline(never)]
59+
fn identity<T>(x: T) -> T {
60+
x
61+
}

0 commit comments

Comments
 (0)
Please sign in to comment.