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::{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        // See if the core engine can handle this intrinsic.
32        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                // We haven't handled the intrinsic, let's see if we can use a fallback body.
41                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                // Jump to the unwind block to begin unwinding.
67                this.unwind_to_block(unwind)?;
68                interp_ok(None)
69            }
70            EmulateItemResult::AlreadyJumped => interp_ok(None),
71        }
72    }
73
74    /// Emulates a Miri-supported intrinsic (not supported by the core engine).
75    /// Returns `Ok(true)` if the intrinsic was handled.
76    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            // Basic control flow
95            "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                // This pushed a stack frame, don't jump to `ret`.
103                return interp_ok(EmulateItemResult::AlreadyJumped);
104            }
105
106            // Raw memory accesses
107            "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            // Memory model / provenance manipulation
124            "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            // We want to return either `true` or `false` at random, or else something like
136            // ```
137            // if !is_val_statically_known(0) { unreachable_unchecked(); }
138            // ```
139            // Would not be considered UB, or the other way around (`is_val_statically_known(0)`).
140            "is_val_statically_known" => {
141                let [_arg] = check_intrinsic_arg_count(args)?;
142                // FIXME: should we check for validity here? It's tricky because we do not have a
143                // place. Codegen does not seem to set any attributes like `noundef` for intrinsic
144                // calls, so we don't *have* to do anything.
145                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                // Sqrt is specified to be fully precise.
214                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                // Sqrt is specified to be fully precise.
222                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                // Using host floats (but it's fine, these operations do not have
239                // guaranteed precision).
240                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                // Apply a relative error of 16ULP to introduce some non-determinism
253                // simulating imprecise implementations and optimizations.
254                // FIXME: temporarily disabled as it breaks std tests.
255                // let res = apply_random_float_error_ulp(
256                //     this,
257                //     res,
258                //     4, // log2(16)
259                // );
260                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                // Using host floats (but it's fine, these operations do not have
275                // guaranteed precision).
276                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                // Apply a relative error of 16ULP to introduce some non-determinism
289                // simulating imprecise implementations and optimizations.
290                // FIXME: temporarily disabled as it breaks std tests.
291                // let res = apply_random_float_error_ulp(
292                //     this,
293                //     res,
294                //     4, // log2(16)
295                // );
296                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                // FIXME: Using host floats, to work around https://github.com/rust-lang/rustc_apfloat/issues/11
306                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                // FIXME: Using host floats, to work around https://github.com/rust-lang/rustc_apfloat/issues/11
316                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                    // FIXME: Using host floats, to work around https://github.com/rust-lang/rustc_apfloat/issues/11
329                    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                    // FIXME: Using host floats, to work around https://github.com/rust-lang/rustc_apfloat/issues/11
344                    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                // FIXME: apply random relative error but without altering behaviour of powf
354                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                // Using host floats (but it's fine, this operation does not have guaranteed precision).
358                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                // FIXME: apply random relative error but without altering behaviour of powf
364                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                // Using host floats (but it's fine, this operation does not have guaranteed precision).
368                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                // FIXME: apply random relative error but without altering behaviour of powi
375                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                // Using host floats (but it's fine, this operation does not have guaranteed precision).
379                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                // FIXME: apply random relative error but without altering behaviour of powi
385                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                // Using host floats (but it's fine, this operation does not have guaranteed precision).
389                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                // `binary_op` already called `generate_nan` if needed.
414                // Apply a relative error of 4ULP to simulate non-deterministic precision loss
415                // due to optimizations.
416                let res = apply_random_float_error_to_imm(this, res, 2 /* log2(4) */)?;
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                // This cannot be a NaN so we also don't have to apply any non-determinism.
463                // (Also, `binary_op` already called `generate_nan` if needed.)
464                if !float_finite(&res)? {
465                    throw_ub_format!("`{intrinsic_name}` intrinsic produced non-finite value as result");
466                }
467                // Apply a relative error of 4ULP to simulate non-deterministic precision loss
468                // due to optimizations.
469                let res = apply_random_float_error_to_imm(this, res, 2 /* log2(4) */)?;
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            // Other
490            "breakpoint" => {
491                let [] = check_intrinsic_arg_count(args)?;
492                // normally this would raise a SIGTRAP, which aborts if no debugger is connected
493                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
503/// Applies a random 16ULP floating point error to `val` and returns the new value.
504/// Will fail if `val` is not a floating point number.
505fn 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}