miri/intrinsics/
mod.rs

1#![warn(clippy::arithmetic_side_effects)]
2
3mod atomic;
4mod simd;
5
6use rand::Rng;
7use rustc_abi::Size;
8use rustc_apfloat::{self, Float, Round};
9use rustc_middle::mir;
10use rustc_middle::ty::{self, FloatTy};
11use rustc_span::{Symbol, sym};
12
13use self::atomic::EvalContextExt as _;
14use self::helpers::{ToHost, ToSoft};
15use self::simd::EvalContextExt as _;
16use crate::*;
17
18/// Check that the number of args is what we expect.
19fn check_intrinsic_arg_count<'a, 'tcx, const N: usize>(
20    args: &'a [OpTy<'tcx>],
21) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]>
22where
23    &'a [OpTy<'tcx>; N]: TryFrom<&'a [OpTy<'tcx>]>,
24{
25    if let Ok(ops) = args.try_into() {
26        return interp_ok(ops);
27    }
28    throw_ub_format!(
29        "incorrect number of arguments for intrinsic: got {}, expected {}",
30        args.len(),
31        N
32    )
33}
34
35impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
36pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
37    fn call_intrinsic(
38        &mut self,
39        instance: ty::Instance<'tcx>,
40        args: &[OpTy<'tcx>],
41        dest: &PlaceTy<'tcx>,
42        ret: Option<mir::BasicBlock>,
43        unwind: mir::UnwindAction,
44    ) -> InterpResult<'tcx, Option<ty::Instance<'tcx>>> {
45        let this = self.eval_context_mut();
46
47        // Force use of fallback body, if available.
48        if this.machine.force_intrinsic_fallback
49            && !this.tcx.intrinsic(instance.def_id()).unwrap().must_be_overridden
50        {
51            return interp_ok(Some(ty::Instance {
52                def: ty::InstanceKind::Item(instance.def_id()),
53                args: instance.args,
54            }));
55        }
56
57        // See if the core engine can handle this intrinsic.
58        if this.eval_intrinsic(instance, args, dest, ret)? {
59            return interp_ok(None);
60        }
61        let intrinsic_name = this.tcx.item_name(instance.def_id());
62        let intrinsic_name = intrinsic_name.as_str();
63
64        // FIXME: avoid allocating memory
65        let dest = this.force_allocation(dest)?;
66
67        match this.emulate_intrinsic_by_name(intrinsic_name, instance.args, args, &dest, ret)? {
68            EmulateItemResult::NotSupported => {
69                // We haven't handled the intrinsic, let's see if we can use a fallback body.
70                if this.tcx.intrinsic(instance.def_id()).unwrap().must_be_overridden {
71                    throw_unsup_format!("unimplemented intrinsic: `{intrinsic_name}`")
72                }
73                let intrinsic_fallback_is_spec = Symbol::intern("intrinsic_fallback_is_spec");
74                if this
75                    .tcx
76                    .get_attrs_by_path(instance.def_id(), &[sym::miri, intrinsic_fallback_is_spec])
77                    .next()
78                    .is_none()
79                {
80                    throw_unsup_format!(
81                        "Miri can only use intrinsic fallback bodies that exactly reflect the specification: they fully check for UB and are as non-deterministic as possible. After verifying that `{intrinsic_name}` does so, add the `#[miri::intrinsic_fallback_is_spec]` attribute to it; also ping @rust-lang/miri when you do that"
82                    );
83                }
84                interp_ok(Some(ty::Instance {
85                    def: ty::InstanceKind::Item(instance.def_id()),
86                    args: instance.args,
87                }))
88            }
89            EmulateItemResult::NeedsReturn => {
90                trace!("{:?}", this.dump_place(&dest.clone().into()));
91                this.return_to_block(ret)?;
92                interp_ok(None)
93            }
94            EmulateItemResult::NeedsUnwind => {
95                // Jump to the unwind block to begin unwinding.
96                this.unwind_to_block(unwind)?;
97                interp_ok(None)
98            }
99            EmulateItemResult::AlreadyJumped => interp_ok(None),
100        }
101    }
102
103    /// Emulates a Miri-supported intrinsic (not supported by the core engine).
104    /// Returns `Ok(true)` if the intrinsic was handled.
105    fn emulate_intrinsic_by_name(
106        &mut self,
107        intrinsic_name: &str,
108        generic_args: ty::GenericArgsRef<'tcx>,
109        args: &[OpTy<'tcx>],
110        dest: &MPlaceTy<'tcx>,
111        ret: Option<mir::BasicBlock>,
112    ) -> InterpResult<'tcx, EmulateItemResult> {
113        let this = self.eval_context_mut();
114
115        if let Some(name) = intrinsic_name.strip_prefix("atomic_") {
116            return this.emulate_atomic_intrinsic(name, generic_args, args, dest);
117        }
118        if let Some(name) = intrinsic_name.strip_prefix("simd_") {
119            return this.emulate_simd_intrinsic(name, generic_args, args, dest);
120        }
121
122        match intrinsic_name {
123            // Basic control flow
124            "abort" => {
125                throw_machine_stop!(TerminationInfo::Abort(
126                    "the program aborted execution".to_owned()
127                ));
128            }
129            "catch_unwind" => {
130                let [try_fn, data, catch_fn] = check_intrinsic_arg_count(args)?;
131                this.handle_catch_unwind(try_fn, data, catch_fn, dest, ret)?;
132                // This pushed a stack frame, don't jump to `ret`.
133                return interp_ok(EmulateItemResult::AlreadyJumped);
134            }
135
136            // Raw memory accesses
137            "volatile_load" => {
138                let [place] = check_intrinsic_arg_count(args)?;
139                let place = this.deref_pointer(place)?;
140                this.copy_op(&place, dest)?;
141            }
142            "volatile_store" => {
143                let [place, dest] = check_intrinsic_arg_count(args)?;
144                let place = this.deref_pointer(place)?;
145                this.copy_op(dest, &place)?;
146            }
147
148            "volatile_set_memory" => {
149                let [ptr, val_byte, count] = check_intrinsic_arg_count(args)?;
150                this.write_bytes_intrinsic(ptr, val_byte, count, "volatile_set_memory")?;
151            }
152
153            // Memory model / provenance manipulation
154            "ptr_mask" => {
155                let [ptr, mask] = check_intrinsic_arg_count(args)?;
156
157                let ptr = this.read_pointer(ptr)?;
158                let mask = this.read_target_usize(mask)?;
159
160                let masked_addr = Size::from_bytes(ptr.addr().bytes() & mask);
161
162                this.write_pointer(Pointer::new(ptr.provenance, masked_addr), dest)?;
163            }
164
165            // We want to return either `true` or `false` at random, or else something like
166            // ```
167            // if !is_val_statically_known(0) { unreachable_unchecked(); }
168            // ```
169            // Would not be considered UB, or the other way around (`is_val_statically_known(0)`).
170            "is_val_statically_known" => {
171                let [_arg] = check_intrinsic_arg_count(args)?;
172                // FIXME: should we check for validity here? It's tricky because we do not have a
173                // place. Codegen does not seem to set any attributes like `noundef` for intrinsic
174                // calls, so we don't *have* to do anything.
175                let branch: bool = this.machine.rng.get_mut().random();
176                this.write_scalar(Scalar::from_bool(branch), dest)?;
177            }
178
179            "sqrtf32" => {
180                let [f] = check_intrinsic_arg_count(args)?;
181                let f = this.read_scalar(f)?.to_f32()?;
182                // Sqrt is specified to be fully precise.
183                let res = math::sqrt(f);
184                let res = this.adjust_nan(res, &[f]);
185                this.write_scalar(res, dest)?;
186            }
187            "sqrtf64" => {
188                let [f] = check_intrinsic_arg_count(args)?;
189                let f = this.read_scalar(f)?.to_f64()?;
190                // Sqrt is specified to be fully precise.
191                let res = math::sqrt(f);
192                let res = this.adjust_nan(res, &[f]);
193                this.write_scalar(res, dest)?;
194            }
195
196            #[rustfmt::skip]
197            | "sinf32"
198            | "cosf32"
199            | "expf32"
200            | "exp2f32"
201            | "logf32"
202            | "log10f32"
203            | "log2f32"
204            => {
205                let [f] = check_intrinsic_arg_count(args)?;
206                let f = this.read_scalar(f)?.to_f32()?;
207
208                let res = math::fixed_float_value(this, intrinsic_name, &[f]).unwrap_or_else(|| {
209                    // Using host floats (but it's fine, these operations do not have
210                    // guaranteed precision).
211                    let host = f.to_host();
212                    let res = match intrinsic_name {
213                        "sinf32" => host.sin(),
214                        "cosf32" => host.cos(),
215                        "expf32" => host.exp(),
216                        "exp2f32" => host.exp2(),
217                        "logf32" => host.ln(),
218                        "log10f32" => host.log10(),
219                        "log2f32" => host.log2(),
220                        _ => bug!(),
221                    };
222                    let res = res.to_soft();
223
224                    // Apply a relative error of 4ULP to introduce some non-determinism
225                    // simulating imprecise implementations and optimizations.
226                    let res = math::apply_random_float_error_ulp(
227                        this,
228                        res,
229                        4,
230                    );
231
232                    // Clamp the result to the guaranteed range of this function according to the C standard,
233                    // if any.
234                    math::clamp_float_value(intrinsic_name, res)
235                });
236                let res = this.adjust_nan(res, &[f]);
237                this.write_scalar(res, dest)?;
238            }
239
240            #[rustfmt::skip]
241            | "sinf64"
242            | "cosf64"
243            | "expf64"
244            | "exp2f64"
245            | "logf64"
246            | "log10f64"
247            | "log2f64"
248            => {
249                let [f] = check_intrinsic_arg_count(args)?;
250                let f = this.read_scalar(f)?.to_f64()?;
251
252                let res = math::fixed_float_value(this, intrinsic_name, &[f]).unwrap_or_else(|| {
253                    // Using host floats (but it's fine, these operations do not have
254                    // guaranteed precision).
255                    let host = f.to_host();
256                    let res = match intrinsic_name {
257                        "sinf64" => host.sin(),
258                        "cosf64" => host.cos(),
259                        "expf64" => host.exp(),
260                        "exp2f64" => host.exp2(),
261                        "logf64" => host.ln(),
262                        "log10f64" => host.log10(),
263                        "log2f64" => host.log2(),
264                        _ => bug!(),
265                    };
266                    let res = res.to_soft();
267
268                    // Apply a relative error of 4ULP to introduce some non-determinism
269                    // simulating imprecise implementations and optimizations.
270                    let res = math::apply_random_float_error_ulp(
271                        this,
272                        res,
273                        4,
274                    );
275
276                    // Clamp the result to the guaranteed range of this function according to the C standard,
277                    // if any.
278                    math::clamp_float_value(intrinsic_name, res)
279                });
280                let res = this.adjust_nan(res, &[f]);
281                this.write_scalar(res, dest)?;
282            }
283
284            "fmaf32" => {
285                let [a, b, c] = check_intrinsic_arg_count(args)?;
286                let a = this.read_scalar(a)?.to_f32()?;
287                let b = this.read_scalar(b)?.to_f32()?;
288                let c = this.read_scalar(c)?.to_f32()?;
289                let res = a.mul_add(b, c).value;
290                let res = this.adjust_nan(res, &[a, b, c]);
291                this.write_scalar(res, dest)?;
292            }
293            "fmaf64" => {
294                let [a, b, c] = check_intrinsic_arg_count(args)?;
295                let a = this.read_scalar(a)?.to_f64()?;
296                let b = this.read_scalar(b)?.to_f64()?;
297                let c = this.read_scalar(c)?.to_f64()?;
298                let res = a.mul_add(b, c).value;
299                let res = this.adjust_nan(res, &[a, b, c]);
300                this.write_scalar(res, dest)?;
301            }
302
303            "fmuladdf32" => {
304                let [a, b, c] = check_intrinsic_arg_count(args)?;
305                let a = this.read_scalar(a)?.to_f32()?;
306                let b = this.read_scalar(b)?.to_f32()?;
307                let c = this.read_scalar(c)?.to_f32()?;
308                let fuse: bool = this.machine.float_nondet && this.machine.rng.get_mut().random();
309                let res = if fuse { a.mul_add(b, c).value } else { ((a * b).value + c).value };
310                let res = this.adjust_nan(res, &[a, b, c]);
311                this.write_scalar(res, dest)?;
312            }
313            "fmuladdf64" => {
314                let [a, b, c] = check_intrinsic_arg_count(args)?;
315                let a = this.read_scalar(a)?.to_f64()?;
316                let b = this.read_scalar(b)?.to_f64()?;
317                let c = this.read_scalar(c)?.to_f64()?;
318                let fuse: bool = this.machine.float_nondet && this.machine.rng.get_mut().random();
319                let res = if fuse { a.mul_add(b, c).value } else { ((a * b).value + c).value };
320                let res = this.adjust_nan(res, &[a, b, c]);
321                this.write_scalar(res, dest)?;
322            }
323
324            "powf32" => {
325                let [f1, f2] = check_intrinsic_arg_count(args)?;
326                let f1 = this.read_scalar(f1)?.to_f32()?;
327                let f2 = this.read_scalar(f2)?.to_f32()?;
328
329                let res =
330                    math::fixed_float_value(this, intrinsic_name, &[f1, f2]).unwrap_or_else(|| {
331                        // Using host floats (but it's fine, this operation does not have guaranteed precision).
332                        let res = f1.to_host().powf(f2.to_host()).to_soft();
333
334                        // Apply a relative error of 4ULP to introduce some non-determinism
335                        // simulating imprecise implementations and optimizations.
336                        math::apply_random_float_error_ulp(this, res, 4)
337                    });
338                let res = this.adjust_nan(res, &[f1, f2]);
339                this.write_scalar(res, dest)?;
340            }
341            "powf64" => {
342                let [f1, f2] = check_intrinsic_arg_count(args)?;
343                let f1 = this.read_scalar(f1)?.to_f64()?;
344                let f2 = this.read_scalar(f2)?.to_f64()?;
345
346                let res =
347                    math::fixed_float_value(this, intrinsic_name, &[f1, f2]).unwrap_or_else(|| {
348                        // Using host floats (but it's fine, this operation does not have guaranteed precision).
349                        let res = f1.to_host().powf(f2.to_host()).to_soft();
350
351                        // Apply a relative error of 4ULP to introduce some non-determinism
352                        // simulating imprecise implementations and optimizations.
353                        math::apply_random_float_error_ulp(this, res, 4)
354                    });
355                let res = this.adjust_nan(res, &[f1, f2]);
356                this.write_scalar(res, dest)?;
357            }
358
359            "powif32" => {
360                let [f, i] = check_intrinsic_arg_count(args)?;
361                let f = this.read_scalar(f)?.to_f32()?;
362                let i = this.read_scalar(i)?.to_i32()?;
363
364                let res = math::fixed_powi_value(this, f, i).unwrap_or_else(|| {
365                    // Using host floats (but it's fine, this operation does not have guaranteed precision).
366                    let res = f.to_host().powi(i).to_soft();
367
368                    // Apply a relative error of 4ULP to introduce some non-determinism
369                    // simulating imprecise implementations and optimizations.
370                    math::apply_random_float_error_ulp(this, res, 4)
371                });
372                let res = this.adjust_nan(res, &[f]);
373                this.write_scalar(res, dest)?;
374            }
375            "powif64" => {
376                let [f, i] = check_intrinsic_arg_count(args)?;
377                let f = this.read_scalar(f)?.to_f64()?;
378                let i = this.read_scalar(i)?.to_i32()?;
379
380                let res = math::fixed_powi_value(this, f, i).unwrap_or_else(|| {
381                    // Using host floats (but it's fine, this operation does not have guaranteed precision).
382                    let res = f.to_host().powi(i).to_soft();
383
384                    // Apply a relative error of 4ULP to introduce some non-determinism
385                    // simulating imprecise implementations and optimizations.
386                    math::apply_random_float_error_ulp(this, res, 4)
387                });
388                let res = this.adjust_nan(res, &[f]);
389                this.write_scalar(res, dest)?;
390            }
391
392            #[rustfmt::skip]
393            | "fadd_fast"
394            | "fsub_fast"
395            | "fmul_fast"
396            | "fdiv_fast"
397            | "frem_fast"
398            => {
399                let [a, b] = check_intrinsic_arg_count(args)?;
400                let a = this.read_immediate(a)?;
401                let b = this.read_immediate(b)?;
402                let op = match intrinsic_name {
403                    "fadd_fast" => mir::BinOp::Add,
404                    "fsub_fast" => mir::BinOp::Sub,
405                    "fmul_fast" => mir::BinOp::Mul,
406                    "fdiv_fast" => mir::BinOp::Div,
407                    "frem_fast" => mir::BinOp::Rem,
408                    _ => bug!(),
409                };
410                let float_finite = |x: &ImmTy<'tcx>| -> InterpResult<'tcx, bool> {
411                    let ty::Float(fty) = x.layout.ty.kind() else {
412                        bug!("float_finite: non-float input type {}", x.layout.ty)
413                    };
414                    interp_ok(match fty {
415                        FloatTy::F16 => x.to_scalar().to_f16()?.is_finite(),
416                        FloatTy::F32 => x.to_scalar().to_f32()?.is_finite(),
417                        FloatTy::F64 => x.to_scalar().to_f64()?.is_finite(),
418                        FloatTy::F128 => x.to_scalar().to_f128()?.is_finite(),
419                    })
420                };
421                match (float_finite(&a)?, float_finite(&b)?) {
422                    (false, false) => throw_ub_format!(
423                        "`{intrinsic_name}` intrinsic called with non-finite value as both parameters",
424                    ),
425                    (false, _) => throw_ub_format!(
426                        "`{intrinsic_name}` intrinsic called with non-finite value as first parameter",
427                    ),
428                    (_, false) => throw_ub_format!(
429                        "`{intrinsic_name}` intrinsic called with non-finite value as second parameter",
430                    ),
431                    _ => {}
432                }
433                let res = this.binary_op(op, &a, &b)?;
434                // This cannot be a NaN so we also don't have to apply any non-determinism.
435                // (Also, `binary_op` already called `generate_nan` if needed.)
436                if !float_finite(&res)? {
437                    throw_ub_format!("`{intrinsic_name}` intrinsic produced non-finite value as result");
438                }
439                // Apply a relative error of 4ULP to simulate non-deterministic precision loss
440                // due to optimizations.
441                let res = math::apply_random_float_error_to_imm(this, res, 4)?;
442                this.write_immediate(*res, dest)?;
443            }
444
445            "float_to_int_unchecked" => {
446                let [val] = check_intrinsic_arg_count(args)?;
447                let val = this.read_immediate(val)?;
448
449                let res = this
450                    .float_to_int_checked(&val, dest.layout, Round::TowardZero)?
451                    .ok_or_else(|| {
452                        err_ub_format!(
453                            "`float_to_int_unchecked` intrinsic called on {val} which cannot be represented in target type `{:?}`",
454                            dest.layout.ty
455                        )
456                    })?;
457
458                this.write_immediate(*res, dest)?;
459            }
460
461            // Other
462            "breakpoint" => {
463                let [] = check_intrinsic_arg_count(args)?;
464                // normally this would raise a SIGTRAP, which aborts if no debugger is connected
465                throw_machine_stop!(TerminationInfo::Abort(format!("trace/breakpoint trap")))
466            }
467
468            "assert_inhabited" | "assert_zero_valid" | "assert_mem_uninitialized_valid" => {
469                // Make these a NOP, so we get the better Miri-native error messages.
470            }
471
472            _ => return interp_ok(EmulateItemResult::NotSupported),
473        }
474
475        interp_ok(EmulateItemResult::NeedsReturn)
476    }
477}