Skip to content

Commit

Permalink
Refactor float_to_int_checked to remove its generic parameter and r…
Browse files Browse the repository at this point in the history
…educe code duplication a bit
  • Loading branch information
eduardosm committed Nov 23, 2023
1 parent 95b63b9 commit a2cc292
Show file tree
Hide file tree
Showing 30 changed files with 172 additions and 203 deletions.
111 changes: 65 additions & 46 deletions src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,18 @@ use std::time::Duration;

use log::trace;

use rustc_apfloat::Float as _;
use rustc_hir::def::{DefKind, Namespace};
use rustc_hir::def_id::{DefId, CRATE_DEF_INDEX};
use rustc_index::IndexVec;
use rustc_middle::mir;
use rustc_middle::ty::{
self,
layout::{IntegerExt as _, LayoutOf, TyAndLayout},
IntTy, Ty, TyCtxt, UintTy,
layout::{LayoutOf, TyAndLayout},
FloatTy, IntTy, Ty, TyCtxt, UintTy,
};
use rustc_span::{def_id::CrateNum, sym, Span, Symbol};
use rustc_target::abi::{Align, FieldIdx, FieldsShape, Integer, Size, Variants};
use rustc_target::abi::{Align, FieldIdx, FieldsShape, Size, Variants};
use rustc_target::spec::abi::Abi;

use rand::RngCore;
Expand Down Expand Up @@ -986,65 +987,83 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
}
}

/// Converts `f` to integer type `dest_ty` after rounding with mode `round`.
/// Converts `src` from floating point to integer type `dest_ty`
/// after rounding with mode `round`.
/// Returns `None` if `f` is NaN or out of range.
fn float_to_int_checked<F>(
fn float_to_int_checked(
&self,
f: F,
src: &ImmTy<'tcx, Provenance>,
cast_to: TyAndLayout<'tcx>,
round: rustc_apfloat::Round,
) -> Option<ImmTy<'tcx, Provenance>>
where
F: rustc_apfloat::Float + Into<Scalar<Provenance>>,
{
) -> Option<ImmTy<'tcx, Provenance>> {
let this = self.eval_context_ref();

let val = match cast_to.ty.kind() {
// Unsigned
ty::Uint(t) => {
let size = Integer::from_uint_ty(this, *t).size();
let res = f.to_u128_r(size.bits_usize(), round, &mut false);
if res.status.intersects(
rustc_apfloat::Status::INVALID_OP
| rustc_apfloat::Status::OVERFLOW
| rustc_apfloat::Status::UNDERFLOW,
) {
// Floating point value is NaN (flagged with INVALID_OP) or outside the range
// of values of the integer type (flagged with OVERFLOW or UNDERFLOW).
return None;
} else {
// Floating point value can be represented by the integer type after rounding.
// The INEXACT flag is ignored on purpose to allow rounding.
Scalar::from_uint(res.value, size)
}
let int_size = cast_to.layout.size;
let (val, status) = match (src.layout.ty.kind(), cast_to.ty.kind()) {
// f32 to unsigned
(ty::Float(FloatTy::F32), ty::Uint(_)) => {
let res = src.to_scalar().to_f32().unwrap().to_u128_r(
int_size.bits_usize(),
round,
&mut false,
);
(Scalar::from_uint(res.value, int_size), res.status)
}
// Signed
ty::Int(t) => {
let size = Integer::from_int_ty(this, *t).size();
let res = f.to_i128_r(size.bits_usize(), round, &mut false);
if res.status.intersects(
rustc_apfloat::Status::INVALID_OP
| rustc_apfloat::Status::OVERFLOW
| rustc_apfloat::Status::UNDERFLOW,
) {
// Floating point value is NaN (flagged with INVALID_OP) or outside the range
// of values of the integer type (flagged with OVERFLOW or UNDERFLOW).
return None;
} else {
// Floating point value can be represented by the integer type after rounding.
// The INEXACT flag is ignored on purpose to allow rounding.
Scalar::from_int(res.value, size)
}
// f32 to signed
(ty::Float(FloatTy::F32), ty::Int(_)) => {
let res = src.to_scalar().to_f32().unwrap().to_i128_r(
int_size.bits_usize(),
round,
&mut false,
);
(Scalar::from_int(res.value, int_size), res.status)
}
// f64 to unsigned
(ty::Float(FloatTy::F64), ty::Uint(_)) => {
let res = src.to_scalar().to_f64().unwrap().to_u128_r(
int_size.bits_usize(),
round,
&mut false,
);
(Scalar::from_uint(res.value, int_size), res.status)
}
// f64 to signed
(ty::Float(FloatTy::F64), ty::Int(_)) => {
let res = src.to_scalar().to_f64().unwrap().to_i128_r(
int_size.bits_usize(),
round,
&mut false,
);
(Scalar::from_int(res.value, int_size), res.status)
}
// Nothing else
(_, ty::Uint(_) | ty::Int(_)) =>
span_bug!(
this.cur_span(),
"attempted float-to-int conversion with non-float input type {}",
cast_to.ty,
),
_ =>
span_bug!(
this.cur_span(),
"attempted float-to-int conversion with non-int output type {}",
cast_to.ty,
),
};
Some(ImmTy::from_scalar(val, cast_to))

if status.intersects(
rustc_apfloat::Status::INVALID_OP
| rustc_apfloat::Status::OVERFLOW
| rustc_apfloat::Status::UNDERFLOW,
) {
// Floating point value is NaN (flagged with INVALID_OP) or outside the range
// of values of the integer type (flagged with OVERFLOW or UNDERFLOW).
None
} else {
// Floating point value can be represented by the integer type after rounding.
// The INEXACT flag is ignored on purpose to allow rounding.
Some(ImmTy::from_scalar(val, cast_to))
}
}

/// Returns an integer type that is twice wide as `ty`
Expand Down
38 changes: 8 additions & 30 deletions src/shims/intrinsics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,36 +365,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let [val] = check_arg_count(args)?;
let val = this.read_immediate(val)?;

let res = match val.layout.ty.kind() {
ty::Float(FloatTy::F32) => {
let f = val.to_scalar().to_f32()?;
this
.float_to_int_checked(f, dest.layout, Round::TowardZero)
.ok_or_else(|| {
err_ub_format!(
"`float_to_int_unchecked` intrinsic called on {f} which cannot be represented in target type `{:?}`",
dest.layout.ty
)
})?
}
ty::Float(FloatTy::F64) => {
let f = val.to_scalar().to_f64()?;
this
.float_to_int_checked(f, dest.layout, Round::TowardZero)
.ok_or_else(|| {
err_ub_format!(
"`float_to_int_unchecked` intrinsic called on {f} which cannot be represented in target type `{:?}`",
dest.layout.ty
)
})?
}
_ =>
span_bug!(
this.cur_span(),
"`float_to_int_unchecked` called with non-float input type {:?}",
val.layout.ty
),
};
let res = this
.float_to_int_checked(&val, dest.layout, Round::TowardZero)
.ok_or_else(|| {
err_ub_format!(
"`float_to_int_unchecked` intrinsic called on {val} which cannot be represented in target type `{:?}`",
dest.layout.ty
)
})?;

this.write_immediate(*res, dest)?;
}
Expand Down
17 changes: 3 additions & 14 deletions src/shims/intrinsics/simd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -447,22 +447,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
(ty::Float(_), ty::Int(_) | ty::Uint(_)) if safe_cast =>
this.float_to_float_or_int(&op, dest.layout)?,
// Float-to-int in unchecked mode
(ty::Float(FloatTy::F32), ty::Int(_) | ty::Uint(_)) if unsafe_cast => {
let f = op.to_scalar().to_f32()?;
this.float_to_int_checked(f, dest.layout, Round::TowardZero)
(ty::Float(_), ty::Int(_) | ty::Uint(_)) if unsafe_cast => {
this.float_to_int_checked(&op, dest.layout, Round::TowardZero)
.ok_or_else(|| {
err_ub_format!(
"`simd_cast` intrinsic called on {f} which cannot be represented in target type `{:?}`",
dest.layout.ty
)
})?
}
(ty::Float(FloatTy::F64), ty::Int(_) | ty::Uint(_)) if unsafe_cast => {
let f = op.to_scalar().to_f64()?;
this.float_to_int_checked(f, dest.layout, Round::TowardZero)
.ok_or_else(|| {
err_ub_format!(
"`simd_cast` intrinsic called on {f} which cannot be represented in target type `{:?}`",
"`simd_cast` intrinsic called on {op} which cannot be represented in target type `{:?}`",
dest.layout.ty
)
})?
Expand Down
34 changes: 34 additions & 0 deletions src/shims/x86/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,40 @@ fn bin_op_simd_float_all<'tcx, F: rustc_apfloat::Float>(
Ok(())
}

/// Converts each element of `op` from floating point to signed integer.
///
/// When the input value is NaN or out of range, fall back to minimum value.
///
/// If `op` has more elements than `dest`, extra elements are ignored. If `op`
/// has less elements than `dest`, remaining is filled with zeros.
fn convert_float_to_int<'tcx>(
this: &mut crate::MiriInterpCx<'_, 'tcx>,
op: &OpTy<'tcx, Provenance>,
rnd: rustc_apfloat::Round,
dest: &PlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx, ()> {
let (op, op_len) = this.operand_to_simd(op)?;
let (dest, dest_len) = this.place_to_simd(dest)?;

for i in 0..op_len.min(dest_len) {
let op = this.read_immediate(&this.project_index(&op, i)?)?;
let dest = this.project_index(&dest, i)?;

let res = this.float_to_int_checked(&op, dest.layout, rnd).unwrap_or_else(|| {
// Fallback to minimum acording to SSE/AVX semantics.
ImmTy::from_int(dest.layout.size.signed_int_min(), dest.layout)
});
this.write_immediate(*res, &dest)?;
}
// Fill remaining with zeros
for i in op_len..dest_len {
let dest = this.project_index(&dest, i)?;
this.write_scalar(Scalar::from_int(0, dest.layout.size), &dest)?;
}

Ok(())
}

/// Horizontaly performs `which` operation on adjacent values of
/// `left` and `right` SIMD vectors and stores the result in `dest`.
fn horizontal_bin_op<'tcx>(
Expand Down
4 changes: 2 additions & 2 deletions src/shims/x86/sse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
let [op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let (op, _) = this.operand_to_simd(op)?;

let op = this.read_scalar(&this.project_index(&op, 0)?)?.to_f32()?;
let op = this.read_immediate(&this.project_index(&op, 0)?)?;

let rnd = match unprefixed_name {
// "current SSE rounding mode", assume nearest
Expand All @@ -180,7 +180,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
_ => unreachable!(),
};

let res = this.float_to_int_checked(op, dest.layout, rnd).unwrap_or_else(|| {
let res = this.float_to_int_checked(&op, dest.layout, rnd).unwrap_or_else(|| {
// Fallback to minimum acording to SSE semantics.
ImmTy::from_int(dest.layout.size.signed_int_min(), dest.layout)
});
Expand Down
75 changes: 12 additions & 63 deletions src/shims/x86/sse2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use rustc_middle::ty::Ty;
use rustc_span::Symbol;
use rustc_target::spec::abi::Abi;

use super::{bin_op_simd_float_all, bin_op_simd_float_first, FloatBinOp};
use super::{bin_op_simd_float_all, bin_op_simd_float_first, convert_float_to_int, FloatBinOp};
use crate::*;
use shims::foreign_items::EmulateForeignItemResult;

Expand Down Expand Up @@ -260,37 +260,25 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
this.write_scalar(Scalar::from_u64(res), &dest)?;
}
}
// Used to implement the _mm_cvtps_epi32 and _mm_cvttps_epi32 functions.
// Converts packed f32 to packed i32.
"cvtps2dq" | "cvttps2dq" => {
// Used to implement the _mm_cvtps_epi32, _mm_cvttps_epi32, _mm_cvtpd_epi32
// and _mm_cvttpd_epi32 functions.
// Converts packed f32/f64 to packed i32.
"cvtps2dq" | "cvttps2dq" | "cvtpd2dq" | "cvttpd2dq" => {
let [op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;

let (op, op_len) = this.operand_to_simd(op)?;
let (dest, dest_len) = this.place_to_simd(dest)?;

assert_eq!(dest_len, op_len);

let rnd = match unprefixed_name {
// "current SSE rounding mode", assume nearest
// https://www.felixcloutier.com/x86/cvtps2dq
"cvtps2dq" => rustc_apfloat::Round::NearestTiesToEven,
// https://www.felixcloutier.com/x86/cvtpd2dq
"cvtps2dq" | "cvtpd2dq" => rustc_apfloat::Round::NearestTiesToEven,
// always truncate
// https://www.felixcloutier.com/x86/cvttps2dq
"cvttps2dq" => rustc_apfloat::Round::TowardZero,
// https://www.felixcloutier.com/x86/cvttpd2dq
"cvttps2dq" | "cvttpd2dq" => rustc_apfloat::Round::TowardZero,
_ => unreachable!(),
};

for i in 0..dest_len {
let op = this.read_scalar(&this.project_index(&op, i)?)?.to_f32()?;
let dest = this.project_index(&dest, i)?;

let res =
this.float_to_int_checked(op, dest.layout, rnd).unwrap_or_else(|| {
// Fallback to minimum acording to SSE2 semantics.
ImmTy::from_int(i32::MIN, this.machine.layouts.i32)
});
this.write_immediate(*res, &dest)?;
}
convert_float_to_int(this, op, rnd, dest)?;
}
// Used to implement the _mm_packs_epi16 function.
// Converts two 16-bit integer vectors to a single 8-bit integer
Expand Down Expand Up @@ -527,53 +515,14 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
};
this.write_scalar(Scalar::from_i32(i32::from(res)), dest)?;
}
// Used to implement the _mm_cvtpd_epi32 and _mm_cvttpd_epi32 functions.
// Converts packed f64 to packed i32.
"cvtpd2dq" | "cvttpd2dq" => {
let [op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;

let (op, op_len) = this.operand_to_simd(op)?;
let (dest, dest_len) = this.place_to_simd(dest)?;

// op is f64x2, dest is i32x4
assert_eq!(op_len, 2);
assert_eq!(dest_len, 4);

let rnd = match unprefixed_name {
// "current SSE rounding mode", assume nearest
// https://www.felixcloutier.com/x86/cvtpd2dq
"cvtpd2dq" => rustc_apfloat::Round::NearestTiesToEven,
// always truncate
// https://www.felixcloutier.com/x86/cvttpd2dq
"cvttpd2dq" => rustc_apfloat::Round::TowardZero,
_ => unreachable!(),
};

for i in 0..op_len {
let op = this.read_scalar(&this.project_index(&op, i)?)?.to_f64()?;
let dest = this.project_index(&dest, i)?;

let res =
this.float_to_int_checked(op, dest.layout, rnd).unwrap_or_else(|| {
// Fallback to minimum acording to SSE2 semantics.
ImmTy::from_int(i32::MIN, this.machine.layouts.i32)
});
this.write_immediate(*res, &dest)?;
}
// Fill the remaining with zeros
for i in op_len..dest_len {
let dest = this.project_index(&dest, i)?;
this.write_scalar(Scalar::from_i32(0), &dest)?;
}
}
// Use to implement the _mm_cvtsd_si32, _mm_cvttsd_si32,
// _mm_cvtsd_si64 and _mm_cvttsd_si64 functions.
// Converts the first component of `op` from f64 to i32/i64.
"cvtsd2si" | "cvttsd2si" | "cvtsd2si64" | "cvttsd2si64" => {
let [op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let (op, _) = this.operand_to_simd(op)?;

let op = this.read_scalar(&this.project_index(&op, 0)?)?.to_f64()?;
let op = this.read_immediate(&this.project_index(&op, 0)?)?;

let rnd = match unprefixed_name {
// "current SSE rounding mode", assume nearest
Expand All @@ -585,7 +534,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
_ => unreachable!(),
};

let res = this.float_to_int_checked(op, dest.layout, rnd).unwrap_or_else(|| {
let res = this.float_to_int_checked(&op, dest.layout, rnd).unwrap_or_else(|| {
// Fallback to minimum acording to SSE semantics.
ImmTy::from_int(dest.layout.size.signed_int_min(), dest.layout)
});
Expand Down
Loading

0 comments on commit a2cc292

Please sign in to comment.