miri/intrinsics/
mod.rs

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
22/// Check that the number of args is what we expect.
23fn 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        // Force use of fallback body, if available.
52        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        // See if the core engine can handle this intrinsic.
62        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        // FIXME: avoid allocating memory
69        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                // We haven't handled the intrinsic, let's see if we can use a fallback body.
74                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                // Jump to the unwind block to begin unwinding.
100                this.unwind_to_block(unwind)?;
101                interp_ok(None)
102            }
103            EmulateItemResult::AlreadyJumped => interp_ok(None),
104        }
105    }
106
107    /// Emulates a Miri-supported intrinsic (not supported by the core engine).
108    /// Returns `Ok(true)` if the intrinsic was handled.
109    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            // Basic control flow
128            "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                // This pushed a stack frame, don't jump to `ret`.
137                return interp_ok(EmulateItemResult::AlreadyJumped);
138            }
139
140            // Raw memory accesses
141            "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            // Memory model / provenance manipulation
158            "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            // We want to return either `true` or `false` at random, or else something like
170            // ```
171            // if !is_val_statically_known(0) { unreachable_unchecked(); }
172            // ```
173            // Would not be considered UB, or the other way around (`is_val_statically_known(0)`).
174            "is_val_statically_known" => {
175                let [_arg] = check_intrinsic_arg_count(args)?;
176                // FIXME: should we check for validity here? It's tricky because we do not have a
177                // place. Codegen does not seem to set any attributes like `noundef` for intrinsic
178                // calls, so we don't *have* to do anything.
179                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                // Sqrt is specified to be fully precise.
187                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                // Sqrt is specified to be fully precise.
195                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                    // Using host floats (but it's fine, these operations do not have
214                    // guaranteed precision).
215                    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                    // Apply a relative error of 4ULP to introduce some non-determinism
229                    // simulating imprecise implementations and optimizations.
230                    let res = apply_random_float_error_ulp(
231                        this,
232                        res,
233                        4,
234                    );
235
236                    // Clamp the result to the guaranteed range of this function according to the C standard,
237                    // if any.
238                    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                    // Using host floats (but it's fine, these operations do not have
258                    // guaranteed precision).
259                    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                    // Apply a relative error of 4ULP to introduce some non-determinism
273                    // simulating imprecise implementations and optimizations.
274                    let res = apply_random_float_error_ulp(
275                        this,
276                        res,
277                        4,
278                    );
279
280                    // Clamp the result to the guaranteed range of this function according to the C standard,
281                    // if any.
282                    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                    // Using host floats (but it's fine, this operation does not have guaranteed precision).
335                    let res = f1.to_host().powf(f2.to_host()).to_soft();
336
337                    // Apply a relative error of 4ULP to introduce some non-determinism
338                    // simulating imprecise implementations and optimizations.
339                    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                    // Using host floats (but it's fine, this operation does not have guaranteed precision).
351                    let res = f1.to_host().powf(f2.to_host()).to_soft();
352
353                    // Apply a relative error of 4ULP to introduce some non-determinism
354                    // simulating imprecise implementations and optimizations.
355                    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                    // Using host floats (but it's fine, this operation does not have guaranteed precision).
368                    let res = f.to_host().powi(i).to_soft();
369
370                    // Apply a relative error of 4ULP to introduce some non-determinism
371                    // simulating imprecise implementations and optimizations.
372                    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                    // Using host floats (but it's fine, this operation does not have guaranteed precision).
384                    let res = f.to_host().powi(i).to_soft();
385
386                    // Apply a relative error of 4ULP to introduce some non-determinism
387                    // simulating imprecise implementations and optimizations.
388                    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                // This cannot be a NaN so we also don't have to apply any non-determinism.
437                // (Also, `binary_op` already called `generate_nan` if needed.)
438                if !float_finite(&res)? {
439                    throw_ub_format!("`{intrinsic_name}` intrinsic produced non-finite value as result");
440                }
441                // Apply a relative error of 4ULP to simulate non-deterministic precision loss
442                // due to optimizations.
443                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            // Other
464            "breakpoint" => {
465                let [] = check_intrinsic_arg_count(args)?;
466                // normally this would raise a SIGTRAP, which aborts if no debugger is connected
467                throw_machine_stop!(TerminationInfo::Abort(format!("trace/breakpoint trap")))
468            }
469
470            "assert_inhabited" | "assert_zero_valid" | "assert_mem_uninitialized_valid" => {
471                // Make these a NOP, so we get the better Miri-native error messages.
472            }
473
474            _ => return interp_ok(EmulateItemResult::NotSupported),
475        }
476
477        interp_ok(EmulateItemResult::NeedsReturn)
478    }
479}
480
481/// For the intrinsics:
482/// - sinf32, sinf64
483/// - cosf32, cosf64
484/// - expf32, expf64, exp2f32, exp2f64
485/// - logf32, logf64, log2f32, log2f64, log10f32, log10f64
486/// - powf32, powf64
487///
488/// # Return
489///
490/// Returns `Some(output)` if the `intrinsic` results in a defined fixed `output` specified in the C standard
491/// (specifically, C23 annex F.10)  when given `args` as arguments. Outputs that are unaffected by a relative error
492/// (such as INF and zero) are not handled here, they are assumed to be handled by the underlying
493/// implementation. Returns `None` if no specific value is guaranteed.
494///
495/// # Note
496///
497/// For `powf*` operations of the form:
498///
499/// - `(SNaN)^(±0)`
500/// - `1^(SNaN)`
501///
502/// The result is implementation-defined:
503/// - musl returns for both `1.0`
504/// - glibc returns for both `NaN`
505///
506/// This discrepancy exists because SNaN handling is not consistently defined across platforms,
507/// and the C standard leaves behavior for SNaNs unspecified.
508///
509/// Miri chooses to adhere to both implementations and returns either one of them non-deterministically.
510fn 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        // cos(+- 0) = 1
518        ("cosf32" | "cosf64", [input]) if input.is_zero() => one,
519
520        // e^0 = 1
521        ("expf32" | "expf64" | "exp2f32" | "exp2f64", [input]) if input.is_zero() => one,
522
523        // (-1)^(±INF) = 1
524        ("powf32" | "powf64", [base, exp]) if *base == -one && exp.is_infinite() => one,
525
526        // 1^y = 1 for any y, even a NaN
527        ("powf32" | "powf64", [base, exp]) if *base == one => {
528            let rng = ecx.machine.rng.get_mut();
529            // SNaN exponents get special treatment: they might return 1, or a NaN.
530            let return_nan = exp.is_signaling() && ecx.machine.float_nondet && rng.random();
531            // Handle both the musl and glibc cases non-deterministically.
532            if return_nan { ecx.generate_nan(args) } else { one }
533        }
534
535        // x^(±0) = 1 for any x, even a NaN
536        ("powf32" | "powf64", [base, exp]) if exp.is_zero() => {
537            let rng = ecx.machine.rng.get_mut();
538            // SNaN bases get special treatment: they might return 1, or a NaN.
539            let return_nan = base.is_signaling() && ecx.machine.float_nondet && rng.random();
540            // Handle both the musl and glibc cases non-deterministically.
541            if return_nan { ecx.generate_nan(args) } else { one }
542        }
543
544        // There are a lot of cases for fixed outputs according to the C Standard, but these are
545        // mainly INF or zero which are not affected by the applied error.
546        _ => return None,
547    })
548}
549
550/// Returns `Some(output)` if `powi` (called `pown` in C) results in a fixed value specified in the
551/// C standard (specifically, C23 annex F.10.4.6) when doing `base^exp`. Otherwise, returns `None`.
552fn 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            // For SNaN treatment, we are consistent with `powf`above.
563            // (We wouldn't have two, unlike powf all implementations seem to agree for powi,
564            // but for now we are maximally conservative.)
565            if return_nan { ecx.generate_nan(&[base]) } else { one }
566        }
567
568        _ => return None,
569    })
570}
571
572/// Given an floating-point operation and a floating-point value, clamps the result to the output
573/// range of the given operation.
574fn clamp_float_value<S: Semantics>(intrinsic_name: &str, val: IeeeFloat<S>) -> IeeeFloat<S> {
575    match intrinsic_name {
576        // sin and cos: [-1, 1]
577        "sinf32" | "cosf32" | "sinf64" | "cosf64" =>
578            val.clamp(IeeeFloat::<S>::one().neg(), IeeeFloat::<S>::one()),
579        // exp: [0, +INF]
580        "expf32" | "exp2f32" | "expf64" | "exp2f64" =>
581            IeeeFloat::<S>::maximum(val, IeeeFloat::<S>::ZERO),
582        _ => val,
583    }
584}