Skip to content

Commit

Permalink
Auto merge of #1325 - RalfJung:float_to_int_unchecked, r=RalfJung
Browse files Browse the repository at this point in the history
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
  • Loading branch information
bors committed Apr 18, 2020
2 parents 72667b5 + bb38ab4 commit 45113eb
Show file tree
Hide file tree
Showing 25 changed files with 425 additions and 68 deletions.
72 changes: 70 additions & 2 deletions src/shims/intrinsics.rs
@@ -1,9 +1,10 @@
use std::iter;
use std::convert::TryFrom;

use rustc_ast::ast::FloatTy;
use rustc_middle::{mir, ty};
use rustc_apfloat::Float;
use rustc_target::abi::{Align, LayoutOf};
use rustc_apfloat::{Float, Round};
use rustc_target::abi::{Align, LayoutOf, Size};

use crate::*;

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

"float_to_int_unchecked" => {
let val = this.read_immediate(args[0])?;

let res = match val.layout.ty.kind {
ty::Float(FloatTy::F32) => {
this.float_to_int_unchecked(val.to_scalar()?.to_f32()?, dest.layout.ty)?
}
ty::Float(FloatTy::F64) => {
this.float_to_int_unchecked(val.to_scalar()?.to_f64()?, dest.layout.ty)?
}
_ => bug!("`float_to_int_unchecked` called with non-float input type {:?}", val.layout.ty),
};

this.write_scalar(res, dest)?;
}

// Atomic operations
#[rustfmt::skip]
| "atomic_load"
Expand Down Expand Up @@ -493,4 +510,55 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
this.go_to_block(ret);
Ok(())
}

fn float_to_int_unchecked<F>(
&self,
f: F,
dest_ty: ty::Ty<'tcx>,
) -> InterpResult<'tcx, Scalar<Tag>>
where
F: Float + Into<Scalar<Tag>>
{
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 width = t.bit_width().unwrap_or_else(|| this.pointer_size().bits());
let res = f.to_u128(usize::try_from(width).unwrap());
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::from_bits(width))
} else {
// `f` was not representable in this integer type.
throw_ub_format!(
"`float_to_int_unchecked` intrinsic called on {} which cannot be represented in target type `{:?}`",
f, dest_ty,
);
}
}
// Signed
ty::Int(t) => {
let width = t.bit_width().unwrap_or_else(|| this.pointer_size().bits());
let res = f.to_i128(usize::try_from(width).unwrap());
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::from_bits(width))
} else {
// `f` was not representable in this integer type.
throw_ub_format!(
"`float_to_int_unchecked` intrinsic called on {} which cannot be represented in target type `{:?}`",
f, dest_ty,
);
}
}
// Nothing else
_ => bug!("`float_to_int_unchecked` called with non-int output type {:?}", dest_ty),
})
}
}
3 changes: 1 addition & 2 deletions tests/compile-fail/intrinsics/copy_overlapping.rs
@@ -1,4 +1,3 @@
//error-pattern: copy_nonoverlapping called on overlapping ranges
#![feature(intrinsics)]

// Directly call intrinsic to avoid debug assertions in libstd
Expand All @@ -11,6 +10,6 @@ fn main() {
unsafe {
let a = data.as_mut_ptr();
let b = a.wrapping_offset(1) as *mut _;
copy_nonoverlapping(a, b, 2);
copy_nonoverlapping(a, b, 2); //~ ERROR copy_nonoverlapping called on overlapping ranges
}
}
3 changes: 1 addition & 2 deletions tests/compile-fail/intrinsics/copy_unaligned.rs
@@ -1,4 +1,3 @@
//error-pattern: accessing memory with alignment 1, but alignment 2 is required
#![feature(intrinsics)]

// Directly call intrinsic to avoid debug assertions in libstd
Expand All @@ -10,5 +9,5 @@ fn main() {
let mut data = [0u16; 8];
let ptr = (&mut data[0] as *mut u16 as *mut u8).wrapping_add(1) as *mut u16;
// Even copying 0 elements to something unaligned should error
unsafe { copy_nonoverlapping(&data[5], ptr, 0); }
unsafe { copy_nonoverlapping(&data[5], ptr, 0); } //~ ERROR accessing memory with alignment 1, but alignment 2 is required
}
10 changes: 10 additions & 0 deletions tests/compile-fail/intrinsics/float_to_int_32_inf1.rs
@@ -0,0 +1,10 @@
#![feature(intrinsics)]

// Directly call intrinsic to avoid debug assertions in libstd
extern "rust-intrinsic" {
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
}

fn main() {
unsafe { float_to_int_unchecked::<f32, i32>(f32::INFINITY); } //~ ERROR: cannot be represented in target type `i32`
}
10 changes: 10 additions & 0 deletions tests/compile-fail/intrinsics/float_to_int_32_infneg1.rs
@@ -0,0 +1,10 @@
#![feature(intrinsics)]

// Directly call intrinsic to avoid debug assertions in libstd
extern "rust-intrinsic" {
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
}

fn main() {
unsafe { float_to_int_unchecked::<f32, i32>(f32::NEG_INFINITY); } //~ ERROR: cannot be represented in target type `i32`
}
10 changes: 10 additions & 0 deletions tests/compile-fail/intrinsics/float_to_int_32_nan.rs
@@ -0,0 +1,10 @@
#![feature(intrinsics)]

// Directly call intrinsic to avoid debug assertions in libstd
extern "rust-intrinsic" {
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
}

fn main() {
unsafe { float_to_int_unchecked::<f32, u32>(f32::NAN); } //~ ERROR: cannot be represented in target type `u32`
}
10 changes: 10 additions & 0 deletions tests/compile-fail/intrinsics/float_to_int_32_nanneg.rs
@@ -0,0 +1,10 @@
#![feature(intrinsics)]

// Directly call intrinsic to avoid debug assertions in libstd
extern "rust-intrinsic" {
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
}

fn main() {
unsafe { float_to_int_unchecked::<f32, u32>(-f32::NAN); } //~ ERROR: cannot be represented in target type `u32`
}
10 changes: 10 additions & 0 deletions tests/compile-fail/intrinsics/float_to_int_32_neg.rs
@@ -0,0 +1,10 @@
#![feature(intrinsics)]

// Directly call intrinsic to avoid debug assertions in libstd
extern "rust-intrinsic" {
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
}

fn main() {
unsafe { float_to_int_unchecked::<f32, u32>(-1.000000001f32); } //~ ERROR: cannot be represented in target type `u32`
}
10 changes: 10 additions & 0 deletions tests/compile-fail/intrinsics/float_to_int_32_too_big1.rs
@@ -0,0 +1,10 @@
#![feature(intrinsics)]

// Directly call intrinsic to avoid debug assertions in libstd
extern "rust-intrinsic" {
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
}

fn main() {
unsafe { float_to_int_unchecked::<f32, i32>(2147483648.0f32); } //~ ERROR: cannot be represented in target type `i32`
}
10 changes: 10 additions & 0 deletions tests/compile-fail/intrinsics/float_to_int_32_too_big2.rs
@@ -0,0 +1,10 @@
#![feature(intrinsics)]

// Directly call intrinsic to avoid debug assertions in libstd
extern "rust-intrinsic" {
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
}

fn main() {
unsafe { float_to_int_unchecked::<f32, u32>((u32::MAX-127) as f32); } //~ ERROR: cannot be represented in target type `u32`
}
10 changes: 10 additions & 0 deletions tests/compile-fail/intrinsics/float_to_int_32_too_small1.rs
@@ -0,0 +1,10 @@
#![feature(intrinsics)]

// Directly call intrinsic to avoid debug assertions in libstd
extern "rust-intrinsic" {
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
}

fn main() {
unsafe { float_to_int_unchecked::<f32, i32>(-2147483904.0f32); } //~ ERROR: cannot be represented in target type `i32`
}
10 changes: 10 additions & 0 deletions tests/compile-fail/intrinsics/float_to_int_64_inf1.rs
@@ -0,0 +1,10 @@
#![feature(intrinsics)]

// Directly call intrinsic to avoid debug assertions in libstd
extern "rust-intrinsic" {
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
}

fn main() {
unsafe { float_to_int_unchecked::<f64, u128>(f64::INFINITY); } //~ ERROR: cannot be represented in target type `u128`
}
10 changes: 10 additions & 0 deletions tests/compile-fail/intrinsics/float_to_int_64_infneg1.rs
@@ -0,0 +1,10 @@
#![feature(intrinsics)]

// Directly call intrinsic to avoid debug assertions in libstd
extern "rust-intrinsic" {
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
}

fn main() {
unsafe { float_to_int_unchecked::<f64, u128>(f64::NEG_INFINITY); } //~ ERROR: cannot be represented in target type `u128`
}
10 changes: 10 additions & 0 deletions tests/compile-fail/intrinsics/float_to_int_64_infneg2.rs
@@ -0,0 +1,10 @@
#![feature(intrinsics)]

// Directly call intrinsic to avoid debug assertions in libstd
extern "rust-intrinsic" {
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
}

fn main() {
unsafe { float_to_int_unchecked::<f64, i128>(f64::NEG_INFINITY); } //~ ERROR: cannot be represented in target type `i128`
}
10 changes: 10 additions & 0 deletions tests/compile-fail/intrinsics/float_to_int_64_nan.rs
@@ -0,0 +1,10 @@
#![feature(intrinsics)]

// Directly call intrinsic to avoid debug assertions in libstd
extern "rust-intrinsic" {
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
}

fn main() {
unsafe { float_to_int_unchecked::<f64, u32>(f64::NAN); } //~ ERROR: cannot be represented in target type `u32`
}
10 changes: 10 additions & 0 deletions tests/compile-fail/intrinsics/float_to_int_64_neg.rs
@@ -0,0 +1,10 @@
#![feature(intrinsics)]

// Directly call intrinsic to avoid debug assertions in libstd
extern "rust-intrinsic" {
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
}

fn main() {
unsafe { float_to_int_unchecked::<f64, u128>(-1.0000000000001f64); } //~ ERROR: cannot be represented in target type `u128`
}
10 changes: 10 additions & 0 deletions tests/compile-fail/intrinsics/float_to_int_64_too_big1.rs
@@ -0,0 +1,10 @@
#![feature(intrinsics)]

// Directly call intrinsic to avoid debug assertions in libstd
extern "rust-intrinsic" {
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
}

fn main() {
unsafe { float_to_int_unchecked::<f64, i32>(2147483648.0f64); } //~ ERROR: cannot be represented in target type `i32`
}
10 changes: 10 additions & 0 deletions tests/compile-fail/intrinsics/float_to_int_64_too_big2.rs
@@ -0,0 +1,10 @@
#![feature(intrinsics)]

// Directly call intrinsic to avoid debug assertions in libstd
extern "rust-intrinsic" {
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
}

fn main() {
unsafe { float_to_int_unchecked::<f64, i64>(9223372036854775808.0f64); } //~ ERROR: cannot be represented in target type `i64`
}
10 changes: 10 additions & 0 deletions tests/compile-fail/intrinsics/float_to_int_64_too_big3.rs
@@ -0,0 +1,10 @@
#![feature(intrinsics)]

// Directly call intrinsic to avoid debug assertions in libstd
extern "rust-intrinsic" {
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
}

fn main() {
unsafe { float_to_int_unchecked::<f64, u64>(18446744073709551616.0f64); } //~ ERROR: cannot be represented in target type `u64`
}
10 changes: 10 additions & 0 deletions tests/compile-fail/intrinsics/float_to_int_64_too_big4.rs
@@ -0,0 +1,10 @@
#![feature(intrinsics)]

// Directly call intrinsic to avoid debug assertions in libstd
extern "rust-intrinsic" {
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
}

fn main() {
unsafe { float_to_int_unchecked::<f64, u128>(u128::MAX as f64); } //~ ERROR: cannot be represented in target type `u128`
}
10 changes: 10 additions & 0 deletions tests/compile-fail/intrinsics/float_to_int_64_too_big5.rs
@@ -0,0 +1,10 @@
#![feature(intrinsics)]

// Directly call intrinsic to avoid debug assertions in libstd
extern "rust-intrinsic" {
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
}

fn main() {
unsafe { float_to_int_unchecked::<f64, i128>(240282366920938463463374607431768211455.0f64); } //~ ERROR: cannot be represented in target type `i128`
}
10 changes: 10 additions & 0 deletions tests/compile-fail/intrinsics/float_to_int_64_too_small1.rs
@@ -0,0 +1,10 @@
#![feature(intrinsics)]

// Directly call intrinsic to avoid debug assertions in libstd
extern "rust-intrinsic" {
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
}

fn main() {
unsafe { float_to_int_unchecked::<f64, i32>(-2147483649.0f64); } //~ ERROR: cannot be represented in target type `i32`
}
10 changes: 10 additions & 0 deletions tests/compile-fail/intrinsics/float_to_int_64_too_small2.rs
@@ -0,0 +1,10 @@
#![feature(intrinsics)]

// Directly call intrinsic to avoid debug assertions in libstd
extern "rust-intrinsic" {
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
}

fn main() {
unsafe { float_to_int_unchecked::<f64, i64>(-9223372036854777856.0f64); } //~ ERROR: cannot be represented in target type `i64`
}
10 changes: 10 additions & 0 deletions tests/compile-fail/intrinsics/float_to_int_64_too_small3.rs
@@ -0,0 +1,10 @@
#![feature(intrinsics)]

// Directly call intrinsic to avoid debug assertions in libstd
extern "rust-intrinsic" {
fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> Int;
}

fn main() {
unsafe { float_to_int_unchecked::<f64, i128>(-240282366920938463463374607431768211455.0f64); } //~ ERROR: cannot be represented in target type `i128`
}

0 comments on commit 45113eb

Please sign in to comment.