Skip to content

Commit 0d6b52c

Browse files
author
Robin Kruppe
committed
Saturating casts between integers and floats (both directions).
This affects regular code generation as well as constant evaluation in trans, but not the HIR constant evaluator because that one returns an error for overflowing casts and NaN-to-int casts. That error is conservatively correct and we should be careful to not accept more code in constant expressions. The changes to code generation are guarded by a new -Z flag, to be able to evaluate the performance impact. The trans constant evaluation changes are unconditional because they have no run time impact and don't affect type checking either.
1 parent 7ade24f commit 0d6b52c

File tree

10 files changed

+453
-13
lines changed

10 files changed

+453
-13
lines changed

src/Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/librustc/session/config.rs

+3
Original file line numberDiff line numberDiff line change
@@ -1107,6 +1107,9 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options,
11071107
"control whether #[inline] functions are in all cgus"),
11081108
tls_model: Option<String> = (None, parse_opt_string, [TRACKED],
11091109
"choose the TLS model to use (rustc --print tls-models for details)"),
1110+
saturating_float_casts: bool = (false, parse_bool, [TRACKED],
1111+
"make casts between integers and floats safe: clip out-of-range inputs to the min/max \
1112+
integer or to infinity respectively, and turn `NAN` into 0 when casting to integers"),
11101113
}
11111114

11121115
pub fn default_lib_output() -> CrateType {

src/librustc_llvm/ffi.rs

+2
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,8 @@ extern "C" {
628628
pub fn LLVMConstIntGetSExtValue(ConstantVal: ValueRef) -> c_longlong;
629629
pub fn LLVMRustConstInt128Get(ConstantVal: ValueRef, SExt: bool,
630630
high: *mut u64, low: *mut u64) -> bool;
631+
pub fn LLVMRustIsConstantFP(ConstantVal: ValueRef) -> bool;
632+
pub fn LLVMRustConstFloatGetBits(ConstantVal: ValueRef) -> u64;
631633

632634

633635
// Operations on composite constants

src/librustc_trans/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ owning_ref = "0.3.3"
1919
rustc-demangle = "0.1.4"
2020
rustc = { path = "../librustc" }
2121
rustc_allocator = { path = "../librustc_allocator" }
22+
rustc_apfloat = { path = "../librustc_apfloat" }
2223
rustc_back = { path = "../librustc_back" }
2324
rustc_const_math = { path = "../librustc_const_math" }
2425
rustc_data_structures = { path = "../librustc_data_structures" }

src/librustc_trans/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#![feature(custom_attribute)]
2525
#![allow(unused_attributes)]
2626
#![feature(i128_type)]
27+
#![feature(i128)]
2728
#![feature(libc)]
2829
#![feature(quote)]
2930
#![feature(rustc_diagnostic_macros)]
@@ -43,6 +44,7 @@ extern crate libc;
4344
extern crate owning_ref;
4445
#[macro_use] extern crate rustc;
4546
extern crate rustc_allocator;
47+
extern crate rustc_apfloat;
4648
extern crate rustc_back;
4749
extern crate rustc_data_structures;
4850
extern crate rustc_incremental;

src/librustc_trans/mir/constant.rs

+49-7
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use rustc::ty::{self, Ty, TyCtxt, TypeFoldable};
2121
use rustc::ty::layout::{self, LayoutTyper};
2222
use rustc::ty::cast::{CastTy, IntTy};
2323
use rustc::ty::subst::{Kind, Substs, Subst};
24+
use rustc_apfloat::{ieee, Float};
2425
use rustc_data_structures::indexed_vec::{Idx, IndexVec};
2526
use {adt, base, machine};
2627
use abi::{self, Abi};
@@ -689,20 +690,16 @@ impl<'a, 'tcx> MirConstContext<'a, 'tcx> {
689690
llvm::LLVMConstIntCast(llval, ll_t_out.to_ref(), s)
690691
}
691692
(CastTy::Int(_), CastTy::Float) => {
692-
if signed {
693-
llvm::LLVMConstSIToFP(llval, ll_t_out.to_ref())
694-
} else {
695-
llvm::LLVMConstUIToFP(llval, ll_t_out.to_ref())
696-
}
693+
const_cast_int_to_float(self.ccx, llval, signed, ll_t_out)
697694
}
698695
(CastTy::Float, CastTy::Float) => {
699696
llvm::LLVMConstFPCast(llval, ll_t_out.to_ref())
700697
}
701698
(CastTy::Float, CastTy::Int(IntTy::I)) => {
702-
llvm::LLVMConstFPToSI(llval, ll_t_out.to_ref())
699+
const_cast_from_float(&operand, true, ll_t_out)
703700
}
704701
(CastTy::Float, CastTy::Int(_)) => {
705-
llvm::LLVMConstFPToUI(llval, ll_t_out.to_ref())
702+
const_cast_from_float(&operand, false, ll_t_out)
706703
}
707704
(CastTy::Ptr(_), CastTy::Ptr(_)) |
708705
(CastTy::FnPtr, CastTy::Ptr(_)) |
@@ -955,6 +952,51 @@ pub fn const_scalar_checked_binop<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
955952
}
956953
}
957954

955+
unsafe fn const_cast_from_float(operand: &Const, signed: bool, int_ty: Type) -> ValueRef {
956+
let llval = operand.llval;
957+
// Note: this breaks if addresses can be turned into integers (is that possible?)
958+
// But at least an ICE is better than producing undef.
959+
assert!(llvm::LLVMRustIsConstantFP(llval),
960+
"const_cast_from_float: invalid llval {:?}", Value(llval));
961+
let bits = llvm::LLVMRustConstFloatGetBits(llval) as u128;
962+
let int_width = int_ty.int_width() as usize;
963+
let float_bits = match operand.ty.sty {
964+
ty::TyFloat(fty) => fty.bit_width(),
965+
_ => bug!("const_cast_from_float: operand not a float"),
966+
};
967+
// Ignore the Status, to_i128 does the Right Thing(tm) on overflow and NaN even though it
968+
// sets INVALID_OP.
969+
let cast_result = match float_bits {
970+
32 if signed => ieee::Single::from_bits(bits).to_i128(int_width).value as u128,
971+
64 if signed => ieee::Double::from_bits(bits).to_i128(int_width).value as u128,
972+
32 => ieee::Single::from_bits(bits).to_u128(int_width).value,
973+
64 => ieee::Double::from_bits(bits).to_u128(int_width).value,
974+
n => bug!("unsupported float width {}", n),
975+
};
976+
C_big_integral(int_ty, cast_result)
977+
}
978+
979+
unsafe fn const_cast_int_to_float(ccx: &CrateContext,
980+
llval: ValueRef,
981+
signed: bool,
982+
float_ty: Type) -> ValueRef {
983+
// Note: this breaks if addresses can be turned into integers (is that possible?)
984+
// But at least an ICE is better than producing undef.
985+
let value = const_to_opt_u128(llval, signed).unwrap_or_else(|| {
986+
panic!("could not get z128 value of constant integer {:?}",
987+
Value(llval));
988+
});
989+
// If this is an u128 cast and the value is > f32::MAX + 0.5 ULP, round up to infinity.
990+
if signed {
991+
llvm::LLVMConstSIToFP(llval, float_ty.to_ref())
992+
} else if value >= 0xffffff80000000000000000000000000_u128 && float_ty.float_width() == 32 {
993+
let infinity_bits = C_u32(ccx, ieee::Single::INFINITY.to_bits() as u32);
994+
consts::bitcast(infinity_bits, float_ty)
995+
} else {
996+
llvm::LLVMConstUIToFP(llval, float_ty.to_ref())
997+
}
998+
}
999+
9581000
impl<'a, 'tcx> MirContext<'a, 'tcx> {
9591001
pub fn trans_constant(&mut self,
9601002
bcx: &Builder<'a, 'tcx>,

src/librustc_trans/mir/rvalue.rs

+176-6
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,14 @@ use rustc::ty::layout::{Layout, LayoutTyper};
1515
use rustc::mir::tcx::LvalueTy;
1616
use rustc::mir;
1717
use rustc::middle::lang_items::ExchangeMallocFnLangItem;
18+
use rustc_apfloat::{ieee, Float, Status, Round};
19+
use std::{u128, i128};
1820

1921
use base;
2022
use builder::Builder;
2123
use callee;
22-
use common::{self, val_ty, C_bool, C_i32, C_null, C_usize, C_uint};
24+
use common::{self, val_ty, C_bool, C_i32, C_u32, C_u64, C_null, C_usize, C_uint, C_big_integral};
25+
use consts;
2326
use adt;
2427
use machine;
2528
use monomorphize;
@@ -333,14 +336,12 @@ impl<'a, 'tcx> MirContext<'a, 'tcx> {
333336
bcx.ptrtoint(llval, ll_t_out),
334337
(CastTy::Int(_), CastTy::Ptr(_)) =>
335338
bcx.inttoptr(llval, ll_t_out),
336-
(CastTy::Int(_), CastTy::Float) if signed =>
337-
bcx.sitofp(llval, ll_t_out),
338339
(CastTy::Int(_), CastTy::Float) =>
339-
bcx.uitofp(llval, ll_t_out),
340+
cast_int_to_float(&bcx, signed, llval, ll_t_in, ll_t_out),
340341
(CastTy::Float, CastTy::Int(IntTy::I)) =>
341-
bcx.fptosi(llval, ll_t_out),
342+
cast_float_to_int(&bcx, true, llval, ll_t_in, ll_t_out),
342343
(CastTy::Float, CastTy::Int(_)) =>
343-
bcx.fptoui(llval, ll_t_out),
344+
cast_float_to_int(&bcx, false, llval, ll_t_in, ll_t_out),
344345
_ => bug!("unsupported cast: {:?} to {:?}", operand.ty, cast_ty)
345346
};
346347
OperandValue::Immediate(newval)
@@ -815,3 +816,172 @@ fn get_overflow_intrinsic(oop: OverflowOp, bcx: &Builder, ty: Ty) -> ValueRef {
815816

816817
bcx.ccx.get_intrinsic(&name)
817818
}
819+
820+
fn cast_int_to_float(bcx: &Builder,
821+
signed: bool,
822+
x: ValueRef,
823+
int_ty: Type,
824+
float_ty: Type) -> ValueRef {
825+
// Most integer types, even i128, fit into [-f32::MAX, f32::MAX] after rounding.
826+
// It's only u128 -> f32 that can cause overflows (i.e., should yield infinity).
827+
// LLVM's uitofp produces undef in those cases, so we manually check for that case.
828+
let is_u128_to_f32 = !signed && int_ty.int_width() == 128 && float_ty.float_width() == 32;
829+
if is_u128_to_f32 && bcx.sess().opts.debugging_opts.saturating_float_casts {
830+
// f32::MAX + 0.5 ULP as u128. All inputs greater or equal to this should be
831+
// rounded to infinity, for everything else LLVM's uitofp works just fine.
832+
let max = C_big_integral(int_ty, 0xffffff80000000000000000000000000_u128);
833+
let overflow = bcx.icmp(llvm::IntUGE, x, max);
834+
let infinity_bits = C_u32(bcx.ccx, ieee::Single::INFINITY.to_bits() as u32);
835+
let infinity = consts::bitcast(infinity_bits, float_ty);
836+
bcx.select(overflow, infinity, bcx.uitofp(x, float_ty))
837+
} else {
838+
if signed {
839+
bcx.sitofp(x, float_ty)
840+
} else {
841+
bcx.uitofp(x, float_ty)
842+
}
843+
}
844+
}
845+
846+
fn cast_float_to_int(bcx: &Builder,
847+
signed: bool,
848+
x: ValueRef,
849+
float_ty: Type,
850+
int_ty: Type) -> ValueRef {
851+
if !bcx.sess().opts.debugging_opts.saturating_float_casts {
852+
if signed {
853+
return bcx.fptosi(x, int_ty);
854+
} else {
855+
return bcx.fptoui(x, int_ty);
856+
}
857+
}
858+
// LLVM's fpto[su]i returns undef when the input x is infinite, NaN, or does not fit into the
859+
// destination integer type after rounding towards zero. This `undef` value can cause UB in
860+
// safe code (see issue #10184), so we implement a saturating conversion on top of it:
861+
// Semantically, the mathematical value of the input is rounded towards zero to the next
862+
// mathematical integer, and then the result is clamped into the range of the destination
863+
// integer type. Positive and negative infinity are mapped to the maximum and minimum value of
864+
// the destination integer type. NaN is mapped to 0.
865+
//
866+
// Define f_min and f_max as the largest and smallest (finite) floats that are exactly equal to
867+
// a value representable in int_ty.
868+
// They are exactly equal to int_ty::{MIN,MAX} if float_ty has enough significand bits.
869+
// Otherwise, int_ty::MAX must be rounded towards zero, as it is one less than a power of two.
870+
// int_ty::MIN, however, is either zero or a negative power of two and is thus exactly
871+
// representable. Note that this only works if float_ty's exponent range is sufficently large.
872+
// f16 or 256 bit integers would break this property. Right now the smallest float type is f32
873+
// with exponents ranging up to 127, which is barely enough for i128::MIN = -2^127.
874+
// On the other hand, f_max works even if int_ty::MAX is greater than float_ty::MAX. Because
875+
// we're rounding towards zero, we just get float_ty::MAX (which is always an integer).
876+
// This already happens today with u128::MAX = 2^128 - 1 > f32::MAX.
877+
fn compute_clamp_bounds<F: Float>(signed: bool, int_ty: Type) -> (u128, u128, Status) {
878+
let f_min = if signed {
879+
let int_min = i128::MIN >> (128 - int_ty.int_width());
880+
let rounded_min = F::from_i128_r(int_min, Round::TowardZero);
881+
assert_eq!(rounded_min.status, Status::OK);
882+
rounded_min.value
883+
} else {
884+
F::ZERO
885+
};
886+
887+
let rounded_max = F::from_u128_r(int_max(signed, int_ty), Round::TowardZero);
888+
assert!(rounded_max.value.is_finite());
889+
890+
(f_min.to_bits(), rounded_max.value.to_bits(), rounded_max.status)
891+
}
892+
fn int_max(signed: bool, int_ty: Type) -> u128 {
893+
let shift_amount = 128 - int_ty.int_width();
894+
if signed {
895+
i128::MAX as u128 >> shift_amount
896+
} else {
897+
u128::MAX >> shift_amount
898+
}
899+
}
900+
let (f_min, f_max, f_max_status) = match float_ty.float_width() {
901+
32 => compute_clamp_bounds::<ieee::Single>(signed, int_ty),
902+
64 => compute_clamp_bounds::<ieee::Double>(signed, int_ty),
903+
n => bug!("unsupported float width {}", n),
904+
};
905+
let float_bits_to_llval = |bits| {
906+
let bits_llval = match float_ty.float_width() {
907+
32 => C_u32(bcx.ccx, bits as u32),
908+
64 => C_u64(bcx.ccx, bits as u64),
909+
n => bug!("unsupported float width {}", n),
910+
};
911+
consts::bitcast(bits_llval, float_ty)
912+
};
913+
let f_min = float_bits_to_llval(f_min);
914+
let f_max = float_bits_to_llval(f_max);
915+
// To implement saturation, we perform the following steps (not all steps are necessary for
916+
// all combinations of int_ty and float_ty, but we'll deal with that below):
917+
//
918+
// 1. Clamp x into the range [f_min, f_max] in such a way that NaN becomes f_min.
919+
// 2. If x is NaN, replace the result of the clamping with 0.0, otherwise
920+
// keep the clamping result.
921+
// 3. Now cast the result of step 2 with fpto[su]i.
922+
// 4. If x > f_max, return int_ty::MAX, otherwise return the result of step 3.
923+
//
924+
// This avoids undef because values in range [f_min, f_max] by definition fit into the
925+
// destination type. More importantly, it correctly implements saturating conversion.
926+
// Proof (sketch):
927+
// If x is NaN, step 2 yields 0.0, which is converted to 0 in step 3, and NaN > f_max does
928+
// not hold in step 4, therefore 0 is returned, as desired.
929+
// Otherwise, x is finite or infinite and thus can be compared with f_min and f_max.
930+
// This yields three cases to consider:
931+
// (1) if x in [f_min, f_max], steps 1, 2, and 4 do nothing and the result of fpto[su]i
932+
// is returned, which agrees with saturating conversion for inputs in that range.
933+
// (2) if x > f_max, then x is larger than int_ty::MAX and step 4 correctly returns
934+
// int_ty::MAX. This holds even if f_max is rounded (i.e., if f_max < int_ty::MAX)
935+
// because in those cases, nextUp(f_max) is already larger than int_ty::MAX.
936+
// (3) if x < f_min, then x is smaller than int_ty::MIN and is clamped to f_min. As shown
937+
// earlier, f_min exactly equals int_ty::MIN and therefore no fixup analogous to step 4
938+
// is needed. Instead, step 3 casts f_min to int_ty::MIN and step 4 returns this cast
939+
// result, as desired.
940+
// QED.
941+
942+
// Step 1: Clamping. Computed as:
943+
// clamped_to_min = if f_min < x { x } else { f_min };
944+
// clamped_x = if f_max < clamped_to_min { f_max } else { clamped_to_min };
945+
// Note that for x = NaN, both of the above variables become f_min.
946+
let clamped_to_min = bcx.select(bcx.fcmp(llvm::RealOLT, f_min, x), x, f_min);
947+
let clamped_x = bcx.select(
948+
bcx.fcmp(llvm::RealOLT, f_max, clamped_to_min),
949+
f_max,
950+
clamped_to_min
951+
);
952+
953+
// Step 2: NaN replacement.
954+
// For unsigned types, f_min == 0.0 and therefore clamped_x is already zero.
955+
// Therefore we only need to execute this step for signed integer types.
956+
let clamped_x = if signed {
957+
let zero = match float_ty.float_width() {
958+
32 => float_bits_to_llval(ieee::Single::ZERO.to_bits()),
959+
64 => float_bits_to_llval(ieee::Double::ZERO.to_bits()),
960+
n => bug!("unsupported float width {}", n),
961+
};
962+
// LLVM has no isNaN predicate, so we use (x == x) instead
963+
bcx.select(bcx.fcmp(llvm::RealOEQ, x, x), clamped_x, zero)
964+
} else {
965+
clamped_x
966+
};
967+
968+
// Step 3: fpto[su]i cast
969+
let cast_result = if signed {
970+
bcx.fptosi(clamped_x, int_ty)
971+
} else {
972+
bcx.fptoui(clamped_x, int_ty)
973+
};
974+
975+
// Step 4: f_max fixup.
976+
// Note that x > f_max implies that x was clamped to f_max in step 1, and therefore the
977+
// cast result is the integer equal to f_max. If the conversion from int_ty::MAX to f_max
978+
// was exact, then the result of casting f_max is again int_ty::MAX, so we'd return the same
979+
// value whether or not x > f_max holds. Therefore, we only need to execute this step
980+
// if f_max is inexact.
981+
if f_max_status.contains(Status::INEXACT) {
982+
let int_max = C_big_integral(int_ty, int_max(signed, int_ty));
983+
bcx.select(bcx.fcmp(llvm::RealOGT, x, f_max), int_max, cast_result)
984+
} else {
985+
cast_result
986+
}
987+
}

src/rustllvm/RustWrapper.cpp

+13
Original file line numberDiff line numberDiff line change
@@ -1373,6 +1373,19 @@ extern "C" bool LLVMRustConstInt128Get(LLVMValueRef CV, bool sext, uint64_t *hig
13731373
return true;
13741374
}
13751375

1376+
extern "C" uint64_t LLVMRustConstFloatGetBits(LLVMValueRef CV) {
1377+
auto C = unwrap<llvm::ConstantFP>(CV);
1378+
APInt Bits = C->getValueAPF().bitcastToAPInt();
1379+
if (!Bits.isIntN(64)) {
1380+
report_fatal_error("Float bit pattern >64 bits");
1381+
}
1382+
return Bits.getLimitedValue();
1383+
}
1384+
1385+
extern "C" bool LLVMRustIsConstantFP(LLVMValueRef CV) {
1386+
return isa<llvm::ConstantFP>(unwrap<llvm::Value>(CV));
1387+
}
1388+
13761389
extern "C" LLVMContextRef LLVMRustGetValueContext(LLVMValueRef V) {
13771390
return wrap(&unwrap(V)->getContext());
13781391
}

0 commit comments

Comments
 (0)