Skip to content

Commit 3726998

Browse files
committed
Auto merge of #3025 - eduardosm:float-to-int, r=RalfJung
Add checked float-to-int helper function As discussed in #2989 (comment)
2 parents 56862c3 + 5141436 commit 3726998

File tree

6 files changed

+132
-117
lines changed

6 files changed

+132
-117
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

+25-60
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,
12-
ty::{self, FloatTy, Ty},
12+
ty::{self, FloatTy},
1313
};
14-
use rustc_target::abi::{Integer, Size};
14+
use rustc_target::abi::Size;
1515

1616
use crate::*;
1717
use atomic::EvalContextExt as _;
@@ -356,10 +356,28 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
356356
let val = this.read_immediate(val)?;
357357

358358
let res = match val.layout.ty.kind() {
359-
ty::Float(FloatTy::F32) =>
360-
this.float_to_int_unchecked(val.to_scalar().to_f32()?, dest.layout.ty)?,
361-
ty::Float(FloatTy::F64) =>
362-
this.float_to_int_unchecked(val.to_scalar().to_f64()?, dest.layout.ty)?,
359+
ty::Float(FloatTy::F32) => {
360+
let f = val.to_scalar().to_f32()?;
361+
this
362+
.float_to_int_checked(f, dest.layout.ty, Round::TowardZero)
363+
.ok_or_else(|| {
364+
err_ub_format!(
365+
"`float_to_int_unchecked` intrinsic called on {f} which cannot be represented in target type `{:?}`",
366+
dest.layout.ty
367+
)
368+
})?
369+
}
370+
ty::Float(FloatTy::F64) => {
371+
let f = val.to_scalar().to_f64()?;
372+
this
373+
.float_to_int_checked(f, dest.layout.ty, Round::TowardZero)
374+
.ok_or_else(|| {
375+
err_ub_format!(
376+
"`float_to_int_unchecked` intrinsic called on {f} which cannot be represented in target type `{:?}`",
377+
dest.layout.ty
378+
)
379+
})?
380+
}
363381
_ =>
364382
span_bug!(
365383
this.cur_span(),
@@ -383,57 +401,4 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
383401

384402
Ok(())
385403
}
386-
387-
fn float_to_int_unchecked<F>(
388-
&self,
389-
f: F,
390-
dest_ty: Ty<'tcx>,
391-
) -> InterpResult<'tcx, Scalar<Provenance>>
392-
where
393-
F: Float + Into<Scalar<Provenance>>,
394-
{
395-
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-
})
438-
}
439404
}

src/shims/intrinsics/simd.rs

+31-16
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use rustc_apfloat::Float;
1+
use rustc_apfloat::{Float, Round};
22
use rustc_middle::ty::layout::{HasParamEnv, LayoutOf};
33
use rustc_middle::{mir, ty, ty::FloatTy};
44
use rustc_target::abi::{Endian, HasDataLayout, Size};
@@ -420,7 +420,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
420420
}
421421
}
422422
}
423-
#[rustfmt::skip]
424423
"cast" | "as" | "cast_ptr" | "expose_addr" | "from_exposed_addr" => {
425424
let [op] = check_arg_count(args)?;
426425
let (op, op_len) = this.operand_to_simd(op)?;
@@ -440,7 +439,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
440439

441440
let val = match (op.layout.ty.kind(), dest.layout.ty.kind()) {
442441
// Int-to-(int|float): always safe
443-
(ty::Int(_) | ty::Uint(_), ty::Int(_) | ty::Uint(_) | ty::Float(_)) if safe_cast || unsafe_cast =>
442+
(ty::Int(_) | ty::Uint(_), ty::Int(_) | ty::Uint(_) | ty::Float(_))
443+
if safe_cast || unsafe_cast =>
444444
this.int_to_int_or_float(&op, dest.layout.ty)?,
445445
// Float-to-float: always safe
446446
(ty::Float(_), ty::Float(_)) if safe_cast || unsafe_cast =>
@@ -449,21 +449,36 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
449449
(ty::Float(_), ty::Int(_) | ty::Uint(_)) if safe_cast =>
450450
this.float_to_float_or_int(&op, dest.layout.ty)?,
451451
// Float-to-int in unchecked mode
452-
(ty::Float(FloatTy::F32), ty::Int(_) | ty::Uint(_)) if unsafe_cast =>
453-
this.float_to_int_unchecked(op.to_scalar().to_f32()?, dest.layout.ty)?.into(),
454-
(ty::Float(FloatTy::F64), ty::Int(_) | ty::Uint(_)) if unsafe_cast =>
455-
this.float_to_int_unchecked(op.to_scalar().to_f64()?, dest.layout.ty)?.into(),
456-
// Ptr-to-ptr cast
457-
(ty::RawPtr(..), ty::RawPtr(..)) if ptr_cast => {
458-
this.ptr_to_ptr(&op, dest.layout.ty)?
459-
}
460-
// Ptr/Int casts
461-
(ty::RawPtr(..), ty::Int(_) | ty::Uint(_)) if expose_cast => {
462-
this.pointer_expose_address_cast(&op, dest.layout.ty)?
452+
(ty::Float(FloatTy::F32), ty::Int(_) | ty::Uint(_)) if unsafe_cast => {
453+
let f = op.to_scalar().to_f32()?;
454+
this.float_to_int_checked(f, dest.layout.ty, Round::TowardZero)
455+
.ok_or_else(|| {
456+
err_ub_format!(
457+
"`simd_cast` intrinsic called on {f} which cannot be represented in target type `{:?}`",
458+
dest.layout.ty
459+
)
460+
})?
461+
.into()
463462
}
464-
(ty::Int(_) | ty::Uint(_), ty::RawPtr(..)) if from_exposed_cast => {
465-
this.pointer_from_exposed_address_cast(&op, dest.layout.ty)?
463+
(ty::Float(FloatTy::F64), ty::Int(_) | ty::Uint(_)) if unsafe_cast => {
464+
let f = op.to_scalar().to_f64()?;
465+
this.float_to_int_checked(f, dest.layout.ty, Round::TowardZero)
466+
.ok_or_else(|| {
467+
err_ub_format!(
468+
"`simd_cast` intrinsic called on {f} which cannot be represented in target type `{:?}`",
469+
dest.layout.ty
470+
)
471+
})?
472+
.into()
466473
}
474+
// Ptr-to-ptr cast
475+
(ty::RawPtr(..), ty::RawPtr(..)) if ptr_cast =>
476+
this.ptr_to_ptr(&op, dest.layout.ty)?,
477+
// Ptr/Int casts
478+
(ty::RawPtr(..), ty::Int(_) | ty::Uint(_)) if expose_cast =>
479+
this.pointer_expose_address_cast(&op, dest.layout.ty)?,
480+
(ty::Int(_) | ty::Uint(_), ty::RawPtr(..)) if from_exposed_cast =>
481+
this.pointer_from_exposed_address_cast(&op, dest.layout.ty)?,
467482
// Error otherwise
468483
_ =>
469484
throw_unsup_format!(

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

tests/fail/intrinsics/simd-float-to-int.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 3.40282347E+38 which cannot be represented in target type `i32`
1+
error: Undefined Behavior: `simd_cast` intrinsic called on 3.40282347E+38 which cannot be represented in target type `i32`
22
--> $DIR/simd-float-to-int.rs:LL:CC
33
|
44
LL | let _x: i32x2 = f32x2::from_array([f32::MAX, f32::MIN]).to_int_unchecked();
5-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on 3.40282347E+38 which cannot be represented in target type `i32`
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `simd_cast` intrinsic called on 3.40282347E+38 which cannot be represented in target type `i32`
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)