Merge pull request #4484 from RalfJung/math-shims

move math shims to their own files, and some refactoring in fixed_float_value
This commit is contained in:
Ralf Jung 2025-09-08 11:53:31 +00:00 committed by GitHub
commit 943aa93d46
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 603 additions and 532 deletions

View file

@ -0,0 +1,311 @@
use rand::Rng;
use rustc_apfloat::{self, Float, Round};
use rustc_middle::mir;
use rustc_middle::ty::{self, FloatTy};
use self::helpers::{ToHost, ToSoft};
use super::check_intrinsic_arg_count;
use crate::*;
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
fn emulate_math_intrinsic(
&mut self,
intrinsic_name: &str,
_generic_args: ty::GenericArgsRef<'tcx>,
args: &[OpTy<'tcx>],
dest: &MPlaceTy<'tcx>,
) -> InterpResult<'tcx, EmulateItemResult> {
let this = self.eval_context_mut();
match intrinsic_name {
// Operations we can do with soft-floats.
"sqrtf32" => {
let [f] = check_intrinsic_arg_count(args)?;
let f = this.read_scalar(f)?.to_f32()?;
// Sqrt is specified to be fully precise.
let res = math::sqrt(f);
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
}
"sqrtf64" => {
let [f] = check_intrinsic_arg_count(args)?;
let f = this.read_scalar(f)?.to_f64()?;
// Sqrt is specified to be fully precise.
let res = math::sqrt(f);
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
}
"fmaf32" => {
let [a, b, c] = check_intrinsic_arg_count(args)?;
let a = this.read_scalar(a)?.to_f32()?;
let b = this.read_scalar(b)?.to_f32()?;
let c = this.read_scalar(c)?.to_f32()?;
let res = a.mul_add(b, c).value;
let res = this.adjust_nan(res, &[a, b, c]);
this.write_scalar(res, dest)?;
}
"fmaf64" => {
let [a, b, c] = check_intrinsic_arg_count(args)?;
let a = this.read_scalar(a)?.to_f64()?;
let b = this.read_scalar(b)?.to_f64()?;
let c = this.read_scalar(c)?.to_f64()?;
let res = a.mul_add(b, c).value;
let res = this.adjust_nan(res, &[a, b, c]);
this.write_scalar(res, dest)?;
}
"fmuladdf32" => {
let [a, b, c] = check_intrinsic_arg_count(args)?;
let a = this.read_scalar(a)?.to_f32()?;
let b = this.read_scalar(b)?.to_f32()?;
let c = this.read_scalar(c)?.to_f32()?;
let fuse: bool = this.machine.float_nondet && this.machine.rng.get_mut().random();
let res = if fuse { a.mul_add(b, c).value } else { ((a * b).value + c).value };
let res = this.adjust_nan(res, &[a, b, c]);
this.write_scalar(res, dest)?;
}
"fmuladdf64" => {
let [a, b, c] = check_intrinsic_arg_count(args)?;
let a = this.read_scalar(a)?.to_f64()?;
let b = this.read_scalar(b)?.to_f64()?;
let c = this.read_scalar(c)?.to_f64()?;
let fuse: bool = this.machine.float_nondet && this.machine.rng.get_mut().random();
let res = if fuse { a.mul_add(b, c).value } else { ((a * b).value + c).value };
let res = this.adjust_nan(res, &[a, b, c]);
this.write_scalar(res, dest)?;
}
#[rustfmt::skip]
| "fadd_fast"
| "fsub_fast"
| "fmul_fast"
| "fdiv_fast"
| "frem_fast"
=> {
let [a, b] = check_intrinsic_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> {
let ty::Float(fty) = x.layout.ty.kind() else {
bug!("float_finite: non-float input type {}", x.layout.ty)
};
interp_ok(match fty {
FloatTy::F16 => x.to_scalar().to_f16()?.is_finite(),
FloatTy::F32 => x.to_scalar().to_f32()?.is_finite(),
FloatTy::F64 => x.to_scalar().to_f64()?.is_finite(),
FloatTy::F128 => x.to_scalar().to_f128()?.is_finite(),
})
};
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",
),
_ => {}
}
let res = this.binary_op(op, &a, &b)?;
// This cannot be a NaN so we also don't have to apply any non-determinism.
// (Also, `binary_op` already called `generate_nan` if needed.)
if !float_finite(&res)? {
throw_ub_format!("`{intrinsic_name}` intrinsic produced non-finite value as result");
}
// Apply a relative error of 4ULP to simulate non-deterministic precision loss
// due to optimizations.
let res = math::apply_random_float_error_to_imm(this, res, 4)?;
this.write_immediate(*res, dest)?;
}
"float_to_int_unchecked" => {
let [val] = check_intrinsic_arg_count(args)?;
let val = this.read_immediate(val)?;
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)?;
}
// Operations that need host floats.
#[rustfmt::skip]
| "sinf32"
| "cosf32"
| "expf32"
| "exp2f32"
| "logf32"
| "log10f32"
| "log2f32"
=> {
let [f] = check_intrinsic_arg_count(args)?;
let f = this.read_scalar(f)?.to_f32()?;
let res = math::fixed_float_value(this, intrinsic_name, &[f]).unwrap_or_else(|| {
// Using host floats (but it's fine, these operations do not have
// guaranteed precision).
let host = f.to_host();
let res = match intrinsic_name {
"sinf32" => host.sin(),
"cosf32" => host.cos(),
"expf32" => host.exp(),
"exp2f32" => host.exp2(),
"logf32" => host.ln(),
"log10f32" => host.log10(),
"log2f32" => host.log2(),
_ => bug!(),
};
let res = res.to_soft();
// Apply a relative error of 4ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
let res = math::apply_random_float_error_ulp(
this,
res,
4,
);
// Clamp the result to the guaranteed range of this function according to the C standard,
// if any.
math::clamp_float_value(intrinsic_name, res)
});
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
}
#[rustfmt::skip]
| "sinf64"
| "cosf64"
| "expf64"
| "exp2f64"
| "logf64"
| "log10f64"
| "log2f64"
=> {
let [f] = check_intrinsic_arg_count(args)?;
let f = this.read_scalar(f)?.to_f64()?;
let res = math::fixed_float_value(this, intrinsic_name, &[f]).unwrap_or_else(|| {
// Using host floats (but it's fine, these operations do not have
// guaranteed precision).
let host = f.to_host();
let res = match intrinsic_name {
"sinf64" => host.sin(),
"cosf64" => host.cos(),
"expf64" => host.exp(),
"exp2f64" => host.exp2(),
"logf64" => host.ln(),
"log10f64" => host.log10(),
"log2f64" => host.log2(),
_ => bug!(),
};
let res = res.to_soft();
// Apply a relative error of 4ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
let res = math::apply_random_float_error_ulp(
this,
res,
4,
);
// Clamp the result to the guaranteed range of this function according to the C standard,
// if any.
math::clamp_float_value(intrinsic_name, res)
});
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
}
"powf32" => {
let [f1, f2] = check_intrinsic_arg_count(args)?;
let f1 = this.read_scalar(f1)?.to_f32()?;
let f2 = this.read_scalar(f2)?.to_f32()?;
let res =
math::fixed_float_value(this, intrinsic_name, &[f1, f2]).unwrap_or_else(|| {
// Using host floats (but it's fine, this operation does not have guaranteed precision).
let res = f1.to_host().powf(f2.to_host()).to_soft();
// Apply a relative error of 4ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
math::apply_random_float_error_ulp(this, res, 4)
});
let res = this.adjust_nan(res, &[f1, f2]);
this.write_scalar(res, dest)?;
}
"powf64" => {
let [f1, f2] = check_intrinsic_arg_count(args)?;
let f1 = this.read_scalar(f1)?.to_f64()?;
let f2 = this.read_scalar(f2)?.to_f64()?;
let res =
math::fixed_float_value(this, intrinsic_name, &[f1, f2]).unwrap_or_else(|| {
// Using host floats (but it's fine, this operation does not have guaranteed precision).
let res = f1.to_host().powf(f2.to_host()).to_soft();
// Apply a relative error of 4ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
math::apply_random_float_error_ulp(this, res, 4)
});
let res = this.adjust_nan(res, &[f1, f2]);
this.write_scalar(res, dest)?;
}
"powif32" => {
let [f, i] = check_intrinsic_arg_count(args)?;
let f = this.read_scalar(f)?.to_f32()?;
let i = this.read_scalar(i)?.to_i32()?;
let res = math::fixed_powi_value(this, f, i).unwrap_or_else(|| {
// Using host floats (but it's fine, this operation does not have guaranteed precision).
let res = f.to_host().powi(i).to_soft();
// Apply a relative error of 4ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
math::apply_random_float_error_ulp(this, res, 4)
});
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
}
"powif64" => {
let [f, i] = check_intrinsic_arg_count(args)?;
let f = this.read_scalar(f)?.to_f64()?;
let i = this.read_scalar(i)?.to_i32()?;
let res = math::fixed_powi_value(this, f, i).unwrap_or_else(|| {
// Using host floats (but it's fine, this operation does not have guaranteed precision).
let res = f.to_host().powi(i).to_soft();
// Apply a relative error of 4ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
math::apply_random_float_error_ulp(this, res, 4)
});
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
}
_ => return interp_ok(EmulateItemResult::NotSupported),
}
interp_ok(EmulateItemResult::NeedsReturn)
}
}

View file

@ -1,6 +1,7 @@
#![warn(clippy::arithmetic_side_effects)]
mod atomic;
mod math;
mod simd;
pub use self::atomic::AtomicRmwOp;
@ -8,13 +9,11 @@ pub use self::atomic::AtomicRmwOp;
#[rustfmt::skip] // prevent `use` reordering
use rand::Rng;
use rustc_abi::Size;
use rustc_apfloat::{self, Float, Round};
use rustc_middle::mir;
use rustc_middle::ty::{self, FloatTy};
use rustc_middle::{mir, ty};
use rustc_span::{Symbol, sym};
use self::atomic::EvalContextExt as _;
use self::helpers::{ToHost, ToSoft};
use self::math::EvalContextExt as _;
use self::simd::EvalContextExt as _;
use crate::*;
@ -179,288 +178,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
this.write_scalar(Scalar::from_bool(branch), dest)?;
}
"sqrtf32" => {
let [f] = check_intrinsic_arg_count(args)?;
let f = this.read_scalar(f)?.to_f32()?;
// Sqrt is specified to be fully precise.
let res = math::sqrt(f);
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
}
"sqrtf64" => {
let [f] = check_intrinsic_arg_count(args)?;
let f = this.read_scalar(f)?.to_f64()?;
// Sqrt is specified to be fully precise.
let res = math::sqrt(f);
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
}
#[rustfmt::skip]
| "sinf32"
| "cosf32"
| "expf32"
| "exp2f32"
| "logf32"
| "log10f32"
| "log2f32"
=> {
let [f] = check_intrinsic_arg_count(args)?;
let f = this.read_scalar(f)?.to_f32()?;
let res = math::fixed_float_value(this, intrinsic_name, &[f]).unwrap_or_else(|| {
// Using host floats (but it's fine, these operations do not have
// guaranteed precision).
let host = f.to_host();
let res = match intrinsic_name {
"sinf32" => host.sin(),
"cosf32" => host.cos(),
"expf32" => host.exp(),
"exp2f32" => host.exp2(),
"logf32" => host.ln(),
"log10f32" => host.log10(),
"log2f32" => host.log2(),
_ => bug!(),
};
let res = res.to_soft();
// Apply a relative error of 4ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
let res = math::apply_random_float_error_ulp(
this,
res,
4,
);
// Clamp the result to the guaranteed range of this function according to the C standard,
// if any.
math::clamp_float_value(intrinsic_name, res)
});
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
}
#[rustfmt::skip]
| "sinf64"
| "cosf64"
| "expf64"
| "exp2f64"
| "logf64"
| "log10f64"
| "log2f64"
=> {
let [f] = check_intrinsic_arg_count(args)?;
let f = this.read_scalar(f)?.to_f64()?;
let res = math::fixed_float_value(this, intrinsic_name, &[f]).unwrap_or_else(|| {
// Using host floats (but it's fine, these operations do not have
// guaranteed precision).
let host = f.to_host();
let res = match intrinsic_name {
"sinf64" => host.sin(),
"cosf64" => host.cos(),
"expf64" => host.exp(),
"exp2f64" => host.exp2(),
"logf64" => host.ln(),
"log10f64" => host.log10(),
"log2f64" => host.log2(),
_ => bug!(),
};
let res = res.to_soft();
// Apply a relative error of 4ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
let res = math::apply_random_float_error_ulp(
this,
res,
4,
);
// Clamp the result to the guaranteed range of this function according to the C standard,
// if any.
math::clamp_float_value(intrinsic_name, res)
});
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
}
"fmaf32" => {
let [a, b, c] = check_intrinsic_arg_count(args)?;
let a = this.read_scalar(a)?.to_f32()?;
let b = this.read_scalar(b)?.to_f32()?;
let c = this.read_scalar(c)?.to_f32()?;
let res = a.mul_add(b, c).value;
let res = this.adjust_nan(res, &[a, b, c]);
this.write_scalar(res, dest)?;
}
"fmaf64" => {
let [a, b, c] = check_intrinsic_arg_count(args)?;
let a = this.read_scalar(a)?.to_f64()?;
let b = this.read_scalar(b)?.to_f64()?;
let c = this.read_scalar(c)?.to_f64()?;
let res = a.mul_add(b, c).value;
let res = this.adjust_nan(res, &[a, b, c]);
this.write_scalar(res, dest)?;
}
"fmuladdf32" => {
let [a, b, c] = check_intrinsic_arg_count(args)?;
let a = this.read_scalar(a)?.to_f32()?;
let b = this.read_scalar(b)?.to_f32()?;
let c = this.read_scalar(c)?.to_f32()?;
let fuse: bool = this.machine.float_nondet && this.machine.rng.get_mut().random();
let res = if fuse { a.mul_add(b, c).value } else { ((a * b).value + c).value };
let res = this.adjust_nan(res, &[a, b, c]);
this.write_scalar(res, dest)?;
}
"fmuladdf64" => {
let [a, b, c] = check_intrinsic_arg_count(args)?;
let a = this.read_scalar(a)?.to_f64()?;
let b = this.read_scalar(b)?.to_f64()?;
let c = this.read_scalar(c)?.to_f64()?;
let fuse: bool = this.machine.float_nondet && this.machine.rng.get_mut().random();
let res = if fuse { a.mul_add(b, c).value } else { ((a * b).value + c).value };
let res = this.adjust_nan(res, &[a, b, c]);
this.write_scalar(res, dest)?;
}
"powf32" => {
let [f1, f2] = check_intrinsic_arg_count(args)?;
let f1 = this.read_scalar(f1)?.to_f32()?;
let f2 = this.read_scalar(f2)?.to_f32()?;
let res =
math::fixed_float_value(this, intrinsic_name, &[f1, f2]).unwrap_or_else(|| {
// Using host floats (but it's fine, this operation does not have guaranteed precision).
let res = f1.to_host().powf(f2.to_host()).to_soft();
// Apply a relative error of 4ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
math::apply_random_float_error_ulp(this, res, 4)
});
let res = this.adjust_nan(res, &[f1, f2]);
this.write_scalar(res, dest)?;
}
"powf64" => {
let [f1, f2] = check_intrinsic_arg_count(args)?;
let f1 = this.read_scalar(f1)?.to_f64()?;
let f2 = this.read_scalar(f2)?.to_f64()?;
let res =
math::fixed_float_value(this, intrinsic_name, &[f1, f2]).unwrap_or_else(|| {
// Using host floats (but it's fine, this operation does not have guaranteed precision).
let res = f1.to_host().powf(f2.to_host()).to_soft();
// Apply a relative error of 4ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
math::apply_random_float_error_ulp(this, res, 4)
});
let res = this.adjust_nan(res, &[f1, f2]);
this.write_scalar(res, dest)?;
}
"powif32" => {
let [f, i] = check_intrinsic_arg_count(args)?;
let f = this.read_scalar(f)?.to_f32()?;
let i = this.read_scalar(i)?.to_i32()?;
let res = math::fixed_powi_value(this, f, i).unwrap_or_else(|| {
// Using host floats (but it's fine, this operation does not have guaranteed precision).
let res = f.to_host().powi(i).to_soft();
// Apply a relative error of 4ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
math::apply_random_float_error_ulp(this, res, 4)
});
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
}
"powif64" => {
let [f, i] = check_intrinsic_arg_count(args)?;
let f = this.read_scalar(f)?.to_f64()?;
let i = this.read_scalar(i)?.to_i32()?;
let res = math::fixed_powi_value(this, f, i).unwrap_or_else(|| {
// Using host floats (but it's fine, this operation does not have guaranteed precision).
let res = f.to_host().powi(i).to_soft();
// Apply a relative error of 4ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
math::apply_random_float_error_ulp(this, res, 4)
});
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
}
#[rustfmt::skip]
| "fadd_fast"
| "fsub_fast"
| "fmul_fast"
| "fdiv_fast"
| "frem_fast"
=> {
let [a, b] = check_intrinsic_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> {
let ty::Float(fty) = x.layout.ty.kind() else {
bug!("float_finite: non-float input type {}", x.layout.ty)
};
interp_ok(match fty {
FloatTy::F16 => x.to_scalar().to_f16()?.is_finite(),
FloatTy::F32 => x.to_scalar().to_f32()?.is_finite(),
FloatTy::F64 => x.to_scalar().to_f64()?.is_finite(),
FloatTy::F128 => x.to_scalar().to_f128()?.is_finite(),
})
};
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",
),
_ => {}
}
let res = this.binary_op(op, &a, &b)?;
// This cannot be a NaN so we also don't have to apply any non-determinism.
// (Also, `binary_op` already called `generate_nan` if needed.)
if !float_finite(&res)? {
throw_ub_format!("`{intrinsic_name}` intrinsic produced non-finite value as result");
}
// Apply a relative error of 4ULP to simulate non-deterministic precision loss
// due to optimizations.
let res = math::apply_random_float_error_to_imm(this, res, 4)?;
this.write_immediate(*res, dest)?;
}
"float_to_int_unchecked" => {
let [val] = check_intrinsic_arg_count(args)?;
let val = this.read_immediate(val)?;
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)?;
}
// Other
"breakpoint" => {
let [] = check_intrinsic_arg_count(args)?;
@ -472,7 +189,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// Make these a NOP, so we get the better Miri-native error messages.
}
_ => return interp_ok(EmulateItemResult::NotSupported),
_ => return this.emulate_math_intrinsic(intrinsic_name, generic_args, args, dest),
}
interp_ok(EmulateItemResult::NeedsReturn)

View file

@ -210,61 +210,66 @@ where
let pi_over_2 = (pi / two).value;
let pi_over_4 = (pi_over_2 / two).value;
Some(match (intrinsic_name, args) {
// Remove `f32`/`f64` suffix, if any.
let name = intrinsic_name
.strip_suffix("f32")
.or_else(|| intrinsic_name.strip_suffix("f64"))
.unwrap_or(intrinsic_name);
// Also strip trailing `f` (indicates "float"), with an exception for "erf" to avoid
// removing that `f`.
let name = if name == "erf" { name } else { name.strip_suffix("f").unwrap_or(name) };
Some(match (name, args) {
// cos(±0) and cosh(±0)= 1
("cosf32" | "cosf64" | "coshf" | "cosh", [input]) if input.is_zero() => one,
("cos" | "cosh", [input]) if input.is_zero() => one,
// e^0 = 1
("expf32" | "expf64" | "exp2f32" | "exp2f64", [input]) if input.is_zero() => one,
("exp" | "exp2", [input]) if input.is_zero() => one,
// tanh(±INF) = ±1
("tanhf" | "tanh", [input]) if input.is_infinite() => one.copy_sign(*input),
("tanh", [input]) if input.is_infinite() => one.copy_sign(*input),
// atan(±INF) = ±π/2
("atanf" | "atan", [input]) if input.is_infinite() => pi_over_2.copy_sign(*input),
("atan", [input]) if input.is_infinite() => pi_over_2.copy_sign(*input),
// erf(±INF) = ±1
("erff" | "erf", [input]) if input.is_infinite() => one.copy_sign(*input),
("erf", [input]) if input.is_infinite() => one.copy_sign(*input),
// erfc(-INF) = 2
("erfcf" | "erfc", [input]) if input.is_neg_infinity() => (one + one).value,
("erfc", [input]) if input.is_neg_infinity() => (one + one).value,
// hypot(x, ±0) = abs(x), if x is not a NaN.
("_hypotf" | "hypotf" | "_hypot" | "hypot", [x, y]) if !x.is_nan() && y.is_zero() =>
x.abs(),
// `_hypot` is the Windows name for this.
("_hypot" | "hypot", [x, y]) if !x.is_nan() && y.is_zero() => x.abs(),
// atan2(±0,0) = ±π.
// atan2(±0, y) = ±π for y < 0.
// Must check for non NaN because `y.is_negative()` also applies to NaN.
("atan2f" | "atan2", [x, y]) if (x.is_zero() && (y.is_negative() && !y.is_nan())) =>
pi.copy_sign(*x),
("atan2", [x, y]) if (x.is_zero() && (y.is_negative() && !y.is_nan())) => pi.copy_sign(*x),
// atan2(±x,−∞) = ±π for finite x > 0.
("atan2f" | "atan2", [x, y])
if (!x.is_zero() && !x.is_infinite()) && y.is_neg_infinity() =>
("atan2", [x, y]) if (!x.is_zero() && !x.is_infinite()) && y.is_neg_infinity() =>
pi.copy_sign(*x),
// atan2(x, ±0) = −π/2 for x < 0.
// atan2(x, ±0) = π/2 for x > 0.
("atan2f" | "atan2", [x, y]) if !x.is_zero() && y.is_zero() => pi_over_2.copy_sign(*x),
("atan2", [x, y]) if !x.is_zero() && y.is_zero() => pi_over_2.copy_sign(*x),
//atan2(±∞, −∞) = ±3π/4
("atan2f" | "atan2", [x, y]) if x.is_infinite() && y.is_neg_infinity() =>
("atan2", [x, y]) if x.is_infinite() && y.is_neg_infinity() =>
(pi_over_4 * three).value.copy_sign(*x),
//atan2(±∞, +∞) = ±π/4
("atan2f" | "atan2", [x, y]) if x.is_infinite() && y.is_pos_infinity() =>
pi_over_4.copy_sign(*x),
("atan2", [x, y]) if x.is_infinite() && y.is_pos_infinity() => pi_over_4.copy_sign(*x),
// atan2(±∞, y) returns ±π/2 for finite y.
("atan2f" | "atan2", [x, y]) if x.is_infinite() && (!y.is_infinite() && !y.is_nan()) =>
("atan2", [x, y]) if x.is_infinite() && (!y.is_infinite() && !y.is_nan()) =>
pi_over_2.copy_sign(*x),
// (-1)^(±INF) = 1
("powf32" | "powf64", [base, exp]) if *base == -one && exp.is_infinite() => one,
("pow", [base, exp]) if *base == -one && exp.is_infinite() => one,
// 1^y = 1 for any y, even a NaN
("powf32" | "powf64", [base, exp]) if *base == one => {
("pow", [base, exp]) if *base == one => {
let rng = this.machine.rng.get_mut();
// SNaN exponents get special treatment: they might return 1, or a NaN.
let return_nan = exp.is_signaling() && this.machine.float_nondet && rng.random();
@ -273,7 +278,7 @@ where
}
// x^(±0) = 1 for any x, even a NaN
("powf32" | "powf64", [base, exp]) if exp.is_zero() => {
("pow", [base, exp]) if exp.is_zero() => {
let rng = this.machine.rng.get_mut();
// SNaN bases get special treatment: they might return 1, or a NaN.
let return_nan = base.is_signaling() && this.machine.float_nondet && rng.random();

View file

@ -3,7 +3,6 @@ use std::io::Write;
use std::path::Path;
use rustc_abi::{Align, AlignFromBytesError, CanonAbi, Size};
use rustc_apfloat::Float;
use rustc_ast::expand::allocator::alloc_error_handler_name;
use rustc_hir::attrs::Linkage;
use rustc_hir::def::DefKind;
@ -15,7 +14,6 @@ use rustc_middle::{mir, ty};
use rustc_span::Symbol;
use rustc_target::callconv::FnAbi;
use self::helpers::{ToHost, ToSoft};
use super::alloc::EvalContextExt as _;
use super::backtrace::EvalContextExt as _;
use crate::helpers::EvalContextExt as _;
@ -818,225 +816,6 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
this.write_pointer(ptr_dest, dest)?;
}
// math functions (note that there are also intrinsics for some other functions)
#[rustfmt::skip]
| "cbrtf"
| "coshf"
| "sinhf"
| "tanf"
| "tanhf"
| "acosf"
| "asinf"
| "atanf"
| "log1pf"
| "expm1f"
| "tgammaf"
| "erff"
| "erfcf"
=> {
let [f] = this.check_shim_sig_lenient(abi, CanonAbi::C , link_name, args)?;
let f = this.read_scalar(f)?.to_f32()?;
let res = math::fixed_float_value(this, link_name.as_str(), &[f]).unwrap_or_else(|| {
// Using host floats (but it's fine, these operations do not have
// guaranteed precision).
let f_host = f.to_host();
let res = match link_name.as_str() {
"cbrtf" => f_host.cbrt(),
"coshf" => f_host.cosh(),
"sinhf" => f_host.sinh(),
"tanf" => f_host.tan(),
"tanhf" => f_host.tanh(),
"acosf" => f_host.acos(),
"asinf" => f_host.asin(),
"atanf" => f_host.atan(),
"log1pf" => f_host.ln_1p(),
"expm1f" => f_host.exp_m1(),
"tgammaf" => f_host.gamma(),
"erff" => f_host.erf(),
"erfcf" => f_host.erfc(),
_ => bug!(),
};
let res = res.to_soft();
// Apply a relative error of 4ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
let res = math::apply_random_float_error_ulp(this, res, 4);
// Clamp the result to the guaranteed range of this function according to the C standard,
// if any.
math::clamp_float_value(link_name.as_str(), res)
});
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
}
#[rustfmt::skip]
| "_hypotf"
| "hypotf"
| "atan2f"
| "fdimf"
=> {
let [f1, f2] = this.check_shim_sig_lenient(abi, CanonAbi::C , link_name, args)?;
let f1 = this.read_scalar(f1)?.to_f32()?;
let f2 = this.read_scalar(f2)?.to_f32()?;
let res = math::fixed_float_value(this, link_name.as_str(), &[f1, f2])
.unwrap_or_else(|| {
let res = match link_name.as_str() {
// underscore case for windows, here and below
// (see https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/floating-point-primitives?view=vs-2019)
// Using host floats (but it's fine, these operations do not have guaranteed precision).
"_hypotf" | "hypotf" => f1.to_host().hypot(f2.to_host()).to_soft(),
"atan2f" => f1.to_host().atan2(f2.to_host()).to_soft(),
#[allow(deprecated)]
"fdimf" => f1.to_host().abs_sub(f2.to_host()).to_soft(),
_ => bug!(),
};
// Apply a relative error of 4ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
let res = math::apply_random_float_error_ulp(this, res, 4);
// Clamp the result to the guaranteed range of this function according to the C standard,
// if any.
math::clamp_float_value(link_name.as_str(), res)
});
let res = this.adjust_nan(res, &[f1, f2]);
this.write_scalar(res, dest)?;
}
#[rustfmt::skip]
| "cbrt"
| "cosh"
| "sinh"
| "tan"
| "tanh"
| "acos"
| "asin"
| "atan"
| "log1p"
| "expm1"
| "tgamma"
| "erf"
| "erfc"
=> {
let [f] = this.check_shim_sig_lenient(abi, CanonAbi::C , link_name, args)?;
let f = this.read_scalar(f)?.to_f64()?;
let res = math::fixed_float_value(this, link_name.as_str(), &[f]).unwrap_or_else(|| {
// Using host floats (but it's fine, these operations do not have
// guaranteed precision).
let f_host = f.to_host();
let res = match link_name.as_str() {
"cbrt" => f_host.cbrt(),
"cosh" => f_host.cosh(),
"sinh" => f_host.sinh(),
"tan" => f_host.tan(),
"tanh" => f_host.tanh(),
"acos" => f_host.acos(),
"asin" => f_host.asin(),
"atan" => f_host.atan(),
"log1p" => f_host.ln_1p(),
"expm1" => f_host.exp_m1(),
"tgamma" => f_host.gamma(),
"erf" => f_host.erf(),
"erfc" => f_host.erfc(),
_ => bug!(),
};
let res = res.to_soft();
// Apply a relative error of 4ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
let res = math::apply_random_float_error_ulp(this, res, 4);
// Clamp the result to the guaranteed range of this function according to the C standard,
// if any.
math::clamp_float_value(link_name.as_str(), res)
});
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
}
#[rustfmt::skip]
| "_hypot"
| "hypot"
| "atan2"
| "fdim"
=> {
let [f1, f2] = this.check_shim_sig_lenient(abi, CanonAbi::C , link_name, args)?;
let f1 = this.read_scalar(f1)?.to_f64()?;
let f2 = this.read_scalar(f2)?.to_f64()?;
let res = math::fixed_float_value(this, link_name.as_str(), &[f1, f2]).unwrap_or_else(|| {
let res = match link_name.as_str() {
// underscore case for windows, here and below
// (see https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/floating-point-primitives?view=vs-2019)
// Using host floats (but it's fine, these operations do not have guaranteed precision).
"_hypot" | "hypot" => f1.to_host().hypot(f2.to_host()).to_soft(),
"atan2" => f1.to_host().atan2(f2.to_host()).to_soft(),
#[allow(deprecated)]
"fdim" => f1.to_host().abs_sub(f2.to_host()).to_soft(),
_ => bug!(),
};
// Apply a relative error of 4ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
let res = math::apply_random_float_error_ulp(this, res, 4);
// Clamp the result to the guaranteed range of this function according to the C standard,
// if any.
math::clamp_float_value(link_name.as_str(), res)
});
let res = this.adjust_nan(res, &[f1, f2]);
this.write_scalar(res, dest)?;
}
#[rustfmt::skip]
| "_ldexp"
| "ldexp"
| "scalbn"
=> {
let [x, exp] = this.check_shim_sig_lenient(abi, CanonAbi::C , link_name, args)?;
// For radix-2 (binary) systems, `ldexp` and `scalbn` are the same.
let x = this.read_scalar(x)?.to_f64()?;
let exp = this.read_scalar(exp)?.to_i32()?;
let res = x.scalbn(exp);
let res = this.adjust_nan(res, &[x]);
this.write_scalar(res, dest)?;
}
"lgammaf_r" => {
let [x, signp] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
let x = this.read_scalar(x)?.to_f32()?;
let signp = this.deref_pointer_as(signp, this.machine.layouts.i32)?;
// Using host floats (but it's fine, these operations do not have guaranteed precision).
let (res, sign) = x.to_host().ln_gamma();
this.write_int(sign, &signp)?;
let res = res.to_soft();
// Apply a relative error of 4ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
let res = math::apply_random_float_error_ulp(this, res, 4);
// Clamp the result to the guaranteed range of this function according to the C standard,
// if any.
let res = math::clamp_float_value(link_name.as_str(), res);
let res = this.adjust_nan(res, &[x]);
this.write_scalar(res, dest)?;
}
"lgamma_r" => {
let [x, signp] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
let x = this.read_scalar(x)?.to_f64()?;
let signp = this.deref_pointer_as(signp, this.machine.layouts.i32)?;
// Using host floats (but it's fine, these operations do not have guaranteed precision).
let (res, sign) = x.to_host().ln_gamma();
this.write_int(sign, &signp)?;
let res = res.to_soft();
// Apply a relative error of 4ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
let res = math::apply_random_float_error_ulp(this, res, 4);
// Clamp the result to the guaranteed range of this function according to the C standard,
// if any.
let res = math::clamp_float_value(link_name.as_str(), res);
let res = this.adjust_nan(res, &[x]);
this.write_scalar(res, dest)?;
}
// LLVM intrinsics
"llvm.prefetch" => {
let [p, rw, loc, ty] =
@ -1118,8 +897,18 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
}
}
// Platform-specific shims
_ =>
// Fallback to shims in submodules.
_ => {
// Math shims
#[expect(irrefutable_let_patterns)]
if let res = shims::math::EvalContextExt::emulate_foreign_item_inner(
this, link_name, abi, args, dest,
)? && !matches!(res, EmulateItemResult::NotSupported)
{
return interp_ok(res);
}
// Platform-specific shims
return match this.tcx.sess.target.os.as_ref() {
_ if this.target_os_is_unix() =>
shims::unix::foreign_items::EvalContextExt::emulate_foreign_item_inner(
@ -1134,7 +923,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
this, link_name, abi, args, dest,
),
_ => interp_ok(EmulateItemResult::NotSupported),
},
};
}
};
// We only fall through to here if we did *not* hit the `_` arm above,
// i.e., if we actually emulated the function with one of the shims.

View file

@ -0,0 +1,247 @@
use rustc_abi::CanonAbi;
use rustc_apfloat::Float;
use rustc_middle::ty::Ty;
use rustc_span::Symbol;
use rustc_target::callconv::FnAbi;
use self::helpers::{ToHost, ToSoft};
use crate::*;
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
fn emulate_foreign_item_inner(
&mut self,
link_name: Symbol,
abi: &FnAbi<'tcx, Ty<'tcx>>,
args: &[OpTy<'tcx>],
dest: &MPlaceTy<'tcx>,
) -> InterpResult<'tcx, EmulateItemResult> {
let this = self.eval_context_mut();
// math functions (note that there are also intrinsics for some other functions)
match link_name.as_str() {
// math functions (note that there are also intrinsics for some other functions)
#[rustfmt::skip]
| "cbrtf"
| "coshf"
| "sinhf"
| "tanf"
| "tanhf"
| "acosf"
| "asinf"
| "atanf"
| "log1pf"
| "expm1f"
| "tgammaf"
| "erff"
| "erfcf"
=> {
let [f] = this.check_shim_sig_lenient(abi, CanonAbi::C , link_name, args)?;
let f = this.read_scalar(f)?.to_f32()?;
let res = math::fixed_float_value(this, link_name.as_str(), &[f]).unwrap_or_else(|| {
// Using host floats (but it's fine, these operations do not have
// guaranteed precision).
let f_host = f.to_host();
let res = match link_name.as_str() {
"cbrtf" => f_host.cbrt(),
"coshf" => f_host.cosh(),
"sinhf" => f_host.sinh(),
"tanf" => f_host.tan(),
"tanhf" => f_host.tanh(),
"acosf" => f_host.acos(),
"asinf" => f_host.asin(),
"atanf" => f_host.atan(),
"log1pf" => f_host.ln_1p(),
"expm1f" => f_host.exp_m1(),
"tgammaf" => f_host.gamma(),
"erff" => f_host.erf(),
"erfcf" => f_host.erfc(),
_ => bug!(),
};
let res = res.to_soft();
// Apply a relative error of 4ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
let res = math::apply_random_float_error_ulp(this, res, 4);
// Clamp the result to the guaranteed range of this function according to the C standard,
// if any.
math::clamp_float_value(link_name.as_str(), res)
});
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
}
#[rustfmt::skip]
| "_hypotf"
| "hypotf"
| "atan2f"
| "fdimf"
=> {
let [f1, f2] = this.check_shim_sig_lenient(abi, CanonAbi::C , link_name, args)?;
let f1 = this.read_scalar(f1)?.to_f32()?;
let f2 = this.read_scalar(f2)?.to_f32()?;
let res = math::fixed_float_value(this, link_name.as_str(), &[f1, f2])
.unwrap_or_else(|| {
let res = match link_name.as_str() {
// underscore case for windows, here and below
// (see https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/floating-point-primitives?view=vs-2019)
// Using host floats (but it's fine, these operations do not have guaranteed precision).
"_hypotf" | "hypotf" => f1.to_host().hypot(f2.to_host()).to_soft(),
"atan2f" => f1.to_host().atan2(f2.to_host()).to_soft(),
#[allow(deprecated)]
"fdimf" => f1.to_host().abs_sub(f2.to_host()).to_soft(),
_ => bug!(),
};
// Apply a relative error of 4ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
let res = math::apply_random_float_error_ulp(this, res, 4);
// Clamp the result to the guaranteed range of this function according to the C standard,
// if any.
math::clamp_float_value(link_name.as_str(), res)
});
let res = this.adjust_nan(res, &[f1, f2]);
this.write_scalar(res, dest)?;
}
#[rustfmt::skip]
| "cbrt"
| "cosh"
| "sinh"
| "tan"
| "tanh"
| "acos"
| "asin"
| "atan"
| "log1p"
| "expm1"
| "tgamma"
| "erf"
| "erfc"
=> {
let [f] = this.check_shim_sig_lenient(abi, CanonAbi::C , link_name, args)?;
let f = this.read_scalar(f)?.to_f64()?;
let res = math::fixed_float_value(this, link_name.as_str(), &[f]).unwrap_or_else(|| {
// Using host floats (but it's fine, these operations do not have
// guaranteed precision).
let f_host = f.to_host();
let res = match link_name.as_str() {
"cbrt" => f_host.cbrt(),
"cosh" => f_host.cosh(),
"sinh" => f_host.sinh(),
"tan" => f_host.tan(),
"tanh" => f_host.tanh(),
"acos" => f_host.acos(),
"asin" => f_host.asin(),
"atan" => f_host.atan(),
"log1p" => f_host.ln_1p(),
"expm1" => f_host.exp_m1(),
"tgamma" => f_host.gamma(),
"erf" => f_host.erf(),
"erfc" => f_host.erfc(),
_ => bug!(),
};
let res = res.to_soft();
// Apply a relative error of 4ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
let res = math::apply_random_float_error_ulp(this, res, 4);
// Clamp the result to the guaranteed range of this function according to the C standard,
// if any.
math::clamp_float_value(link_name.as_str(), res)
});
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
}
#[rustfmt::skip]
| "_hypot"
| "hypot"
| "atan2"
| "fdim"
=> {
let [f1, f2] = this.check_shim_sig_lenient(abi, CanonAbi::C , link_name, args)?;
let f1 = this.read_scalar(f1)?.to_f64()?;
let f2 = this.read_scalar(f2)?.to_f64()?;
let res = math::fixed_float_value(this, link_name.as_str(), &[f1, f2]).unwrap_or_else(|| {
let res = match link_name.as_str() {
// underscore case for windows, here and below
// (see https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/floating-point-primitives?view=vs-2019)
// Using host floats (but it's fine, these operations do not have guaranteed precision).
"_hypot" | "hypot" => f1.to_host().hypot(f2.to_host()).to_soft(),
"atan2" => f1.to_host().atan2(f2.to_host()).to_soft(),
#[allow(deprecated)]
"fdim" => f1.to_host().abs_sub(f2.to_host()).to_soft(),
_ => bug!(),
};
// Apply a relative error of 4ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
let res = math::apply_random_float_error_ulp(this, res, 4);
// Clamp the result to the guaranteed range of this function according to the C standard,
// if any.
math::clamp_float_value(link_name.as_str(), res)
});
let res = this.adjust_nan(res, &[f1, f2]);
this.write_scalar(res, dest)?;
}
#[rustfmt::skip]
| "_ldexp"
| "ldexp"
| "scalbn"
=> {
let [x, exp] = this.check_shim_sig_lenient(abi, CanonAbi::C , link_name, args)?;
// For radix-2 (binary) systems, `ldexp` and `scalbn` are the same.
let x = this.read_scalar(x)?.to_f64()?;
let exp = this.read_scalar(exp)?.to_i32()?;
let res = x.scalbn(exp);
let res = this.adjust_nan(res, &[x]);
this.write_scalar(res, dest)?;
}
"lgammaf_r" => {
let [x, signp] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
let x = this.read_scalar(x)?.to_f32()?;
let signp = this.deref_pointer_as(signp, this.machine.layouts.i32)?;
// Using host floats (but it's fine, these operations do not have guaranteed precision).
let (res, sign) = x.to_host().ln_gamma();
this.write_int(sign, &signp)?;
let res = res.to_soft();
// Apply a relative error of 4ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
let res = math::apply_random_float_error_ulp(this, res, 4);
// Clamp the result to the guaranteed range of this function according to the C standard,
// if any.
let res = math::clamp_float_value(link_name.as_str(), res);
let res = this.adjust_nan(res, &[x]);
this.write_scalar(res, dest)?;
}
"lgamma_r" => {
let [x, signp] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
let x = this.read_scalar(x)?.to_f64()?;
let signp = this.deref_pointer_as(signp, this.machine.layouts.i32)?;
// Using host floats (but it's fine, these operations do not have guaranteed precision).
let (res, sign) = x.to_host().ln_gamma();
this.write_int(sign, &signp)?;
let res = res.to_soft();
// Apply a relative error of 4ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
let res = math::apply_random_float_error_ulp(this, res, 4);
// Clamp the result to the guaranteed range of this function according to the C standard,
// if any.
let res = math::clamp_float_value(link_name.as_str(), res);
let res = this.adjust_nan(res, &[x]);
this.write_scalar(res, dest)?;
}
_ => return interp_ok(EmulateItemResult::NotSupported),
}
interp_ok(EmulateItemResult::NeedsReturn)
}
}

View file

@ -4,6 +4,7 @@ mod aarch64;
mod alloc;
mod backtrace;
mod files;
mod math;
#[cfg(all(unix, feature = "native-lib"))]
mod native_lib;
mod unix;