Skip to main content

miri/intrinsics/
math.rs

1use rustc_apfloat::ieee::{DoubleS, HalfS, IeeeFloat, Semantics, SingleS};
2use rustc_apfloat::{self, Float, FloatConvert, Round};
3use rustc_middle::mir;
4use rustc_middle::ty::{self, FloatTy};
5
6use self::math::{HostFloatOperation, HostUnaryFloatOp, IeeeExt, host_unary_float_op};
7use super::check_intrinsic_arg_count;
8use crate::*;
9
10fn sqrt<'tcx, F: Float + FloatConvert<F> + Into<Scalar>>(
11    this: &mut MiriInterpCx<'tcx>,
12    args: &[OpTy<'tcx>],
13    dest: &MPlaceTy<'tcx>,
14) -> InterpResult<'tcx> {
15    let [f] = check_intrinsic_arg_count(args)?;
16    math::sqrt_op::<F>(this, f, dest)
17}
18
19/// Determine which float operation on which type this is.
20fn is_host_unary_float_op(intrinsic_name: &str) -> Option<(FloatTy, HostUnaryFloatOp)> {
21    let (op, ty) = intrinsic_name.rsplit_once('f')?;
22
23    let float_ty = match ty {
24        "16" => FloatTy::F16,
25        "32" => FloatTy::F32,
26        "64" => FloatTy::F64,
27        "128" => FloatTy::F128,
28        _ => return None,
29    };
30
31    let host_float_op = match op {
32        "sin" => HostUnaryFloatOp::Sin,
33        "cos" => HostUnaryFloatOp::Cos,
34        "exp" => HostUnaryFloatOp::Exp,
35        "exp2" => HostUnaryFloatOp::Exp2,
36        "log" => HostUnaryFloatOp::Log,
37        "log10" => HostUnaryFloatOp::Log10,
38        "log2" => HostUnaryFloatOp::Log2,
39        _ => return None,
40    };
41
42    Some((float_ty, host_float_op))
43}
44
45fn pow_intrinsic<'tcx, S: Semantics>(
46    this: &mut MiriInterpCx<'tcx>,
47    args: &[OpTy<'tcx>],
48    dest: &MPlaceTy<'tcx>,
49) -> InterpResult<'tcx, ()>
50where
51    IeeeFloat<S>: HostFloatOperation + IeeeExt + Float + Into<Scalar>,
52{
53    let [f1, f2] = check_intrinsic_arg_count(args)?;
54    let f1: IeeeFloat<S> = this.read_scalar(f1)?.to_float()?;
55    let f2: IeeeFloat<S> = this.read_scalar(f2)?.to_float()?;
56
57    let res = math::fixed_float_value(this, "pow", &[f1, f2]).unwrap_or_else(|| {
58        // Using host floats (but it's fine, this operation does not have guaranteed precision).
59        let res = f1.host_powf(f2);
60
61        // Apply a relative error of 4ULP to introduce some non-determinism
62        // simulating imprecise implementations and optimizations.
63        math::apply_random_float_error_ulp(this, res, 4)
64    });
65    let res = this.adjust_nan(res, &[f1, f2]);
66    this.write_scalar(res, dest)?;
67    interp_ok(())
68}
69fn powi_intrinsic<'tcx, S: Semantics>(
70    this: &mut MiriInterpCx<'tcx>,
71    args: &[OpTy<'tcx>],
72    dest: &MPlaceTy<'tcx>,
73) -> InterpResult<'tcx, ()>
74where
75    IeeeFloat<S>: HostFloatOperation + IeeeExt + Float + Into<Scalar>,
76{
77    let [f, i] = check_intrinsic_arg_count(args)?;
78    let f: IeeeFloat<S> = this.read_scalar(f)?.to_float()?;
79    let i = this.read_scalar(i)?.to_i32()?;
80
81    let res = math::fixed_powi_value(this, f, i).unwrap_or_else(|| {
82        // Using host floats (but it's fine, this operation does not have guaranteed precision).
83        let res = f.host_powi(i);
84
85        // Apply a relative error of 4ULP to introduce some non-determinism
86        // simulating imprecise implementations and optimizations.
87        math::apply_random_float_error_ulp(this, res, 4)
88    });
89    let res = this.adjust_nan(res, &[f]);
90    this.write_scalar(res, dest)?;
91    interp_ok(())
92}
93
94impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
95pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
96    fn emulate_math_intrinsic(
97        &mut self,
98        intrinsic_name: &str,
99        _generic_args: ty::GenericArgsRef<'tcx>,
100        args: &[OpTy<'tcx>],
101        dest: &MPlaceTy<'tcx>,
102    ) -> InterpResult<'tcx, EmulateItemResult> {
103        let this = self.eval_context_mut();
104
105        match intrinsic_name {
106            // Operations we can do with soft-floats.
107            "sqrtf16" => sqrt::<rustc_apfloat::ieee::Half>(this, args, dest)?,
108            "sqrtf32" => sqrt::<rustc_apfloat::ieee::Single>(this, args, dest)?,
109            "sqrtf64" => sqrt::<rustc_apfloat::ieee::Double>(this, args, dest)?,
110            "sqrtf128" => sqrt::<rustc_apfloat::ieee::Quad>(this, args, dest)?,
111
112            #[rustfmt::skip]
113            | "fadd_fast"
114            | "fsub_fast"
115            | "fmul_fast"
116            | "fdiv_fast"
117            | "frem_fast"
118            => {
119                let [a, b] = check_intrinsic_arg_count(args)?;
120                let a = this.read_immediate(a)?;
121                let b = this.read_immediate(b)?;
122                let op = match intrinsic_name {
123                    "fadd_fast" => mir::BinOp::Add,
124                    "fsub_fast" => mir::BinOp::Sub,
125                    "fmul_fast" => mir::BinOp::Mul,
126                    "fdiv_fast" => mir::BinOp::Div,
127                    "frem_fast" => mir::BinOp::Rem,
128                    _ => bug!(),
129                };
130                let float_finite = |x: &ImmTy<'tcx>| -> InterpResult<'tcx, bool> {
131                    let ty::Float(fty) = x.layout.ty.kind() else {
132                        bug!("float_finite: non-float input type {}", x.layout.ty)
133                    };
134                    interp_ok(match fty {
135                        FloatTy::F16 => x.to_scalar().to_f16()?.is_finite(),
136                        FloatTy::F32 => x.to_scalar().to_f32()?.is_finite(),
137                        FloatTy::F64 => x.to_scalar().to_f64()?.is_finite(),
138                        FloatTy::F128 => x.to_scalar().to_f128()?.is_finite(),
139                    })
140                };
141                match (float_finite(&a)?, float_finite(&b)?) {
142                    (false, false) => throw_ub_format!(
143                        "`{intrinsic_name}` intrinsic called with non-finite value as both parameters",
144                    ),
145                    (false, _) => throw_ub_format!(
146                        "`{intrinsic_name}` intrinsic called with non-finite value as first parameter",
147                    ),
148                    (_, false) => throw_ub_format!(
149                        "`{intrinsic_name}` intrinsic called with non-finite value as second parameter",
150                    ),
151                    _ => {}
152                }
153                let res = this.binary_op(op, &a, &b)?;
154                // This cannot be a NaN so we also don't have to apply any non-determinism.
155                // (Also, `binary_op` already called `generate_nan` if needed.)
156                if !float_finite(&res)? {
157                    throw_ub_format!("`{intrinsic_name}` intrinsic produced non-finite value as result");
158                }
159                // Apply a relative error of 4ULP to simulate non-deterministic precision loss
160                // due to optimizations.
161                let res = math::apply_random_float_error_to_imm(this, res, 4)?;
162                this.write_immediate(*res, dest)?;
163            }
164
165            "float_to_int_unchecked" => {
166                let [val] = check_intrinsic_arg_count(args)?;
167                let val = this.read_immediate(val)?;
168
169                let res = this
170                    .float_to_int_checked(&val, dest.layout, Round::TowardZero)?
171                    .ok_or_else(|| {
172                        err_ub_format!(
173                            "`float_to_int_unchecked` intrinsic called on {val} which cannot be represented in target type `{:?}`",
174                            dest.layout.ty
175                        )
176                    })?;
177
178                this.write_immediate(*res, dest)?;
179            }
180
181            // Operations that need host floats.
182            _ if let Some((float_ty, op)) = is_host_unary_float_op(intrinsic_name) => {
183                let [f] = check_intrinsic_arg_count(args)?;
184                match float_ty {
185                    FloatTy::F16 => host_unary_float_op::<HalfS>(this, f, op, dest)?,
186                    FloatTy::F32 => host_unary_float_op::<SingleS>(this, f, op, dest)?,
187                    FloatTy::F64 => host_unary_float_op::<DoubleS>(this, f, op, dest)?,
188                    FloatTy::F128 => todo!("f128"), // FIXME(f128)
189                };
190            }
191
192            "powf16" => pow_intrinsic::<HalfS>(this, args, dest)?,
193            "powf32" => pow_intrinsic::<SingleS>(this, args, dest)?,
194            "powf64" => pow_intrinsic::<DoubleS>(this, args, dest)?,
195            "powf128" => todo!("f128"), // FIXME(f128)
196
197            "powif16" => powi_intrinsic::<HalfS>(this, args, dest)?,
198            "powif32" => powi_intrinsic::<SingleS>(this, args, dest)?,
199            "powif64" => powi_intrinsic::<DoubleS>(this, args, dest)?,
200            "powif128" => todo!("f128"), // FIXME(f128)
201
202            _ => return interp_ok(EmulateItemResult::NotSupported),
203        }
204
205        interp_ok(EmulateItemResult::NeedsReturn)
206    }
207}