1#![warn(clippy::arithmetic_side_effects)]
2
3mod aarch64;
4mod loongarch;
5mod math;
6mod simd;
7mod x86;
8
9#[rustfmt::skip] use rand::RngExt;
11use rustc_abi::{Endian, Size};
12use rustc_middle::{mir, ty};
13use rustc_span::sym;
14use rustc_target::spec::Arch;
15
16use self::math::EvalContextExt as _;
17use self::simd::EvalContextExt as _;
18use crate::*;
19
20fn 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 ecx = self.eval_context_mut();
48
49 if ecx.eval_intrinsic(instance, args, dest, ret)? {
51 return interp_ok(None);
52 }
53 let intrinsic_name = ecx.tcx.item_name(instance.def_id());
54 let intrinsic_name = intrinsic_name.as_str();
55
56 let res = ecx.emulate_intrinsic_by_name(intrinsic_name, instance.args, args, dest, ret)?;
57 res.jump_to_next_block(ecx, dest, ret, Some(unwind), |ecx| {
58 if ecx.tcx.intrinsic(instance.def_id()).unwrap().must_be_overridden {
60 throw_unsup_format!("unimplemented intrinsic: `{intrinsic_name}`")
61 }
62 let intrinsic_fallback_is_spec = ecx
63 .tcx
64 .tool_attrs(
65 instance.def_id(),
66 &[sym::miri, sym::intrinsic_fallback_is_spec],
67 )
68 .next()
69 .is_some();
70 if !intrinsic_fallback_is_spec {
71 throw_unsup_format!(
72 "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"
73 );
74 }
75 interp_ok(Some(ty::Instance {
76 def: ty::InstanceKind::Item(instance.def_id()),
77 args: instance.args,
78 }))
79 })
80 }
81
82 fn emulate_intrinsic_by_name(
85 &mut self,
86 intrinsic_name: &str,
87 generic_args: ty::GenericArgs<'tcx>,
88 args: &[OpTy<'tcx>],
89 dest: &PlaceTy<'tcx>,
90 ret: Option<mir::BasicBlock>,
91 ) -> InterpResult<'tcx, EmulateItemResult> {
92 let ecx = self.eval_context_mut();
93
94 if let Some(name) = intrinsic_name.strip_prefix("simd_") {
95 return ecx.emulate_simd_intrinsic(name, args, dest);
96 }
97
98 match intrinsic_name {
99 "abort_immediate" => {
101 throw_machine_stop!(TerminationInfo::Abort(
103 "the program aborted execution".to_owned()
104 ));
105 }
106 "catch_unwind" => {
107 let [try_fn, data, catch_fn] = check_intrinsic_arg_count(args)?;
108 ecx.handle_catch_unwind(try_fn, data, catch_fn, dest, ret)?;
109 return interp_ok(EmulateItemResult::AlreadyJumped);
111 }
112
113 "ptr_mask" => {
115 let [ptr, mask] = check_intrinsic_arg_count(args)?;
116
117 let ptr = ecx.read_pointer(ptr)?;
118 let mask = ecx.read_target_usize(mask)?;
119
120 let masked_addr = Size::from_bytes(ptr.addr().bytes() & mask);
121
122 ecx.write_pointer(Pointer::new(ptr.provenance, masked_addr), dest)?;
123 }
124
125 "is_val_statically_known" => {
131 let [_arg] = check_intrinsic_arg_count(args)?;
132 let branch: bool = ecx.machine.rng.get_mut().random();
136 ecx.write_scalar(Scalar::from_bool(branch), dest)?;
137 }
138
139 "breakpoint" => {
141 let [] = check_intrinsic_arg_count(args)?;
142 throw_machine_stop!(TerminationInfo::Abort(format!("trace/breakpoint trap")))
144 }
145
146 "assert_inhabited" | "assert_zero_valid" | "assert_mem_uninitialized_valid" => {
147 }
149
150 _ => return ecx.emulate_math_intrinsic(intrinsic_name, generic_args, args, dest),
151 }
152
153 interp_ok(EmulateItemResult::NeedsReturn)
154 }
155
156 fn call_llvm_intrinsic(
157 &mut self,
158 instance: ty::Instance<'tcx>,
159 args: &[OpTy<'tcx>],
160 dest: &PlaceTy<'tcx>,
161 ret: Option<mir::BasicBlock>,
162 ) -> InterpResult<'tcx> {
163 let ecx = self.eval_context_mut();
164
165 let link_name = ecx.tcx.codegen_fn_attrs(instance.def_id()).symbol_name.unwrap();
166
167 let dest = ecx.force_allocation(dest)?;
169
170 let res = 'handled: {
171 match link_name.as_str() {
172 "llvm.prefetch.p0" => {
174 let [p, rw, loc, ty] = ecx.check_shim_sig_llvm_intrinsic(link_name, args)?;
175
176 let _ = ecx.read_pointer(p)?;
177 let rw = ecx.read_scalar(rw)?.to_i32()?;
178 let loc = ecx.read_scalar(loc)?.to_i32()?;
179 let ty = ecx.read_scalar(ty)?.to_i32()?;
180
181 if ty == 1 {
182 if !matches!(rw, 0 | 1) {
186 throw_unsup_format!(
187 "invalid `rw` value passed to `llvm.prefetch`: {rw}"
188 );
189 }
190 if !matches!(loc, 0..=3) {
191 throw_unsup_format!(
192 "invalid `loc` value passed to `llvm.prefetch`: {loc}"
193 );
194 }
195 } else {
196 throw_unsup_format!("unsupported `llvm.prefetch` type argument: {ty}");
197 }
198 }
199 name if name.starts_with("llvm.ctpop.v")
202 && ecx.tcx.sess.target.endian == Endian::Little =>
203 {
204 let [op] = ecx.check_shim_sig_llvm_intrinsic(link_name, args)?;
205
206 let (op, op_len) = ecx.project_to_simd(op)?;
207 let (dest, dest_len) = ecx.project_to_simd(&dest)?;
208
209 assert_eq!(dest_len, op_len);
210
211 for i in 0..dest_len {
212 let op = ecx.read_immediate(&ecx.project_index(&op, i)?)?;
213 let res = op.to_scalar().to_uint(op.layout.size)?.count_ones();
216
217 ecx.write_scalar(
218 Scalar::from_uint(res, op.layout.size),
219 &ecx.project_index(&dest, i)?,
220 )?;
221 }
222 }
223
224 name if name.starts_with("llvm.x86.")
226 && matches!(ecx.tcx.sess.target.arch, Arch::X86 | Arch::X86_64)
227 && ecx.tcx.sess.target.endian == Endian::Little =>
228 break 'handled x86::EvalContextExt::emulate_x86_intrinsic(
229 ecx, link_name, args, &dest,
230 )?,
231 name if name.starts_with("llvm.aarch64.")
232 && ecx.tcx.sess.target.arch == Arch::AArch64
233 && ecx.tcx.sess.target.endian == Endian::Little =>
234 break 'handled aarch64::EvalContextExt::emulate_aarch64_intrinsic(
235 ecx, link_name, args, &dest,
236 )?,
237 name if name.starts_with("llvm.loongarch.")
238 && matches!(
239 ecx.tcx.sess.target.arch,
240 Arch::LoongArch32 | Arch::LoongArch64
241 )
242 && ecx.tcx.sess.target.endian == Endian::Little =>
243 break 'handled loongarch::EvalContextExt::emulate_loongarch_intrinsic(
244 ecx, link_name, args, &dest,
245 )?,
246 _ => break 'handled EmulateItemResult::NotSupported,
247 }
248 EmulateItemResult::NeedsReturn
249 };
250
251 res.jump_to_next_block(ecx, &dest.clone().into(), ret, None, |ecx| {
253 throw_machine_stop!(TerminationInfo::UnsupportedForeignItem(format!(
254 "can't call LLVM intrinsic `{link_name}` on architecture `{arch}`",
255 arch = ecx.tcx.sess.target.arch,
256 )));
257 })
258 }
259}