Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement float_to_int_unchecked #1325

Merged
merged 7 commits into from Apr 18, 2020
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 @@ -491,4 +508,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>(340282366920938463463374607431768211455.0f64); } //~ ERROR: cannot be represented in target type `u128`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this test is correct, but it looks misleading or confusing. 340282366920938463463374607431768211455 (the integer) is u128::MAX, so at first glance the conversion should succeed. But the 340282366920938463463374607431768211455.0f64 float is in fact not equal to that integer. That integer cannot be represented exactly in f64, so the Rust parser presumably rounds to the nearest representable float which is one more, 2^128, and does indeed overflow conversion to u128.

(I’m using Python to figure this out since it has infinite-capacity integers and f64 floats. a = 340282366920938463463374607431768211455; (hex(a), int(float(a)) - a, a.bit_length()) prints ('0xffffffffffffffffffffffffffffffff', 1, 128).)

But the point of these tests is not to test the Rust parser. I feel it’d be better to pick a decimal representation of the float that would, if precision and range were infinite, have the same numerical value as the actual f64 value does.

In fact it’d be even better if the source code of test cases could contain something closer to the memory representation of f64. Hex float syntax exists, but not in Rust literals. I don’t know if this would be worth using a library for parsing it from strings. I see some other tests use f32::from_bits with hex integers, but that’s much harder for humans to read.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, that number here and these from_bits constants are all derived from https://github.com/WebAssembly/testsuite/blob/master/conversions.wast.

I could change this to u128::MAX as f64, if you think that's better.

}
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`
}