miri/intrinsics/
math.rs

1use rustc_apfloat::{self, Float, FloatConvert, Round};
2use rustc_middle::mir;
3use rustc_middle::ty::{self, FloatTy};
4
5use self::helpers::{ToHost, ToSoft};
6use super::check_intrinsic_arg_count;
7use crate::*;
8
9fn sqrt<'tcx, F: Float + FloatConvert<F> + Into<Scalar>>(
10    this: &mut MiriInterpCx<'tcx>,
11    args: &[OpTy<'tcx>],
12    dest: &MPlaceTy<'tcx>,
13) -> InterpResult<'tcx> {
14    let [f] = check_intrinsic_arg_count(args)?;
15    let f = this.read_scalar(f)?;
16    let f: F = f.to_float()?;
17    // Sqrt is specified to be fully precise.
18    let res = math::sqrt(f);
19    let res = this.adjust_nan(res, &[f]);
20    this.write_scalar(res, dest)
21}
22
23impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
24pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
25    fn emulate_math_intrinsic(
26        &mut self,
27        intrinsic_name: &str,
28        _generic_args: ty::GenericArgsRef<'tcx>,
29        args: &[OpTy<'tcx>],
30        dest: &MPlaceTy<'tcx>,
31    ) -> InterpResult<'tcx, EmulateItemResult> {
32        let this = self.eval_context_mut();
33
34        match intrinsic_name {
35            // Operations we can do with soft-floats.
36            "sqrtf16" => sqrt::<rustc_apfloat::ieee::Half>(this, args, dest)?,
37            "sqrtf32" => sqrt::<rustc_apfloat::ieee::Single>(this, args, dest)?,
38            "sqrtf64" => sqrt::<rustc_apfloat::ieee::Double>(this, args, dest)?,
39            "sqrtf128" => sqrt::<rustc_apfloat::ieee::Quad>(this, args, dest)?,
40
41            #[rustfmt::skip]
42            | "fadd_fast"
43            | "fsub_fast"
44            | "fmul_fast"
45            | "fdiv_fast"
46            | "frem_fast"
47            => {
48                let [a, b] = check_intrinsic_arg_count(args)?;
49                let a = this.read_immediate(a)?;
50                let b = this.read_immediate(b)?;
51                let op = match intrinsic_name {
52                    "fadd_fast" => mir::BinOp::Add,
53                    "fsub_fast" => mir::BinOp::Sub,
54                    "fmul_fast" => mir::BinOp::Mul,
55                    "fdiv_fast" => mir::BinOp::Div,
56                    "frem_fast" => mir::BinOp::Rem,
57                    _ => bug!(),
58                };
59                let float_finite = |x: &ImmTy<'tcx>| -> InterpResult<'tcx, bool> {
60                    let ty::Float(fty) = x.layout.ty.kind() else {
61                        bug!("float_finite: non-float input type {}", x.layout.ty)
62                    };
63                    interp_ok(match fty {
64                        FloatTy::F16 => x.to_scalar().to_f16()?.is_finite(),
65                        FloatTy::F32 => x.to_scalar().to_f32()?.is_finite(),
66                        FloatTy::F64 => x.to_scalar().to_f64()?.is_finite(),
67                        FloatTy::F128 => x.to_scalar().to_f128()?.is_finite(),
68                    })
69                };
70                match (float_finite(&a)?, float_finite(&b)?) {
71                    (false, false) => throw_ub_format!(
72                        "`{intrinsic_name}` intrinsic called with non-finite value as both parameters",
73                    ),
74                    (false, _) => throw_ub_format!(
75                        "`{intrinsic_name}` intrinsic called with non-finite value as first parameter",
76                    ),
77                    (_, false) => throw_ub_format!(
78                        "`{intrinsic_name}` intrinsic called with non-finite value as second parameter",
79                    ),
80                    _ => {}
81                }
82                let res = this.binary_op(op, &a, &b)?;
83                // This cannot be a NaN so we also don't have to apply any non-determinism.
84                // (Also, `binary_op` already called `generate_nan` if needed.)
85                if !float_finite(&res)? {
86                    throw_ub_format!("`{intrinsic_name}` intrinsic produced non-finite value as result");
87                }
88                // Apply a relative error of 4ULP to simulate non-deterministic precision loss
89                // due to optimizations.
90                let res = math::apply_random_float_error_to_imm(this, res, 4)?;
91                this.write_immediate(*res, dest)?;
92            }
93
94            "float_to_int_unchecked" => {
95                let [val] = check_intrinsic_arg_count(args)?;
96                let val = this.read_immediate(val)?;
97
98                let res = this
99                    .float_to_int_checked(&val, dest.layout, Round::TowardZero)?
100                    .ok_or_else(|| {
101                        err_ub_format!(
102                            "`float_to_int_unchecked` intrinsic called on {val} which cannot be represented in target type `{:?}`",
103                            dest.layout.ty
104                        )
105                    })?;
106
107                this.write_immediate(*res, dest)?;
108            }
109
110            // Operations that need host floats.
111            #[rustfmt::skip]
112            | "sinf32"
113            | "cosf32"
114            | "expf32"
115            | "exp2f32"
116            | "logf32"
117            | "log10f32"
118            | "log2f32"
119            => {
120                let [f] = check_intrinsic_arg_count(args)?;
121                let f = this.read_scalar(f)?.to_f32()?;
122
123                let res = math::fixed_float_value(this, intrinsic_name, &[f]).unwrap_or_else(|| {
124                    // Using host floats (but it's fine, these operations do not have
125                    // guaranteed precision).
126                    let host = f.to_host();
127                    let res = match intrinsic_name {
128                        "sinf32" => host.sin(),
129                        "cosf32" => host.cos(),
130                        "expf32" => host.exp(),
131                        "exp2f32" => host.exp2(),
132                        "logf32" => host.ln(),
133                        "log10f32" => host.log10(),
134                        "log2f32" => host.log2(),
135                        _ => bug!(),
136                    };
137                    let res = res.to_soft();
138
139                    // Apply a relative error of 4ULP to introduce some non-determinism
140                    // simulating imprecise implementations and optimizations.
141                    let res = math::apply_random_float_error_ulp(
142                        this,
143                        res,
144                        4,
145                    );
146
147                    // Clamp the result to the guaranteed range of this function according to the C standard,
148                    // if any.
149                    math::clamp_float_value(intrinsic_name, res)
150                });
151                let res = this.adjust_nan(res, &[f]);
152                this.write_scalar(res, dest)?;
153            }
154
155            #[rustfmt::skip]
156            | "sinf64"
157            | "cosf64"
158            | "expf64"
159            | "exp2f64"
160            | "logf64"
161            | "log10f64"
162            | "log2f64"
163            => {
164                let [f] = check_intrinsic_arg_count(args)?;
165                let f = this.read_scalar(f)?.to_f64()?;
166
167                let res = math::fixed_float_value(this, intrinsic_name, &[f]).unwrap_or_else(|| {
168                    // Using host floats (but it's fine, these operations do not have
169                    // guaranteed precision).
170                    let host = f.to_host();
171                    let res = match intrinsic_name {
172                        "sinf64" => host.sin(),
173                        "cosf64" => host.cos(),
174                        "expf64" => host.exp(),
175                        "exp2f64" => host.exp2(),
176                        "logf64" => host.ln(),
177                        "log10f64" => host.log10(),
178                        "log2f64" => host.log2(),
179                        _ => bug!(),
180                    };
181                    let res = res.to_soft();
182
183                    // Apply a relative error of 4ULP to introduce some non-determinism
184                    // simulating imprecise implementations and optimizations.
185                    let res = math::apply_random_float_error_ulp(
186                        this,
187                        res,
188                        4,
189                    );
190
191                    // Clamp the result to the guaranteed range of this function according to the C standard,
192                    // if any.
193                    math::clamp_float_value(intrinsic_name, res)
194                });
195                let res = this.adjust_nan(res, &[f]);
196                this.write_scalar(res, dest)?;
197            }
198
199            "powf32" => {
200                let [f1, f2] = check_intrinsic_arg_count(args)?;
201                let f1 = this.read_scalar(f1)?.to_f32()?;
202                let f2 = this.read_scalar(f2)?.to_f32()?;
203
204                let res =
205                    math::fixed_float_value(this, intrinsic_name, &[f1, f2]).unwrap_or_else(|| {
206                        // Using host floats (but it's fine, this operation does not have guaranteed precision).
207                        let res = f1.to_host().powf(f2.to_host()).to_soft();
208
209                        // Apply a relative error of 4ULP to introduce some non-determinism
210                        // simulating imprecise implementations and optimizations.
211                        math::apply_random_float_error_ulp(this, res, 4)
212                    });
213                let res = this.adjust_nan(res, &[f1, f2]);
214                this.write_scalar(res, dest)?;
215            }
216            "powf64" => {
217                let [f1, f2] = check_intrinsic_arg_count(args)?;
218                let f1 = this.read_scalar(f1)?.to_f64()?;
219                let f2 = this.read_scalar(f2)?.to_f64()?;
220
221                let res =
222                    math::fixed_float_value(this, intrinsic_name, &[f1, f2]).unwrap_or_else(|| {
223                        // Using host floats (but it's fine, this operation does not have guaranteed precision).
224                        let res = f1.to_host().powf(f2.to_host()).to_soft();
225
226                        // Apply a relative error of 4ULP to introduce some non-determinism
227                        // simulating imprecise implementations and optimizations.
228                        math::apply_random_float_error_ulp(this, res, 4)
229                    });
230                let res = this.adjust_nan(res, &[f1, f2]);
231                this.write_scalar(res, dest)?;
232            }
233
234            "powif32" => {
235                let [f, i] = check_intrinsic_arg_count(args)?;
236                let f = this.read_scalar(f)?.to_f32()?;
237                let i = this.read_scalar(i)?.to_i32()?;
238
239                let res = math::fixed_powi_value(this, f, i).unwrap_or_else(|| {
240                    // Using host floats (but it's fine, this operation does not have guaranteed precision).
241                    let res = f.to_host().powi(i).to_soft();
242
243                    // Apply a relative error of 4ULP to introduce some non-determinism
244                    // simulating imprecise implementations and optimizations.
245                    math::apply_random_float_error_ulp(this, res, 4)
246                });
247                let res = this.adjust_nan(res, &[f]);
248                this.write_scalar(res, dest)?;
249            }
250            "powif64" => {
251                let [f, i] = check_intrinsic_arg_count(args)?;
252                let f = this.read_scalar(f)?.to_f64()?;
253                let i = this.read_scalar(i)?.to_i32()?;
254
255                let res = math::fixed_powi_value(this, f, i).unwrap_or_else(|| {
256                    // Using host floats (but it's fine, this operation does not have guaranteed precision).
257                    let res = f.to_host().powi(i).to_soft();
258
259                    // Apply a relative error of 4ULP to introduce some non-determinism
260                    // simulating imprecise implementations and optimizations.
261                    math::apply_random_float_error_ulp(this, res, 4)
262                });
263                let res = this.adjust_nan(res, &[f]);
264                this.write_scalar(res, dest)?;
265            }
266
267            _ => return interp_ok(EmulateItemResult::NotSupported),
268        }
269
270        interp_ok(EmulateItemResult::NeedsReturn)
271    }
272}