1#![warn(clippy::arithmetic_side_effects)]
2
3mod atomic;
4mod simd;
5
6use std::ops::Neg;
7
8use rand::Rng;
9use rustc_abi::Size;
10use rustc_apfloat::ieee::{IeeeFloat, Semantics};
11use rustc_apfloat::{self, Float, Round};
12use rustc_middle::mir;
13use rustc_middle::ty::{self, FloatTy};
14use rustc_span::{Symbol, sym};
15
16use self::atomic::EvalContextExt as _;
17use self::helpers::{ToHost, ToSoft};
18use self::simd::EvalContextExt as _;
19use crate::math::{IeeeExt, apply_random_float_error_ulp};
20use crate::*;
21
22fn check_intrinsic_arg_count<'a, 'tcx, const N: usize>(
24 args: &'a [OpTy<'tcx>],
25) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]>
26where
27 &'a [OpTy<'tcx>; N]: TryFrom<&'a [OpTy<'tcx>]>,
28{
29 if let Ok(ops) = args.try_into() {
30 return interp_ok(ops);
31 }
32 throw_ub_format!(
33 "incorrect number of arguments for intrinsic: got {}, expected {}",
34 args.len(),
35 N
36 )
37}
38
39impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
40pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
41 fn call_intrinsic(
42 &mut self,
43 instance: ty::Instance<'tcx>,
44 args: &[OpTy<'tcx>],
45 dest: &PlaceTy<'tcx>,
46 ret: Option<mir::BasicBlock>,
47 unwind: mir::UnwindAction,
48 ) -> InterpResult<'tcx, Option<ty::Instance<'tcx>>> {
49 let this = self.eval_context_mut();
50
51 if this.machine.force_intrinsic_fallback
53 && !this.tcx.intrinsic(instance.def_id()).unwrap().must_be_overridden
54 {
55 return interp_ok(Some(ty::Instance {
56 def: ty::InstanceKind::Item(instance.def_id()),
57 args: instance.args,
58 }));
59 }
60
61 if this.eval_intrinsic(instance, args, dest, ret)? {
63 return interp_ok(None);
64 }
65 let intrinsic_name = this.tcx.item_name(instance.def_id());
66 let intrinsic_name = intrinsic_name.as_str();
67
68 let dest = this.force_allocation(dest)?;
70
71 match this.emulate_intrinsic_by_name(intrinsic_name, instance.args, args, &dest, ret)? {
72 EmulateItemResult::NotSupported => {
73 if this.tcx.intrinsic(instance.def_id()).unwrap().must_be_overridden {
75 throw_unsup_format!("unimplemented intrinsic: `{intrinsic_name}`")
76 }
77 let intrinsic_fallback_is_spec = Symbol::intern("intrinsic_fallback_is_spec");
78 if this
79 .tcx
80 .get_attrs_by_path(instance.def_id(), &[sym::miri, intrinsic_fallback_is_spec])
81 .next()
82 .is_none()
83 {
84 throw_unsup_format!(
85 "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"
86 );
87 }
88 interp_ok(Some(ty::Instance {
89 def: ty::InstanceKind::Item(instance.def_id()),
90 args: instance.args,
91 }))
92 }
93 EmulateItemResult::NeedsReturn => {
94 trace!("{:?}", this.dump_place(&dest.clone().into()));
95 this.return_to_block(ret)?;
96 interp_ok(None)
97 }
98 EmulateItemResult::NeedsUnwind => {
99 this.unwind_to_block(unwind)?;
101 interp_ok(None)
102 }
103 EmulateItemResult::AlreadyJumped => interp_ok(None),
104 }
105 }
106
107 fn emulate_intrinsic_by_name(
110 &mut self,
111 intrinsic_name: &str,
112 generic_args: ty::GenericArgsRef<'tcx>,
113 args: &[OpTy<'tcx>],
114 dest: &MPlaceTy<'tcx>,
115 ret: Option<mir::BasicBlock>,
116 ) -> InterpResult<'tcx, EmulateItemResult> {
117 let this = self.eval_context_mut();
118
119 if let Some(name) = intrinsic_name.strip_prefix("atomic_") {
120 return this.emulate_atomic_intrinsic(name, generic_args, args, dest);
121 }
122 if let Some(name) = intrinsic_name.strip_prefix("simd_") {
123 return this.emulate_simd_intrinsic(name, generic_args, args, dest);
124 }
125
126 match intrinsic_name {
127 "abort" => {
129 throw_machine_stop!(TerminationInfo::Abort(
130 "the program aborted execution".to_owned()
131 ));
132 }
133 "catch_unwind" => {
134 let [try_fn, data, catch_fn] = check_intrinsic_arg_count(args)?;
135 this.handle_catch_unwind(try_fn, data, catch_fn, dest, ret)?;
136 return interp_ok(EmulateItemResult::AlreadyJumped);
138 }
139
140 "volatile_load" => {
142 let [place] = check_intrinsic_arg_count(args)?;
143 let place = this.deref_pointer(place)?;
144 this.copy_op(&place, dest)?;
145 }
146 "volatile_store" => {
147 let [place, dest] = check_intrinsic_arg_count(args)?;
148 let place = this.deref_pointer(place)?;
149 this.copy_op(dest, &place)?;
150 }
151
152 "volatile_set_memory" => {
153 let [ptr, val_byte, count] = check_intrinsic_arg_count(args)?;
154 this.write_bytes_intrinsic(ptr, val_byte, count, "volatile_set_memory")?;
155 }
156
157 "ptr_mask" => {
159 let [ptr, mask] = check_intrinsic_arg_count(args)?;
160
161 let ptr = this.read_pointer(ptr)?;
162 let mask = this.read_target_usize(mask)?;
163
164 let masked_addr = Size::from_bytes(ptr.addr().bytes() & mask);
165
166 this.write_pointer(Pointer::new(ptr.provenance, masked_addr), dest)?;
167 }
168
169 "is_val_statically_known" => {
175 let [_arg] = check_intrinsic_arg_count(args)?;
176 let branch: bool = this.machine.rng.get_mut().random();
180 this.write_scalar(Scalar::from_bool(branch), dest)?;
181 }
182
183 "sqrtf32" => {
184 let [f] = check_intrinsic_arg_count(args)?;
185 let f = this.read_scalar(f)?.to_f32()?;
186 let res = math::sqrt(f);
188 let res = this.adjust_nan(res, &[f]);
189 this.write_scalar(res, dest)?;
190 }
191 "sqrtf64" => {
192 let [f] = check_intrinsic_arg_count(args)?;
193 let f = this.read_scalar(f)?.to_f64()?;
194 let res = math::sqrt(f);
196 let res = this.adjust_nan(res, &[f]);
197 this.write_scalar(res, dest)?;
198 }
199
200 #[rustfmt::skip]
201 | "sinf32"
202 | "cosf32"
203 | "expf32"
204 | "exp2f32"
205 | "logf32"
206 | "log10f32"
207 | "log2f32"
208 => {
209 let [f] = check_intrinsic_arg_count(args)?;
210 let f = this.read_scalar(f)?.to_f32()?;
211
212 let res = fixed_float_value(this, intrinsic_name, &[f]).unwrap_or_else(|| {
213 let host = f.to_host();
216 let res = match intrinsic_name {
217 "sinf32" => host.sin(),
218 "cosf32" => host.cos(),
219 "expf32" => host.exp(),
220 "exp2f32" => host.exp2(),
221 "logf32" => host.ln(),
222 "log10f32" => host.log10(),
223 "log2f32" => host.log2(),
224 _ => bug!(),
225 };
226 let res = res.to_soft();
227
228 let res = apply_random_float_error_ulp(
231 this,
232 res,
233 4,
234 );
235
236 clamp_float_value(intrinsic_name, res)
239 });
240 let res = this.adjust_nan(res, &[f]);
241 this.write_scalar(res, dest)?;
242 }
243
244 #[rustfmt::skip]
245 | "sinf64"
246 | "cosf64"
247 | "expf64"
248 | "exp2f64"
249 | "logf64"
250 | "log10f64"
251 | "log2f64"
252 => {
253 let [f] = check_intrinsic_arg_count(args)?;
254 let f = this.read_scalar(f)?.to_f64()?;
255
256 let res = fixed_float_value(this, intrinsic_name, &[f]).unwrap_or_else(|| {
257 let host = f.to_host();
260 let res = match intrinsic_name {
261 "sinf64" => host.sin(),
262 "cosf64" => host.cos(),
263 "expf64" => host.exp(),
264 "exp2f64" => host.exp2(),
265 "logf64" => host.ln(),
266 "log10f64" => host.log10(),
267 "log2f64" => host.log2(),
268 _ => bug!(),
269 };
270 let res = res.to_soft();
271
272 let res = apply_random_float_error_ulp(
275 this,
276 res,
277 4,
278 );
279
280 clamp_float_value(intrinsic_name, res)
283 });
284 let res = this.adjust_nan(res, &[f]);
285 this.write_scalar(res, dest)?;
286 }
287
288 "fmaf32" => {
289 let [a, b, c] = check_intrinsic_arg_count(args)?;
290 let a = this.read_scalar(a)?.to_f32()?;
291 let b = this.read_scalar(b)?.to_f32()?;
292 let c = this.read_scalar(c)?.to_f32()?;
293 let res = a.mul_add(b, c).value;
294 let res = this.adjust_nan(res, &[a, b, c]);
295 this.write_scalar(res, dest)?;
296 }
297 "fmaf64" => {
298 let [a, b, c] = check_intrinsic_arg_count(args)?;
299 let a = this.read_scalar(a)?.to_f64()?;
300 let b = this.read_scalar(b)?.to_f64()?;
301 let c = this.read_scalar(c)?.to_f64()?;
302 let res = a.mul_add(b, c).value;
303 let res = this.adjust_nan(res, &[a, b, c]);
304 this.write_scalar(res, dest)?;
305 }
306
307 "fmuladdf32" => {
308 let [a, b, c] = check_intrinsic_arg_count(args)?;
309 let a = this.read_scalar(a)?.to_f32()?;
310 let b = this.read_scalar(b)?.to_f32()?;
311 let c = this.read_scalar(c)?.to_f32()?;
312 let fuse: bool = this.machine.float_nondet && this.machine.rng.get_mut().random();
313 let res = if fuse { a.mul_add(b, c).value } else { ((a * b).value + c).value };
314 let res = this.adjust_nan(res, &[a, b, c]);
315 this.write_scalar(res, dest)?;
316 }
317 "fmuladdf64" => {
318 let [a, b, c] = check_intrinsic_arg_count(args)?;
319 let a = this.read_scalar(a)?.to_f64()?;
320 let b = this.read_scalar(b)?.to_f64()?;
321 let c = this.read_scalar(c)?.to_f64()?;
322 let fuse: bool = this.machine.float_nondet && this.machine.rng.get_mut().random();
323 let res = if fuse { a.mul_add(b, c).value } else { ((a * b).value + c).value };
324 let res = this.adjust_nan(res, &[a, b, c]);
325 this.write_scalar(res, dest)?;
326 }
327
328 "powf32" => {
329 let [f1, f2] = check_intrinsic_arg_count(args)?;
330 let f1 = this.read_scalar(f1)?.to_f32()?;
331 let f2 = this.read_scalar(f2)?.to_f32()?;
332
333 let res = fixed_float_value(this, intrinsic_name, &[f1, f2]).unwrap_or_else(|| {
334 let res = f1.to_host().powf(f2.to_host()).to_soft();
336
337 apply_random_float_error_ulp(this, res, 4)
340 });
341 let res = this.adjust_nan(res, &[f1, f2]);
342 this.write_scalar(res, dest)?;
343 }
344 "powf64" => {
345 let [f1, f2] = check_intrinsic_arg_count(args)?;
346 let f1 = this.read_scalar(f1)?.to_f64()?;
347 let f2 = this.read_scalar(f2)?.to_f64()?;
348
349 let res = fixed_float_value(this, intrinsic_name, &[f1, f2]).unwrap_or_else(|| {
350 let res = f1.to_host().powf(f2.to_host()).to_soft();
352
353 apply_random_float_error_ulp(this, res, 4)
356 });
357 let res = this.adjust_nan(res, &[f1, f2]);
358 this.write_scalar(res, dest)?;
359 }
360
361 "powif32" => {
362 let [f, i] = check_intrinsic_arg_count(args)?;
363 let f = this.read_scalar(f)?.to_f32()?;
364 let i = this.read_scalar(i)?.to_i32()?;
365
366 let res = fixed_powi_float_value(this, f, i).unwrap_or_else(|| {
367 let res = f.to_host().powi(i).to_soft();
369
370 apply_random_float_error_ulp(this, res, 4)
373 });
374 let res = this.adjust_nan(res, &[f]);
375 this.write_scalar(res, dest)?;
376 }
377 "powif64" => {
378 let [f, i] = check_intrinsic_arg_count(args)?;
379 let f = this.read_scalar(f)?.to_f64()?;
380 let i = this.read_scalar(i)?.to_i32()?;
381
382 let res = fixed_powi_float_value(this, f, i).unwrap_or_else(|| {
383 let res = f.to_host().powi(i).to_soft();
385
386 apply_random_float_error_ulp(this, res, 4)
389 });
390 let res = this.adjust_nan(res, &[f]);
391 this.write_scalar(res, dest)?;
392 }
393
394 #[rustfmt::skip]
395 | "fadd_fast"
396 | "fsub_fast"
397 | "fmul_fast"
398 | "fdiv_fast"
399 | "frem_fast"
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_fast" => mir::BinOp::Add,
406 "fsub_fast" => mir::BinOp::Sub,
407 "fmul_fast" => mir::BinOp::Mul,
408 "fdiv_fast" => mir::BinOp::Div,
409 "frem_fast" => mir::BinOp::Rem,
410 _ => bug!(),
411 };
412 let float_finite = |x: &ImmTy<'tcx>| -> InterpResult<'tcx, bool> {
413 let ty::Float(fty) = x.layout.ty.kind() else {
414 bug!("float_finite: non-float input type {}", x.layout.ty)
415 };
416 interp_ok(match fty {
417 FloatTy::F16 => x.to_scalar().to_f16()?.is_finite(),
418 FloatTy::F32 => x.to_scalar().to_f32()?.is_finite(),
419 FloatTy::F64 => x.to_scalar().to_f64()?.is_finite(),
420 FloatTy::F128 => x.to_scalar().to_f128()?.is_finite(),
421 })
422 };
423 match (float_finite(&a)?, float_finite(&b)?) {
424 (false, false) => throw_ub_format!(
425 "`{intrinsic_name}` intrinsic called with non-finite value as both parameters",
426 ),
427 (false, _) => throw_ub_format!(
428 "`{intrinsic_name}` intrinsic called with non-finite value as first parameter",
429 ),
430 (_, false) => throw_ub_format!(
431 "`{intrinsic_name}` intrinsic called with non-finite value as second parameter",
432 ),
433 _ => {}
434 }
435 let res = this.binary_op(op, &a, &b)?;
436 if !float_finite(&res)? {
439 throw_ub_format!("`{intrinsic_name}` intrinsic produced non-finite value as result");
440 }
441 let res = crate::math::apply_random_float_error_to_imm(this, res, 4)?;
444 this.write_immediate(*res, dest)?;
445 }
446
447 "float_to_int_unchecked" => {
448 let [val] = check_intrinsic_arg_count(args)?;
449 let val = this.read_immediate(val)?;
450
451 let res = this
452 .float_to_int_checked(&val, dest.layout, Round::TowardZero)?
453 .ok_or_else(|| {
454 err_ub_format!(
455 "`float_to_int_unchecked` intrinsic called on {val} which cannot be represented in target type `{:?}`",
456 dest.layout.ty
457 )
458 })?;
459
460 this.write_immediate(*res, dest)?;
461 }
462
463 "breakpoint" => {
465 let [] = check_intrinsic_arg_count(args)?;
466 throw_machine_stop!(TerminationInfo::Abort(format!("trace/breakpoint trap")))
468 }
469
470 "assert_inhabited" | "assert_zero_valid" | "assert_mem_uninitialized_valid" => {
471 }
473
474 _ => return interp_ok(EmulateItemResult::NotSupported),
475 }
476
477 interp_ok(EmulateItemResult::NeedsReturn)
478 }
479}
480
481fn fixed_float_value<S: Semantics>(
511 ecx: &mut MiriInterpCx<'_>,
512 intrinsic_name: &str,
513 args: &[IeeeFloat<S>],
514) -> Option<IeeeFloat<S>> {
515 let one = IeeeFloat::<S>::one();
516 Some(match (intrinsic_name, args) {
517 ("cosf32" | "cosf64", [input]) if input.is_zero() => one,
519
520 ("expf32" | "expf64" | "exp2f32" | "exp2f64", [input]) if input.is_zero() => one,
522
523 ("powf32" | "powf64", [base, exp]) if *base == -one && exp.is_infinite() => one,
525
526 ("powf32" | "powf64", [base, exp]) if *base == one => {
528 let rng = ecx.machine.rng.get_mut();
529 let return_nan = exp.is_signaling() && ecx.machine.float_nondet && rng.random();
531 if return_nan { ecx.generate_nan(args) } else { one }
533 }
534
535 ("powf32" | "powf64", [base, exp]) if exp.is_zero() => {
537 let rng = ecx.machine.rng.get_mut();
538 let return_nan = base.is_signaling() && ecx.machine.float_nondet && rng.random();
540 if return_nan { ecx.generate_nan(args) } else { one }
542 }
543
544 _ => return None,
547 })
548}
549
550fn fixed_powi_float_value<S: Semantics>(
553 ecx: &mut MiriInterpCx<'_>,
554 base: IeeeFloat<S>,
555 exp: i32,
556) -> Option<IeeeFloat<S>> {
557 Some(match exp {
558 0 => {
559 let one = IeeeFloat::<S>::one();
560 let rng = ecx.machine.rng.get_mut();
561 let return_nan = ecx.machine.float_nondet && rng.random() && base.is_signaling();
562 if return_nan { ecx.generate_nan(&[base]) } else { one }
566 }
567
568 _ => return None,
569 })
570}
571
572fn clamp_float_value<S: Semantics>(intrinsic_name: &str, val: IeeeFloat<S>) -> IeeeFloat<S> {
575 match intrinsic_name {
576 "sinf32" | "cosf32" | "sinf64" | "cosf64" =>
578 val.clamp(IeeeFloat::<S>::one().neg(), IeeeFloat::<S>::one()),
579 "expf32" | "exp2f32" | "expf64" | "exp2f64" =>
581 IeeeFloat::<S>::maximum(val, IeeeFloat::<S>::ZERO),
582 _ => val,
583 }
584}