Skip to content

Commit b57bc6d

Browse files
committed
Add checked float-to-int helper function
1 parent c6bd71a commit b57bc6d

File tree

4 files changed

+81
-83
lines changed

4 files changed

+81
-83
lines changed

src/helpers.rs

+62-3
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ use rustc_index::IndexVec;
1313
use rustc_middle::mir;
1414
use rustc_middle::ty::{
1515
self,
16-
layout::{LayoutOf, TyAndLayout},
17-
List, TyCtxt,
16+
layout::{IntegerExt as _, LayoutOf, TyAndLayout},
17+
List, Ty, TyCtxt,
1818
};
1919
use rustc_span::{def_id::CrateNum, sym, Span, Symbol};
20-
use rustc_target::abi::{Align, FieldIdx, FieldsShape, Size, Variants};
20+
use rustc_target::abi::{Align, FieldIdx, FieldsShape, Integer, Size, Variants};
2121
use rustc_target::spec::abi::Abi;
2222

2323
use rand::RngCore;
@@ -1011,6 +1011,65 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
10111011
None => tcx.item_name(def_id),
10121012
}
10131013
}
1014+
1015+
/// Converts `f` to integer type `dest_ty` after rounding with mode `round`.
1016+
/// Returns `None` if `f` is NaN or out of range.
1017+
fn float_to_int_checked<F>(
1018+
&self,
1019+
f: F,
1020+
dest_ty: Ty<'tcx>,
1021+
round: rustc_apfloat::Round,
1022+
) -> Option<Scalar<Provenance>>
1023+
where
1024+
F: rustc_apfloat::Float + Into<Scalar<Provenance>>,
1025+
{
1026+
let this = self.eval_context_ref();
1027+
1028+
match dest_ty.kind() {
1029+
// Unsigned
1030+
ty::Uint(t) => {
1031+
let size = Integer::from_uint_ty(this, *t).size();
1032+
let res = f.to_u128_r(size.bits_usize(), round, &mut false);
1033+
if res.status.intersects(
1034+
rustc_apfloat::Status::INVALID_OP
1035+
| rustc_apfloat::Status::OVERFLOW
1036+
| rustc_apfloat::Status::UNDERFLOW,
1037+
) {
1038+
// Floating point value is NaN (flagged with INVALID_OP) or outside the range
1039+
// of values of the integer type (flagged with OVERFLOW or UNDERFLOW).
1040+
None
1041+
} else {
1042+
// Floating point value can be represented by the integer type after rounding.
1043+
// The INEXACT flag is ignored on purpose to allow rounding.
1044+
Some(Scalar::from_uint(res.value, size))
1045+
}
1046+
}
1047+
// Signed
1048+
ty::Int(t) => {
1049+
let size = Integer::from_int_ty(this, *t).size();
1050+
let res = f.to_i128_r(size.bits_usize(), round, &mut false);
1051+
if res.status.intersects(
1052+
rustc_apfloat::Status::INVALID_OP
1053+
| rustc_apfloat::Status::OVERFLOW
1054+
| rustc_apfloat::Status::UNDERFLOW,
1055+
) {
1056+
// Floating point value is NaN (flagged with INVALID_OP) or outside the range
1057+
// of values of the integer type (flagged with OVERFLOW or UNDERFLOW).
1058+
None
1059+
} else {
1060+
// Floating point value can be represented by the integer type after rounding.
1061+
// The INEXACT flag is ignored on purpose to allow rounding.
1062+
Some(Scalar::from_int(res.value, size))
1063+
}
1064+
}
1065+
// Nothing else
1066+
_ =>
1067+
span_bug!(
1068+
this.cur_span(),
1069+
"attempted float-to-int conversion with non-int output type {dest_ty:?}"
1070+
),
1071+
}
1072+
}
10141073
}
10151074

10161075
impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {

src/shims/intrinsics/mod.rs

+7-44
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ use std::iter;
66
use log::trace;
77

88
use rustc_apfloat::{Float, Round};
9-
use rustc_middle::ty::layout::{IntegerExt, LayoutOf};
9+
use rustc_middle::ty::layout::LayoutOf;
1010
use rustc_middle::{
1111
mir,
1212
ty::{self, FloatTy, Ty},
1313
};
14-
use rustc_target::abi::{Integer, Size};
14+
use rustc_target::abi::Size;
1515

1616
use crate::*;
1717
use atomic::EvalContextExt as _;
@@ -393,47 +393,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
393393
F: Float + Into<Scalar<Provenance>>,
394394
{
395395
let this = self.eval_context_ref();
396-
397-
// Step 1: cut off the fractional part of `f`. The result of this is
398-
// guaranteed to be precisely representable in IEEE floats.
399-
let f = f.round_to_integral(Round::TowardZero).value;
400-
401-
// Step 2: Cast the truncated float to the target integer type and see if we lose any information in this step.
402-
Ok(match dest_ty.kind() {
403-
// Unsigned
404-
ty::Uint(t) => {
405-
let size = Integer::from_uint_ty(this, *t).size();
406-
let res = f.to_u128(size.bits_usize());
407-
if res.status.is_empty() {
408-
// No status flags means there was no further rounding or other loss of precision.
409-
Scalar::from_uint(res.value, size)
410-
} else {
411-
// `f` was not representable in this integer type.
412-
throw_ub_format!(
413-
"`float_to_int_unchecked` intrinsic called on {f} which cannot be represented in target type `{dest_ty:?}`",
414-
);
415-
}
416-
}
417-
// Signed
418-
ty::Int(t) => {
419-
let size = Integer::from_int_ty(this, *t).size();
420-
let res = f.to_i128(size.bits_usize());
421-
if res.status.is_empty() {
422-
// No status flags means there was no further rounding or other loss of precision.
423-
Scalar::from_int(res.value, size)
424-
} else {
425-
// `f` was not representable in this integer type.
426-
throw_ub_format!(
427-
"`float_to_int_unchecked` intrinsic called on {f} which cannot be represented in target type `{dest_ty:?}`",
428-
);
429-
}
430-
}
431-
// Nothing else
432-
_ =>
433-
span_bug!(
434-
this.cur_span(),
435-
"`float_to_int_unchecked` called with non-int output type {dest_ty:?}"
436-
),
437-
})
396+
Ok(this
397+
.float_to_int_checked(f, dest_ty, Round::TowardZero)
398+
.ok_or_else(|| err_ub_format!(
399+
"`float_to_int_unchecked` intrinsic called on {f} which cannot be represented in target type `{dest_ty:?}`",
400+
))?)
438401
}
439402
}

src/shims/x86/sse.rs

+10-34
Original file line numberDiff line numberDiff line change
@@ -195,24 +195,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
195195
_ => unreachable!(),
196196
};
197197

198-
let mut exact = false;
199-
let cvt = op.to_i128_r(32, rnd, &mut exact);
200-
let res = if cvt.status.intersects(
201-
rustc_apfloat::Status::INVALID_OP
202-
| rustc_apfloat::Status::OVERFLOW
203-
| rustc_apfloat::Status::UNDERFLOW,
204-
) {
205-
// Input is NaN (flagged with INVALID_OP) or does not fit
206-
// in an i32 (flagged with OVERFLOW or UNDERFLOW), fallback
207-
// to minimum acording to SSE semantics. The INEXACT flag
208-
// is ignored on purpose because rounding can happen during
209-
// float-to-int conversion.
210-
i32::MIN
211-
} else {
212-
i32::try_from(cvt.value).unwrap()
213-
};
198+
let res = this.float_to_int_checked(op, dest.layout.ty, rnd).unwrap_or_else(|| {
199+
// Fallback to minimum acording to SSE semantics.
200+
Scalar::from_i32(i32::MIN)
201+
});
214202

215-
this.write_scalar(Scalar::from_i32(res), dest)?;
203+
this.write_scalar(res, dest)?;
216204
}
217205
// Use to implement _mm_cvtss_si64 and _mm_cvttss_si64.
218206
// Converts the first component of `op` from f32 to i64.
@@ -232,24 +220,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
232220
_ => unreachable!(),
233221
};
234222

235-
let mut exact = false;
236-
let cvt = op.to_i128_r(64, rnd, &mut exact);
237-
let res = if cvt.status.intersects(
238-
rustc_apfloat::Status::INVALID_OP
239-
| rustc_apfloat::Status::OVERFLOW
240-
| rustc_apfloat::Status::UNDERFLOW,
241-
) {
242-
// Input is NaN (flagged with INVALID_OP) or does not fit
243-
// in an i64 (flagged with OVERFLOW or UNDERFLOW), fallback
244-
// to minimum acording to SSE semantics. The INEXACT flag
245-
// is ignored on purpose because rounding can happen during
246-
// float-to-int conversion.
247-
i64::MIN
248-
} else {
249-
i64::try_from(cvt.value).unwrap()
250-
};
223+
let res = this.float_to_int_checked(op, dest.layout.ty, rnd).unwrap_or_else(|| {
224+
// Fallback to minimum acording to SSE semantics.
225+
Scalar::from_i64(i64::MIN)
226+
});
251227

252-
this.write_scalar(Scalar::from_i64(res), dest)?;
228+
this.write_scalar(res, dest)?;
253229
}
254230
// Used to implement the _mm_cvtsi32_ss function.
255231
// Converts `right` from i32 to f32. Returns a SIMD vector with

tests/fail/intrinsics/float_to_int_64_neg.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on -1 which cannot be represented in target type `u128`
1+
error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on -1.0000000000000999 which cannot be represented in target type `u128`
22
--> $DIR/float_to_int_64_neg.rs:LL:CC
33
|
44
LL | float_to_int_unchecked::<f64, u128>(-1.0000000000001f64);
5-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on -1 which cannot be represented in target type `u128`
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on -1.0000000000000999 which cannot be represented in target type `u128`
66
|
77
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
88
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information

0 commit comments

Comments
 (0)