miri/intrinsics/
mod.rs

1#![warn(clippy::arithmetic_side_effects)]
2
3mod atomic;
4mod math;
5mod simd;
6
7pub use self::atomic::AtomicRmwOp;
8
9#[rustfmt::skip] // prevent `use` reordering
10use rand::Rng;
11use rustc_abi::Size;
12use rustc_middle::{mir, ty};
13use rustc_span::{Symbol, sym};
14
15use self::atomic::EvalContextExt as _;
16use self::math::EvalContextExt as _;
17use self::simd::EvalContextExt as _;
18use crate::*;
19
20/// Check that the number of args is what we expect.
21fn check_intrinsic_arg_count<'a, 'tcx, const N: usize>(
22    args: &'a [OpTy<'tcx>],
23) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]>
24where
25    &'a [OpTy<'tcx>; N]: TryFrom<&'a [OpTy<'tcx>]>,
26{
27    if let Ok(ops) = args.try_into() {
28        return interp_ok(ops);
29    }
30    throw_ub_format!(
31        "incorrect number of arguments for intrinsic: got {}, expected {}",
32        args.len(),
33        N
34    )
35}
36
37impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
38pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
39    fn call_intrinsic(
40        &mut self,
41        instance: ty::Instance<'tcx>,
42        args: &[OpTy<'tcx>],
43        dest: &PlaceTy<'tcx>,
44        ret: Option<mir::BasicBlock>,
45        unwind: mir::UnwindAction,
46    ) -> InterpResult<'tcx, Option<ty::Instance<'tcx>>> {
47        let this = self.eval_context_mut();
48
49        // Force use of fallback body, if available.
50        if this.machine.force_intrinsic_fallback
51            && !this.tcx.intrinsic(instance.def_id()).unwrap().must_be_overridden
52        {
53            return interp_ok(Some(ty::Instance {
54                def: ty::InstanceKind::Item(instance.def_id()),
55                args: instance.args,
56            }));
57        }
58
59        // See if the core engine can handle this intrinsic.
60        if this.eval_intrinsic(instance, args, dest, ret)? {
61            return interp_ok(None);
62        }
63        let intrinsic_name = this.tcx.item_name(instance.def_id());
64        let intrinsic_name = intrinsic_name.as_str();
65
66        // FIXME: avoid allocating memory
67        let dest = this.force_allocation(dest)?;
68
69        match this.emulate_intrinsic_by_name(intrinsic_name, instance.args, args, &dest, ret)? {
70            EmulateItemResult::NotSupported => {
71                // We haven't handled the intrinsic, let's see if we can use a fallback body.
72                if this.tcx.intrinsic(instance.def_id()).unwrap().must_be_overridden {
73                    throw_unsup_format!("unimplemented intrinsic: `{intrinsic_name}`")
74                }
75                let intrinsic_fallback_is_spec = Symbol::intern("intrinsic_fallback_is_spec");
76                if this
77                    .tcx
78                    .get_attrs_by_path(instance.def_id(), &[sym::miri, intrinsic_fallback_is_spec])
79                    .next()
80                    .is_none()
81                {
82                    throw_unsup_format!(
83                        "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"
84                    );
85                }
86                interp_ok(Some(ty::Instance {
87                    def: ty::InstanceKind::Item(instance.def_id()),
88                    args: instance.args,
89                }))
90            }
91            EmulateItemResult::NeedsReturn => {
92                trace!("{:?}", this.dump_place(&dest.clone().into()));
93                this.return_to_block(ret)?;
94                interp_ok(None)
95            }
96            EmulateItemResult::NeedsUnwind => {
97                // Jump to the unwind block to begin unwinding.
98                this.unwind_to_block(unwind)?;
99                interp_ok(None)
100            }
101            EmulateItemResult::AlreadyJumped => interp_ok(None),
102        }
103    }
104
105    /// Emulates a Miri-supported intrinsic (not supported by the core engine).
106    /// Returns `Ok(true)` if the intrinsic was handled.
107    fn emulate_intrinsic_by_name(
108        &mut self,
109        intrinsic_name: &str,
110        generic_args: ty::GenericArgsRef<'tcx>,
111        args: &[OpTy<'tcx>],
112        dest: &MPlaceTy<'tcx>,
113        ret: Option<mir::BasicBlock>,
114    ) -> InterpResult<'tcx, EmulateItemResult> {
115        let this = self.eval_context_mut();
116
117        if let Some(name) = intrinsic_name.strip_prefix("atomic_") {
118            return this.emulate_atomic_intrinsic(name, generic_args, args, dest);
119        }
120        if let Some(name) = intrinsic_name.strip_prefix("simd_") {
121            return this.emulate_simd_intrinsic(name, generic_args, args, dest);
122        }
123
124        match intrinsic_name {
125            // Basic control flow
126            "abort" => {
127                throw_machine_stop!(TerminationInfo::Abort(
128                    "the program aborted execution".to_owned()
129                ));
130            }
131            "catch_unwind" => {
132                let [try_fn, data, catch_fn] = check_intrinsic_arg_count(args)?;
133                this.handle_catch_unwind(try_fn, data, catch_fn, dest, ret)?;
134                // This pushed a stack frame, don't jump to `ret`.
135                return interp_ok(EmulateItemResult::AlreadyJumped);
136            }
137
138            // Raw memory accesses
139            "volatile_load" => {
140                let [place] = check_intrinsic_arg_count(args)?;
141                let place = this.deref_pointer(place)?;
142                this.copy_op(&place, dest)?;
143            }
144            "volatile_store" => {
145                let [place, dest] = check_intrinsic_arg_count(args)?;
146                let place = this.deref_pointer(place)?;
147                this.copy_op(dest, &place)?;
148            }
149
150            "volatile_set_memory" => {
151                let [ptr, val_byte, count] = check_intrinsic_arg_count(args)?;
152                this.write_bytes_intrinsic(ptr, val_byte, count, "volatile_set_memory")?;
153            }
154
155            // Memory model / provenance manipulation
156            "ptr_mask" => {
157                let [ptr, mask] = check_intrinsic_arg_count(args)?;
158
159                let ptr = this.read_pointer(ptr)?;
160                let mask = this.read_target_usize(mask)?;
161
162                let masked_addr = Size::from_bytes(ptr.addr().bytes() & mask);
163
164                this.write_pointer(Pointer::new(ptr.provenance, masked_addr), dest)?;
165            }
166
167            // We want to return either `true` or `false` at random, or else something like
168            // ```
169            // if !is_val_statically_known(0) { unreachable_unchecked(); }
170            // ```
171            // Would not be considered UB, or the other way around (`is_val_statically_known(0)`).
172            "is_val_statically_known" => {
173                let [_arg] = check_intrinsic_arg_count(args)?;
174                // FIXME: should we check for validity here? It's tricky because we do not have a
175                // place. Codegen does not seem to set any attributes like `noundef` for intrinsic
176                // calls, so we don't *have* to do anything.
177                let branch: bool = this.machine.rng.get_mut().random();
178                this.write_scalar(Scalar::from_bool(branch), dest)?;
179            }
180
181            // Other
182            "breakpoint" => {
183                let [] = check_intrinsic_arg_count(args)?;
184                // normally this would raise a SIGTRAP, which aborts if no debugger is connected
185                throw_machine_stop!(TerminationInfo::Abort(format!("trace/breakpoint trap")))
186            }
187
188            "assert_inhabited" | "assert_zero_valid" | "assert_mem_uninitialized_valid" => {
189                // Make these a NOP, so we get the better Miri-native error messages.
190            }
191
192            _ => return this.emulate_math_intrinsic(intrinsic_name, generic_args, args, dest),
193        }
194
195        interp_ok(EmulateItemResult::NeedsReturn)
196    }
197}