Auto merge of #114735 - RalfJung:miri, r=RalfJung

update Miri

r? `@ghost`
This commit is contained in:
bors 2023-08-11 20:02:14 +00:00
commit a6f8aa5a09
16 changed files with 1795 additions and 55 deletions

View File

@ -173,7 +173,7 @@ impl Command {
// the merge has confused the heck out of josh in the past.
// We pass `--no-verify` to avoid running git hooks like `./miri fmt` that could in turn
// trigger auto-actions.
sh.write_file("rust-version", &commit)?;
sh.write_file("rust-version", format!("{commit}\n"))?;
const PREPARING_COMMIT_MESSAGE: &str = "Preparing for merge from rustc";
cmd!(sh, "git commit rust-version --no-verify -m {PREPARING_COMMIT_MESSAGE}")
.run()

View File

@ -1 +1 @@
fca59ab5f0e7df7d816bed77a32abc0045ebe80b
9fa6bdd764a1f7bdf69eccceeace6d13f38cb2e1

View File

@ -283,7 +283,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
// interleaving, but wether UB happens can depend on whether a write occurs in the
// future...
let is_write = new_perm.initial_state.is_active()
|| (new_perm.initial_state.is_resrved() && new_perm.protector.is_some());
|| (new_perm.initial_state.is_reserved() && new_perm.protector.is_some());
if is_write {
// Need to get mutable access to alloc_extra.
// (Cannot always do this as we can do read-only reborrowing on read-only allocations.)

View File

@ -152,7 +152,7 @@ impl Permission {
matches!(self.inner, Active)
}
pub fn is_resrved(self) -> bool {
pub fn is_reserved(self) -> bool {
matches!(self.inner, Reserved { .. })
}

View File

@ -42,30 +42,19 @@ impl<T> RangeObjectMap<T> {
/// in an existing allocation, then returns Err containing the position
/// where such allocation should be inserted
fn find_offset(&self, offset: Size) -> Result<Position, Position> {
// We do a binary search.
let mut left = 0usize; // inclusive
let mut right = self.v.len(); // exclusive
loop {
if left == right {
// No element contains the given offset. But the
// position is where such element should be placed at.
return Err(left);
}
let candidate = left.checked_add(right).unwrap() / 2;
let elem = &self.v[candidate];
self.v.binary_search_by(|elem| -> std::cmp::Ordering {
if offset < elem.range.start {
// We are too far right (offset is further left).
debug_assert!(candidate < right); // we are making progress
right = candidate;
// (`Greater` means that `elem` is greater than the desired target.)
std::cmp::Ordering::Greater
} else if offset >= elem.range.end() {
// We are too far left (offset is further right).
debug_assert!(candidate >= left); // we are making progress
left = candidate + 1;
std::cmp::Ordering::Less
} else {
// This is it!
return Ok(candidate);
std::cmp::Ordering::Equal
}
}
})
}
/// Determines whether a given access on `range` overlaps with

View File

@ -301,7 +301,7 @@ pub fn create_ecx<'mir, 'tcx: 'mir>(
// Third argument (`argv`): created from `config.args`.
let argv = {
// Put each argument in memory, collect pointers.
let mut argvs = Vec::<Immediate<Provenance>>::new();
let mut argvs = Vec::<Immediate<Provenance>>::with_capacity(config.args.len());
for arg in config.args.iter() {
// Make space for `0` terminator.
let size = u64::try_from(arg.len()).unwrap().checked_add(1).unwrap();

View File

@ -1,6 +1,5 @@
pub mod convert;
use std::any::Any;
use std::cmp;
use std::iter;
use std::num::NonZeroUsize;
@ -25,24 +24,6 @@ use rand::RngCore;
use crate::*;
/// A trait to work around not having trait object upcasting:
/// Add `AsAny` as supertrait and your trait objects can be turned into `&dyn Any` on which you can
/// then call `downcast`.
pub trait AsAny: Any {
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
}
impl<T: Any> AsAny for T {
#[inline(always)]
fn as_any(&self) -> &dyn Any {
self
}
#[inline(always)]
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
// This mapping should match `decode_error_kind` in
// <https://github.com/rust-lang/rust/blob/master/library/std/src/sys/unix/mod.rs>.
const UNIX_IO_ERROR_TABLE: &[(&str, std::io::ErrorKind)] = {

View File

@ -1,4 +1,5 @@
#![feature(rustc_private)]
#![feature(float_gamma)]
#![feature(map_try_insert)]
#![feature(never_type)]
#![feature(try_blocks)]
@ -10,6 +11,7 @@
#![feature(round_ties_even)]
#![feature(os_str_bytes)]
#![feature(lint_reasons)]
#![feature(trait_upcasting)]
// Configure clippy and other lints
#![allow(
clippy::collapsible_else_if,
@ -19,6 +21,7 @@
clippy::enum_variant_names,
clippy::field_reassign_with_default,
clippy::manual_map,
clippy::neg_cmp_op_on_partial_ord,
clippy::new_without_default,
clippy::single_match,
clippy::useless_format,

View File

@ -815,6 +815,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
| "atanf"
| "log1pf"
| "expm1f"
| "tgammaf"
=> {
let [f] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
// FIXME: Using host floats.
@ -830,6 +831,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
"atanf" => f.atan(),
"log1pf" => f.ln_1p(),
"expm1f" => f.exp_m1(),
"tgammaf" => f.gamma(),
_ => bug!(),
};
this.write_scalar(Scalar::from_u32(res.to_bits()), dest)?;
@ -866,6 +868,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
| "atan"
| "log1p"
| "expm1"
| "tgamma"
=> {
let [f] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
// FIXME: Using host floats.
@ -881,6 +884,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
"atan" => f.atan(),
"log1p" => f.ln_1p(),
"expm1" => f.exp_m1(),
"tgamma" => f.gamma(),
_ => bug!(),
};
this.write_scalar(Scalar::from_u64(res.to_bits()), dest)?;
@ -917,6 +921,53 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let res = x.scalbn(exp);
this.write_scalar(Scalar::from_f64(res), dest)?;
}
"lgammaf_r" => {
let [x, signp] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
// FIXME: Using host floats.
let x = f32::from_bits(this.read_scalar(x)?.to_u32()?);
let signp = this.deref_pointer(signp)?;
let (res, sign) = x.ln_gamma();
this.write_int(sign, &signp)?;
this.write_scalar(Scalar::from_u32(res.to_bits()), dest)?;
}
"lgamma_r" => {
let [x, signp] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
// FIXME: Using host floats.
let x = f64::from_bits(this.read_scalar(x)?.to_u64()?);
let signp = this.deref_pointer(signp)?;
let (res, sign) = x.ln_gamma();
this.write_int(sign, &signp)?;
this.write_scalar(Scalar::from_u64(res.to_bits()), dest)?;
}
"llvm.prefetch" => {
let [p, rw, loc, ty] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let _ = this.read_pointer(p)?;
let rw = this.read_scalar(rw)?.to_i32()?;
let loc = this.read_scalar(loc)?.to_i32()?;
let ty = this.read_scalar(ty)?.to_i32()?;
if ty == 1 {
// Data cache prefetch.
// Notably, we do not have to check the pointer, this operation is never UB!
if !matches!(rw, 0 | 1) {
throw_unsup_format!("invalid `rw` value passed to `llvm.prefetch`: {}", rw);
}
if !matches!(loc, 0..=3) {
throw_unsup_format!(
"invalid `loc` value passed to `llvm.prefetch`: {}",
loc
);
}
} else {
throw_unsup_format!("unsupported `llvm.prefetch` type argument: {}", ty);
}
}
// Architecture-specific shims
"llvm.x86.addcarry.64" if this.tcx.sess.target.arch == "x86_64" => {
@ -970,6 +1021,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
}
}
name if name.starts_with("llvm.x86.sse.") => {
return shims::x86::sse::EvalContextExt::emulate_x86_sse_intrinsic(
this, link_name, abi, args, dest,
);
}
// Platform-specific shims
_ =>
return match this.tcx.sess.target.os.as_ref() {

View File

@ -7,6 +7,7 @@ pub mod foreign_items;
pub mod intrinsics;
pub mod unix;
pub mod windows;
mod x86;
pub mod dlsym;
pub mod env;

View File

@ -1,3 +1,4 @@
use std::any::Any;
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::convert::TryInto;
@ -24,7 +25,7 @@ pub struct FileHandle {
writable: bool,
}
pub trait FileDescriptor: std::fmt::Debug + helpers::AsAny {
pub trait FileDescriptor: std::fmt::Debug + Any {
fn name(&self) -> &'static str;
fn read<'tcx>(
@ -72,6 +73,18 @@ pub trait FileDescriptor: std::fmt::Debug + helpers::AsAny {
}
}
impl dyn FileDescriptor {
#[inline(always)]
pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
(self as &dyn Any).downcast_ref()
}
#[inline(always)]
pub fn downcast_mut<T: Any>(&mut self) -> Option<&mut T> {
(self as &mut dyn Any).downcast_mut()
}
}
impl FileDescriptor for FileHandle {
fn name(&self) -> &'static str {
"FILE"
@ -689,7 +702,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
// FIXME: Support fullfsync for all FDs
let FileHandle { file, writable } =
file_descriptor.as_any().downcast_ref::<FileHandle>().ok_or_else(|| {
file_descriptor.downcast_ref::<FileHandle>().ok_or_else(|| {
err_unsup_format!(
"`F_FULLFSYNC` is only supported on file-backed file descriptors"
)
@ -1522,7 +1535,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
// FIXME: Support ftruncate64 for all FDs
let FileHandle { file, writable } =
file_descriptor.as_any().downcast_ref::<FileHandle>().ok_or_else(|| {
file_descriptor.downcast_ref::<FileHandle>().ok_or_else(|| {
err_unsup_format!(
"`ftruncate64` is only supported on file-backed file descriptors"
)
@ -1568,7 +1581,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
// FIXME: Support fsync for all FDs
let FileHandle { file, writable } =
file_descriptor.as_any().downcast_ref::<FileHandle>().ok_or_else(|| {
file_descriptor.downcast_ref::<FileHandle>().ok_or_else(|| {
err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
})?;
let io_result = maybe_sync_file(file, *writable, File::sync_all);
@ -1593,7 +1606,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
// FIXME: Support fdatasync for all FDs
let FileHandle { file, writable } =
file_descriptor.as_any().downcast_ref::<FileHandle>().ok_or_else(|| {
file_descriptor.downcast_ref::<FileHandle>().ok_or_else(|| {
err_unsup_format!(
"`fdatasync` is only supported on file-backed file descriptors"
)
@ -1643,7 +1656,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
// FIXME: Support sync_data_range for all FDs
let FileHandle { file, writable } =
file_descriptor.as_any().downcast_ref::<FileHandle>().ok_or_else(|| {
file_descriptor.downcast_ref::<FileHandle>().ok_or_else(|| {
err_unsup_format!(
"`sync_data_range` is only supported on file-backed file descriptors"
)
@ -1953,7 +1966,6 @@ impl FileMetadata {
let file = match option {
Some(file_descriptor) =>
&file_descriptor
.as_any()
.downcast_ref::<FileHandle>()
.ok_or_else(|| {
err_unsup_format!(

View File

@ -1,3 +1,5 @@
use std::cell::Cell;
use rustc_middle::ty::ScalarInt;
use crate::*;
@ -7,8 +9,6 @@ use socketpair::SocketPair;
use shims::unix::fs::EvalContextExt as _;
use std::cell::Cell;
pub mod epoll;
pub mod event;
pub mod socketpair;
@ -81,7 +81,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
if let Some(epfd) = this.machine.file_handler.handles.get_mut(&epfd) {
let epfd = epfd
.as_any_mut()
.downcast_mut::<Epoll>()
.ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_ctl`"))?;
@ -93,7 +92,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
} else if op == epoll_ctl_del {
if let Some(epfd) = this.machine.file_handler.handles.get_mut(&epfd) {
let epfd = epfd
.as_any_mut()
.downcast_mut::<Epoll>()
.ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_ctl`"))?;
@ -154,7 +152,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
if let Some(epfd) = this.machine.file_handler.handles.get_mut(&epfd) {
let _epfd = epfd
.as_any_mut()
.downcast_mut::<Epoll>()
.ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_wait`"))?;

View File

@ -0,0 +1 @@
pub(super) mod sse;

View File

@ -0,0 +1,609 @@
use rustc_apfloat::{ieee::Single, Float as _};
use rustc_middle::mir;
use rustc_span::Symbol;
use rustc_target::spec::abi::Abi;
use rand::Rng as _;
use crate::*;
use shims::foreign_items::EmulateByNameResult;
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn emulate_x86_sse_intrinsic(
&mut self,
link_name: Symbol,
abi: Abi,
args: &[OpTy<'tcx, Provenance>],
dest: &PlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> {
let this = self.eval_context_mut();
// Prefix should have already been checked.
let unprefixed_name = link_name.as_str().strip_prefix("llvm.x86.sse.").unwrap();
// All these intrinsics operate on 128-bit (f32x4) SIMD vectors unless stated otherwise.
// Many intrinsic names are sufixed with "ps" (packed single) or "ss" (scalar single),
// where single means single precision floating point (f32). "ps" means thet the operation
// is performed on each element of the vector, while "ss" means that the operation is
// performed only on the first element, copying the remaining elements from the input
// vector (for binary operations, from the left-hand side).
match unprefixed_name {
// Used to implement _mm_{add,sub,mul,div,min,max}_ss functions.
// Performs the operations on the first component of `left` and
// `right` and copies the remaining components from `left`.
"add.ss" | "sub.ss" | "mul.ss" | "div.ss" | "min.ss" | "max.ss" => {
let [left, right] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let which = match unprefixed_name {
"add.ss" => FloatBinOp::Arith(mir::BinOp::Add),
"sub.ss" => FloatBinOp::Arith(mir::BinOp::Sub),
"mul.ss" => FloatBinOp::Arith(mir::BinOp::Mul),
"div.ss" => FloatBinOp::Arith(mir::BinOp::Div),
"min.ss" => FloatBinOp::Min,
"max.ss" => FloatBinOp::Max,
_ => unreachable!(),
};
bin_op_ss(this, which, left, right, dest)?;
}
// Used to implement _mm_min_ps and _mm_max_ps functions.
// Note that the semantics are a bit different from Rust simd_min
// and simd_max intrinsics regarding handling of NaN and -0.0: Rust
// matches the IEEE min/max operations, while x86 has different
// semantics.
"min.ps" | "max.ps" => {
let [left, right] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let which = match unprefixed_name {
"min.ps" => FloatBinOp::Min,
"max.ps" => FloatBinOp::Max,
_ => unreachable!(),
};
bin_op_ps(this, which, left, right, dest)?;
}
// Used to implement _mm_{sqrt,rcp,rsqrt}_ss functions.
// Performs the operations on the first component of `op` and
// copies the remaining components from `op`.
"sqrt.ss" | "rcp.ss" | "rsqrt.ss" => {
let [op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let which = match unprefixed_name {
"sqrt.ss" => FloatUnaryOp::Sqrt,
"rcp.ss" => FloatUnaryOp::Rcp,
"rsqrt.ss" => FloatUnaryOp::Rsqrt,
_ => unreachable!(),
};
unary_op_ss(this, which, op, dest)?;
}
// Used to implement _mm_{sqrt,rcp,rsqrt}_ss functions.
// Performs the operations on all components of `op`.
"sqrt.ps" | "rcp.ps" | "rsqrt.ps" => {
let [op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let which = match unprefixed_name {
"sqrt.ps" => FloatUnaryOp::Sqrt,
"rcp.ps" => FloatUnaryOp::Rcp,
"rsqrt.ps" => FloatUnaryOp::Rsqrt,
_ => unreachable!(),
};
unary_op_ps(this, which, op, dest)?;
}
// Used to implement the _mm_cmp_ss function.
// Performs a comparison operation on the first component of `left`
// and `right`, returning 0 if false or `u32::MAX` if true. The remaining
// components are copied from `left`.
"cmp.ss" => {
let [left, right, imm] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let which = match this.read_scalar(imm)?.to_i8()? {
0 => FloatBinOp::Cmp(FloatCmpOp::Eq),
1 => FloatBinOp::Cmp(FloatCmpOp::Lt),
2 => FloatBinOp::Cmp(FloatCmpOp::Le),
3 => FloatBinOp::Cmp(FloatCmpOp::Unord),
4 => FloatBinOp::Cmp(FloatCmpOp::Neq),
5 => FloatBinOp::Cmp(FloatCmpOp::Nlt),
6 => FloatBinOp::Cmp(FloatCmpOp::Nle),
7 => FloatBinOp::Cmp(FloatCmpOp::Ord),
imm => {
throw_unsup_format!(
"invalid 3rd parameter of llvm.x86.sse.cmp.ps: {}",
imm
);
}
};
bin_op_ss(this, which, left, right, dest)?;
}
// Used to implement the _mm_cmp_ps function.
// Performs a comparison operation on each component of `left`
// and `right`. For each component, returns 0 if false or u32::MAX
// if true.
"cmp.ps" => {
let [left, right, imm] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let which = match this.read_scalar(imm)?.to_i8()? {
0 => FloatBinOp::Cmp(FloatCmpOp::Eq),
1 => FloatBinOp::Cmp(FloatCmpOp::Lt),
2 => FloatBinOp::Cmp(FloatCmpOp::Le),
3 => FloatBinOp::Cmp(FloatCmpOp::Unord),
4 => FloatBinOp::Cmp(FloatCmpOp::Neq),
5 => FloatBinOp::Cmp(FloatCmpOp::Nlt),
6 => FloatBinOp::Cmp(FloatCmpOp::Nle),
7 => FloatBinOp::Cmp(FloatCmpOp::Ord),
imm => {
throw_unsup_format!(
"invalid 3rd parameter of llvm.x86.sse.cmp.ps: {}",
imm
);
}
};
bin_op_ps(this, which, left, right, dest)?;
}
// Used to implement _mm_{,u}comi{eq,lt,le,gt,ge,neq}_ps functions.
// Compares the first component of `left` and `right` and returns
// a scalar value (0 or 1).
"comieq.ss" | "comilt.ss" | "comile.ss" | "comigt.ss" | "comige.ss" | "comineq.ss"
| "ucomieq.ss" | "ucomilt.ss" | "ucomile.ss" | "ucomigt.ss" | "ucomige.ss"
| "ucomineq.ss" => {
let [left, right] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let (left, left_len) = this.operand_to_simd(left)?;
let (right, right_len) = this.operand_to_simd(right)?;
assert_eq!(left_len, right_len);
let left = this.read_scalar(&this.project_index(&left, 0)?)?.to_f32()?;
let right = this.read_scalar(&this.project_index(&right, 0)?)?.to_f32()?;
// The difference between the com* and *ucom variants is signaling
// of exceptions when either argument is a quiet NaN. We do not
// support accessing the SSE status register from miri (or from Rust,
// for that matter), so we treat equally both variants.
let res = match unprefixed_name {
"comieq.ss" | "ucomieq.ss" => left == right,
"comilt.ss" | "ucomilt.ss" => left < right,
"comile.ss" | "ucomile.ss" => left <= right,
"comigt.ss" | "ucomigt.ss" => left > right,
"comige.ss" | "ucomige.ss" => left >= right,
"comineq.ss" | "ucomineq.ss" => left != right,
_ => unreachable!(),
};
this.write_scalar(Scalar::from_i32(i32::from(res)), dest)?;
}
// Use to implement _mm_cvtss_si32 and _mm_cvttss_si32.
// Converts the first component of `op` from f32 to i32.
"cvtss2si" | "cvttss2si" => {
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 rnd = match unprefixed_name {
// "current SSE rounding mode", assume nearest
// https://www.felixcloutier.com/x86/cvtss2si
"cvtss2si" => rustc_apfloat::Round::NearestTiesToEven,
// always truncate
// https://www.felixcloutier.com/x86/cvttss2si
"cvttss2si" => rustc_apfloat::Round::TowardZero,
_ => unreachable!(),
};
let mut exact = false;
let cvt = op.to_i128_r(32, rnd, &mut exact);
let res = if cvt.status.intersects(
rustc_apfloat::Status::INVALID_OP
| rustc_apfloat::Status::OVERFLOW
| rustc_apfloat::Status::UNDERFLOW,
) {
// Input is NaN (flagged with INVALID_OP) or does not fit
// in an i32 (flagged with OVERFLOW or UNDERFLOW), fallback
// to minimum acording to SSE semantics. The INEXACT flag
// is ignored on purpose because rounding can happen during
// float-to-int conversion.
i32::MIN
} else {
i32::try_from(cvt.value).unwrap()
};
this.write_scalar(Scalar::from_i32(res), dest)?;
}
// Use to implement _mm_cvtss_si64 and _mm_cvttss_si64.
// Converts the first component of `op` from f32 to i64.
"cvtss2si64" | "cvttss2si64" => {
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 rnd = match unprefixed_name {
// "current SSE rounding mode", assume nearest
// https://www.felixcloutier.com/x86/cvtss2si
"cvtss2si64" => rustc_apfloat::Round::NearestTiesToEven,
// always truncate
// https://www.felixcloutier.com/x86/cvttss2si
"cvttss2si64" => rustc_apfloat::Round::TowardZero,
_ => unreachable!(),
};
let mut exact = false;
let cvt = op.to_i128_r(64, rnd, &mut exact);
let res = if cvt.status.intersects(
rustc_apfloat::Status::INVALID_OP
| rustc_apfloat::Status::OVERFLOW
| rustc_apfloat::Status::UNDERFLOW,
) {
// Input is NaN (flagged with INVALID_OP) or does not fit
// in an i64 (flagged with OVERFLOW or UNDERFLOW), fallback
// to minimum acording to SSE semantics. The INEXACT flag
// is ignored on purpose because rounding can happen during
// float-to-int conversion.
i64::MIN
} else {
i64::try_from(cvt.value).unwrap()
};
this.write_scalar(Scalar::from_i64(res), dest)?;
}
// Used to implement the _mm_cvtsi32_ss function.
// Converts `right` from i32 to f32. Returns a SIMD vector with
// the result in the first component and the remaining components
// are copied from `left`.
// https://www.felixcloutier.com/x86/cvtsi2ss
"cvtsi2ss" => {
let [left, right] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let (left, left_len) = this.operand_to_simd(left)?;
let (dest, dest_len) = this.place_to_simd(dest)?;
assert_eq!(dest_len, left_len);
let right = this.read_scalar(right)?.to_i32()?;
let res0 = Scalar::from_f32(Single::from_i128(right.into()).value);
this.write_scalar(res0, &this.project_index(&dest, 0)?)?;
for i in 1..dest_len {
let left = this.read_immediate(&this.project_index(&left, i)?)?;
let dest = this.project_index(&dest, i)?;
this.write_immediate(*left, &dest)?;
}
}
// Used to implement the _mm_cvtsi64_ss function.
// Converts `right` from i64 to f32. Returns a SIMD vector with
// the result in the first component and the remaining components
// are copied from `left`.
// https://www.felixcloutier.com/x86/cvtsi2ss
"cvtsi642ss" => {
let [left, right] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let (left, left_len) = this.operand_to_simd(left)?;
let (dest, dest_len) = this.place_to_simd(dest)?;
assert_eq!(dest_len, left_len);
let right = this.read_scalar(right)?.to_i64()?;
let res0 = Scalar::from_f32(Single::from_i128(right.into()).value);
this.write_scalar(res0, &this.project_index(&dest, 0)?)?;
for i in 1..dest_len {
let left = this.read_immediate(&this.project_index(&left, i)?)?;
let dest = this.project_index(&dest, i)?;
this.write_immediate(*left, &dest)?;
}
}
// Used to implement the _mm_movemask_ps function.
// Returns a scalar integer where the i-th bit is the highest
// bit of the i-th component of `op`.
// https://www.felixcloutier.com/x86/movmskps
"movmsk.ps" => {
let [op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let (op, op_len) = this.operand_to_simd(op)?;
let mut res = 0;
for i in 0..op_len {
let op = this.read_scalar(&this.project_index(&op, i)?)?;
let op = op.to_u32()?;
res |= (op >> 31) << i;
}
this.write_scalar(Scalar::from_u32(res), dest)?;
}
_ => return Ok(EmulateByNameResult::NotSupported),
}
Ok(EmulateByNameResult::NeedsJumping)
}
}
/// Floating point comparison operation
///
/// <https://www.felixcloutier.com/x86/cmpss>
/// <https://www.felixcloutier.com/x86/cmpps>
#[derive(Copy, Clone)]
enum FloatCmpOp {
Eq,
Lt,
Le,
Unord,
Neq,
/// Not less-than
Nlt,
/// Not less-or-equal
Nle,
/// Ordered, i.e. neither of them is NaN
Ord,
}
#[derive(Copy, Clone)]
enum FloatBinOp {
/// Arithmetic operation
Arith(mir::BinOp),
/// Comparison
Cmp(FloatCmpOp),
/// Minimum value (with SSE semantics)
///
/// <https://www.felixcloutier.com/x86/minss>
/// <https://www.felixcloutier.com/x86/minps>
Min,
/// Maximum value (with SSE semantics)
///
/// <https://www.felixcloutier.com/x86/maxss>
/// <https://www.felixcloutier.com/x86/maxps>
Max,
}
/// Performs `which` scalar operation on `left` and `right` and returns
/// the result.
fn bin_op_f32<'tcx>(
this: &crate::MiriInterpCx<'_, 'tcx>,
which: FloatBinOp,
left: &ImmTy<'tcx, Provenance>,
right: &ImmTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
match which {
FloatBinOp::Arith(which) => {
let (res, _, _) = this.overflowing_binary_op(which, left, right)?;
Ok(res)
}
FloatBinOp::Cmp(which) => {
let left = left.to_scalar().to_f32()?;
let right = right.to_scalar().to_f32()?;
// FIXME: Make sure that these operations match the semantics of cmpps
let res = match which {
FloatCmpOp::Eq => left == right,
FloatCmpOp::Lt => left < right,
FloatCmpOp::Le => left <= right,
FloatCmpOp::Unord => left.is_nan() || right.is_nan(),
FloatCmpOp::Neq => left != right,
FloatCmpOp::Nlt => !(left < right),
FloatCmpOp::Nle => !(left <= right),
FloatCmpOp::Ord => !left.is_nan() && !right.is_nan(),
};
Ok(Scalar::from_u32(if res { u32::MAX } else { 0 }))
}
FloatBinOp::Min => {
let left = left.to_scalar().to_f32()?;
let right = right.to_scalar().to_f32()?;
// SSE semantics to handle zero and NaN. Note that `x == Single::ZERO`
// is true when `x` is either +0 or -0.
if (left == Single::ZERO && right == Single::ZERO)
|| left.is_nan()
|| right.is_nan()
|| left >= right
{
Ok(Scalar::from_f32(right))
} else {
Ok(Scalar::from_f32(left))
}
}
FloatBinOp::Max => {
let left = left.to_scalar().to_f32()?;
let right = right.to_scalar().to_f32()?;
// SSE semantics to handle zero and NaN. Note that `x == Single::ZERO`
// is true when `x` is either +0 or -0.
if (left == Single::ZERO && right == Single::ZERO)
|| left.is_nan()
|| right.is_nan()
|| left <= right
{
Ok(Scalar::from_f32(right))
} else {
Ok(Scalar::from_f32(left))
}
}
}
}
/// Performs `which` operation on the first component of `left` and `right`
/// and copies the other components from `left`. The result is stored in `dest`.
fn bin_op_ss<'tcx>(
this: &mut crate::MiriInterpCx<'_, 'tcx>,
which: FloatBinOp,
left: &OpTy<'tcx, Provenance>,
right: &OpTy<'tcx, Provenance>,
dest: &PlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx, ()> {
let (left, left_len) = this.operand_to_simd(left)?;
let (right, right_len) = this.operand_to_simd(right)?;
let (dest, dest_len) = this.place_to_simd(dest)?;
assert_eq!(dest_len, left_len);
assert_eq!(dest_len, right_len);
let res0 = bin_op_f32(
this,
which,
&this.read_immediate(&this.project_index(&left, 0)?)?,
&this.read_immediate(&this.project_index(&right, 0)?)?,
)?;
this.write_scalar(res0, &this.project_index(&dest, 0)?)?;
for i in 1..dest_len {
let left = this.read_immediate(&this.project_index(&left, i)?)?;
let dest = this.project_index(&dest, i)?;
this.write_immediate(*left, &dest)?;
}
Ok(())
}
/// Performs `which` operation on each component of `left`, and
/// `right` storing the result is stored in `dest`.
fn bin_op_ps<'tcx>(
this: &mut crate::MiriInterpCx<'_, 'tcx>,
which: FloatBinOp,
left: &OpTy<'tcx, Provenance>,
right: &OpTy<'tcx, Provenance>,
dest: &PlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx, ()> {
let (left, left_len) = this.operand_to_simd(left)?;
let (right, right_len) = this.operand_to_simd(right)?;
let (dest, dest_len) = this.place_to_simd(dest)?;
assert_eq!(dest_len, left_len);
assert_eq!(dest_len, right_len);
for i in 0..dest_len {
let left = this.read_immediate(&this.project_index(&left, i)?)?;
let right = this.read_immediate(&this.project_index(&right, i)?)?;
let dest = this.project_index(&dest, i)?;
let res = bin_op_f32(this, which, &left, &right)?;
this.write_scalar(res, &dest)?;
}
Ok(())
}
#[derive(Copy, Clone)]
enum FloatUnaryOp {
/// sqrt(x)
///
/// <https://www.felixcloutier.com/x86/sqrtss>
/// <https://www.felixcloutier.com/x86/sqrtps>
Sqrt,
/// Approximation of 1/x
///
/// <https://www.felixcloutier.com/x86/rcpss>
/// <https://www.felixcloutier.com/x86/rcpps>
Rcp,
/// Approximation of 1/sqrt(x)
///
/// <https://www.felixcloutier.com/x86/rsqrtss>
/// <https://www.felixcloutier.com/x86/rsqrtps>
Rsqrt,
}
/// Performs `which` scalar operation on `op` and returns the result.
#[allow(clippy::arithmetic_side_effects)] // floating point operations without side effects
fn unary_op_f32<'tcx>(
this: &mut crate::MiriInterpCx<'_, 'tcx>,
which: FloatUnaryOp,
op: &ImmTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
match which {
FloatUnaryOp::Sqrt => {
let op = op.to_scalar();
// FIXME using host floats
Ok(Scalar::from_u32(f32::from_bits(op.to_u32()?).sqrt().to_bits()))
}
FloatUnaryOp::Rcp => {
let op = op.to_scalar().to_f32()?;
let div = (Single::from_u128(1).value / op).value;
// Apply a relative error with a magnitude on the order of 2^-12 to simulate the
// inaccuracy of RCP.
let res = apply_random_float_error(this, div, -12);
Ok(Scalar::from_f32(res))
}
FloatUnaryOp::Rsqrt => {
let op = op.to_scalar().to_u32()?;
// FIXME using host floats
let sqrt = Single::from_bits(f32::from_bits(op).sqrt().to_bits().into());
let rsqrt = (Single::from_u128(1).value / sqrt).value;
// Apply a relative error with a magnitude on the order of 2^-12 to simulate the
// inaccuracy of RSQRT.
let res = apply_random_float_error(this, rsqrt, -12);
Ok(Scalar::from_f32(res))
}
}
}
/// Disturbes a floating-point result by a relative error on the order of (-2^scale, 2^scale).
#[allow(clippy::arithmetic_side_effects)] // floating point arithmetic cannot panic
fn apply_random_float_error<F: rustc_apfloat::Float>(
this: &mut crate::MiriInterpCx<'_, '_>,
val: F,
err_scale: i32,
) -> F {
let rng = this.machine.rng.get_mut();
// generates rand(0, 2^64) * 2^(scale - 64) = rand(0, 1) * 2^scale
let err =
F::from_u128(rng.gen::<u64>().into()).value.scalbn(err_scale.checked_sub(64).unwrap());
// give it a random sign
let err = if rng.gen::<bool>() { -err } else { err };
// multiple the value with (1+err)
(val * (F::from_u128(1).value + err).value).value
}
/// Performs `which` operation on the first component of `op` and copies
/// the other components. The result is stored in `dest`.
fn unary_op_ss<'tcx>(
this: &mut crate::MiriInterpCx<'_, 'tcx>,
which: FloatUnaryOp,
op: &OpTy<'tcx, Provenance>,
dest: &PlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx, ()> {
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 res0 = unary_op_f32(this, which, &this.read_immediate(&this.project_index(&op, 0)?)?)?;
this.write_scalar(res0, &this.project_index(&dest, 0)?)?;
for i in 1..dest_len {
let op = this.read_immediate(&this.project_index(&op, i)?)?;
let dest = this.project_index(&dest, i)?;
this.write_immediate(*op, &dest)?;
}
Ok(())
}
/// Performs `which` operation on each component of `op`, storing the
/// result is stored in `dest`.
fn unary_op_ps<'tcx>(
this: &mut crate::MiriInterpCx<'_, 'tcx>,
which: FloatUnaryOp,
op: &OpTy<'tcx, Provenance>,
dest: &PlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx, ()> {
let (op, op_len) = this.operand_to_simd(op)?;
let (dest, dest_len) = this.place_to_simd(dest)?;
assert_eq!(dest_len, op_len);
for i in 0..dest_len {
let op = this.read_immediate(&this.project_index(&op, i)?)?;
let dest = this.project_index(&dest, i)?;
let res = unary_op_f32(this, which, &op)?;
this.write_scalar(res, &dest)?;
}
Ok(())
}

View File

@ -1,5 +1,4 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// SPDX-FileCopyrightText: The Rust Project Developers (see https://thanks.rust-lang.org)
#![feature(float_gamma)]
macro_rules! assert_approx_eq {
($a:expr, $b:expr) => {{
@ -130,4 +129,20 @@ pub fn main() {
);
assert_approx_eq!(0.5f32.atanh(), 0.54930614433405484569762261846126285f32);
assert_approx_eq!(0.5f64.atanh(), 0.54930614433405484569762261846126285f64);
assert_approx_eq!(5.0f32.gamma(), 24.0);
assert_approx_eq!(5.0f64.gamma(), 24.0);
// These fail even on the host, precision seems to be terrible.
//assert_approx_eq!(-0.5f32.gamma(), -2.0 * f32::consts::PI.sqrt());
//assert_approx_eq!(-0.5f64.gamma(), -2.0 * f64::consts::PI.sqrt());
assert_eq!(2.0f32.ln_gamma(), (0.0, 1));
assert_eq!(2.0f64.ln_gamma(), (0.0, 1));
// Gamma(-0.5) = -2*sqrt(π)
let (val, sign) = (-0.5f32).ln_gamma();
assert_approx_eq!(val, (2.0 * f32::consts::PI.sqrt()).ln());
assert_eq!(sign, -1);
let (val, sign) = (-0.5f64).ln_gamma();
assert_approx_eq!(val, (2.0 * f64::consts::PI.sqrt()).ln());
assert_eq!(sign, -1);
}

File diff suppressed because it is too large Load Diff