From 354a5cb250fe386075d33539a6c2e9a75d9d7fc5 Mon Sep 17 00:00:00 2001 From: Robin Kruppe Date: Sun, 15 Oct 2017 22:28:49 +0200 Subject: [PATCH] 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 | 95 +++++++++++-------- 6 files changed, 156 insertions(+), 82 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 2048127222b..09c9cecdcee 100644 --- a/src/librustc_apfloat/lib.rs +++ b/src/librustc_apfloat/lib.rs @@ -96,7 +96,7 @@ pub fn and(self, value: T) -> StatusAnd { } 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 6f640e580c9..ac0e4dde0c1 100644 --- a/src/librustc_llvm/ffi.rs +++ b/src/librustc_llvm/ffi.rs @@ -628,8 +628,6 @@ pub fn LLVMStructTypeInContext(C: ContextRef, 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 6b2342e4933..6573e507bd3 100644 --- a/src/librustc_trans/mir/constant.rs +++ b/src/librustc_trans/mir/constant.rs @@ -21,7 +21,7 @@ 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 @@ fn const_rvalue(&self, rvalue: &mir::Rvalue<'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 db6802320b3..20ea8d70302 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 00000000000..2efefd92691 --- /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 53e0cea64e9..1a30013c05d 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 - // 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_c!(SECOND_LARGEST_F32, f32 -> u128, 0xfffffe00000000000000000000000000); - test!(!0, u128 -> f32, f32::INFINITY); + // 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!(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); }