Skip to content

Commit ccba80a

Browse files
committed
Ensure floats are returned losslessly by the C ABI on 32-bit x86
1 parent 2e071b2 commit ccba80a

19 files changed

Lines changed: 920 additions & 93 deletions

File tree

compiler/rustc_codegen_llvm/src/abi.rs

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use rustc_abi::{
66
RegKind, Size, X86Call,
77
};
88
use rustc_codegen_ssa::MemFlags;
9+
use rustc_codegen_ssa::common::RealPredicate;
910
use rustc_codegen_ssa::mir::operand::{OperandRef, OperandValue};
1011
use rustc_codegen_ssa::mir::place::{PlaceRef, PlaceValue};
1112
use rustc_codegen_ssa::traits::*;
@@ -175,6 +176,9 @@ impl LlvmType for Reg {
175176

176177
impl LlvmType for CastTarget {
177178
fn llvm_type<'ll>(&self, cx: &CodegenCx<'ll, '_>) -> &'ll Type {
179+
if self.x87_floating_point_stack {
180+
return cx.type_x86_fp80();
181+
}
178182
let rest_ll_unit = self.rest.unit.llvm_type(cx);
179183
let rest_count = if self.rest.total == Size::ZERO {
180184
0
@@ -324,6 +328,7 @@ impl<'ll, 'tcx> ArgAbiBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> {
324328
) {
325329
arg_abi.store_fn_arg(self, idx, dst)
326330
}
331+
327332
fn store_arg(
328333
&mut self,
329334
arg_abi: &ArgAbi<'tcx, Ty<'tcx>>,
@@ -332,6 +337,165 @@ impl<'ll, 'tcx> ArgAbiBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> {
332337
) {
333338
arg_abi.store(self, val, dst)
334339
}
340+
341+
fn x87_lossless_float_to_fp_stack(&mut self, value: &'ll Value, no_undef: bool) -> &'ll Value {
342+
// If value is (partially) uninitialized (e.g. when returning `MaybeUninit<f64>`) then e.g.
343+
// branching on it could lead to undefined behaviour. To ensure that doesn't happen and that
344+
// any initialized bytes within a partially uninitialized value survive the round trip,
345+
// freeze the value.
346+
let value = if no_undef { value } else { self.freeze(value) };
347+
// While we only need to manually convert sNaNs, all NaNs can be converted the same way and
348+
// checking whether `value` is NaN only takes a single floating-point x86 instruction,
349+
// whereas checking if it is a signalling NaNs requires bit operations. LLVM also generally
350+
// won't know a non-constant `value` is not a sNaN but could be a qNaN, so being more
351+
// general here doesn't prevent the branch from being optimised out in likely scenarios.
352+
let is_nan = self.fcmp(RealPredicate::RealUNO, value, value);
353+
let is_nan_block = self.append_sibling_block("float_pre_ret.is_nan");
354+
let is_not_nan_block = self.append_sibling_block("float_pre_ret.is_not_nan");
355+
let after_block = self.append_sibling_block("float_pre_ret.after");
356+
let dbg_loc = self.get_dbg_loc();
357+
self.cond_br(is_nan, is_nan_block, is_not_nan_block);
358+
359+
self.switch_to_block(is_nan_block);
360+
if let Some(dbg_loc) = dbg_loc {
361+
self.set_dbg_loc(dbg_loc);
362+
}
363+
// This manually converts a NaN to x86_fp80 to avoid setting the quiet NaN bit of
364+
// signalling NaNs.
365+
let num_bits = self.float_width(self.val_ty(value)) as u64;
366+
assert!(
367+
num_bits == 32 || num_bits == 64,
368+
"attempt to return float on x87 floating point stack with width {num_bits}"
369+
);
370+
let bits_ty = self.type_ix(num_bits);
371+
let bits = self.bitcast(value, bits_ty);
372+
// The high 16 bits of an x86_fp80 are the exponent and sign (the sign is the highest
373+
// bit) NaNs always have all bits of the exponent set to 1, so the only bit that is
374+
// needed from `value` is the sign bit.
375+
// Shift out the lower bits.
376+
let exp_and_sign = self.lshr(bits, self.const_uint(bits_ty, num_bits - 16));
377+
// Set the exponent to all 1s.
378+
let exp_and_sign = self.or(exp_and_sign, self.const_uint(bits_ty, 0x7FFF));
379+
// Shift the exponent and sign into position.
380+
let exp_and_sign = self.zext(exp_and_sign, self.type_ix(80));
381+
let exp_and_sign = self.shl(exp_and_sign, self.const_uint_big(self.type_ix(80), 64));
382+
383+
// The fraction of the input NaN needs to be shifted left to just before x86_fp80's
384+
// explicit integer bit. There's no need to manually set the integer bit itself, as it
385+
// will be already set to 1 due to the all 1s exponent in the input NaN.
386+
let (fraction_bits, fraction) = match num_bits {
387+
32 => (f32::MANTISSA_DIGITS - 1, self.zext(bits, self.type_i64())),
388+
64 => (f64::MANTISSA_DIGITS - 1, bits),
389+
_ => bug!(),
390+
};
391+
// Shift the fraction into position.
392+
let fraction = self.shl(fraction, self.const_u64(63 - u64::from(fraction_bits)));
393+
let fraction = self.zext(fraction, self.type_ix(80));
394+
395+
let is_nan_res = self.or(exp_and_sign, fraction);
396+
let is_nan_res = self.bitcast(is_nan_res, self.type_x86_fp80());
397+
self.br(after_block);
398+
399+
self.switch_to_block(is_not_nan_block);
400+
if let Some(dbg_loc) = dbg_loc {
401+
self.set_dbg_loc(dbg_loc);
402+
}
403+
let is_not_nan_res = self.fpext(value, self.type_x86_fp80());
404+
self.br(after_block);
405+
406+
self.switch_to_block(after_block);
407+
if let Some(dbg_loc) = dbg_loc {
408+
self.set_dbg_loc(dbg_loc);
409+
}
410+
self.phi(
411+
self.type_x86_fp80(),
412+
&[is_nan_res, is_not_nan_res],
413+
&[is_nan_block, is_not_nan_block],
414+
)
415+
}
416+
417+
fn x87_lossless_fp_stack_to_float(
418+
&mut self,
419+
value: &'ll Value,
420+
float_type: &'ll Type,
421+
no_undef: bool,
422+
) -> &'ll Value {
423+
// If value is (partially) uninitialized (e.g. when returning `MaybeUninit<f64>`) then e.g.
424+
// branching on it could lead to undefined behaviour. To ensure that doesn't happen and that
425+
// any initialized bytes within a partially uninitialized value survive the round trip,
426+
// freeze the value.
427+
let value = if no_undef { value } else { self.freeze(value) };
428+
let num_bits = self.float_width(float_type) as u64;
429+
let fraction_bits = u64::from(match num_bits {
430+
32 => f32::MANTISSA_DIGITS - 1,
431+
64 => f64::MANTISSA_DIGITS - 1,
432+
_ => bug!("attempt to return float on x87 floating point stack with width {num_bits}"),
433+
});
434+
let dest_bits_type = self.type_ix(num_bits);
435+
// While we only need to manually convert sNaNs, all NaNs can be converted the same way and
436+
// checking whether `value` is NaN only takes a single floating-point x86 instruction,
437+
// whereas checking if it is a signalling NaNs requires bit operations. LLVM also generally
438+
// won't know a non-constant `value` is not a sNaN but could be a qNaN, so being more
439+
// general here doesn't prevent the branch from being optimised out in likely scenarios.
440+
let is_nan = self.fcmp(RealPredicate::RealUNO, value, value);
441+
let is_nan_block = self.append_sibling_block("float_post_ret.is_nan");
442+
let is_not_nan_block = self.append_sibling_block("float_post_ret.is_not_nan");
443+
let after_block = self.append_sibling_block("float_post_ret.after");
444+
let dbg_loc = self.get_dbg_loc();
445+
self.cond_br(is_nan, is_nan_block, is_not_nan_block);
446+
447+
self.switch_to_block(is_nan_block);
448+
if let Some(dbg_loc) = dbg_loc {
449+
self.set_dbg_loc(dbg_loc);
450+
}
451+
// This block converts a NaN to `x86_fp80` manually to avoid setting the quiet NaN bit of
452+
// signalling NaNs.
453+
// We don't handle the "invalid operand" bitpatterns here (which are treated like NaNs) as
454+
// they can't be generated by any post-80387 hardware, and the return value should have been
455+
// converted from an actual `f32`/`f64`. Even on non-SSE targets current compilers don't
456+
// seem to miscompile code so badly as to allow user-supplied `x86_fp80` "invalid operands"
457+
// to be returned as `f32`/`f64`. Similarly, sNaNs are never produced by the hardware so we
458+
// don't handle the case where the only fraction bits set are truncated, as that can never
459+
// happen with sNaNs converted from `f32`s/`f64`s.
460+
let bits = self.bitcast(value, self.type_ix(80));
461+
// Mask out the extra 1s in the exponent as `f32`/`f64` have less bits in their
462+
// exponents than `x86_fp80`.
463+
let exp_and_sign_bits = num_bits - fraction_bits;
464+
let exp_and_sign_mask = u16::MAX << (16 - exp_and_sign_bits);
465+
let exp_and_sign_mask = u128::from(exp_and_sign_mask) << 64;
466+
let exp_and_sign = self.and(bits, self.const_uint_big(self.type_ix(80), exp_and_sign_mask));
467+
// Shift the exponent and sign into position
468+
let exp_and_sign = self
469+
.lshr(exp_and_sign, self.const_uint_big(self.type_ix(80), 80 - u128::from(num_bits)));
470+
let exp_and_sign = self.trunc(exp_and_sign, dest_bits_type);
471+
472+
// Truncate off the exponent and sign
473+
let fraction = self.trunc(bits, self.type_i64());
474+
// Shift the fraction in to position. There's no need to mask out `x86_fp80`'s
475+
// explicit integer bit as the fraction is right next to the exponent which is all
476+
// 1s anyway.
477+
let fraction = self.lshr(fraction, self.const_u64(63 - fraction_bits));
478+
let fraction = if num_bits != 64 { self.trunc(fraction, dest_bits_type) } else { fraction };
479+
480+
// Combine the parts into the resulting float
481+
let is_nan_res = self.or(exp_and_sign, fraction);
482+
let is_nan_res = self.bitcast(is_nan_res, float_type);
483+
self.br(after_block);
484+
485+
self.switch_to_block(is_not_nan_block);
486+
if let Some(dbg_loc) = dbg_loc {
487+
self.set_dbg_loc(dbg_loc);
488+
}
489+
// Use a regular floating point conversion when the value it not a NaN.
490+
let is_not_nan_res = self.fptrunc(value, float_type);
491+
self.br(after_block);
492+
493+
self.switch_to_block(after_block);
494+
if let Some(dbg_loc) = dbg_loc {
495+
self.set_dbg_loc(dbg_loc);
496+
}
497+
self.phi(float_type, &[is_nan_res, is_not_nan_res], &[is_nan_block, is_not_nan_block])
498+
}
335499
}
336500

337501
pub(crate) trait FnAbiLlvmExt<'ll, 'tcx> {

compiler/rustc_codegen_llvm/src/builder.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1555,6 +1555,12 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
15551555
}
15561556
}
15571557

1558+
impl<'ll> Builder<'_, 'll, '_> {
1559+
pub(crate) fn freeze(&mut self, value: &'ll Value) -> &'ll Value {
1560+
unsafe { llvm::LLVMBuildFreeze(self.llbuilder, value, UNNAMED) }
1561+
}
1562+
}
1563+
15581564
impl<'ll> StaticBuilderMethods for Builder<'_, 'll, '_> {
15591565
fn get_static(&mut self, def_id: DefId) -> &'ll Value {
15601566
// Forward to the `get_static` method of `CodegenCx`

compiler/rustc_codegen_llvm/src/llvm/ffi.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -937,6 +937,7 @@ unsafe extern "C" {
937937
pub(crate) fn LLVMFloatTypeInContext(C: &Context) -> &Type;
938938
pub(crate) fn LLVMDoubleTypeInContext(C: &Context) -> &Type;
939939
pub(crate) fn LLVMFP128TypeInContext(C: &Context) -> &Type;
940+
pub(crate) fn LLVMX86FP80TypeInContext(C: &Context) -> &Type;
940941

941942
// Operations on non-IEEE real types
942943
pub(crate) fn LLVMBFloatTypeInContext(C: &Context) -> &Type;
@@ -1596,6 +1597,11 @@ unsafe extern "C" {
15961597
Index: c_uint,
15971598
Name: *const c_char,
15981599
) -> &'a Value;
1600+
pub(crate) fn LLVMBuildFreeze<'a>(
1601+
B: &Builder<'a>,
1602+
Val: &'a Value,
1603+
Name: *const c_char,
1604+
) -> &'a Value;
15991605

16001606
// Atomic Operations
16011607
pub(crate) fn LLVMBuildAtomicCmpXchg<'a>(

compiler/rustc_codegen_llvm/src/type_.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,12 @@ impl<'ll, CX: Borrow<SCx<'ll>>> BaseTypeCodegenMethods for GenericCx<'ll, CX> {
278278
}
279279
}
280280

281+
impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> {
282+
pub(crate) fn type_x86_fp80(&self) -> &'ll Type {
283+
unsafe { llvm::LLVMX86FP80TypeInContext(self.llcx) }
284+
}
285+
}
286+
281287
pub(crate) fn llvm_type_ptr(llcx: &llvm::Context) -> &Type {
282288
llvm_type_ptr_in_address_space(llcx, AddressSpace::ZERO)
283289
}

compiler/rustc_codegen_ssa/src/mir/block.rs

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use rustc_middle::ty::{self, Instance, Ty, TypeVisitableExt};
1919
use rustc_middle::{bug, span_bug};
2020
use rustc_session::config::OptLevel;
2121
use rustc_span::{Span, Spanned};
22-
use rustc_target::callconv::{ArgAbi, ArgAttributes, CastTarget, FnAbi, PassMode};
22+
use rustc_target::callconv::{ArgAbi, ArgAttribute, ArgAttributes, CastTarget, FnAbi, PassMode};
2323
use tracing::{debug, info};
2424

2525
use super::operand::OperandRef;
@@ -281,7 +281,9 @@ impl<'a, 'tcx> TerminatorCodegenHelper<'tcx> {
281281
// If the return value was retagged as it was stored,
282282
// then we might be in a different basic block now.
283283
// Update the cached block for `target` to point to this new
284-
// block, where codegen will continue.
284+
// block, where codegen will continue. Additionally, store_return() may have
285+
// required a branch into a new codegen backend basic block (currently this occurs
286+
// when `cast_target.x87_floating_point_stack` is set).
285287
fx.cached_llbbs[target] = CachedLlbb::Some(bx.llbb());
286288
}
287289
MergingSucc::False
@@ -2511,7 +2513,7 @@ fn load_cast<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
25112513
align: Align,
25122514
) -> Bx::Value {
25132515
let cast_ty = bx.cast_backend_type(cast);
2514-
if let Some(offset_from_start) = cast.rest_offset {
2516+
let value = if let Some(offset_from_start) = cast.rest_offset {
25152517
assert_eq!(cast.prefix.len(), 1);
25162518
assert_eq!(cast.rest.unit.size, cast.rest.total);
25172519
let first_ty = bx.reg_backend_type(&cast.prefix[0]);
@@ -2523,7 +2525,21 @@ fn load_cast<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
25232525
let res = bx.insert_value(res, first, 0);
25242526
bx.insert_value(res, second, 1)
25252527
} else {
2526-
bx.load(cast_ty, ptr, align)
2528+
let load_ty = if cast.x87_floating_point_stack {
2529+
match cast.rest.unit.size.bytes() {
2530+
4 => bx.type_f32(),
2531+
8 => bx.type_f64(),
2532+
_ => bug!(),
2533+
}
2534+
} else {
2535+
cast_ty
2536+
};
2537+
bx.load(load_ty, ptr, align)
2538+
};
2539+
if cast.x87_floating_point_stack {
2540+
bx.x87_lossless_float_to_fp_stack(value, cast.attrs.contains(ArgAttribute::NoUndef))
2541+
} else {
2542+
value
25272543
}
25282544
}
25292545

@@ -2534,6 +2550,20 @@ pub fn store_cast<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
25342550
ptr: Bx::Value,
25352551
align: Align,
25362552
) {
2553+
let value = if cast.x87_floating_point_stack {
2554+
let float_type = match cast.rest.unit.size.bytes() {
2555+
4 => bx.type_f32(),
2556+
8 => bx.type_f64(),
2557+
_ => bug!(),
2558+
};
2559+
bx.x87_lossless_fp_stack_to_float(
2560+
value,
2561+
float_type,
2562+
cast.attrs.contains(ArgAttribute::NoUndef),
2563+
)
2564+
} else {
2565+
value
2566+
};
25372567
if let Some(offset_from_start) = cast.rest_offset {
25382568
assert_eq!(cast.prefix.len(), 1);
25392569
assert_eq!(cast.rest.unit.size, cast.rest.total);

compiler/rustc_codegen_ssa/src/traits/type_.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,42 @@ pub trait ArgAbiBuilderMethods<'tcx>: BackendTypes {
157157
val: Self::Value,
158158
dst: PlaceRef<'tcx, Self::Value>,
159159
);
160+
/// Losslessly convert a `f32` or `f64` to a float to be returned on the x87 floating point
161+
/// stack. Because `MaybeUninit` `f32`s/`f64`s are also returned on the floating point stack,
162+
/// `value` being uninitialized must not cause undefined behaviour unless `true` is passed in
163+
/// the `no_undef` argument. This method is used to avoid an LLVM bug where signalling NaNs get
164+
/// quietened when being returned on the x87 stack on 32-bit x86. For more details, see:
165+
/// * <https://github.com/rust-lang/rust/issues/115567>
166+
/// * <https://github.com/llvm/llvm-project/issues/66803>
167+
fn x87_lossless_float_to_fp_stack(
168+
&mut self,
169+
value: Self::Value,
170+
no_undef: bool,
171+
) -> Self::Value {
172+
let _ = no_undef;
173+
// Default to leaving the value unchanged. The backend can override this method if it needs
174+
// extra codegen to avoid quietening signalling NaNs.
175+
value
176+
}
177+
/// Losslessly convert a `f32` or `f64` from a float that was returned on the x87 floating point
178+
/// stack. Because `MaybeUninit` `f32`s/`f64`s are also returned on the floating point stack,
179+
/// `value` being uninitialized must not cause undefined behaviour unless `true` is passed in
180+
/// the `no_undef` argument. This method is used to avoid an LLVM bug where signalling NaNs get
181+
/// quietened when being returned on the x87 stack on 32-bit x86. For more details, see:
182+
/// * <https://github.com/rust-lang/rust/issues/115567>
183+
/// * <https://github.com/llvm/llvm-project/issues/66803>
184+
fn x87_lossless_fp_stack_to_float(
185+
&mut self,
186+
value: Self::Value,
187+
float_type: Self::Type,
188+
no_undef: bool,
189+
) -> Self::Value {
190+
let _ = float_type;
191+
let _ = no_undef;
192+
// Default to leaving the value unchanged. The backend can override this method if it needs
193+
// extra codegen to avoid quietening signalling NaNs.
194+
value
195+
}
160196
}
161197

162198
pub trait TypeCodegenMethods<'tcx> = DerivedTypeCodegenMethods<'tcx>

0 commit comments

Comments
 (0)