Add checked float-to-int helper function

This commit is contained in:
Eduardo Sánchez Muñoz 2023-08-11 17:58:09 +02:00
parent a45f181eb0
commit fe35dd1afc
4 changed files with 81 additions and 83 deletions

View File

@ -13,11 +13,11 @@
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 @@ fn item_link_name(&self, def_id: DefId) -> Symbol {
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<F>(
&self,
f: F,
dest_ty: Ty<'tcx>,
round: rustc_apfloat::Round,
) -> Option<Scalar<Provenance>>
where
F: rustc_apfloat::Float + Into<Scalar<Provenance>>,
{
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> {

View File

@ -6,12 +6,12 @@
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 @@ fn float_to_int_unchecked<F>(
F: Float + Into<Scalar<Provenance>>,
{
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:?}`",
))?)
}
}

View File

@ -195,24 +195,12 @@ fn emulate_x86_sse_intrinsic(
_ => 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 @@ fn emulate_x86_sse_intrinsic(
_ => 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

View File

@ -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::<f64, u128>(-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