1#![warn(clippy::arithmetic_side_effects)]
2
3mod atomic;
4mod simd;
5
6use rand::Rng;
7use rustc_abi::Size;
8use rustc_apfloat::{Float, Round};
9use rustc_middle::mir;
10use rustc_middle::ty::{self, FloatTy, ScalarInt};
11use rustc_span::{Symbol, sym};
12
13use self::atomic::EvalContextExt as _;
14use self::helpers::{ToHost, ToSoft, check_intrinsic_arg_count};
15use self::simd::EvalContextExt as _;
16use crate::math::apply_random_float_error_ulp;
17use crate::*;
18
19impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
20pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
21 fn call_intrinsic(
22 &mut self,
23 instance: ty::Instance<'tcx>,
24 args: &[OpTy<'tcx>],
25 dest: &MPlaceTy<'tcx>,
26 ret: Option<mir::BasicBlock>,
27 unwind: mir::UnwindAction,
28 ) -> InterpResult<'tcx, Option<ty::Instance<'tcx>>> {
29 let this = self.eval_context_mut();
30
31 if this.eval_intrinsic(instance, args, dest, ret)? {
33 return interp_ok(None);
34 }
35 let intrinsic_name = this.tcx.item_name(instance.def_id());
36 let intrinsic_name = intrinsic_name.as_str();
37
38 match this.emulate_intrinsic_by_name(intrinsic_name, instance.args, args, dest, ret)? {
39 EmulateItemResult::NotSupported => {
40 if this.tcx.intrinsic(instance.def_id()).unwrap().must_be_overridden {
42 throw_unsup_format!("unimplemented intrinsic: `{intrinsic_name}`")
43 }
44 let intrinsic_fallback_is_spec = Symbol::intern("intrinsic_fallback_is_spec");
45 if this
46 .tcx
47 .get_attrs_by_path(instance.def_id(), &[sym::miri, intrinsic_fallback_is_spec])
48 .next()
49 .is_none()
50 {
51 throw_unsup_format!(
52 "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"
53 );
54 }
55 interp_ok(Some(ty::Instance {
56 def: ty::InstanceKind::Item(instance.def_id()),
57 args: instance.args,
58 }))
59 }
60 EmulateItemResult::NeedsReturn => {
61 trace!("{:?}", this.dump_place(&dest.clone().into()));
62 this.return_to_block(ret)?;
63 interp_ok(None)
64 }
65 EmulateItemResult::NeedsUnwind => {
66 this.unwind_to_block(unwind)?;
68 interp_ok(None)
69 }
70 EmulateItemResult::AlreadyJumped => interp_ok(None),
71 }
72 }
73
74 fn emulate_intrinsic_by_name(
77 &mut self,
78 intrinsic_name: &str,
79 generic_args: ty::GenericArgsRef<'tcx>,
80 args: &[OpTy<'tcx>],
81 dest: &MPlaceTy<'tcx>,
82 ret: Option<mir::BasicBlock>,
83 ) -> InterpResult<'tcx, EmulateItemResult> {
84 let this = self.eval_context_mut();
85
86 if let Some(name) = intrinsic_name.strip_prefix("atomic_") {
87 return this.emulate_atomic_intrinsic(name, args, dest);
88 }
89 if let Some(name) = intrinsic_name.strip_prefix("simd_") {
90 return this.emulate_simd_intrinsic(name, generic_args, args, dest);
91 }
92
93 match intrinsic_name {
94 "abort" => {
96 throw_machine_stop!(TerminationInfo::Abort(
97 "the program aborted execution".to_owned()
98 ));
99 }
100 "catch_unwind" => {
101 this.handle_catch_unwind(args, dest, ret)?;
102 return interp_ok(EmulateItemResult::AlreadyJumped);
104 }
105
106 "volatile_load" => {
108 let [place] = check_intrinsic_arg_count(args)?;
109 let place = this.deref_pointer(place)?;
110 this.copy_op(&place, dest)?;
111 }
112 "volatile_store" => {
113 let [place, dest] = check_intrinsic_arg_count(args)?;
114 let place = this.deref_pointer(place)?;
115 this.copy_op(dest, &place)?;
116 }
117
118 "volatile_set_memory" => {
119 let [ptr, val_byte, count] = check_intrinsic_arg_count(args)?;
120 this.write_bytes_intrinsic(ptr, val_byte, count, "volatile_set_memory")?;
121 }
122
123 "ptr_mask" => {
125 let [ptr, mask] = check_intrinsic_arg_count(args)?;
126
127 let ptr = this.read_pointer(ptr)?;
128 let mask = this.read_target_usize(mask)?;
129
130 let masked_addr = Size::from_bytes(ptr.addr().bytes() & mask);
131
132 this.write_pointer(Pointer::new(ptr.provenance, masked_addr), dest)?;
133 }
134
135 "is_val_statically_known" => {
141 let [_arg] = check_intrinsic_arg_count(args)?;
142 let branch: bool = this.machine.rng.get_mut().random();
146 this.write_scalar(Scalar::from_bool(branch), dest)?;
147 }
148
149 "floorf16" | "ceilf16" | "truncf16" | "roundf16" | "round_ties_even_f16" => {
150 let [f] = check_intrinsic_arg_count(args)?;
151 let f = this.read_scalar(f)?.to_f16()?;
152 let mode = match intrinsic_name {
153 "floorf16" => Round::TowardNegative,
154 "ceilf16" => Round::TowardPositive,
155 "truncf16" => Round::TowardZero,
156 "roundf16" => Round::NearestTiesToAway,
157 "round_ties_even_f16" => Round::NearestTiesToEven,
158 _ => bug!(),
159 };
160 let res = f.round_to_integral(mode).value;
161 let res = this.adjust_nan(res, &[f]);
162 this.write_scalar(res, dest)?;
163 }
164 "floorf32" | "ceilf32" | "truncf32" | "roundf32" | "round_ties_even_f32" => {
165 let [f] = check_intrinsic_arg_count(args)?;
166 let f = this.read_scalar(f)?.to_f32()?;
167 let mode = match intrinsic_name {
168 "floorf32" => Round::TowardNegative,
169 "ceilf32" => Round::TowardPositive,
170 "truncf32" => Round::TowardZero,
171 "roundf32" => Round::NearestTiesToAway,
172 "round_ties_even_f32" => Round::NearestTiesToEven,
173 _ => bug!(),
174 };
175 let res = f.round_to_integral(mode).value;
176 let res = this.adjust_nan(res, &[f]);
177 this.write_scalar(res, dest)?;
178 }
179 "floorf64" | "ceilf64" | "truncf64" | "roundf64" | "round_ties_even_f64" => {
180 let [f] = check_intrinsic_arg_count(args)?;
181 let f = this.read_scalar(f)?.to_f64()?;
182 let mode = match intrinsic_name {
183 "floorf64" => Round::TowardNegative,
184 "ceilf64" => Round::TowardPositive,
185 "truncf64" => Round::TowardZero,
186 "roundf64" => Round::NearestTiesToAway,
187 "round_ties_even_f64" => Round::NearestTiesToEven,
188 _ => bug!(),
189 };
190 let res = f.round_to_integral(mode).value;
191 let res = this.adjust_nan(res, &[f]);
192 this.write_scalar(res, dest)?;
193 }
194 "floorf128" | "ceilf128" | "truncf128" | "roundf128" | "round_ties_even_f128" => {
195 let [f] = check_intrinsic_arg_count(args)?;
196 let f = this.read_scalar(f)?.to_f128()?;
197 let mode = match intrinsic_name {
198 "floorf128" => Round::TowardNegative,
199 "ceilf128" => Round::TowardPositive,
200 "truncf128" => Round::TowardZero,
201 "roundf128" => Round::NearestTiesToAway,
202 "round_ties_even_f128" => Round::NearestTiesToEven,
203 _ => bug!(),
204 };
205 let res = f.round_to_integral(mode).value;
206 let res = this.adjust_nan(res, &[f]);
207 this.write_scalar(res, dest)?;
208 }
209
210 "sqrtf32" => {
211 let [f] = check_intrinsic_arg_count(args)?;
212 let f = this.read_scalar(f)?.to_f32()?;
213 let res = math::sqrt(f);
215 let res = this.adjust_nan(res, &[f]);
216 this.write_scalar(res, dest)?;
217 }
218 "sqrtf64" => {
219 let [f] = check_intrinsic_arg_count(args)?;
220 let f = this.read_scalar(f)?.to_f64()?;
221 let res = math::sqrt(f);
223 let res = this.adjust_nan(res, &[f]);
224 this.write_scalar(res, dest)?;
225 }
226
227 #[rustfmt::skip]
228 | "sinf32"
229 | "cosf32"
230 | "expf32"
231 | "exp2f32"
232 | "logf32"
233 | "log10f32"
234 | "log2f32"
235 => {
236 let [f] = check_intrinsic_arg_count(args)?;
237 let f = this.read_scalar(f)?.to_f32()?;
238 let host = f.to_host();
241 let res = match intrinsic_name {
242 "sinf32" => host.sin(),
243 "cosf32" => host.cos(),
244 "expf32" => host.exp(),
245 "exp2f32" => host.exp2(),
246 "logf32" => host.ln(),
247 "log10f32" => host.log10(),
248 "log2f32" => host.log2(),
249 _ => bug!(),
250 };
251 let res = res.to_soft();
252 let res = this.adjust_nan(res, &[f]);
261 this.write_scalar(res, dest)?;
262 }
263 #[rustfmt::skip]
264 | "sinf64"
265 | "cosf64"
266 | "expf64"
267 | "exp2f64"
268 | "logf64"
269 | "log10f64"
270 | "log2f64"
271 => {
272 let [f] = check_intrinsic_arg_count(args)?;
273 let f = this.read_scalar(f)?.to_f64()?;
274 let host = f.to_host();
277 let res = match intrinsic_name {
278 "sinf64" => host.sin(),
279 "cosf64" => host.cos(),
280 "expf64" => host.exp(),
281 "exp2f64" => host.exp2(),
282 "logf64" => host.ln(),
283 "log10f64" => host.log10(),
284 "log2f64" => host.log2(),
285 _ => bug!(),
286 };
287 let res = res.to_soft();
288 let res = this.adjust_nan(res, &[f]);
297 this.write_scalar(res, dest)?;
298 }
299
300 "fmaf32" => {
301 let [a, b, c] = check_intrinsic_arg_count(args)?;
302 let a = this.read_scalar(a)?.to_f32()?;
303 let b = this.read_scalar(b)?.to_f32()?;
304 let c = this.read_scalar(c)?.to_f32()?;
305 let res = a.to_host().mul_add(b.to_host(), c.to_host()).to_soft();
307 let res = this.adjust_nan(res, &[a, b, c]);
308 this.write_scalar(res, dest)?;
309 }
310 "fmaf64" => {
311 let [a, b, c] = check_intrinsic_arg_count(args)?;
312 let a = this.read_scalar(a)?.to_f64()?;
313 let b = this.read_scalar(b)?.to_f64()?;
314 let c = this.read_scalar(c)?.to_f64()?;
315 let res = a.to_host().mul_add(b.to_host(), c.to_host()).to_soft();
317 let res = this.adjust_nan(res, &[a, b, c]);
318 this.write_scalar(res, dest)?;
319 }
320
321 "fmuladdf32" => {
322 let [a, b, c] = check_intrinsic_arg_count(args)?;
323 let a = this.read_scalar(a)?.to_f32()?;
324 let b = this.read_scalar(b)?.to_f32()?;
325 let c = this.read_scalar(c)?.to_f32()?;
326 let fuse: bool = this.machine.rng.get_mut().random();
327 let res = if fuse {
328 a.to_host().mul_add(b.to_host(), c.to_host()).to_soft()
330 } else {
331 ((a * b).value + c).value
332 };
333 let res = this.adjust_nan(res, &[a, b, c]);
334 this.write_scalar(res, dest)?;
335 }
336 "fmuladdf64" => {
337 let [a, b, c] = check_intrinsic_arg_count(args)?;
338 let a = this.read_scalar(a)?.to_f64()?;
339 let b = this.read_scalar(b)?.to_f64()?;
340 let c = this.read_scalar(c)?.to_f64()?;
341 let fuse: bool = this.machine.rng.get_mut().random();
342 let res = if fuse {
343 a.to_host().mul_add(b.to_host(), c.to_host()).to_soft()
345 } else {
346 ((a * b).value + c).value
347 };
348 let res = this.adjust_nan(res, &[a, b, c]);
349 this.write_scalar(res, dest)?;
350 }
351
352 "powf32" => {
353 let [f1, f2] = check_intrinsic_arg_count(args)?;
355 let f1 = this.read_scalar(f1)?.to_f32()?;
356 let f2 = this.read_scalar(f2)?.to_f32()?;
357 let res = f1.to_host().powf(f2.to_host()).to_soft();
359 let res = this.adjust_nan(res, &[f1, f2]);
360 this.write_scalar(res, dest)?;
361 }
362 "powf64" => {
363 let [f1, f2] = check_intrinsic_arg_count(args)?;
365 let f1 = this.read_scalar(f1)?.to_f64()?;
366 let f2 = this.read_scalar(f2)?.to_f64()?;
367 let res = f1.to_host().powf(f2.to_host()).to_soft();
369 let res = this.adjust_nan(res, &[f1, f2]);
370 this.write_scalar(res, dest)?;
371 }
372
373 "powif32" => {
374 let [f, i] = check_intrinsic_arg_count(args)?;
376 let f = this.read_scalar(f)?.to_f32()?;
377 let i = this.read_scalar(i)?.to_i32()?;
378 let res = f.to_host().powi(i).to_soft();
380 let res = this.adjust_nan(res, &[f]);
381 this.write_scalar(res, dest)?;
382 }
383 "powif64" => {
384 let [f, i] = check_intrinsic_arg_count(args)?;
386 let f = this.read_scalar(f)?.to_f64()?;
387 let i = this.read_scalar(i)?.to_i32()?;
388 let res = f.to_host().powi(i).to_soft();
390 let res = this.adjust_nan(res, &[f]);
391 this.write_scalar(res, dest)?;
392 }
393
394 #[rustfmt::skip]
395 | "fadd_algebraic"
396 | "fsub_algebraic"
397 | "fmul_algebraic"
398 | "fdiv_algebraic"
399 | "frem_algebraic"
400 => {
401 let [a, b] = check_intrinsic_arg_count(args)?;
402 let a = this.read_immediate(a)?;
403 let b = this.read_immediate(b)?;
404 let op = match intrinsic_name {
405 "fadd_algebraic" => mir::BinOp::Add,
406 "fsub_algebraic" => mir::BinOp::Sub,
407 "fmul_algebraic" => mir::BinOp::Mul,
408 "fdiv_algebraic" => mir::BinOp::Div,
409 "frem_algebraic" => mir::BinOp::Rem,
410 _ => bug!(),
411 };
412 let res = this.binary_op(op, &a, &b)?;
413 let res = apply_random_float_error_to_imm(this, res, 2 )?;
417 this.write_immediate(*res, dest)?;
418 }
419
420 #[rustfmt::skip]
421 | "fadd_fast"
422 | "fsub_fast"
423 | "fmul_fast"
424 | "fdiv_fast"
425 | "frem_fast"
426 => {
427 let [a, b] = check_intrinsic_arg_count(args)?;
428 let a = this.read_immediate(a)?;
429 let b = this.read_immediate(b)?;
430 let op = match intrinsic_name {
431 "fadd_fast" => mir::BinOp::Add,
432 "fsub_fast" => mir::BinOp::Sub,
433 "fmul_fast" => mir::BinOp::Mul,
434 "fdiv_fast" => mir::BinOp::Div,
435 "frem_fast" => mir::BinOp::Rem,
436 _ => bug!(),
437 };
438 let float_finite = |x: &ImmTy<'tcx>| -> InterpResult<'tcx, bool> {
439 let ty::Float(fty) = x.layout.ty.kind() else {
440 bug!("float_finite: non-float input type {}", x.layout.ty)
441 };
442 interp_ok(match fty {
443 FloatTy::F16 => x.to_scalar().to_f16()?.is_finite(),
444 FloatTy::F32 => x.to_scalar().to_f32()?.is_finite(),
445 FloatTy::F64 => x.to_scalar().to_f64()?.is_finite(),
446 FloatTy::F128 => x.to_scalar().to_f128()?.is_finite(),
447 })
448 };
449 match (float_finite(&a)?, float_finite(&b)?) {
450 (false, false) => throw_ub_format!(
451 "`{intrinsic_name}` intrinsic called with non-finite value as both parameters",
452 ),
453 (false, _) => throw_ub_format!(
454 "`{intrinsic_name}` intrinsic called with non-finite value as first parameter",
455 ),
456 (_, false) => throw_ub_format!(
457 "`{intrinsic_name}` intrinsic called with non-finite value as second parameter",
458 ),
459 _ => {}
460 }
461 let res = this.binary_op(op, &a, &b)?;
462 if !float_finite(&res)? {
465 throw_ub_format!("`{intrinsic_name}` intrinsic produced non-finite value as result");
466 }
467 let res = apply_random_float_error_to_imm(this, res, 2 )?;
470 this.write_immediate(*res, dest)?;
471 }
472
473 "float_to_int_unchecked" => {
474 let [val] = check_intrinsic_arg_count(args)?;
475 let val = this.read_immediate(val)?;
476
477 let res = this
478 .float_to_int_checked(&val, dest.layout, Round::TowardZero)?
479 .ok_or_else(|| {
480 err_ub_format!(
481 "`float_to_int_unchecked` intrinsic called on {val} which cannot be represented in target type `{:?}`",
482 dest.layout.ty
483 )
484 })?;
485
486 this.write_immediate(*res, dest)?;
487 }
488
489 "breakpoint" => {
491 let [] = check_intrinsic_arg_count(args)?;
492 throw_machine_stop!(TerminationInfo::Abort(format!("trace/breakpoint trap")))
494 }
495
496 _ => return interp_ok(EmulateItemResult::NotSupported),
497 }
498
499 interp_ok(EmulateItemResult::NeedsReturn)
500 }
501}
502
503fn apply_random_float_error_to_imm<'tcx>(
506 ecx: &mut MiriInterpCx<'tcx>,
507 val: ImmTy<'tcx>,
508 ulp_exponent: u32,
509) -> InterpResult<'tcx, ImmTy<'tcx>> {
510 let scalar = val.to_scalar_int()?;
511 let res: ScalarInt = match val.layout.ty.kind() {
512 ty::Float(FloatTy::F16) =>
513 apply_random_float_error_ulp(ecx, scalar.to_f16(), ulp_exponent).into(),
514 ty::Float(FloatTy::F32) =>
515 apply_random_float_error_ulp(ecx, scalar.to_f32(), ulp_exponent).into(),
516 ty::Float(FloatTy::F64) =>
517 apply_random_float_error_ulp(ecx, scalar.to_f64(), ulp_exponent).into(),
518 ty::Float(FloatTy::F128) =>
519 apply_random_float_error_ulp(ecx, scalar.to_f128(), ulp_exponent).into(),
520 _ => bug!("intrinsic called with non-float input type"),
521 };
522
523 interp_ok(ImmTy::from_scalar_int(res, val.layout))
524}