Skip to content

Commit

Permalink
Add f16 and f128 support to Miri
Browse files Browse the repository at this point in the history
  • Loading branch information
tgross35 committed Jun 14, 2024
1 parent 836df19 commit df2f8f7
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 122 deletions.
27 changes: 10 additions & 17 deletions compiler/rustc_const_eval/src/interpret/cast.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::assert_matches::assert_matches;

use rustc_apfloat::ieee::{Double, Half, Quad, Single};
use rustc_apfloat::ieee::{Double, Single};
use rustc_apfloat::{Float, FloatConvert};
use rustc_middle::mir::interpret::{InterpResult, PointerArithmetic, Scalar};
use rustc_middle::mir::CastKind;
Expand Down Expand Up @@ -189,10 +189,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
bug!("FloatToFloat/FloatToInt cast: source type {} is not a float type", src.layout.ty)
};
let val = match fty {
FloatTy::F16 => self.cast_from_float(src.to_scalar().to_f16()?, cast_to.ty),
FloatTy::F16 => unimplemented!("f16_f128"),
FloatTy::F32 => self.cast_from_float(src.to_scalar().to_f32()?, cast_to.ty),
FloatTy::F64 => self.cast_from_float(src.to_scalar().to_f64()?, cast_to.ty),
FloatTy::F128 => self.cast_from_float(src.to_scalar().to_f128()?, cast_to.ty),
FloatTy::F128 => unimplemented!("f16_f128"),
};
Ok(ImmTy::from_scalar(val, cast_to))
}
Expand Down Expand Up @@ -298,18 +298,18 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
Float(fty) if signed => {
let v = v as i128;
match fty {
FloatTy::F16 => Scalar::from_f16(Half::from_i128(v).value),
FloatTy::F16 => unimplemented!("f16_f128"),
FloatTy::F32 => Scalar::from_f32(Single::from_i128(v).value),
FloatTy::F64 => Scalar::from_f64(Double::from_i128(v).value),
FloatTy::F128 => Scalar::from_f128(Quad::from_i128(v).value),
FloatTy::F128 => unimplemented!("f16_f128"),
}
}
// unsigned int -> float
Float(fty) => match fty {
FloatTy::F16 => Scalar::from_f16(Half::from_u128(v).value),
FloatTy::F16 => unimplemented!("f16_f128"),
FloatTy::F32 => Scalar::from_f32(Single::from_u128(v).value),
FloatTy::F64 => Scalar::from_f64(Double::from_u128(v).value),
FloatTy::F128 => Scalar::from_f128(Quad::from_u128(v).value),
FloatTy::F128 => unimplemented!("f16_f128"),
},

// u8 -> char
Expand All @@ -323,12 +323,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
/// Low-level cast helper function. Converts an apfloat `f` into int or float types.
fn cast_from_float<F>(&self, f: F, dest_ty: Ty<'tcx>) -> Scalar<M::Provenance>
where
F: Float
+ Into<Scalar<M::Provenance>>
+ FloatConvert<Half>
+ FloatConvert<Single>
+ FloatConvert<Double>
+ FloatConvert<Quad>,
F: Float + Into<Scalar<M::Provenance>> + FloatConvert<Single> + FloatConvert<Double>,
{
use rustc_type_ir::TyKind::*;

Expand Down Expand Up @@ -365,12 +360,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
}
// float -> float
Float(fty) => match fty {
FloatTy::F16 => Scalar::from_f16(adjust_nan(self, f, f.convert(&mut false).value)),
FloatTy::F16 => unimplemented!("f16_f128"),
FloatTy::F32 => Scalar::from_f32(adjust_nan(self, f, f.convert(&mut false).value)),
FloatTy::F64 => Scalar::from_f64(adjust_nan(self, f, f.convert(&mut false).value)),
FloatTy::F128 => {
Scalar::from_f128(adjust_nan(self, f, f.convert(&mut false).value))
}
FloatTy::F128 => unimplemented!("f16_f128"),
},
// That's it.
_ => span_bug!(self.cur_span(), "invalid float to {} cast", dest_ty),
Expand Down
8 changes: 5 additions & 3 deletions src/tools/miri/src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::time::Duration;

use rand::RngCore;

use rustc_apfloat::ieee::{Double, Single};
use rustc_apfloat::ieee::{Double, Half, Quad, Single};
use rustc_apfloat::Float;
use rustc_hir::{
def::{DefKind, Namespace},
Expand Down Expand Up @@ -1201,12 +1201,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
};

let (val, status) = match fty {
FloatTy::F16 => unimplemented!("f16_f128"),
FloatTy::F16 =>
float_to_int_inner::<Half>(this, src.to_scalar().to_f16()?, cast_to, round),
FloatTy::F32 =>
float_to_int_inner::<Single>(this, src.to_scalar().to_f32()?, cast_to, round),
FloatTy::F64 =>
float_to_int_inner::<Double>(this, src.to_scalar().to_f64()?, cast_to, round),
FloatTy::F128 => unimplemented!("f16_f128"),
FloatTy::F128 =>
float_to_int_inner::<Quad>(this, src.to_scalar().to_f128()?, cast_to, round),
};

if status.intersects(
Expand Down
166 changes: 69 additions & 97 deletions src/tools/miri/tests/pass/float.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#![feature(stmt_expr_attributes)]
#![feature(float_gamma)]
#![feature(core_intrinsics)]
#![feature(f128)]
#![feature(f16)]
#![allow(arithmetic_overflow)]

use std::fmt::Debug;
Expand Down Expand Up @@ -41,104 +43,43 @@ trait FloatToInt<Int>: Copy {
unsafe fn cast_unchecked(self) -> Int;
}

impl FloatToInt<i8> for f32 {
fn cast(self) -> i8 {
self as _
}
unsafe fn cast_unchecked(self) -> i8 {
self.to_int_unchecked()
}
}
impl FloatToInt<i32> for f32 {
fn cast(self) -> i32 {
self as _
}
unsafe fn cast_unchecked(self) -> i32 {
self.to_int_unchecked()
}
}
impl FloatToInt<u32> for f32 {
fn cast(self) -> u32 {
self as _
}
unsafe fn cast_unchecked(self) -> u32 {
self.to_int_unchecked()
}
}
impl FloatToInt<i64> for f32 {
fn cast(self) -> i64 {
self as _
}
unsafe fn cast_unchecked(self) -> i64 {
self.to_int_unchecked()
}
}
impl FloatToInt<u64> for f32 {
fn cast(self) -> u64 {
self as _
}
unsafe fn cast_unchecked(self) -> u64 {
self.to_int_unchecked()
}
macro_rules! float_to_int {
($fty:ty => $($ity:ty),+ $(,)?) => {
$(
impl FloatToInt<$ity> for $fty {
fn cast(self) -> $ity {
self as _
}
unsafe fn cast_unchecked(self) -> $ity {
self.to_int_unchecked()
}
}
)*
};
}

impl FloatToInt<i8> for f64 {
fn cast(self) -> i8 {
self as _
}
unsafe fn cast_unchecked(self) -> i8 {
self.to_int_unchecked()
}
}
impl FloatToInt<i32> for f64 {
fn cast(self) -> i32 {
self as _
}
unsafe fn cast_unchecked(self) -> i32 {
self.to_int_unchecked()
}
}
impl FloatToInt<u32> for f64 {
fn cast(self) -> u32 {
self as _
}
unsafe fn cast_unchecked(self) -> u32 {
self.to_int_unchecked()
}
}
impl FloatToInt<i64> for f64 {
fn cast(self) -> i64 {
self as _
}
unsafe fn cast_unchecked(self) -> i64 {
self.to_int_unchecked()
}
}
impl FloatToInt<u64> for f64 {
fn cast(self) -> u64 {
self as _
}
unsafe fn cast_unchecked(self) -> u64 {
self.to_int_unchecked()
}
}
impl FloatToInt<i128> for f64 {
fn cast(self) -> i128 {
self as _
}
unsafe fn cast_unchecked(self) -> i128 {
self.to_int_unchecked()
}
}
impl FloatToInt<u128> for f64 {
fn cast(self) -> u128 {
self as _
}
unsafe fn cast_unchecked(self) -> u128 {
self.to_int_unchecked()
}
// FIXME(f16_f128): this is just used while we don't have `to_int_unchecked` on `f16` and `f128`.
// Just use `float_to_int` once available.
macro_rules! float_to_int_fallback {
($fty:ty => $($ity:ty),+ $(,)?) => {
$(
impl FloatToInt<$ity> for $fty {
fn cast(self) -> $ity {
self as _
}
unsafe fn cast_unchecked(self) -> $ity {
self as _
}
}
)*
};
}

float_to_int_fallback!(f16 => i8, u8, i16, u16, i32, u32, i64, u64, i128, u128);
float_to_int!(f32=> i8, u8, i16, u16, i32, u32, i64, u64, i128, u128);
float_to_int!(f64 => i8, u8, i16, u16, i32, u32, i64, u64, i128, u128);
float_to_int_fallback!(f128 => i8, u8, i16, u16, i32, u32, i64, u64, i128, u128);

/// Test this cast both via `as` and via `approx_unchecked` (i.e., it must not saturate).
#[track_caller]
#[inline(never)]
Expand All @@ -153,18 +94,29 @@ where

fn basic() {
// basic arithmetic
assert_eq(6.0_f16 * 6.0_f16, 36.0_f16);
assert_eq(6.0_f32 * 6.0_f32, 36.0_f32);
assert_eq(6.0_f64 * 6.0_f64, 36.0_f64);
assert_eq(6.0_f128 * 6.0_f128, 36.0_f128);
assert_eq(-{ 5.0_f16 }, -5.0_f16);
assert_eq(-{ 5.0_f32 }, -5.0_f32);
assert_eq(-{ 5.0_f64 }, -5.0_f64);
assert_eq(-{ 5.0_f128 }, -5.0_f128);

// infinities, NaN
// FIXME(f16_f128): add when constants and `is_infinite` are available
assert!((5.0_f32 / 0.0).is_infinite());
assert_ne!({ 5.0_f32 / 0.0 }, { -5.0_f32 / 0.0 });
assert!((5.0_f64 / 0.0).is_infinite());
assert_ne!({ 5.0_f64 / 0.0 }, { 5.0_f64 / -0.0 });
assert_ne!(f32::NAN, f32::NAN);
assert_ne!(f64::NAN, f64::NAN);

// negative zero
let posz = 0.0f16;
let negz = -0.0f16;
assert_eq(posz, negz);
assert_ne!(posz.to_bits(), negz.to_bits());
let posz = 0.0f32;
let negz = -0.0f32;
assert_eq(posz, negz);
Expand All @@ -173,15 +125,30 @@ fn basic() {
let negz = -0.0f64;
assert_eq(posz, negz);
assert_ne!(posz.to_bits(), negz.to_bits());
let posz = 0.0f128;
let negz = -0.0f128;
assert_eq(posz, negz);
assert_ne!(posz.to_bits(), negz.to_bits());

// byte-level transmute
let x: u64 = unsafe { std::mem::transmute(42.0_f64) };
let y: f64 = unsafe { std::mem::transmute(x) };
assert_eq(y, 42.0_f64);
let x: u16 = unsafe { std::mem::transmute(42.0_f16) };
let y: f16 = unsafe { std::mem::transmute(x) };
assert_eq(y, 42.0_f16);
let x: u32 = unsafe { std::mem::transmute(42.0_f32) };
let y: f32 = unsafe { std::mem::transmute(x) };
assert_eq(y, 42.0_f32);
let x: u64 = unsafe { std::mem::transmute(42.0_f64) };
let y: f64 = unsafe { std::mem::transmute(x) };
assert_eq(y, 42.0_f64);
let x: u128 = unsafe { std::mem::transmute(42.0_f128) };
let y: f128 = unsafe { std::mem::transmute(x) };
assert_eq(y, 42.0_f128);

// `%` sign behavior, some of this used to be buggy
assert!((black_box(1.0f16) % 1.0).is_sign_positive());
assert!((black_box(1.0f16) % -1.0).is_sign_positive());
assert!((black_box(-1.0f16) % 1.0).is_sign_negative());
assert!((black_box(-1.0f16) % -1.0).is_sign_negative());
assert!((black_box(1.0f32) % 1.0).is_sign_positive());
assert!((black_box(1.0f32) % -1.0).is_sign_positive());
assert!((black_box(-1.0f32) % 1.0).is_sign_negative());
Expand All @@ -190,7 +157,12 @@ fn basic() {
assert!((black_box(1.0f64) % -1.0).is_sign_positive());
assert!((black_box(-1.0f64) % 1.0).is_sign_negative());
assert!((black_box(-1.0f64) % -1.0).is_sign_negative());
assert!((black_box(1.0f128) % 1.0).is_sign_positive());
assert!((black_box(1.0f128) % -1.0).is_sign_positive());
assert!((black_box(-1.0f128) % 1.0).is_sign_negative());
assert!((black_box(-1.0f128) % -1.0).is_sign_negative());

// FIXME(f16_f128): add when `abs` is available
assert_eq!((-1.0f32).abs(), 1.0f32);
assert_eq!(34.2f64.abs(), 34.2f64);
}
Expand Down
5 changes: 0 additions & 5 deletions tests/crashes/124583.rs

This file was deleted.

4 changes: 4 additions & 0 deletions tests/ui/numbers-arithmetic/f16-f128-lit.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Make sure negation happens correctly. Also included:
// issue: rust-lang/rust#124583
//@ run-pass

#![feature(f16)]
Expand All @@ -8,9 +10,11 @@ fn main() {
assert_eq!((-0.0_f16).to_bits(), 0x8000);
assert_eq!(10.0_f16.to_bits(), 0x4900);
assert_eq!((-10.0_f16).to_bits(), 0xC900);
assert_eq!((-(-0.0f16)).to_bits(), 0x0000);

assert_eq!(0.0_f128.to_bits(), 0x0000_0000_0000_0000_0000_0000_0000_0000);
assert_eq!((-0.0_f128).to_bits(), 0x8000_0000_0000_0000_0000_0000_0000_0000);
assert_eq!(10.0_f128.to_bits(), 0x4002_4000_0000_0000_0000_0000_0000_0000);
assert_eq!((-10.0_f128).to_bits(), 0xC002_4000_0000_0000_0000_0000_0000_0000);
assert_eq!((-(-0.0f128)).to_bits(), 0x0000_0000_0000_0000_0000_0000_0000_0000);
}

0 comments on commit df2f8f7

Please sign in to comment.