Skip to content

Commit 45113eb

Browse files
committed
Auto merge of #1325 - RalfJung:float_to_int_unchecked, r=RalfJung
implement float_to_int_unchecked @hanna-kruppe would be great if you could have a look at this. `float.rs` tests legal casts. `test_cast` checks that both `as` casts and unchecked casts work (i.e., these are not saturating). The `compile-fail` tests should ensure that illegal casts via the intrinsic are detected as such. Fixes #1264
2 parents 72667b5 + bb38ab4 commit 45113eb

25 files changed

+425
-68
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, Round};
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"
@@ -493,4 +510,55 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
493510
this.go_to_block(ret);
494511
Ok(())
495512
}
513+
514+
fn float_to_int_unchecked<F>(
515+
&self,
516+
f: F,
517+
dest_ty: ty::Ty<'tcx>,
518+
) -> InterpResult<'tcx, Scalar<Tag>>
519+
where
520+
F: Float + Into<Scalar<Tag>>
521+
{
522+
let this = self.eval_context_ref();
523+
524+
// Step 1: cut off the fractional part of `f`. The result of this is
525+
// guaranteed to be precisely representable in IEEE floats.
526+
let f = f.round_to_integral(Round::TowardZero).value;
527+
528+
// Step 2: Cast the truncated float to the target integer type and see if we lose any information in this step.
529+
Ok(match dest_ty.kind {
530+
// Unsigned
531+
ty::Uint(t) => {
532+
let width = t.bit_width().unwrap_or_else(|| this.pointer_size().bits());
533+
let res = f.to_u128(usize::try_from(width).unwrap());
534+
if res.status.is_empty() {
535+
// No status flags means there was no further rounding or other loss of precision.
536+
Scalar::from_uint(res.value, Size::from_bits(width))
537+
} else {
538+
// `f` was not representable in this integer type.
539+
throw_ub_format!(
540+
"`float_to_int_unchecked` intrinsic called on {} which cannot be represented in target type `{:?}`",
541+
f, dest_ty,
542+
);
543+
}
544+
}
545+
// Signed
546+
ty::Int(t) => {
547+
let width = t.bit_width().unwrap_or_else(|| this.pointer_size().bits());
548+
let res = f.to_i128(usize::try_from(width).unwrap());
549+
if res.status.is_empty() {
550+
// No status flags means there was no further rounding or other loss of precision.
551+
Scalar::from_int(res.value, Size::from_bits(width))
552+
} else {
553+
// `f` was not representable in this integer type.
554+
throw_ub_format!(
555+
"`float_to_int_unchecked` intrinsic called on {} which cannot be represented in target type `{:?}`",
556+
f, dest_ty,
557+
);
558+
}
559+
}
560+
// Nothing else
561+
_ => bug!("`float_to_int_unchecked` called with non-int output type {:?}", dest_ty),
562+
})
563+
}
496564
}

tests/compile-fail/intrinsics/copy_overlapping.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
//error-pattern: copy_nonoverlapping called on overlapping ranges
21
#![feature(intrinsics)]
32

43
// Directly call intrinsic to avoid debug assertions in libstd
@@ -11,6 +10,6 @@ fn main() {
1110
unsafe {
1211
let a = data.as_mut_ptr();
1312
let b = a.wrapping_offset(1) as *mut _;
14-
copy_nonoverlapping(a, b, 2);
13+
copy_nonoverlapping(a, b, 2); //~ ERROR copy_nonoverlapping called on overlapping ranges
1514
}
1615
}

tests/compile-fail/intrinsics/copy_unaligned.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
//error-pattern: accessing memory with alignment 1, but alignment 2 is required
21
#![feature(intrinsics)]
32

43
// Directly call intrinsic to avoid debug assertions in libstd
@@ -10,5 +9,5 @@ fn main() {
109
let mut data = [0u16; 8];
1110
let ptr = (&mut data[0] as *mut u16 as *mut u8).wrapping_add(1) as *mut u16;
1211
// Even copying 0 elements to something unaligned should error
13-
unsafe { copy_nonoverlapping(&data[5], ptr, 0); }
12+
unsafe { copy_nonoverlapping(&data[5], ptr, 0); } //~ ERROR accessing memory with alignment 1, but alignment 2 is required
1413
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#![feature(intrinsics)]
2+
3+
// Directly call intrinsic to avoid debug assertions in libstd
4+
extern "rust-intrinsic" {
5+
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
6+
}
7+
8+
fn main() {
9+
unsafe { float_to_int_unchecked::<f32, i32>(f32::INFINITY); } //~ ERROR: cannot be represented in target type `i32`
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#![feature(intrinsics)]
2+
3+
// Directly call intrinsic to avoid debug assertions in libstd
4+
extern "rust-intrinsic" {
5+
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
6+
}
7+
8+
fn main() {
9+
unsafe { float_to_int_unchecked::<f32, i32>(f32::NEG_INFINITY); } //~ ERROR: cannot be represented in target type `i32`
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#![feature(intrinsics)]
2+
3+
// Directly call intrinsic to avoid debug assertions in libstd
4+
extern "rust-intrinsic" {
5+
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
6+
}
7+
8+
fn main() {
9+
unsafe { float_to_int_unchecked::<f32, u32>(f32::NAN); } //~ ERROR: cannot be represented in target type `u32`
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#![feature(intrinsics)]
2+
3+
// Directly call intrinsic to avoid debug assertions in libstd
4+
extern "rust-intrinsic" {
5+
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
6+
}
7+
8+
fn main() {
9+
unsafe { float_to_int_unchecked::<f32, u32>(-f32::NAN); } //~ ERROR: cannot be represented in target type `u32`
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#![feature(intrinsics)]
2+
3+
// Directly call intrinsic to avoid debug assertions in libstd
4+
extern "rust-intrinsic" {
5+
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
6+
}
7+
8+
fn main() {
9+
unsafe { float_to_int_unchecked::<f32, u32>(-1.000000001f32); } //~ ERROR: cannot be represented in target type `u32`
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#![feature(intrinsics)]
2+
3+
// Directly call intrinsic to avoid debug assertions in libstd
4+
extern "rust-intrinsic" {
5+
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
6+
}
7+
8+
fn main() {
9+
unsafe { float_to_int_unchecked::<f32, i32>(2147483648.0f32); } //~ ERROR: cannot be represented in target type `i32`
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#![feature(intrinsics)]
2+
3+
// Directly call intrinsic to avoid debug assertions in libstd
4+
extern "rust-intrinsic" {
5+
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
6+
}
7+
8+
fn main() {
9+
unsafe { float_to_int_unchecked::<f32, u32>((u32::MAX-127) as f32); } //~ ERROR: cannot be represented in target type `u32`
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#![feature(intrinsics)]
2+
3+
// Directly call intrinsic to avoid debug assertions in libstd
4+
extern "rust-intrinsic" {
5+
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
6+
}
7+
8+
fn main() {
9+
unsafe { float_to_int_unchecked::<f32, i32>(-2147483904.0f32); } //~ ERROR: cannot be represented in target type `i32`
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#![feature(intrinsics)]
2+
3+
// Directly call intrinsic to avoid debug assertions in libstd
4+
extern "rust-intrinsic" {
5+
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
6+
}
7+
8+
fn main() {
9+
unsafe { float_to_int_unchecked::<f64, u128>(f64::INFINITY); } //~ ERROR: cannot be represented in target type `u128`
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#![feature(intrinsics)]
2+
3+
// Directly call intrinsic to avoid debug assertions in libstd
4+
extern "rust-intrinsic" {
5+
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
6+
}
7+
8+
fn main() {
9+
unsafe { float_to_int_unchecked::<f64, u128>(f64::NEG_INFINITY); } //~ ERROR: cannot be represented in target type `u128`
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#![feature(intrinsics)]
2+
3+
// Directly call intrinsic to avoid debug assertions in libstd
4+
extern "rust-intrinsic" {
5+
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
6+
}
7+
8+
fn main() {
9+
unsafe { float_to_int_unchecked::<f64, i128>(f64::NEG_INFINITY); } //~ ERROR: cannot be represented in target type `i128`
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#![feature(intrinsics)]
2+
3+
// Directly call intrinsic to avoid debug assertions in libstd
4+
extern "rust-intrinsic" {
5+
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
6+
}
7+
8+
fn main() {
9+
unsafe { float_to_int_unchecked::<f64, u32>(f64::NAN); } //~ ERROR: cannot be represented in target type `u32`
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#![feature(intrinsics)]
2+
3+
// Directly call intrinsic to avoid debug assertions in libstd
4+
extern "rust-intrinsic" {
5+
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
6+
}
7+
8+
fn main() {
9+
unsafe { float_to_int_unchecked::<f64, u128>(-1.0000000000001f64); } //~ ERROR: cannot be represented in target type `u128`
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#![feature(intrinsics)]
2+
3+
// Directly call intrinsic to avoid debug assertions in libstd
4+
extern "rust-intrinsic" {
5+
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
6+
}
7+
8+
fn main() {
9+
unsafe { float_to_int_unchecked::<f64, i32>(2147483648.0f64); } //~ ERROR: cannot be represented in target type `i32`
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#![feature(intrinsics)]
2+
3+
// Directly call intrinsic to avoid debug assertions in libstd
4+
extern "rust-intrinsic" {
5+
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
6+
}
7+
8+
fn main() {
9+
unsafe { float_to_int_unchecked::<f64, i64>(9223372036854775808.0f64); } //~ ERROR: cannot be represented in target type `i64`
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#![feature(intrinsics)]
2+
3+
// Directly call intrinsic to avoid debug assertions in libstd
4+
extern "rust-intrinsic" {
5+
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
6+
}
7+
8+
fn main() {
9+
unsafe { float_to_int_unchecked::<f64, u64>(18446744073709551616.0f64); } //~ ERROR: cannot be represented in target type `u64`
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#![feature(intrinsics)]
2+
3+
// Directly call intrinsic to avoid debug assertions in libstd
4+
extern "rust-intrinsic" {
5+
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
6+
}
7+
8+
fn main() {
9+
unsafe { float_to_int_unchecked::<f64, u128>(u128::MAX as f64); } //~ ERROR: cannot be represented in target type `u128`
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#![feature(intrinsics)]
2+
3+
// Directly call intrinsic to avoid debug assertions in libstd
4+
extern "rust-intrinsic" {
5+
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
6+
}
7+
8+
fn main() {
9+
unsafe { float_to_int_unchecked::<f64, i128>(240282366920938463463374607431768211455.0f64); } //~ ERROR: cannot be represented in target type `i128`
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#![feature(intrinsics)]
2+
3+
// Directly call intrinsic to avoid debug assertions in libstd
4+
extern "rust-intrinsic" {
5+
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
6+
}
7+
8+
fn main() {
9+
unsafe { float_to_int_unchecked::<f64, i32>(-2147483649.0f64); } //~ ERROR: cannot be represented in target type `i32`
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#![feature(intrinsics)]
2+
3+
// Directly call intrinsic to avoid debug assertions in libstd
4+
extern "rust-intrinsic" {
5+
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
6+
}
7+
8+
fn main() {
9+
unsafe { float_to_int_unchecked::<f64, i64>(-9223372036854777856.0f64); } //~ ERROR: cannot be represented in target type `i64`
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#![feature(intrinsics)]
2+
3+
// Directly call intrinsic to avoid debug assertions in libstd
4+
extern "rust-intrinsic" {
5+
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
6+
}
7+
8+
fn main() {
9+
unsafe { float_to_int_unchecked::<f64, i128>(-240282366920938463463374607431768211455.0f64); } //~ ERROR: cannot be represented in target type `i128`
10+
}

0 commit comments

Comments
 (0)