Skip to content

Commit 78ce616

Browse files
committed
implement float_to_int_unchecked
1 parent 8d1f533 commit 78ce616

File tree

2 files changed

+73
-5
lines changed

2 files changed

+73
-5
lines changed

src/shims/intrinsics.rs

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
use std::iter;
22
use std::convert::TryFrom;
33

4+
use rustc_ast::ast::FloatTy;
45
use rustc_middle::{mir, ty};
5-
use rustc_apfloat::Float;
6-
use rustc_target::abi::{Align, LayoutOf};
6+
use rustc_apfloat::{Float, FloatConvert, Round, ieee::{Double, Single}};
7+
use rustc_target::abi::{Align, LayoutOf, Size};
78

89
use crate::*;
910

@@ -279,6 +280,22 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
279280
this.write_scalar(Scalar::from_u64(f.powi(i).to_bits()), dest)?;
280281
}
281282

283+
"float_to_int_unchecked" => {
284+
let val = this.read_immediate(args[0])?;
285+
286+
let res = match val.layout.ty.kind {
287+
ty::Float(FloatTy::F32) => {
288+
this.float_to_int_unchecked(val.to_scalar()?.to_f32()?, dest.layout.ty)?
289+
}
290+
ty::Float(FloatTy::F64) => {
291+
this.float_to_int_unchecked(val.to_scalar()?.to_f64()?, dest.layout.ty)?
292+
}
293+
_ => bug!("`float_to_int_unchecked` called with non-float input type {:?}", val.layout.ty),
294+
};
295+
296+
this.write_scalar(res, dest)?;
297+
}
298+
282299
// Atomic operations
283300
#[rustfmt::skip]
284301
| "atomic_load"
@@ -491,4 +508,55 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
491508
this.go_to_block(ret);
492509
Ok(())
493510
}
511+
512+
fn float_to_int_unchecked<F>(
513+
&self,
514+
f: F,
515+
dest_ty: ty::Ty<'tcx>,
516+
) -> InterpResult<'tcx, Scalar<Tag>>
517+
where
518+
F: Float + Into<Scalar<Tag>> + FloatConvert<Single> + FloatConvert<Double>,
519+
{
520+
let this = self.eval_context_ref();
521+
522+
// Step 1: cut off the fractional part of `f`. The result of this is
523+
// guaranteed to be precisely representable in IEEE floats.
524+
let f = f.round_to_integral(Round::TowardZero).value;
525+
526+
// Step 2: Cast the truncated float to the target integer type and see if we lose any information in this step.
527+
Ok(match dest_ty.kind {
528+
// Unsigned
529+
ty::Uint(t) => {
530+
let width = t.bit_width().unwrap_or_else(|| this.pointer_size().bits());
531+
let res = f.to_u128(usize::try_from(width).unwrap());
532+
if res.status.is_empty() {
533+
// No status flags means there was no further rounding or other loss of precision.
534+
Scalar::from_uint(res.value, Size::from_bits(width))
535+
} else {
536+
// `f` was not representable in this integer type.
537+
throw_ub_format!(
538+
"`float_to_int_unchecked` intrinsic called on {} which cannot be represented in target type `{:?}`",
539+
f, dest_ty,
540+
);
541+
}
542+
}
543+
// Signed
544+
ty::Int(t) => {
545+
let width = t.bit_width().unwrap_or_else(|| this.pointer_size().bits());
546+
let res = f.to_i128(usize::try_from(width).unwrap());
547+
if res.status.is_empty() {
548+
// No status flags means there was no further rounding or other loss of precision.
549+
Scalar::from_int(res.value, Size::from_bits(width))
550+
} else {
551+
// `f` was not representable in this integer type.
552+
throw_ub_format!(
553+
"`float_to_int_unchecked` intrinsic called on {} which cannot be represented in target type `{:?}`",
554+
f, dest_ty,
555+
);
556+
}
557+
}
558+
// Nothing else
559+
_ => bug!("`float_to_int_unchecked` called with non-int output type {:?}", dest_ty),
560+
})
561+
}
494562
}

tests/run-pass/float.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,9 @@ fn casts() {
146146
test_cast::<f32, u32>(4294967040.0, 0u32.wrapping_sub(256));
147147
test_cast::<f32, u32>(/*-0x1.ccccccp-1*/ f32::from_bits(0xbf666666), 0);
148148
test_cast::<f32, u32>(/*-0x1.fffffep-1*/ f32::from_bits(0xbf7fffff), 0);
149-
test_cast::<f32, u32>((u32::MAX-127) as f32, u32::MAX); // rounding loss
150149
test_cast::<f32, u32>((u32::MAX-128) as f32, u32::MAX-255); // rounding loss
151150
// unrepresentable casts
151+
assert_eq::<u32>((u32::MAX-127) as f32 as u32, u32::MAX); // rounds up and then becomes unrepresentable
152152
assert_eq::<u32>(4294967296.0f32 as u32, u32::MAX);
153153
assert_eq::<u32>(-5.0f32 as u32, 0);
154154
assert_eq::<u32>(f32::MAX as u32, u32::MAX);
@@ -211,12 +211,12 @@ fn casts() {
211211
test_cast::<f64, u64>(0.0, 0);
212212
test_cast::<f64, u64>(-0.0, 0);
213213
test_cast::<f64, u64>(5.0, 5);
214-
test_cast::<f64, u64>(-5.0, 0);
215214
test_cast::<f64, u64>(1e16, 10000000000000000);
216-
test_cast::<f64, u64>((u64::MAX-1023) as f64, u64::MAX); // rounding loss
217215
test_cast::<f64, u64>((u64::MAX-1024) as f64, u64::MAX-2047); // rounding loss
218216
test_cast::<f64, u64>(9223372036854775808.0, 9223372036854775808);
219217
// unrepresentable casts
218+
assert_eq::<u64>(-5.0f64 as u64, 0);
219+
assert_eq::<u64>((u64::MAX-1023) as f64 as u64, u64::MAX); // rounds up and then becomes unrepresentable
220220
assert_eq::<u64>(18446744073709551616.0f64 as u64, u64::MAX);
221221
assert_eq::<u64>(f64::MAX as u64, u64::MAX);
222222
assert_eq::<u64>(f64::MIN as u64, 0);

0 commit comments

Comments
 (0)