From b57bc6d9ddfd3916ec12d299e4452bf4fc8c8691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20S=C3=A1nchez=20Mu=C3=B1oz?= Date: Fri, 11 Aug 2023 17:58:09 +0200 Subject: [PATCH] Add checked float-to-int helper function --- src/helpers.rs | 65 ++++++++++++++++++- src/shims/intrinsics/mod.rs | 51 ++------------- src/shims/x86/sse.rs | 44 +++---------- .../intrinsics/float_to_int_64_neg.stderr | 4 +- 4 files changed, 81 insertions(+), 83 deletions(-) diff --git a/src/helpers.rs b/src/helpers.rs index ea1931f7a1..b5cc5c7e48 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -13,11 +13,11 @@ use rustc_index::IndexVec; use rustc_middle::mir; use rustc_middle::ty::{ self, - layout::{LayoutOf, TyAndLayout}, - List, TyCtxt, + layout::{IntegerExt as _, LayoutOf, TyAndLayout}, + List, Ty, TyCtxt, }; use rustc_span::{def_id::CrateNum, sym, Span, Symbol}; -use rustc_target::abi::{Align, FieldIdx, FieldsShape, Size, Variants}; +use rustc_target::abi::{Align, FieldIdx, FieldsShape, Integer, Size, Variants}; use rustc_target::spec::abi::Abi; use rand::RngCore; @@ -1011,6 +1011,65 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { None => tcx.item_name(def_id), } } + + /// Converts `f` to integer type `dest_ty` after rounding with mode `round`. + /// Returns `None` if `f` is NaN or out of range. + fn float_to_int_checked( + &self, + f: F, + dest_ty: Ty<'tcx>, + round: rustc_apfloat::Round, + ) -> Option> + where + F: rustc_apfloat::Float + Into>, + { + let this = self.eval_context_ref(); + + match dest_ty.kind() { + // Unsigned + ty::Uint(t) => { + let size = Integer::from_uint_ty(this, *t).size(); + let res = f.to_u128_r(size.bits_usize(), round, &mut false); + if res.status.intersects( + rustc_apfloat::Status::INVALID_OP + | rustc_apfloat::Status::OVERFLOW + | rustc_apfloat::Status::UNDERFLOW, + ) { + // Floating point value is NaN (flagged with INVALID_OP) or outside the range + // of values of the integer type (flagged with OVERFLOW or UNDERFLOW). + None + } else { + // Floating point value can be represented by the integer type after rounding. + // The INEXACT flag is ignored on purpose to allow rounding. + Some(Scalar::from_uint(res.value, size)) + } + } + // Signed + ty::Int(t) => { + let size = Integer::from_int_ty(this, *t).size(); + let res = f.to_i128_r(size.bits_usize(), round, &mut false); + if res.status.intersects( + rustc_apfloat::Status::INVALID_OP + | rustc_apfloat::Status::OVERFLOW + | rustc_apfloat::Status::UNDERFLOW, + ) { + // Floating point value is NaN (flagged with INVALID_OP) or outside the range + // of values of the integer type (flagged with OVERFLOW or UNDERFLOW). + None + } else { + // Floating point value can be represented by the integer type after rounding. + // The INEXACT flag is ignored on purpose to allow rounding. + Some(Scalar::from_int(res.value, size)) + } + } + // Nothing else + _ => + span_bug!( + this.cur_span(), + "attempted float-to-int conversion with non-int output type {dest_ty:?}" + ), + } + } } impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { diff --git a/src/shims/intrinsics/mod.rs b/src/shims/intrinsics/mod.rs index c900ced19c..f0b2850da0 100644 --- a/src/shims/intrinsics/mod.rs +++ b/src/shims/intrinsics/mod.rs @@ -6,12 +6,12 @@ use std::iter; use log::trace; use rustc_apfloat::{Float, Round}; -use rustc_middle::ty::layout::{IntegerExt, LayoutOf}; +use rustc_middle::ty::layout::LayoutOf; use rustc_middle::{ mir, ty::{self, FloatTy, Ty}, }; -use rustc_target::abi::{Integer, Size}; +use rustc_target::abi::Size; use crate::*; use atomic::EvalContextExt as _; @@ -393,47 +393,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { F: Float + Into>, { let this = self.eval_context_ref(); - - // Step 1: cut off the fractional part of `f`. The result of this is - // guaranteed to be precisely representable in IEEE floats. - let f = f.round_to_integral(Round::TowardZero).value; - - // Step 2: Cast the truncated float to the target integer type and see if we lose any information in this step. - Ok(match dest_ty.kind() { - // Unsigned - ty::Uint(t) => { - let size = Integer::from_uint_ty(this, *t).size(); - let res = f.to_u128(size.bits_usize()); - if res.status.is_empty() { - // No status flags means there was no further rounding or other loss of precision. - Scalar::from_uint(res.value, size) - } else { - // `f` was not representable in this integer type. - throw_ub_format!( - "`float_to_int_unchecked` intrinsic called on {f} which cannot be represented in target type `{dest_ty:?}`", - ); - } - } - // Signed - ty::Int(t) => { - let size = Integer::from_int_ty(this, *t).size(); - let res = f.to_i128(size.bits_usize()); - if res.status.is_empty() { - // No status flags means there was no further rounding or other loss of precision. - Scalar::from_int(res.value, size) - } else { - // `f` was not representable in this integer type. - throw_ub_format!( - "`float_to_int_unchecked` intrinsic called on {f} which cannot be represented in target type `{dest_ty:?}`", - ); - } - } - // Nothing else - _ => - span_bug!( - this.cur_span(), - "`float_to_int_unchecked` called with non-int output type {dest_ty:?}" - ), - }) + Ok(this + .float_to_int_checked(f, dest_ty, Round::TowardZero) + .ok_or_else(|| err_ub_format!( + "`float_to_int_unchecked` intrinsic called on {f} which cannot be represented in target type `{dest_ty:?}`", + ))?) } } diff --git a/src/shims/x86/sse.rs b/src/shims/x86/sse.rs index deae0875fb..b18441bb40 100644 --- a/src/shims/x86/sse.rs +++ b/src/shims/x86/sse.rs @@ -195,24 +195,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { _ => unreachable!(), }; - let mut exact = false; - let cvt = op.to_i128_r(32, rnd, &mut exact); - let res = if cvt.status.intersects( - rustc_apfloat::Status::INVALID_OP - | rustc_apfloat::Status::OVERFLOW - | rustc_apfloat::Status::UNDERFLOW, - ) { - // Input is NaN (flagged with INVALID_OP) or does not fit - // in an i32 (flagged with OVERFLOW or UNDERFLOW), fallback - // to minimum acording to SSE semantics. The INEXACT flag - // is ignored on purpose because rounding can happen during - // float-to-int conversion. - i32::MIN - } else { - i32::try_from(cvt.value).unwrap() - }; + let res = this.float_to_int_checked(op, dest.layout.ty, rnd).unwrap_or_else(|| { + // Fallback to minimum acording to SSE semantics. + Scalar::from_i32(i32::MIN) + }); - this.write_scalar(Scalar::from_i32(res), dest)?; + this.write_scalar(res, dest)?; } // Use to implement _mm_cvtss_si64 and _mm_cvttss_si64. // Converts the first component of `op` from f32 to i64. @@ -232,24 +220,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { _ => unreachable!(), }; - let mut exact = false; - let cvt = op.to_i128_r(64, rnd, &mut exact); - let res = if cvt.status.intersects( - rustc_apfloat::Status::INVALID_OP - | rustc_apfloat::Status::OVERFLOW - | rustc_apfloat::Status::UNDERFLOW, - ) { - // Input is NaN (flagged with INVALID_OP) or does not fit - // in an i64 (flagged with OVERFLOW or UNDERFLOW), fallback - // to minimum acording to SSE semantics. The INEXACT flag - // is ignored on purpose because rounding can happen during - // float-to-int conversion. - i64::MIN - } else { - i64::try_from(cvt.value).unwrap() - }; + let res = this.float_to_int_checked(op, dest.layout.ty, rnd).unwrap_or_else(|| { + // Fallback to minimum acording to SSE semantics. + Scalar::from_i64(i64::MIN) + }); - this.write_scalar(Scalar::from_i64(res), dest)?; + this.write_scalar(res, dest)?; } // Used to implement the _mm_cvtsi32_ss function. // Converts `right` from i32 to f32. Returns a SIMD vector with diff --git a/tests/fail/intrinsics/float_to_int_64_neg.stderr b/tests/fail/intrinsics/float_to_int_64_neg.stderr index 7e24f45f65..ddf3249d05 100644 --- a/tests/fail/intrinsics/float_to_int_64_neg.stderr +++ b/tests/fail/intrinsics/float_to_int_64_neg.stderr @@ -1,8 +1,8 @@ -error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on -1 which cannot be represented in target type `u128` +error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on -1.0000000000000999 which cannot be represented in target type `u128` --> $DIR/float_to_int_64_neg.rs:LL:CC | LL | float_to_int_unchecked::(-1.0000000000001f64); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on -1 which cannot be represented in target type `u128` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on -1.0000000000000999 which cannot be represented in target type `u128` | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information