blob: d21a1560699c24f0ddd9806022e3f919aef0ed3e [file] [log] [blame]
mod atomic;
mod simd;
use std::iter;
use log::trace;
use rustc_apfloat::{Float, Round};
use rustc_middle::ty::layout::{IntegerExt, LayoutOf};
use rustc_middle::{
mir,
ty::{self, FloatTy, Ty},
};
use rustc_target::abi::{Integer, Size};
use crate::*;
use atomic::EvalContextExt as _;
use helpers::check_arg_count;
use simd::EvalContextExt as _;
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn call_intrinsic(
&mut self,
instance: ty::Instance<'tcx>,
args: &[OpTy<'tcx, Provenance>],
dest: &PlaceTy<'tcx, Provenance>,
ret: Option<mir::BasicBlock>,
_unwind: StackPopUnwind,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
// See if the core engine can handle this intrinsic.
if this.emulate_intrinsic(instance, args, dest, ret)? {
return Ok(());
}
// All remaining supported intrinsics have a return place.
let intrinsic_name = this.tcx.item_name(instance.def_id());
let intrinsic_name = intrinsic_name.as_str();
let ret = match ret {
None => throw_unsup_format!("unimplemented (diverging) intrinsic: `{intrinsic_name}`"),
Some(p) => p,
};
// Some intrinsics are special and need the "ret".
match intrinsic_name {
"try" => return this.handle_try(args, dest, ret),
_ => {}
}
// The rest jumps to `ret` immediately.
this.emulate_intrinsic_by_name(intrinsic_name, args, dest)?;
trace!("{:?}", this.dump_place(**dest));
this.go_to_block(ret);
Ok(())
}
/// Emulates a Miri-supported intrinsic (not supported by the core engine).
fn emulate_intrinsic_by_name(
&mut self,
intrinsic_name: &str,
args: &[OpTy<'tcx, Provenance>],
dest: &PlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
if let Some(name) = intrinsic_name.strip_prefix("atomic_") {
return this.emulate_atomic_intrinsic(name, args, dest);
}
if let Some(name) = intrinsic_name.strip_prefix("simd_") {
return this.emulate_simd_intrinsic(name, args, dest);
}
match intrinsic_name {
// Miri overwriting CTFE intrinsics.
"ptr_guaranteed_cmp" => {
let [left, right] = check_arg_count(args)?;
let left = this.read_immediate(left)?;
let right = this.read_immediate(right)?;
let (val, _overflowed, _ty) =
this.overflowing_binary_op(mir::BinOp::Eq, &left, &right)?;
// We're type punning a bool as an u8 here.
this.write_scalar(val, dest)?;
}
"const_allocate" => {
// For now, for compatibility with the run-time implementation of this, we just return null.
// See <https://github.com/rust-lang/rust/issues/93935>.
this.write_null(dest)?;
}
"const_deallocate" => {
// complete NOP
}
// Raw memory accesses
"volatile_load" => {
let [place] = check_arg_count(args)?;
let place = this.deref_operand(place)?;
this.copy_op(&place.into(), dest, /*allow_transmute*/ false)?;
}
"volatile_store" => {
let [place, dest] = check_arg_count(args)?;
let place = this.deref_operand(place)?;
this.copy_op(dest, &place.into(), /*allow_transmute*/ false)?;
}
"write_bytes" | "volatile_set_memory" => {
let [ptr, val_byte, count] = check_arg_count(args)?;
let ty = ptr.layout.ty.builtin_deref(true).unwrap().ty;
let ty_layout = this.layout_of(ty)?;
let val_byte = this.read_scalar(val_byte)?.to_u8()?;
let ptr = this.read_pointer(ptr)?;
let count = this.read_target_usize(count)?;
// `checked_mul` enforces a too small bound (the correct one would probably be target_isize_max),
// but no actual allocation can be big enough for the difference to be noticeable.
let byte_count = ty_layout.size.checked_mul(count, this).ok_or_else(|| {
err_ub_format!("overflow computing total size of `{intrinsic_name}`")
})?;
this.write_bytes_ptr(ptr, iter::repeat(val_byte).take(byte_count.bytes_usize()))?;
}
"ptr_mask" => {
let [ptr, mask] = check_arg_count(args)?;
let ptr = this.read_pointer(ptr)?;
let mask = this.read_target_usize(mask)?;
let masked_addr = Size::from_bytes(ptr.addr().bytes() & mask);
this.write_pointer(Pointer::new(ptr.provenance, masked_addr), dest)?;
}
// Floating-point operations
"fabsf32" => {
let [f] = check_arg_count(args)?;
let f = this.read_scalar(f)?.to_f32()?;
// Can be implemented in soft-floats.
this.write_scalar(Scalar::from_f32(f.abs()), dest)?;
}
"fabsf64" => {
let [f] = check_arg_count(args)?;
let f = this.read_scalar(f)?.to_f64()?;
// Can be implemented in soft-floats.
this.write_scalar(Scalar::from_f64(f.abs()), dest)?;
}
#[rustfmt::skip]
| "sinf32"
| "cosf32"
| "sqrtf32"
| "expf32"
| "exp2f32"
| "logf32"
| "log10f32"
| "log2f32"
| "floorf32"
| "ceilf32"
| "truncf32"
| "roundf32"
=> {
let [f] = check_arg_count(args)?;
// FIXME: Using host floats.
let f = f32::from_bits(this.read_scalar(f)?.to_u32()?);
let f = match intrinsic_name {
"sinf32" => f.sin(),
"cosf32" => f.cos(),
"sqrtf32" => f.sqrt(),
"expf32" => f.exp(),
"exp2f32" => f.exp2(),
"logf32" => f.ln(),
"log10f32" => f.log10(),
"log2f32" => f.log2(),
"floorf32" => f.floor(),
"ceilf32" => f.ceil(),
"truncf32" => f.trunc(),
"roundf32" => f.round(),
_ => bug!(),
};
this.write_scalar(Scalar::from_u32(f.to_bits()), dest)?;
}
#[rustfmt::skip]
| "sinf64"
| "cosf64"
| "sqrtf64"
| "expf64"
| "exp2f64"
| "logf64"
| "log10f64"
| "log2f64"
| "floorf64"
| "ceilf64"
| "truncf64"
| "roundf64"
=> {
let [f] = check_arg_count(args)?;
// FIXME: Using host floats.
let f = f64::from_bits(this.read_scalar(f)?.to_u64()?);
let f = match intrinsic_name {
"sinf64" => f.sin(),
"cosf64" => f.cos(),
"sqrtf64" => f.sqrt(),
"expf64" => f.exp(),
"exp2f64" => f.exp2(),
"logf64" => f.ln(),
"log10f64" => f.log10(),
"log2f64" => f.log2(),
"floorf64" => f.floor(),
"ceilf64" => f.ceil(),
"truncf64" => f.trunc(),
"roundf64" => f.round(),
_ => bug!(),
};
this.write_scalar(Scalar::from_u64(f.to_bits()), dest)?;
}
#[rustfmt::skip]
| "fadd_fast"
| "fsub_fast"
| "fmul_fast"
| "fdiv_fast"
| "frem_fast"
=> {
let [a, b] = check_arg_count(args)?;
let a = this.read_immediate(a)?;
let b = this.read_immediate(b)?;
let op = match intrinsic_name {
"fadd_fast" => mir::BinOp::Add,
"fsub_fast" => mir::BinOp::Sub,
"fmul_fast" => mir::BinOp::Mul,
"fdiv_fast" => mir::BinOp::Div,
"frem_fast" => mir::BinOp::Rem,
_ => bug!(),
};
let float_finite = |x: &ImmTy<'tcx, _>| -> InterpResult<'tcx, bool> {
Ok(match x.layout.ty.kind() {
ty::Float(FloatTy::F32) => x.to_scalar().to_f32()?.is_finite(),
ty::Float(FloatTy::F64) => x.to_scalar().to_f64()?.is_finite(),
_ => bug!(
"`{intrinsic_name}` called with non-float input type {ty:?}",
ty = x.layout.ty,
),
})
};
match (float_finite(&a)?, float_finite(&b)?) {
(false, false) => throw_ub_format!(
"`{intrinsic_name}` intrinsic called with non-finite value as both parameters",
),
(false, _) => throw_ub_format!(
"`{intrinsic_name}` intrinsic called with non-finite value as first parameter",
),
(_, false) => throw_ub_format!(
"`{intrinsic_name}` intrinsic called with non-finite value as second parameter",
),
_ => {}
}
this.binop_ignore_overflow(op, &a, &b, dest)?;
}
#[rustfmt::skip]
| "minnumf32"
| "maxnumf32"
| "copysignf32"
=> {
let [a, b] = check_arg_count(args)?;
let a = this.read_scalar(a)?.to_f32()?;
let b = this.read_scalar(b)?.to_f32()?;
let res = match intrinsic_name {
"minnumf32" => a.min(b),
"maxnumf32" => a.max(b),
"copysignf32" => a.copy_sign(b),
_ => bug!(),
};
this.write_scalar(Scalar::from_f32(res), dest)?;
}
#[rustfmt::skip]
| "minnumf64"
| "maxnumf64"
| "copysignf64"
=> {
let [a, b] = check_arg_count(args)?;
let a = this.read_scalar(a)?.to_f64()?;
let b = this.read_scalar(b)?.to_f64()?;
let res = match intrinsic_name {
"minnumf64" => a.min(b),
"maxnumf64" => a.max(b),
"copysignf64" => a.copy_sign(b),
_ => bug!(),
};
this.write_scalar(Scalar::from_f64(res), dest)?;
}
"powf32" => {
let [f, f2] = check_arg_count(args)?;
// FIXME: Using host floats.
let f = f32::from_bits(this.read_scalar(f)?.to_u32()?);
let f2 = f32::from_bits(this.read_scalar(f2)?.to_u32()?);
let res = f.powf(f2);
this.write_scalar(Scalar::from_u32(res.to_bits()), dest)?;
}
"powf64" => {
let [f, f2] = check_arg_count(args)?;
// FIXME: Using host floats.
let f = f64::from_bits(this.read_scalar(f)?.to_u64()?);
let f2 = f64::from_bits(this.read_scalar(f2)?.to_u64()?);
let res = f.powf(f2);
this.write_scalar(Scalar::from_u64(res.to_bits()), dest)?;
}
"fmaf32" => {
let [a, b, c] = check_arg_count(args)?;
// FIXME: Using host floats, to work around https://github.com/rust-lang/miri/issues/2468.
let a = f32::from_bits(this.read_scalar(a)?.to_u32()?);
let b = f32::from_bits(this.read_scalar(b)?.to_u32()?);
let c = f32::from_bits(this.read_scalar(c)?.to_u32()?);
let res = a.mul_add(b, c);
this.write_scalar(Scalar::from_u32(res.to_bits()), dest)?;
}
"fmaf64" => {
let [a, b, c] = check_arg_count(args)?;
// FIXME: Using host floats, to work around https://github.com/rust-lang/miri/issues/2468.
let a = f64::from_bits(this.read_scalar(a)?.to_u64()?);
let b = f64::from_bits(this.read_scalar(b)?.to_u64()?);
let c = f64::from_bits(this.read_scalar(c)?.to_u64()?);
let res = a.mul_add(b, c);
this.write_scalar(Scalar::from_u64(res.to_bits()), dest)?;
}
"powif32" => {
let [f, i] = check_arg_count(args)?;
// FIXME: Using host floats.
let f = f32::from_bits(this.read_scalar(f)?.to_u32()?);
let i = this.read_scalar(i)?.to_i32()?;
let res = f.powi(i);
this.write_scalar(Scalar::from_u32(res.to_bits()), dest)?;
}
"powif64" => {
let [f, i] = check_arg_count(args)?;
// FIXME: Using host floats.
let f = f64::from_bits(this.read_scalar(f)?.to_u64()?);
let i = this.read_scalar(i)?.to_i32()?;
let res = f.powi(i);
this.write_scalar(Scalar::from_u64(res.to_bits()), dest)?;
}
"float_to_int_unchecked" => {
let [val] = check_arg_count(args)?;
let val = this.read_immediate(val)?;
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)?,
_ =>
span_bug!(
this.cur_span(),
"`float_to_int_unchecked` called with non-float input type {:?}",
val.layout.ty
),
};
this.write_scalar(res, dest)?;
}
// Other
"breakpoint" => {
let [] = check_arg_count(args)?;
// normally this would raise a SIGTRAP, which aborts if no debugger is connected
throw_machine_stop!(TerminationInfo::Abort(format!("Trace/breakpoint trap")))
}
name => throw_unsup_format!("unimplemented intrinsic: `{name}`"),
}
Ok(())
}
fn float_to_int_unchecked<F>(
&self,
f: F,
dest_ty: Ty<'tcx>,
) -> InterpResult<'tcx, Scalar<Provenance>>
where
F: Float + Into<Scalar<Provenance>>,
{
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 size = Integer::from_uint_ty(this, *t).size();
let res = f.to_u128(size.bits_usize());
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)
} else {
// `f` was not representable in this integer type.
throw_ub_format!(
"`float_to_int_unchecked` intrinsic called on {f} which cannot be represented in target type `{dest_ty:?}`",
);
}
}
// Signed
ty::Int(t) => {
let size = Integer::from_int_ty(this, *t).size();
let res = f.to_i128(size.bits_usize());
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)
} else {
// `f` was not representable in this integer type.
throw_ub_format!(
"`float_to_int_unchecked` intrinsic called on {f} which cannot be represented in target type `{dest_ty:?}`",
);
}
}
// Nothing else
_ =>
span_bug!(
this.cur_span(),
"`float_to_int_unchecked` called with non-int output type {dest_ty:?}"
),
})
}
}