From 0d6b52c2f3d442d3edfea31bcc439127a4757e5e Mon Sep 17 00:00:00 2001 From: Robin Kruppe Date: Mon, 9 Oct 2017 02:14:00 +0200 Subject: [PATCH 1/7] 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. --- src/Cargo.lock | 1 + src/librustc/session/config.rs | 3 + src/librustc_llvm/ffi.rs | 2 + src/librustc_trans/Cargo.toml | 1 + src/librustc_trans/lib.rs | 2 + src/librustc_trans/mir/constant.rs | 56 +++++- src/librustc_trans/mir/rvalue.rs | 182 +++++++++++++++++++- src/rustllvm/RustWrapper.cpp | 13 ++ src/test/codegen/unchecked-float-casts.rs | 65 +++++++ src/test/run-pass/saturating-float-casts.rs | 141 +++++++++++++++ 10 files changed, 453 insertions(+), 13 deletions(-) create mode 100644 src/test/codegen/unchecked-float-casts.rs create mode 100644 src/test/run-pass/saturating-float-casts.rs diff --git a/src/Cargo.lock b/src/Cargo.lock index c4314b41e3966..0263c74595f26 100644 --- a/src/Cargo.lock +++ b/src/Cargo.lock @@ -1869,6 +1869,7 @@ dependencies = [ "rustc 0.0.0", "rustc-demangle 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_allocator 0.0.0", + "rustc_apfloat 0.0.0", "rustc_back 0.0.0", "rustc_const_math 0.0.0", "rustc_data_structures 0.0.0", diff --git a/src/librustc/session/config.rs b/src/librustc/session/config.rs index 9f50afde14551..4f0e3e8c58a17 100644 --- a/src/librustc/session/config.rs +++ b/src/librustc/session/config.rs @@ -1107,6 +1107,9 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options, "control whether #[inline] functions are in all cgus"), tls_model: Option = (None, parse_opt_string, [TRACKED], "choose the TLS model to use (rustc --print tls-models for details)"), + saturating_float_casts: bool = (false, parse_bool, [TRACKED], + "make casts between integers and floats safe: clip out-of-range inputs to the min/max \ + integer or to infinity respectively, and turn `NAN` into 0 when casting to integers"), } pub fn default_lib_output() -> CrateType { diff --git a/src/librustc_llvm/ffi.rs b/src/librustc_llvm/ffi.rs index ac0e4dde0c102..6f640e580c9d5 100644 --- a/src/librustc_llvm/ffi.rs +++ b/src/librustc_llvm/ffi.rs @@ -628,6 +628,8 @@ extern "C" { pub fn LLVMConstIntGetSExtValue(ConstantVal: ValueRef) -> c_longlong; pub fn LLVMRustConstInt128Get(ConstantVal: ValueRef, SExt: bool, high: *mut u64, low: *mut u64) -> bool; + pub fn LLVMRustIsConstantFP(ConstantVal: ValueRef) -> bool; + pub fn LLVMRustConstFloatGetBits(ConstantVal: ValueRef) -> u64; // Operations on composite constants diff --git a/src/librustc_trans/Cargo.toml b/src/librustc_trans/Cargo.toml index 5b7879ea58ee7..f797464c1f8f1 100644 --- a/src/librustc_trans/Cargo.toml +++ b/src/librustc_trans/Cargo.toml @@ -19,6 +19,7 @@ owning_ref = "0.3.3" rustc-demangle = "0.1.4" rustc = { path = "../librustc" } rustc_allocator = { path = "../librustc_allocator" } +rustc_apfloat = { path = "../librustc_apfloat" } rustc_back = { path = "../librustc_back" } rustc_const_math = { path = "../librustc_const_math" } rustc_data_structures = { path = "../librustc_data_structures" } diff --git a/src/librustc_trans/lib.rs b/src/librustc_trans/lib.rs index c0460fb4852e9..29394af33969f 100644 --- a/src/librustc_trans/lib.rs +++ b/src/librustc_trans/lib.rs @@ -24,6 +24,7 @@ #![feature(custom_attribute)] #![allow(unused_attributes)] #![feature(i128_type)] +#![feature(i128)] #![feature(libc)] #![feature(quote)] #![feature(rustc_diagnostic_macros)] @@ -43,6 +44,7 @@ extern crate libc; extern crate owning_ref; #[macro_use] extern crate rustc; extern crate rustc_allocator; +extern crate rustc_apfloat; extern crate rustc_back; extern crate rustc_data_structures; extern crate rustc_incremental; diff --git a/src/librustc_trans/mir/constant.rs b/src/librustc_trans/mir/constant.rs index cea7b9585d8e3..7fff68abbbedb 100644 --- a/src/librustc_trans/mir/constant.rs +++ b/src/librustc_trans/mir/constant.rs @@ -21,6 +21,7 @@ use rustc::ty::{self, Ty, TyCtxt, TypeFoldable}; use rustc::ty::layout::{self, LayoutTyper}; use rustc::ty::cast::{CastTy, IntTy}; use rustc::ty::subst::{Kind, Substs, Subst}; +use rustc_apfloat::{ieee, Float}; use rustc_data_structures::indexed_vec::{Idx, IndexVec}; use {adt, base, machine}; use abi::{self, Abi}; @@ -689,20 +690,16 @@ impl<'a, 'tcx> MirConstContext<'a, 'tcx> { llvm::LLVMConstIntCast(llval, ll_t_out.to_ref(), s) } (CastTy::Int(_), CastTy::Float) => { - if signed { - llvm::LLVMConstSIToFP(llval, ll_t_out.to_ref()) - } else { - llvm::LLVMConstUIToFP(llval, ll_t_out.to_ref()) - } + const_cast_int_to_float(self.ccx, llval, signed, ll_t_out) } (CastTy::Float, CastTy::Float) => { llvm::LLVMConstFPCast(llval, ll_t_out.to_ref()) } (CastTy::Float, CastTy::Int(IntTy::I)) => { - llvm::LLVMConstFPToSI(llval, ll_t_out.to_ref()) + const_cast_from_float(&operand, true, ll_t_out) } (CastTy::Float, CastTy::Int(_)) => { - llvm::LLVMConstFPToUI(llval, ll_t_out.to_ref()) + const_cast_from_float(&operand, false, ll_t_out) } (CastTy::Ptr(_), CastTy::Ptr(_)) | (CastTy::FnPtr, CastTy::Ptr(_)) | @@ -955,6 +952,51 @@ pub fn const_scalar_checked_binop<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, } } +unsafe fn const_cast_from_float(operand: &Const, signed: bool, int_ty: Type) -> ValueRef { + let llval = operand.llval; + // Note: this breaks if addresses can be turned into integers (is that possible?) + // But at least an ICE is better than producing undef. + assert!(llvm::LLVMRustIsConstantFP(llval), + "const_cast_from_float: invalid llval {:?}", Value(llval)); + let bits = llvm::LLVMRustConstFloatGetBits(llval) as u128; + let int_width = int_ty.int_width() as usize; + let float_bits = match operand.ty.sty { + ty::TyFloat(fty) => fty.bit_width(), + _ => bug!("const_cast_from_float: operand not a float"), + }; + // Ignore the Status, to_i128 does the Right Thing(tm) on overflow and NaN even though it + // sets INVALID_OP. + let cast_result = match float_bits { + 32 if signed => ieee::Single::from_bits(bits).to_i128(int_width).value as u128, + 64 if signed => ieee::Double::from_bits(bits).to_i128(int_width).value as u128, + 32 => ieee::Single::from_bits(bits).to_u128(int_width).value, + 64 => ieee::Double::from_bits(bits).to_u128(int_width).value, + n => bug!("unsupported float width {}", n), + }; + C_big_integral(int_ty, cast_result) +} + +unsafe fn const_cast_int_to_float(ccx: &CrateContext, + llval: ValueRef, + signed: bool, + float_ty: Type) -> ValueRef { + // Note: this breaks if addresses can be turned into integers (is that possible?) + // But at least an ICE is better than producing undef. + let value = const_to_opt_u128(llval, signed).unwrap_or_else(|| { + panic!("could not get z128 value of constant integer {:?}", + Value(llval)); + }); + // If this is an u128 cast and the value is > f32::MAX + 0.5 ULP, round up to infinity. + if signed { + llvm::LLVMConstSIToFP(llval, float_ty.to_ref()) + } else if value >= 0xffffff80000000000000000000000000_u128 && float_ty.float_width() == 32 { + let infinity_bits = C_u32(ccx, ieee::Single::INFINITY.to_bits() as u32); + consts::bitcast(infinity_bits, float_ty) + } else { + llvm::LLVMConstUIToFP(llval, float_ty.to_ref()) + } +} + impl<'a, 'tcx> MirContext<'a, 'tcx> { pub fn trans_constant(&mut self, bcx: &Builder<'a, 'tcx>, diff --git a/src/librustc_trans/mir/rvalue.rs b/src/librustc_trans/mir/rvalue.rs index 777b86387e8bf..1986689c77b04 100644 --- a/src/librustc_trans/mir/rvalue.rs +++ b/src/librustc_trans/mir/rvalue.rs @@ -15,11 +15,14 @@ use rustc::ty::layout::{Layout, LayoutTyper}; use rustc::mir::tcx::LvalueTy; use rustc::mir; use rustc::middle::lang_items::ExchangeMallocFnLangItem; +use rustc_apfloat::{ieee, Float, Status, Round}; +use std::{u128, i128}; use base; use builder::Builder; use callee; -use common::{self, val_ty, C_bool, C_i32, C_null, C_usize, C_uint}; +use common::{self, val_ty, C_bool, C_i32, C_u32, C_u64, C_null, C_usize, C_uint, C_big_integral}; +use consts; use adt; use machine; use monomorphize; @@ -333,14 +336,12 @@ impl<'a, 'tcx> MirContext<'a, 'tcx> { bcx.ptrtoint(llval, ll_t_out), (CastTy::Int(_), CastTy::Ptr(_)) => bcx.inttoptr(llval, ll_t_out), - (CastTy::Int(_), CastTy::Float) if signed => - bcx.sitofp(llval, ll_t_out), (CastTy::Int(_), CastTy::Float) => - bcx.uitofp(llval, ll_t_out), + cast_int_to_float(&bcx, signed, llval, ll_t_in, ll_t_out), (CastTy::Float, CastTy::Int(IntTy::I)) => - bcx.fptosi(llval, ll_t_out), + cast_float_to_int(&bcx, true, llval, ll_t_in, ll_t_out), (CastTy::Float, CastTy::Int(_)) => - bcx.fptoui(llval, ll_t_out), + cast_float_to_int(&bcx, false, llval, ll_t_in, ll_t_out), _ => bug!("unsupported cast: {:?} to {:?}", operand.ty, cast_ty) }; OperandValue::Immediate(newval) @@ -815,3 +816,172 @@ fn get_overflow_intrinsic(oop: OverflowOp, bcx: &Builder, ty: Ty) -> ValueRef { bcx.ccx.get_intrinsic(&name) } + +fn cast_int_to_float(bcx: &Builder, + signed: bool, + x: ValueRef, + int_ty: Type, + float_ty: Type) -> ValueRef { + // Most integer types, even i128, fit into [-f32::MAX, f32::MAX] after rounding. + // It's only u128 -> f32 that can cause overflows (i.e., should yield infinity). + // LLVM's uitofp produces undef in those cases, so we manually check for that case. + let is_u128_to_f32 = !signed && int_ty.int_width() == 128 && float_ty.float_width() == 32; + if is_u128_to_f32 && bcx.sess().opts.debugging_opts.saturating_float_casts { + // f32::MAX + 0.5 ULP as u128. All inputs greater or equal to this should be + // rounded to infinity, for everything else LLVM's uitofp works just fine. + let max = C_big_integral(int_ty, 0xffffff80000000000000000000000000_u128); + let overflow = bcx.icmp(llvm::IntUGE, x, max); + let infinity_bits = C_u32(bcx.ccx, ieee::Single::INFINITY.to_bits() as u32); + let infinity = consts::bitcast(infinity_bits, float_ty); + bcx.select(overflow, infinity, bcx.uitofp(x, float_ty)) + } else { + if signed { + bcx.sitofp(x, float_ty) + } else { + bcx.uitofp(x, float_ty) + } + } +} + +fn cast_float_to_int(bcx: &Builder, + signed: bool, + x: ValueRef, + float_ty: Type, + int_ty: Type) -> ValueRef { + if !bcx.sess().opts.debugging_opts.saturating_float_casts { + if signed { + return bcx.fptosi(x, int_ty); + } else { + return bcx.fptoui(x, int_ty); + } + } + // LLVM's fpto[su]i returns undef when the input x is infinite, NaN, or does not fit into the + // destination integer type after rounding towards zero. This `undef` value can cause UB in + // safe code (see issue #10184), so we implement a saturating conversion on top of it: + // Semantically, the mathematical value of the input is rounded towards zero to the next + // mathematical integer, and then the result is clamped into the range of the destination + // integer type. Positive and negative infinity are mapped to the maximum and minimum value of + // the destination integer type. NaN is mapped to 0. + // + // Define f_min and f_max as the largest and smallest (finite) floats that are exactly equal to + // a value representable in int_ty. + // They are exactly equal to int_ty::{MIN,MAX} if float_ty has enough significand bits. + // Otherwise, int_ty::MAX must be rounded towards zero, as it is one less than a power of two. + // int_ty::MIN, however, is either zero or a negative power of two and is thus exactly + // representable. Note that this only works if float_ty's exponent range is sufficently large. + // f16 or 256 bit integers would break this property. Right now the smallest float type is f32 + // with exponents ranging up to 127, which is barely enough for i128::MIN = -2^127. + // On the other hand, f_max works even if int_ty::MAX is greater than float_ty::MAX. Because + // we're rounding towards zero, we just get float_ty::MAX (which is always an integer). + // This already happens today with u128::MAX = 2^128 - 1 > f32::MAX. + fn compute_clamp_bounds(signed: bool, int_ty: Type) -> (u128, u128, Status) { + let f_min = if signed { + let int_min = i128::MIN >> (128 - int_ty.int_width()); + let rounded_min = F::from_i128_r(int_min, Round::TowardZero); + assert_eq!(rounded_min.status, Status::OK); + rounded_min.value + } else { + F::ZERO + }; + + let rounded_max = F::from_u128_r(int_max(signed, int_ty), Round::TowardZero); + assert!(rounded_max.value.is_finite()); + + (f_min.to_bits(), rounded_max.value.to_bits(), rounded_max.status) + } + fn int_max(signed: bool, int_ty: Type) -> u128 { + let shift_amount = 128 - int_ty.int_width(); + if signed { + i128::MAX as u128 >> shift_amount + } else { + u128::MAX >> shift_amount + } + } + let (f_min, f_max, f_max_status) = match float_ty.float_width() { + 32 => compute_clamp_bounds::(signed, int_ty), + 64 => compute_clamp_bounds::(signed, int_ty), + n => bug!("unsupported float width {}", n), + }; + let float_bits_to_llval = |bits| { + let bits_llval = match float_ty.float_width() { + 32 => C_u32(bcx.ccx, bits as u32), + 64 => C_u64(bcx.ccx, bits as u64), + n => bug!("unsupported float width {}", n), + }; + consts::bitcast(bits_llval, float_ty) + }; + let f_min = float_bits_to_llval(f_min); + let f_max = float_bits_to_llval(f_max); + // To implement saturation, we perform the following steps (not all steps are necessary for + // all combinations of int_ty and float_ty, but we'll deal with that below): + // + // 1. Clamp x into the range [f_min, f_max] in such a way that NaN becomes f_min. + // 2. If x is NaN, replace the result of the clamping with 0.0, otherwise + // keep the clamping result. + // 3. Now cast the result of step 2 with fpto[su]i. + // 4. If x > f_max, return int_ty::MAX, otherwise return the result of step 3. + // + // This avoids undef because values in range [f_min, f_max] by definition fit into the + // destination type. More importantly, it correctly implements saturating conversion. + // Proof (sketch): + // If x is NaN, step 2 yields 0.0, which is converted to 0 in step 3, and NaN > f_max does + // not hold in step 4, therefore 0 is returned, as desired. + // Otherwise, x is finite or infinite and thus can be compared with f_min and f_max. + // This yields three cases to consider: + // (1) if x in [f_min, f_max], steps 1, 2, and 4 do nothing and the result of fpto[su]i + // is returned, which agrees with saturating conversion for inputs in that range. + // (2) if x > f_max, then x is larger than int_ty::MAX and step 4 correctly returns + // int_ty::MAX. This holds even if f_max is rounded (i.e., if f_max < int_ty::MAX) + // because in those cases, nextUp(f_max) is already larger than int_ty::MAX. + // (3) if x < f_min, then x is smaller than int_ty::MIN and is clamped to f_min. As shown + // earlier, f_min exactly equals int_ty::MIN and therefore no fixup analogous to step 4 + // is needed. Instead, step 3 casts f_min to int_ty::MIN and step 4 returns this cast + // result, as desired. + // QED. + + // Step 1: Clamping. Computed as: + // clamped_to_min = if f_min < x { x } else { f_min }; + // clamped_x = if f_max < clamped_to_min { f_max } else { clamped_to_min }; + // Note that for x = NaN, both of the above variables become f_min. + let clamped_to_min = bcx.select(bcx.fcmp(llvm::RealOLT, f_min, x), x, f_min); + let clamped_x = bcx.select( + bcx.fcmp(llvm::RealOLT, f_max, clamped_to_min), + f_max, + clamped_to_min + ); + + // Step 2: NaN replacement. + // For unsigned types, f_min == 0.0 and therefore clamped_x is already zero. + // Therefore we only need to execute this step for signed integer types. + let clamped_x = if signed { + let zero = match float_ty.float_width() { + 32 => float_bits_to_llval(ieee::Single::ZERO.to_bits()), + 64 => float_bits_to_llval(ieee::Double::ZERO.to_bits()), + n => bug!("unsupported float width {}", n), + }; + // LLVM has no isNaN predicate, so we use (x == x) instead + bcx.select(bcx.fcmp(llvm::RealOEQ, x, x), clamped_x, zero) + } else { + clamped_x + }; + + // Step 3: fpto[su]i cast + let cast_result = if signed { + bcx.fptosi(clamped_x, int_ty) + } else { + bcx.fptoui(clamped_x, int_ty) + }; + + // Step 4: f_max fixup. + // Note that x > f_max implies that x was clamped to f_max in step 1, and therefore the + // cast result is the integer equal to f_max. If the conversion from int_ty::MAX to f_max + // was exact, then the result of casting f_max is again int_ty::MAX, so we'd return the same + // value whether or not x > f_max holds. Therefore, we only need to execute this step + // if f_max is inexact. + if f_max_status.contains(Status::INEXACT) { + let int_max = C_big_integral(int_ty, int_max(signed, int_ty)); + bcx.select(bcx.fcmp(llvm::RealOGT, x, f_max), int_max, cast_result) + } else { + cast_result + } +} diff --git a/src/rustllvm/RustWrapper.cpp b/src/rustllvm/RustWrapper.cpp index 20ea8d7030220..db6802320b3c7 100644 --- a/src/rustllvm/RustWrapper.cpp +++ b/src/rustllvm/RustWrapper.cpp @@ -1373,6 +1373,19 @@ extern "C" bool LLVMRustConstInt128Get(LLVMValueRef CV, bool sext, uint64_t *hig return true; } +extern "C" uint64_t LLVMRustConstFloatGetBits(LLVMValueRef CV) { + auto C = unwrap(CV); + APInt Bits = C->getValueAPF().bitcastToAPInt(); + if (!Bits.isIntN(64)) { + report_fatal_error("Float bit pattern >64 bits"); + } + return Bits.getLimitedValue(); +} + +extern "C" bool LLVMRustIsConstantFP(LLVMValueRef CV) { + return isa(unwrap(CV)); +} + extern "C" LLVMContextRef LLVMRustGetValueContext(LLVMValueRef V) { return wrap(&unwrap(V)->getContext()); } diff --git a/src/test/codegen/unchecked-float-casts.rs b/src/test/codegen/unchecked-float-casts.rs new file mode 100644 index 0000000000000..64ab19cceee2d --- /dev/null +++ b/src/test/codegen/unchecked-float-casts.rs @@ -0,0 +1,65 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// compile-flags: -C no-prepopulate-passes + +// This file tests that we don't generate any code for saturation if +// -Z saturating-float-casts is not enabled. + +#![crate_type = "lib"] +#![feature(i128_type)] + +// CHECK-LABEL: @f32_to_u32 +#[no_mangle] +pub fn f32_to_u32(x: f32) -> u32 { + // CHECK: fptoui + // CHECK-NOT: fcmp + // CHECK-NOT: icmp + // CHECK-NOT: select + x as u32 +} + +// CHECK-LABEL: @f32_to_i32 +#[no_mangle] +pub fn f32_to_i32(x: f32) -> i32 { + // CHECK: fptosi + // CHECK-NOT: fcmp + // CHECK-NOT: icmp + // CHECK-NOT: select + x as i32 +} + +#[no_mangle] +pub fn f64_to_u8(x: f32) -> u16 { + // CHECK-NOT: fcmp + // CHECK-NOT: icmp + // CHECK-NOT: select + x as u16 +} + +// CHECK-LABEL: @i32_to_f64 +#[no_mangle] +pub fn i32_to_f64(x: i32) -> f64 { + // CHECK: sitofp + // CHECK-NOT: fcmp + // CHECK-NOT: icmp + // CHECK-NOT: select + x as f64 +} + +// CHECK-LABEL: @u128_to_f32 +#[no_mangle] +pub fn u128_to_f32(x: u128) -> f32 { + // CHECK: uitofp + // CHECK-NOT: fcmp + // CHECK-NOT: icmp + // CHECK-NOT: select + x as f32 +} diff --git a/src/test/run-pass/saturating-float-casts.rs b/src/test/run-pass/saturating-float-casts.rs new file mode 100644 index 0000000000000..53e0cea64e985 --- /dev/null +++ b/src/test/run-pass/saturating-float-casts.rs @@ -0,0 +1,141 @@ +// Copyright 2012 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// compile-flags: -Z saturating-float-casts + +#![feature(test, i128, i128_type, stmt_expr_attributes)] +#![deny(overflowing_literals)] +extern crate test; + +use std::{f32, f64}; +use std::{u8, i8, u16, i16, u32, i32, u64, i64, u128, i128}; +use test::black_box; + +macro_rules! test { + ($val:expr, $src_ty:ident -> $dest_ty:ident, $expected:expr) => ( + // black_box disables constant evaluation to test run-time conversions: + assert_eq!(black_box::<$src_ty>($val) as $dest_ty, $expected, + "run time {} -> {}", stringify!($src_ty), stringify!($dest_ty)); + // ... whereas this variant triggers constant evaluation: + { + const X: $src_ty = $val; + const Y: $dest_ty = X as $dest_ty; + assert_eq!(Y, $expected, + "const eval {} -> {}", stringify!($src_ty), stringify!($dest_ty)); + } + ); + + ($fval:expr, f* -> $ity:ident, $ival:expr) => ( + test!($fval, f32 -> $ity, $ival); + test!($fval, f64 -> $ity, $ival); + ) +} + +macro_rules! common_fptoi_tests { + ($fty:ident -> $($ity:ident)+) => ({ $( + test!($fty::NAN, $fty -> $ity, 0); + test!($fty::INFINITY, $fty -> $ity, $ity::MAX); + test!($fty::NEG_INFINITY, $fty -> $ity, $ity::MIN); + // These two tests are not solely float->int tests, in particular the latter relies on + // `u128::MAX as f32` not being UB. But that's okay, since this file tests int->float + // as well, the test is just slightly misplaced. + test!($ity::MIN as $fty, $fty -> $ity, $ity::MIN); + test!($ity::MAX as $fty, $fty -> $ity, $ity::MAX); + test!(0., $fty -> $ity, 0); + test!($fty::MIN_POSITIVE, $fty -> $ity, 0); + test!(-0.9, $fty -> $ity, 0); + test!(1., $fty -> $ity, 1); + test!(42., $fty -> $ity, 42); + )+ }); + + (f* -> $($ity:ident)+) => ({ + common_fptoi_tests!(f32 -> $($ity)+); + common_fptoi_tests!(f64 -> $($ity)+); + }) +} + +macro_rules! fptoui_tests { + ($fty: ident -> $($ity: ident)+) => ({ $( + test!(-0., $fty -> $ity, 0); + test!(-$fty::MIN_POSITIVE, $fty -> $ity, 0); + test!(-0.99999994, $fty -> $ity, 0); + test!(-1., $fty -> $ity, 0); + test!(-100., $fty -> $ity, 0); + test!(#[allow(overflowing_literals)] -1e50, $fty -> $ity, 0); + test!(#[allow(overflowing_literals)] -1e130, $fty -> $ity, 0); + )+ }); + + (f* -> $($ity:ident)+) => ({ + fptoui_tests!(f32 -> $($ity)+); + fptoui_tests!(f64 -> $($ity)+); + }) +} + +pub fn main() { + common_fptoi_tests!(f* -> i8 i16 i32 i64 i128 u8 u16 u32 u64 u128); + fptoui_tests!(f* -> u8 u16 u32 u64 u128); + + // The following tests cover edge cases for some integer types. + + // u8 + test!(254., f* -> u8, 254); + test!(256., f* -> u8, 255); + + // i8 + test!(-127., f* -> i8, -127); + test!(-129., f* -> i8, -128); + test!(126., f* -> i8, 126); + test!(128., f* -> i8, 127); + + // i32 + // -2147483648. is i32::MIN (exactly) + test!(-2147483648., f* -> i32, i32::MIN); + // 2147483648. is i32::MAX rounded up + test!(2147483648., f32 -> i32, 2147483647); + // With 24 significand bits, floats with magnitude in [2^30 + 1, 2^31] are rounded to + // multiples of 2^7. Therefore, nextDown(round(i32::MAX)) is 2^31 - 128: + test!(2147483520., f32 -> i32, 2147483520); + // Similarly, nextUp(i32::MIN) is i32::MIN + 2^8 and nextDown(i32::MIN) is i32::MIN - 2^7 + test!(-2147483904., f* -> i32, i32::MIN); + test!(-2147483520., f* -> i32, -2147483520); + + // u32 -- round(MAX) and nextUp(round(MAX)) + test!(4294967040., f* -> u32, 4294967040); + test!(4294967296., f* -> u32, 4294967295); + + // u128 + // # float->int + test!(f32::MAX, f32 -> u128, 0xffffff00000000000000000000000000); + // nextDown(f32::MAX) = 2^128 - 2 * 2^104 + const SECOND_LARGEST_F32: f32 = 340282326356119256160033759537265639424.; + test!(SECOND_LARGEST_F32, f32 -> u128, 0xfffffe00000000000000000000000000); + // # int->float + // f32::MAX - 0.5 ULP and smaller should be rounded down + test!(0xfffffe00000000000000000000000000, u128 -> f32, SECOND_LARGEST_F32); + test!(0xfffffe7fffffffffffffffffffffffff, u128 -> f32, SECOND_LARGEST_F32); + test!(0xfffffe80000000000000000000000000, u128 -> f32, SECOND_LARGEST_F32); + // numbers within < 0.5 ULP of f32::MAX it should be rounded to f32::MAX + test!(0xfffffe80000000000000000000000001, u128 -> f32, f32::MAX); + test!(0xfffffeffffffffffffffffffffffffff, u128 -> f32, f32::MAX); + test!(0xffffff00000000000000000000000000, u128 -> f32, f32::MAX); + test!(0xffffff00000000000000000000000001, u128 -> f32, f32::MAX); + test!(0xffffff7fffffffffffffffffffffffff, u128 -> f32, f32::MAX); + // f32::MAX + 0.5 ULP and greater should be rounded to infinity + test!(0xffffff80000000000000000000000000, u128 -> f32, f32::INFINITY); + test!(0xffffff80000000f00000000000000000, u128 -> f32, f32::INFINITY); + test!(0xffffff87ffffffffffffffff00000001, u128 -> f32, f32::INFINITY); + + test!(!0, u128 -> f32, f32::INFINITY); + + // u128->f64 should not be affected by the u128->f32 checks + test!(0xffffff80000000000000000000000000, u128 -> f64, + 340282356779733661637539395458142568448.0); + test!(u128::MAX, u128 -> f64, 340282366920938463463374607431768211455.0); +} From 964ba2a6e731f79d99248d1f0e67eac17e69e368 Mon Sep 17 00:00:00 2001 From: Robin Kruppe Date: Sun, 15 Oct 2017 20:10:42 +0200 Subject: [PATCH 2/7] Fix bug in rustc_apfloat --- src/librustc_apfloat/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/librustc_apfloat/lib.rs b/src/librustc_apfloat/lib.rs index 9e348f6222329..2048127222bb7 100644 --- a/src/librustc_apfloat/lib.rs +++ b/src/librustc_apfloat/lib.rs @@ -378,7 +378,7 @@ pub trait Float fn from_bits(input: u128) -> Self; fn from_i128_r(input: i128, round: Round) -> StatusAnd { if input < 0 { - Self::from_u128_r(-input as u128, -round).map(|r| -r) + Self::from_u128_r(input.wrapping_neg() as u128, -round).map(|r| -r) } else { Self::from_u128_r(input as u128, round) } From e999e7b8b2e35a495d6b9630ab987c0703c6ab48 Mon Sep 17 00:00:00 2001 From: Robin Kruppe Date: Sun, 15 Oct 2017 21:37:09 +0200 Subject: [PATCH 3/7] Extract (f32::MAX + 0.5 ULP) constant --- src/librustc_const_math/float.rs | 8 ++++++++ src/librustc_trans/mir/constant.rs | 6 +++--- src/librustc_trans/mir/rvalue.rs | 7 ++++--- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/librustc_const_math/float.rs b/src/librustc_const_math/float.rs index b67048939e43e..9d820ea8cbed2 100644 --- a/src/librustc_const_math/float.rs +++ b/src/librustc_const_math/float.rs @@ -203,3 +203,11 @@ impl ::std::ops::Neg for ConstFloat { ConstFloat { bits, ty: self.ty } } } + +/// This is `f32::MAX + (0.5 ULP)` as an integer. Numbers greater or equal to this +/// are rounded to infinity when converted to `f32`. +/// +/// NB: Computed as maximum significand with an extra 1 bit added (for the half ULP) +/// shifted by the maximum exponent (accounting for normalization). +pub const MAX_F32_PLUS_HALF_ULP: u128 = ((1 << (Single::PRECISION + 1)) - 1) + << (Single::MAX_EXP - Single::PRECISION as i16); diff --git a/src/librustc_trans/mir/constant.rs b/src/librustc_trans/mir/constant.rs index 7fff68abbbedb..6b2342e493321 100644 --- a/src/librustc_trans/mir/constant.rs +++ b/src/librustc_trans/mir/constant.rs @@ -11,7 +11,7 @@ use llvm::{self, ValueRef}; use rustc::middle::const_val::{ConstEvalErr, ConstVal, ErrKind}; use rustc_const_math::ConstInt::*; -use rustc_const_math::{ConstInt, ConstMathErr}; +use rustc_const_math::{ConstInt, ConstMathErr, MAX_F32_PLUS_HALF_ULP}; use rustc::hir::def_id::DefId; use rustc::infer::TransNormalize; use rustc::traits; @@ -986,10 +986,10 @@ unsafe fn const_cast_int_to_float(ccx: &CrateContext, panic!("could not get z128 value of constant integer {:?}", Value(llval)); }); - // If this is an u128 cast and the value is > f32::MAX + 0.5 ULP, round up to infinity. if signed { llvm::LLVMConstSIToFP(llval, float_ty.to_ref()) - } else if value >= 0xffffff80000000000000000000000000_u128 && float_ty.float_width() == 32 { + } else if float_ty.float_width() == 32 && value >= MAX_F32_PLUS_HALF_ULP { + // We're casting to f32 and the value is > f32::MAX + 0.5 ULP -> round up to infinity. let infinity_bits = C_u32(ccx, ieee::Single::INFINITY.to_bits() as u32); consts::bitcast(infinity_bits, float_ty) } else { diff --git a/src/librustc_trans/mir/rvalue.rs b/src/librustc_trans/mir/rvalue.rs index 1986689c77b04..d16b2048c3fba 100644 --- a/src/librustc_trans/mir/rvalue.rs +++ b/src/librustc_trans/mir/rvalue.rs @@ -16,6 +16,7 @@ use rustc::mir::tcx::LvalueTy; use rustc::mir; use rustc::middle::lang_items::ExchangeMallocFnLangItem; use rustc_apfloat::{ieee, Float, Status, Round}; +use rustc_const_math::MAX_F32_PLUS_HALF_ULP; use std::{u128, i128}; use base; @@ -827,9 +828,9 @@ fn cast_int_to_float(bcx: &Builder, // LLVM's uitofp produces undef in those cases, so we manually check for that case. let is_u128_to_f32 = !signed && int_ty.int_width() == 128 && float_ty.float_width() == 32; if is_u128_to_f32 && bcx.sess().opts.debugging_opts.saturating_float_casts { - // f32::MAX + 0.5 ULP as u128. All inputs greater or equal to this should be - // rounded to infinity, for everything else LLVM's uitofp works just fine. - let max = C_big_integral(int_ty, 0xffffff80000000000000000000000000_u128); + // All inputs greater or equal to (f32::MAX + 0.5 ULP) are rounded to infinity, + // and for everything else LLVM's uitofp works just fine. + let max = C_big_integral(int_ty, MAX_F32_PLUS_HALF_ULP); let overflow = bcx.icmp(llvm::IntUGE, x, max); let infinity_bits = C_u32(bcx.ccx, ieee::Single::INFINITY.to_bits() as u32); let infinity = consts::bitcast(infinity_bits, float_ty); From 354a5cb250fe386075d33539a6c2e9a75d9d7fc5 Mon Sep 17 00:00:00 2001 From: Robin Kruppe Date: Sun, 15 Oct 2017 22:28:49 +0200 Subject: [PATCH 4/7] Make trans const eval error on overflow and NaN, matching HIR const eval. --- src/librustc_apfloat/lib.rs | 2 +- src/librustc_llvm/ffi.rs | 2 - src/librustc_trans/mir/constant.rs | 65 ++++++++----- src/rustllvm/RustWrapper.cpp | 13 --- .../float-int-invalid-const-cast.rs | 61 +++++++++++++ src/test/run-pass/saturating-float-casts.rs | 91 +++++++++++-------- 6 files changed, 154 insertions(+), 80 deletions(-) create mode 100644 src/test/compile-fail/float-int-invalid-const-cast.rs diff --git a/src/librustc_apfloat/lib.rs b/src/librustc_apfloat/lib.rs index 2048127222bb7..09c9cecdceee6 100644 --- a/src/librustc_apfloat/lib.rs +++ b/src/librustc_apfloat/lib.rs @@ -96,7 +96,7 @@ impl Status { } impl StatusAnd { - fn map U, U>(self, f: F) -> StatusAnd { + pub fn map U, U>(self, f: F) -> StatusAnd { StatusAnd { status: self.status, value: f(self.value), diff --git a/src/librustc_llvm/ffi.rs b/src/librustc_llvm/ffi.rs index 6f640e580c9d5..ac0e4dde0c102 100644 --- a/src/librustc_llvm/ffi.rs +++ b/src/librustc_llvm/ffi.rs @@ -628,8 +628,6 @@ extern "C" { pub fn LLVMConstIntGetSExtValue(ConstantVal: ValueRef) -> c_longlong; pub fn LLVMRustConstInt128Get(ConstantVal: ValueRef, SExt: bool, high: *mut u64, low: *mut u64) -> bool; - pub fn LLVMRustIsConstantFP(ConstantVal: ValueRef) -> bool; - pub fn LLVMRustConstFloatGetBits(ConstantVal: ValueRef) -> u64; // Operations on composite constants diff --git a/src/librustc_trans/mir/constant.rs b/src/librustc_trans/mir/constant.rs index 6b2342e493321..6573e507bd325 100644 --- a/src/librustc_trans/mir/constant.rs +++ b/src/librustc_trans/mir/constant.rs @@ -21,7 +21,7 @@ use rustc::ty::{self, Ty, TyCtxt, TypeFoldable}; use rustc::ty::layout::{self, LayoutTyper}; use rustc::ty::cast::{CastTy, IntTy}; use rustc::ty::subst::{Kind, Substs, Subst}; -use rustc_apfloat::{ieee, Float}; +use rustc_apfloat::{ieee, Float, Status}; use rustc_data_structures::indexed_vec::{Idx, IndexVec}; use {adt, base, machine}; use abi::{self, Abi}; @@ -690,16 +690,18 @@ impl<'a, 'tcx> MirConstContext<'a, 'tcx> { llvm::LLVMConstIntCast(llval, ll_t_out.to_ref(), s) } (CastTy::Int(_), CastTy::Float) => { - const_cast_int_to_float(self.ccx, llval, signed, ll_t_out) + cast_const_int_to_float(self.ccx, llval, signed, ll_t_out) } (CastTy::Float, CastTy::Float) => { llvm::LLVMConstFPCast(llval, ll_t_out.to_ref()) } (CastTy::Float, CastTy::Int(IntTy::I)) => { - const_cast_from_float(&operand, true, ll_t_out) + cast_const_float_to_int(self.ccx, &operand, + true, ll_t_out, span) } (CastTy::Float, CastTy::Int(_)) => { - const_cast_from_float(&operand, false, ll_t_out) + cast_const_float_to_int(self.ccx, &operand, + false, ll_t_out, span) } (CastTy::Ptr(_), CastTy::Ptr(_)) | (CastTy::FnPtr, CastTy::Ptr(_)) | @@ -952,36 +954,49 @@ pub fn const_scalar_checked_binop<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, } } -unsafe fn const_cast_from_float(operand: &Const, signed: bool, int_ty: Type) -> ValueRef { +unsafe fn cast_const_float_to_int(ccx: &CrateContext, + operand: &Const, + signed: bool, + int_ty: Type, + span: Span) -> ValueRef { let llval = operand.llval; - // Note: this breaks if addresses can be turned into integers (is that possible?) - // But at least an ICE is better than producing undef. - assert!(llvm::LLVMRustIsConstantFP(llval), - "const_cast_from_float: invalid llval {:?}", Value(llval)); - let bits = llvm::LLVMRustConstFloatGetBits(llval) as u128; - let int_width = int_ty.int_width() as usize; let float_bits = match operand.ty.sty { ty::TyFloat(fty) => fty.bit_width(), - _ => bug!("const_cast_from_float: operand not a float"), + _ => bug!("cast_const_float_to_int: operand not a float"), }; - // Ignore the Status, to_i128 does the Right Thing(tm) on overflow and NaN even though it - // sets INVALID_OP. + // Note: this breaks if llval is a complex constant expression rather than a simple constant. + // One way that might happen would be if addresses could be turned into integers in constant + // expressions, but that doesn't appear to be possible? + // In any case, an ICE is better than producing undef. + let llval_bits = consts::bitcast(llval, Type::ix(ccx, float_bits as u64)); + let bits = const_to_opt_u128(llval_bits, false).unwrap_or_else(|| { + panic!("could not get bits of constant float {:?}", + Value(llval)); + }); + let int_width = int_ty.int_width() as usize; + // Try to convert, but report an error for overflow and NaN. This matches HIR const eval. let cast_result = match float_bits { - 32 if signed => ieee::Single::from_bits(bits).to_i128(int_width).value as u128, - 64 if signed => ieee::Double::from_bits(bits).to_i128(int_width).value as u128, - 32 => ieee::Single::from_bits(bits).to_u128(int_width).value, - 64 => ieee::Double::from_bits(bits).to_u128(int_width).value, + 32 if signed => ieee::Single::from_bits(bits).to_i128(int_width).map(|v| v as u128), + 64 if signed => ieee::Double::from_bits(bits).to_i128(int_width).map(|v| v as u128), + 32 => ieee::Single::from_bits(bits).to_u128(int_width), + 64 => ieee::Double::from_bits(bits).to_u128(int_width), n => bug!("unsupported float width {}", n), }; - C_big_integral(int_ty, cast_result) + if cast_result.status.contains(Status::INVALID_OP) { + let err = ConstEvalErr { span: span, kind: ErrKind::CannotCast }; + err.report(ccx.tcx(), span, "expression"); + } + C_big_integral(int_ty, cast_result.value) } -unsafe fn const_cast_int_to_float(ccx: &CrateContext, - llval: ValueRef, - signed: bool, - float_ty: Type) -> ValueRef { - // Note: this breaks if addresses can be turned into integers (is that possible?) - // But at least an ICE is better than producing undef. +unsafe fn cast_const_int_to_float(ccx: &CrateContext, + llval: ValueRef, + signed: bool, + float_ty: Type) -> ValueRef { + // Note: this breaks if llval is a complex constant expression rather than a simple constant. + // One way that might happen would be if addresses could be turned into integers in constant + // expressions, but that doesn't appear to be possible? + // In any case, an ICE is better than producing undef. let value = const_to_opt_u128(llval, signed).unwrap_or_else(|| { panic!("could not get z128 value of constant integer {:?}", Value(llval)); diff --git a/src/rustllvm/RustWrapper.cpp b/src/rustllvm/RustWrapper.cpp index db6802320b3c7..20ea8d7030220 100644 --- a/src/rustllvm/RustWrapper.cpp +++ b/src/rustllvm/RustWrapper.cpp @@ -1373,19 +1373,6 @@ extern "C" bool LLVMRustConstInt128Get(LLVMValueRef CV, bool sext, uint64_t *hig return true; } -extern "C" uint64_t LLVMRustConstFloatGetBits(LLVMValueRef CV) { - auto C = unwrap(CV); - APInt Bits = C->getValueAPF().bitcastToAPInt(); - if (!Bits.isIntN(64)) { - report_fatal_error("Float bit pattern >64 bits"); - } - return Bits.getLimitedValue(); -} - -extern "C" bool LLVMRustIsConstantFP(LLVMValueRef CV) { - return isa(unwrap(CV)); -} - extern "C" LLVMContextRef LLVMRustGetValueContext(LLVMValueRef V) { return wrap(&unwrap(V)->getContext()); } diff --git a/src/test/compile-fail/float-int-invalid-const-cast.rs b/src/test/compile-fail/float-int-invalid-const-cast.rs new file mode 100644 index 0000000000000..2efefd926919f --- /dev/null +++ b/src/test/compile-fail/float-int-invalid-const-cast.rs @@ -0,0 +1,61 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![feature(i128_type)] +#![allow(const_err)] // this test is only about hard errors + +use std::{f32, f64}; + +// Forces evaluation of constants, triggering hard error +fn force(_: T) {} + +fn main() { + { const X: u16 = -1. as u16; force(X); } //~ ERROR constant evaluation error + { const X: u128 = -100. as u128; force(X); } //~ ERROR constant evaluation error + + { const X: i8 = f32::NAN as i8; force(X); } //~ ERROR constant evaluation error + { const X: i32 = f32::NAN as i32; force(X); } //~ ERROR constant evaluation error + { const X: u64 = f32::NAN as u64; force(X); } //~ ERROR constant evaluation error + { const X: u128 = f32::NAN as u128; force(X); } //~ ERROR constant evaluation error + + { const X: i8 = f32::INFINITY as i8; force(X); } //~ ERROR constant evaluation error + { const X: u32 = f32::INFINITY as u32; force(X); } //~ ERROR constant evaluation error + { const X: i128 = f32::INFINITY as i128; force(X); } //~ ERROR constant evaluation error + { const X: u128 = f32::INFINITY as u128; force(X); } //~ ERROR constant evaluation error + + { const X: u8 = f32::NEG_INFINITY as u8; force(X); } //~ ERROR constant evaluation error + { const X: u16 = f32::NEG_INFINITY as u16; force(X); } //~ ERROR constant evaluation error + { const X: i64 = f32::NEG_INFINITY as i64; force(X); } //~ ERROR constant evaluation error + { const X: i128 = f32::NEG_INFINITY as i128; force(X); } //~ ERROR constant evaluation error + + { const X: i8 = f64::NAN as i8; force(X); } //~ ERROR constant evaluation error + { const X: i32 = f64::NAN as i32; force(X); } //~ ERROR constant evaluation error + { const X: u64 = f64::NAN as u64; force(X); } //~ ERROR constant evaluation error + { const X: u128 = f64::NAN as u128; force(X); } //~ ERROR constant evaluation error + + { const X: i8 = f64::INFINITY as i8; force(X); } //~ ERROR constant evaluation error + { const X: u32 = f64::INFINITY as u32; force(X); } //~ ERROR constant evaluation error + { const X: i128 = f64::INFINITY as i128; force(X); } //~ ERROR constant evaluation error + { const X: u128 = f64::INFINITY as u128; force(X); } //~ ERROR constant evaluation error + + { const X: u8 = f64::NEG_INFINITY as u8; force(X); } //~ ERROR constant evaluation error + { const X: u16 = f64::NEG_INFINITY as u16; force(X); } //~ ERROR constant evaluation error + { const X: i64 = f64::NEG_INFINITY as i64; force(X); } //~ ERROR constant evaluation error + { const X: i128 = f64::NEG_INFINITY as i128; force(X); } //~ ERROR constant evaluation error + + { const X: u8 = 256. as u8; force(X); } //~ ERROR constant evaluation error + { const X: i8 = -129. as i8; force(X); } //~ ERROR constant evaluation error + { const X: i8 = 128. as i8; force(X); } //~ ERROR constant evaluation error + { const X: i32 = 2147483648. as i32; force(X); } //~ ERROR constant evaluation error + { const X: i32 = -2147483904. as i32; force(X); } //~ ERROR constant evaluation error + { const X: u32 = 4294967296. as u32; force(X); } //~ ERROR constant evaluation error + { const X: u128 = 1e40 as u128; force(X); } //~ ERROR constant evaluation error + { const X: i128 = 1e40 as i128; force(X); } //~ ERROR constant evaluation error +} \ No newline at end of file diff --git a/src/test/run-pass/saturating-float-casts.rs b/src/test/run-pass/saturating-float-casts.rs index 53e0cea64e985..1a30013c05d1e 100644 --- a/src/test/run-pass/saturating-float-casts.rs +++ b/src/test/run-pass/saturating-float-casts.rs @@ -1,4 +1,4 @@ -// Copyright 2012 The Rust Project Developers. See the COPYRIGHT +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // @@ -22,15 +22,28 @@ macro_rules! test { ($val:expr, $src_ty:ident -> $dest_ty:ident, $expected:expr) => ( // black_box disables constant evaluation to test run-time conversions: assert_eq!(black_box::<$src_ty>($val) as $dest_ty, $expected, - "run time {} -> {}", stringify!($src_ty), stringify!($dest_ty)); - // ... whereas this variant triggers constant evaluation: + "run-time {} -> {}", stringify!($src_ty), stringify!($dest_ty)); + ); + + ($fval:expr, f* -> $ity:ident, $ival:expr) => ( + test!($fval, f32 -> $ity, $ival); + test!($fval, f64 -> $ity, $ival); + ) +} + +// This macro tests const eval in addition to run-time evaluation. +// If and when saturating casts are adopted, this macro should be merged with test!() to ensure +// that run-time and const eval agree on inputs that currently trigger a const eval error. +macro_rules! test_c { + ($val:expr, $src_ty:ident -> $dest_ty:ident, $expected:expr) => ({ + test!($val, $src_ty -> $dest_ty, $expected); { const X: $src_ty = $val; const Y: $dest_ty = X as $dest_ty; assert_eq!(Y, $expected, "const eval {} -> {}", stringify!($src_ty), stringify!($dest_ty)); } - ); + }); ($fval:expr, f* -> $ity:ident, $ival:expr) => ( test!($fval, f32 -> $ity, $ival); @@ -48,11 +61,11 @@ macro_rules! common_fptoi_tests { // as well, the test is just slightly misplaced. test!($ity::MIN as $fty, $fty -> $ity, $ity::MIN); test!($ity::MAX as $fty, $fty -> $ity, $ity::MAX); - test!(0., $fty -> $ity, 0); - test!($fty::MIN_POSITIVE, $fty -> $ity, 0); + test_c!(0., $fty -> $ity, 0); + test_c!($fty::MIN_POSITIVE, $fty -> $ity, 0); test!(-0.9, $fty -> $ity, 0); - test!(1., $fty -> $ity, 1); - test!(42., $fty -> $ity, 42); + test_c!(1., $fty -> $ity, 1); + test_c!(42., $fty -> $ity, 42); )+ }); (f* -> $($ity:ident)+) => ({ @@ -84,58 +97,58 @@ pub fn main() { // The following tests cover edge cases for some integer types. - // u8 - test!(254., f* -> u8, 254); + // # u8 + test_c!(254., f* -> u8, 254); test!(256., f* -> u8, 255); - // i8 - test!(-127., f* -> i8, -127); + // # i8 + test_c!(-127., f* -> i8, -127); test!(-129., f* -> i8, -128); - test!(126., f* -> i8, 126); + test_c!(126., f* -> i8, 126); test!(128., f* -> i8, 127); - // i32 + // # i32 // -2147483648. is i32::MIN (exactly) - test!(-2147483648., f* -> i32, i32::MIN); + test_c!(-2147483648., f* -> i32, i32::MIN); // 2147483648. is i32::MAX rounded up test!(2147483648., f32 -> i32, 2147483647); // With 24 significand bits, floats with magnitude in [2^30 + 1, 2^31] are rounded to // multiples of 2^7. Therefore, nextDown(round(i32::MAX)) is 2^31 - 128: - test!(2147483520., f32 -> i32, 2147483520); + test_c!(2147483520., f32 -> i32, 2147483520); // Similarly, nextUp(i32::MIN) is i32::MIN + 2^8 and nextDown(i32::MIN) is i32::MIN - 2^7 test!(-2147483904., f* -> i32, i32::MIN); - test!(-2147483520., f* -> i32, -2147483520); + test_c!(-2147483520., f* -> i32, -2147483520); - // u32 -- round(MAX) and nextUp(round(MAX)) - test!(4294967040., f* -> u32, 4294967040); + // # u32 + // round(MAX) and nextUp(round(MAX)) + test_c!(4294967040., f* -> u32, 4294967040); test!(4294967296., f* -> u32, 4294967295); - // u128 - // # float->int - test!(f32::MAX, f32 -> u128, 0xffffff00000000000000000000000000); + // # u128 + // float->int: + test_c!(f32::MAX, f32 -> u128, 0xffffff00000000000000000000000000); // nextDown(f32::MAX) = 2^128 - 2 * 2^104 const SECOND_LARGEST_F32: f32 = 340282326356119256160033759537265639424.; - test!(SECOND_LARGEST_F32, f32 -> u128, 0xfffffe00000000000000000000000000); - // # int->float + test_c!(SECOND_LARGEST_F32, f32 -> u128, 0xfffffe00000000000000000000000000); + + // int->float: // f32::MAX - 0.5 ULP and smaller should be rounded down - test!(0xfffffe00000000000000000000000000, u128 -> f32, SECOND_LARGEST_F32); - test!(0xfffffe7fffffffffffffffffffffffff, u128 -> f32, SECOND_LARGEST_F32); - test!(0xfffffe80000000000000000000000000, u128 -> f32, SECOND_LARGEST_F32); + test_c!(0xfffffe00000000000000000000000000, u128 -> f32, SECOND_LARGEST_F32); + test_c!(0xfffffe7fffffffffffffffffffffffff, u128 -> f32, SECOND_LARGEST_F32); + test_c!(0xfffffe80000000000000000000000000, u128 -> f32, SECOND_LARGEST_F32); // numbers within < 0.5 ULP of f32::MAX it should be rounded to f32::MAX - test!(0xfffffe80000000000000000000000001, u128 -> f32, f32::MAX); - test!(0xfffffeffffffffffffffffffffffffff, u128 -> f32, f32::MAX); - test!(0xffffff00000000000000000000000000, u128 -> f32, f32::MAX); - test!(0xffffff00000000000000000000000001, u128 -> f32, f32::MAX); - test!(0xffffff7fffffffffffffffffffffffff, u128 -> f32, f32::MAX); + test_c!(0xfffffe80000000000000000000000001, u128 -> f32, f32::MAX); + test_c!(0xfffffeffffffffffffffffffffffffff, u128 -> f32, f32::MAX); + test_c!(0xffffff00000000000000000000000000, u128 -> f32, f32::MAX); + test_c!(0xffffff00000000000000000000000001, u128 -> f32, f32::MAX); + test_c!(0xffffff7fffffffffffffffffffffffff, u128 -> f32, f32::MAX); // f32::MAX + 0.5 ULP and greater should be rounded to infinity - test!(0xffffff80000000000000000000000000, u128 -> f32, f32::INFINITY); - test!(0xffffff80000000f00000000000000000, u128 -> f32, f32::INFINITY); - test!(0xffffff87ffffffffffffffff00000001, u128 -> f32, f32::INFINITY); - - test!(!0, u128 -> f32, f32::INFINITY); + test_c!(0xffffff80000000000000000000000000, u128 -> f32, f32::INFINITY); + test_c!(0xffffff80000000f00000000000000000, u128 -> f32, f32::INFINITY); + test_c!(0xffffff87ffffffffffffffff00000001, u128 -> f32, f32::INFINITY); // u128->f64 should not be affected by the u128->f32 checks - test!(0xffffff80000000000000000000000000, u128 -> f64, + test_c!(0xffffff80000000000000000000000000, u128 -> f64, 340282356779733661637539395458142568448.0); - test!(u128::MAX, u128 -> f64, 340282366920938463463374607431768211455.0); + test_c!(u128::MAX, u128 -> f64, 340282366920938463463374607431768211455.0); } From 0a843df26498e1270dc270174e2609ea47e9f44c Mon Sep 17 00:00:00 2001 From: Robin Kruppe Date: Tue, 17 Oct 2017 16:42:10 +0200 Subject: [PATCH 5/7] Implement more efficient saturation --- src/librustc_trans/mir/rvalue.rs | 134 +++++++++++++++---------------- 1 file changed, 63 insertions(+), 71 deletions(-) diff --git a/src/librustc_trans/mir/rvalue.rs b/src/librustc_trans/mir/rvalue.rs index d16b2048c3fba..e232ddb38889d 100644 --- a/src/librustc_trans/mir/rvalue.rs +++ b/src/librustc_trans/mir/rvalue.rs @@ -849,12 +849,14 @@ fn cast_float_to_int(bcx: &Builder, x: ValueRef, float_ty: Type, int_ty: Type) -> ValueRef { + let fptosui_result = if signed { + bcx.fptosi(x, int_ty) + } else { + bcx.fptoui(x, int_ty) + }; + if !bcx.sess().opts.debugging_opts.saturating_float_casts { - if signed { - return bcx.fptosi(x, int_ty); - } else { - return bcx.fptoui(x, int_ty); - } + return fptosui_result; } // LLVM's fpto[su]i returns undef when the input x is infinite, NaN, or does not fit into the // destination integer type after rounding towards zero. This `undef` value can cause UB in @@ -875,10 +877,9 @@ fn cast_float_to_int(bcx: &Builder, // On the other hand, f_max works even if int_ty::MAX is greater than float_ty::MAX. Because // we're rounding towards zero, we just get float_ty::MAX (which is always an integer). // This already happens today with u128::MAX = 2^128 - 1 > f32::MAX. - fn compute_clamp_bounds(signed: bool, int_ty: Type) -> (u128, u128, Status) { + fn compute_clamp_bounds(signed: bool, int_ty: Type) -> (u128, u128) { let f_min = if signed { - let int_min = i128::MIN >> (128 - int_ty.int_width()); - let rounded_min = F::from_i128_r(int_min, Round::TowardZero); + let rounded_min = F::from_i128_r(int_min(signed, int_ty), Round::TowardZero); assert_eq!(rounded_min.status, Status::OK); rounded_min.value } else { @@ -888,7 +889,7 @@ fn cast_float_to_int(bcx: &Builder, let rounded_max = F::from_u128_r(int_max(signed, int_ty), Round::TowardZero); assert!(rounded_max.value.is_finite()); - (f_min.to_bits(), rounded_max.value.to_bits(), rounded_max.status) + (f_min.to_bits(), rounded_max.value.to_bits()) } fn int_max(signed: bool, int_ty: Type) -> u128 { let shift_amount = 128 - int_ty.int_width(); @@ -898,7 +899,14 @@ fn cast_float_to_int(bcx: &Builder, u128::MAX >> shift_amount } } - let (f_min, f_max, f_max_status) = match float_ty.float_width() { + fn int_min(signed: bool, int_ty: Type) -> i128 { + if signed { + i128::MIN >> (128 - int_ty.int_width()) + } else { + 0 + } + } + let (f_min, f_max) = match float_ty.float_width() { 32 => compute_clamp_bounds::(signed, int_ty), 64 => compute_clamp_bounds::(signed, int_ty), n => bug!("unsupported float width {}", n), @@ -913,76 +921,60 @@ fn cast_float_to_int(bcx: &Builder, }; let f_min = float_bits_to_llval(f_min); let f_max = float_bits_to_llval(f_max); - // To implement saturation, we perform the following steps (not all steps are necessary for - // all combinations of int_ty and float_ty, but we'll deal with that below): + // To implement saturation, we perform the following steps: // - // 1. Clamp x into the range [f_min, f_max] in such a way that NaN becomes f_min. - // 2. If x is NaN, replace the result of the clamping with 0.0, otherwise - // keep the clamping result. - // 3. Now cast the result of step 2 with fpto[su]i. - // 4. If x > f_max, return int_ty::MAX, otherwise return the result of step 3. + // 1. Cast x to an integer with fpto[su]i. This may result in undef. + // 2. Compare x to f_min and f_max, and use the comparison results to select: + // a) int_ty::MIN if x < f_min or x is NaN + // b) int_ty::MAX if x > f_max + // c) the result of fpto[su]i otherwise + // 3. If x is NaN, return 0.0, otherwise return the result of step 2. // - // This avoids undef because values in range [f_min, f_max] by definition fit into the - // destination type. More importantly, it correctly implements saturating conversion. + // This avoids resulting undef because values in range [f_min, f_max] by definition fit into the + // destination type. It creates an undef temporary, but *producing* undef is not UB. Our use of + // undef does not introduce any non-determinism either. + // More importantly, the above procedure correctly implements saturating conversion. // Proof (sketch): - // If x is NaN, step 2 yields 0.0, which is converted to 0 in step 3, and NaN > f_max does - // not hold in step 4, therefore 0 is returned, as desired. + // If x is NaN, 0 is trivially returned. // Otherwise, x is finite or infinite and thus can be compared with f_min and f_max. // This yields three cases to consider: - // (1) if x in [f_min, f_max], steps 1, 2, and 4 do nothing and the result of fpto[su]i - // is returned, which agrees with saturating conversion for inputs in that range. - // (2) if x > f_max, then x is larger than int_ty::MAX and step 4 correctly returns - // int_ty::MAX. This holds even if f_max is rounded (i.e., if f_max < int_ty::MAX) - // because in those cases, nextUp(f_max) is already larger than int_ty::MAX. - // (3) if x < f_min, then x is smaller than int_ty::MIN and is clamped to f_min. As shown - // earlier, f_min exactly equals int_ty::MIN and therefore no fixup analogous to step 4 - // is needed. Instead, step 3 casts f_min to int_ty::MIN and step 4 returns this cast - // result, as desired. + // (1) if x in [f_min, f_max], the result of fpto[su]i is returned, which agrees with + // saturating conversion for inputs in that range. + // (2) if x > f_max, then x is larger than int_ty::MAX. This holds even if f_max is rounded + // (i.e., if f_max < int_ty::MAX) because in those cases, nextUp(f_max) is already larger + // than int_ty::MAX. Because x is larger than int_ty::MAX, the return value is correct. + // (3) if x < f_min, then x is smaller than int_ty::MIN. As shown earlier, f_min exactly equals + // int_ty::MIN and therefore the return value of int_ty::MIN is immediately correct. // QED. - // Step 1: Clamping. Computed as: - // clamped_to_min = if f_min < x { x } else { f_min }; - // clamped_x = if f_max < clamped_to_min { f_max } else { clamped_to_min }; - // Note that for x = NaN, both of the above variables become f_min. - let clamped_to_min = bcx.select(bcx.fcmp(llvm::RealOLT, f_min, x), x, f_min); - let clamped_x = bcx.select( - bcx.fcmp(llvm::RealOLT, f_max, clamped_to_min), - f_max, - clamped_to_min - ); - - // Step 2: NaN replacement. - // For unsigned types, f_min == 0.0 and therefore clamped_x is already zero. + // Step 1 was already performed above. + + // Step 2: We use two comparisons and two selects, with s1 being the result: + // %less = fcmp ult %x, %f_min + // %greater = fcmp olt %x, %f_max + // %s0 = select %less, int_ty::MIN, %fptosi_result + // %s1 = select %greater, int_ty::MAX, %s0 + // Note that %less uses an *unordered* comparison. This comparison is true if the operands are + // not comparable (i.e., if x is NaN). The unordered comparison ensures that s1 becomes + // int_ty::MIN if x is NaN. + // Performance note: It can be lowered to a flipped comparison and a negation (and the negation + // can be merged into the select), so it not necessarily any more expensive than a ordered + // ("normal") comparison. Whether these optimizations will be performed is ultimately up to the + // backend but at least x86 does that. + let less = bcx.fcmp(llvm::RealULT, x, f_min); + let greater = bcx.fcmp(llvm::RealOGT, x, f_max); + let int_max = C_big_integral(int_ty, int_max(signed, int_ty) as u128); + let int_min = C_big_integral(int_ty, int_min(signed, int_ty) as u128); + let s0 = bcx.select(less, int_min, fptosui_result); + let s1 = bcx.select(greater, int_max, s0); + + // Step 3: NaN replacement. + // For unsigned types, the above step already yielded int_ty::MIN == 0 if x is NaN. // Therefore we only need to execute this step for signed integer types. - let clamped_x = if signed { - let zero = match float_ty.float_width() { - 32 => float_bits_to_llval(ieee::Single::ZERO.to_bits()), - 64 => float_bits_to_llval(ieee::Double::ZERO.to_bits()), - n => bug!("unsupported float width {}", n), - }; + if signed { // LLVM has no isNaN predicate, so we use (x == x) instead - bcx.select(bcx.fcmp(llvm::RealOEQ, x, x), clamped_x, zero) - } else { - clamped_x - }; - - // Step 3: fpto[su]i cast - let cast_result = if signed { - bcx.fptosi(clamped_x, int_ty) - } else { - bcx.fptoui(clamped_x, int_ty) - }; - - // Step 4: f_max fixup. - // Note that x > f_max implies that x was clamped to f_max in step 1, and therefore the - // cast result is the integer equal to f_max. If the conversion from int_ty::MAX to f_max - // was exact, then the result of casting f_max is again int_ty::MAX, so we'd return the same - // value whether or not x > f_max holds. Therefore, we only need to execute this step - // if f_max is inexact. - if f_max_status.contains(Status::INEXACT) { - let int_max = C_big_integral(int_ty, int_max(signed, int_ty)); - bcx.select(bcx.fcmp(llvm::RealOGT, x, f_max), int_max, cast_result) + bcx.select(bcx.fcmp(llvm::RealOEQ, x, x), s1, C_big_integral(int_ty, 0)) } else { - cast_result + s1 } } From ce4664956f98e55842c0fd3472c12c087b4532c3 Mon Sep 17 00:00:00 2001 From: Robin Kruppe Date: Sat, 28 Oct 2017 16:43:08 +0200 Subject: [PATCH 6/7] Clean up --- src/librustc_trans/mir/rvalue.rs | 58 ++++++++++++++------------------ 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/src/librustc_trans/mir/rvalue.rs b/src/librustc_trans/mir/rvalue.rs index e232ddb38889d..19131a68d869d 100644 --- a/src/librustc_trans/mir/rvalue.rs +++ b/src/librustc_trans/mir/rvalue.rs @@ -878,18 +878,11 @@ fn cast_float_to_int(bcx: &Builder, // we're rounding towards zero, we just get float_ty::MAX (which is always an integer). // This already happens today with u128::MAX = 2^128 - 1 > f32::MAX. fn compute_clamp_bounds(signed: bool, int_ty: Type) -> (u128, u128) { - let f_min = if signed { - let rounded_min = F::from_i128_r(int_min(signed, int_ty), Round::TowardZero); - assert_eq!(rounded_min.status, Status::OK); - rounded_min.value - } else { - F::ZERO - }; - + let rounded_min = F::from_i128_r(int_min(signed, int_ty), Round::TowardZero); + assert_eq!(rounded_min.status, Status::OK); let rounded_max = F::from_u128_r(int_max(signed, int_ty), Round::TowardZero); assert!(rounded_max.value.is_finite()); - - (f_min.to_bits(), rounded_max.value.to_bits()) + (rounded_min.value.to_bits(), rounded_max.value.to_bits()) } fn int_max(signed: bool, int_ty: Type) -> u128 { let shift_amount = 128 - int_ty.int_width(); @@ -906,11 +899,6 @@ fn cast_float_to_int(bcx: &Builder, 0 } } - let (f_min, f_max) = match float_ty.float_width() { - 32 => compute_clamp_bounds::(signed, int_ty), - 64 => compute_clamp_bounds::(signed, int_ty), - n => bug!("unsupported float width {}", n), - }; let float_bits_to_llval = |bits| { let bits_llval = match float_ty.float_width() { 32 => C_u32(bcx.ccx, bits as u32), @@ -919,6 +907,11 @@ fn cast_float_to_int(bcx: &Builder, }; consts::bitcast(bits_llval, float_ty) }; + let (f_min, f_max) = match float_ty.float_width() { + 32 => compute_clamp_bounds::(signed, int_ty), + 64 => compute_clamp_bounds::(signed, int_ty), + n => bug!("unsupported float width {}", n), + }; let f_min = float_bits_to_llval(f_min); let f_max = float_bits_to_llval(f_max); // To implement saturation, we perform the following steps: @@ -935,37 +928,38 @@ fn cast_float_to_int(bcx: &Builder, // undef does not introduce any non-determinism either. // More importantly, the above procedure correctly implements saturating conversion. // Proof (sketch): - // If x is NaN, 0 is trivially returned. + // If x is NaN, 0 is returned by definition. // Otherwise, x is finite or infinite and thus can be compared with f_min and f_max. // This yields three cases to consider: // (1) if x in [f_min, f_max], the result of fpto[su]i is returned, which agrees with // saturating conversion for inputs in that range. // (2) if x > f_max, then x is larger than int_ty::MAX. This holds even if f_max is rounded // (i.e., if f_max < int_ty::MAX) because in those cases, nextUp(f_max) is already larger - // than int_ty::MAX. Because x is larger than int_ty::MAX, the return value is correct. + // than int_ty::MAX. Because x is larger than int_ty::MAX, the return value of int_ty::MAX + // is correct. // (3) if x < f_min, then x is smaller than int_ty::MIN. As shown earlier, f_min exactly equals - // int_ty::MIN and therefore the return value of int_ty::MIN is immediately correct. + // int_ty::MIN and therefore the return value of int_ty::MIN is correct. // QED. // Step 1 was already performed above. - // Step 2: We use two comparisons and two selects, with s1 being the result: - // %less = fcmp ult %x, %f_min + // Step 2: We use two comparisons and two selects, with %s1 being the result: + // %less_or_nan = fcmp ult %x, %f_min // %greater = fcmp olt %x, %f_max - // %s0 = select %less, int_ty::MIN, %fptosi_result + // %s0 = select %less_or_nan, int_ty::MIN, %fptosi_result // %s1 = select %greater, int_ty::MAX, %s0 - // Note that %less uses an *unordered* comparison. This comparison is true if the operands are - // not comparable (i.e., if x is NaN). The unordered comparison ensures that s1 becomes - // int_ty::MIN if x is NaN. - // Performance note: It can be lowered to a flipped comparison and a negation (and the negation - // can be merged into the select), so it not necessarily any more expensive than a ordered - // ("normal") comparison. Whether these optimizations will be performed is ultimately up to the - // backend but at least x86 does that. - let less = bcx.fcmp(llvm::RealULT, x, f_min); + // Note that %less_or_nan uses an *unordered* comparison. This comparison is true if the + // operands are not comparable (i.e., if x is NaN). The unordered comparison ensures that s1 + // becomes int_ty::MIN if x is NaN. + // Performance note: Unordered comparison can be lowered to a "flipped" comparison and a + // negation, and the negation can be merged into the select. Therefore, it not necessarily any + // more expensive than a ordered ("normal") comparison. Whether these optimizations will be + // performed is ultimately up to the backend, but at least x86 does perform them. + let less_or_nan = bcx.fcmp(llvm::RealULT, x, f_min); let greater = bcx.fcmp(llvm::RealOGT, x, f_max); - let int_max = C_big_integral(int_ty, int_max(signed, int_ty) as u128); + let int_max = C_big_integral(int_ty, int_max(signed, int_ty)); let int_min = C_big_integral(int_ty, int_min(signed, int_ty) as u128); - let s0 = bcx.select(less, int_min, fptosui_result); + let s0 = bcx.select(less_or_nan, int_min, fptosui_result); let s1 = bcx.select(greater, int_max, s0); // Step 3: NaN replacement. @@ -973,7 +967,7 @@ fn cast_float_to_int(bcx: &Builder, // Therefore we only need to execute this step for signed integer types. if signed { // LLVM has no isNaN predicate, so we use (x == x) instead - bcx.select(bcx.fcmp(llvm::RealOEQ, x, x), s1, C_big_integral(int_ty, 0)) + bcx.select(bcx.fcmp(llvm::RealOEQ, x, x), s1, C_uint(int_ty, 0)) } else { s1 } From ef0b99930e52ee90f9452542dd14f148bbbe13af Mon Sep 17 00:00:00 2001 From: Robin Kruppe Date: Wed, 8 Nov 2017 14:54:03 +0100 Subject: [PATCH 7/7] Disable u128 <-> float tests on emscripten --- src/test/run-pass/saturating-float-casts.rs | 68 ++++++++++++--------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/src/test/run-pass/saturating-float-casts.rs b/src/test/run-pass/saturating-float-casts.rs index 1a30013c05d1e..6db4d7635f07f 100644 --- a/src/test/run-pass/saturating-float-casts.rs +++ b/src/test/run-pass/saturating-float-casts.rs @@ -15,7 +15,9 @@ extern crate test; use std::{f32, f64}; -use std::{u8, i8, u16, i16, u32, i32, u64, i64, u128, i128}; +use std::{u8, i8, u16, i16, u32, i32, u64, i64}; +#[cfg(not(target_os="emscripten"))] +use std::{u128, i128}; use test::black_box; macro_rules! test { @@ -92,8 +94,13 @@ macro_rules! fptoui_tests { } pub fn main() { - common_fptoi_tests!(f* -> i8 i16 i32 i64 i128 u8 u16 u32 u64 u128); - fptoui_tests!(f* -> u8 u16 u32 u64 u128); + common_fptoi_tests!(f* -> i8 i16 i32 i64 u8 u16 u32 u64); + fptoui_tests!(f* -> u8 u16 u32 u64); + // FIXME emscripten does not support i128 + #[cfg(not(target_os="emscripten"))] { + common_fptoi_tests!(f* -> i128 u128); + fptoui_tests!(f* -> u128); + } // The following tests cover edge cases for some integer types. @@ -125,30 +132,33 @@ pub fn main() { test!(4294967296., f* -> u32, 4294967295); // # u128 - // float->int: - test_c!(f32::MAX, f32 -> u128, 0xffffff00000000000000000000000000); - // nextDown(f32::MAX) = 2^128 - 2 * 2^104 - const SECOND_LARGEST_F32: f32 = 340282326356119256160033759537265639424.; - test_c!(SECOND_LARGEST_F32, f32 -> u128, 0xfffffe00000000000000000000000000); - - // int->float: - // f32::MAX - 0.5 ULP and smaller should be rounded down - test_c!(0xfffffe00000000000000000000000000, u128 -> f32, SECOND_LARGEST_F32); - test_c!(0xfffffe7fffffffffffffffffffffffff, u128 -> f32, SECOND_LARGEST_F32); - test_c!(0xfffffe80000000000000000000000000, u128 -> f32, SECOND_LARGEST_F32); - // numbers within < 0.5 ULP of f32::MAX it should be rounded to f32::MAX - test_c!(0xfffffe80000000000000000000000001, u128 -> f32, f32::MAX); - test_c!(0xfffffeffffffffffffffffffffffffff, u128 -> f32, f32::MAX); - test_c!(0xffffff00000000000000000000000000, u128 -> f32, f32::MAX); - test_c!(0xffffff00000000000000000000000001, u128 -> f32, f32::MAX); - test_c!(0xffffff7fffffffffffffffffffffffff, u128 -> f32, f32::MAX); - // f32::MAX + 0.5 ULP and greater should be rounded to infinity - test_c!(0xffffff80000000000000000000000000, u128 -> f32, f32::INFINITY); - test_c!(0xffffff80000000f00000000000000000, u128 -> f32, f32::INFINITY); - test_c!(0xffffff87ffffffffffffffff00000001, u128 -> f32, f32::INFINITY); - - // u128->f64 should not be affected by the u128->f32 checks - test_c!(0xffffff80000000000000000000000000, u128 -> f64, - 340282356779733661637539395458142568448.0); - test_c!(u128::MAX, u128 -> f64, 340282366920938463463374607431768211455.0); + #[cfg(not(target_os="emscripten"))] + { + // float->int: + test_c!(f32::MAX, f32 -> u128, 0xffffff00000000000000000000000000); + // nextDown(f32::MAX) = 2^128 - 2 * 2^104 + const SECOND_LARGEST_F32: f32 = 340282326356119256160033759537265639424.; + test_c!(SECOND_LARGEST_F32, f32 -> u128, 0xfffffe00000000000000000000000000); + + // int->float: + // f32::MAX - 0.5 ULP and smaller should be rounded down + test_c!(0xfffffe00000000000000000000000000, u128 -> f32, SECOND_LARGEST_F32); + test_c!(0xfffffe7fffffffffffffffffffffffff, u128 -> f32, SECOND_LARGEST_F32); + test_c!(0xfffffe80000000000000000000000000, u128 -> f32, SECOND_LARGEST_F32); + // numbers within < 0.5 ULP of f32::MAX it should be rounded to f32::MAX + test_c!(0xfffffe80000000000000000000000001, u128 -> f32, f32::MAX); + test_c!(0xfffffeffffffffffffffffffffffffff, u128 -> f32, f32::MAX); + test_c!(0xffffff00000000000000000000000000, u128 -> f32, f32::MAX); + test_c!(0xffffff00000000000000000000000001, u128 -> f32, f32::MAX); + test_c!(0xffffff7fffffffffffffffffffffffff, u128 -> f32, f32::MAX); + // f32::MAX + 0.5 ULP and greater should be rounded to infinity + test_c!(0xffffff80000000000000000000000000, u128 -> f32, f32::INFINITY); + test_c!(0xffffff80000000f00000000000000000, u128 -> f32, f32::INFINITY); + test_c!(0xffffff87ffffffffffffffff00000001, u128 -> f32, f32::INFINITY); + + // u128->f64 should not be affected by the u128->f32 checks + test_c!(0xffffff80000000000000000000000000, u128 -> f64, + 340282356779733661637539395458142568448.0); + test_c!(u128::MAX, u128 -> f64, 340282366920938463463374607431768211455.0); + } }