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, ScalarInt};
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                        2, // log2(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                        2, // log2(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(
340                        this, res, 2, // log2(4)
341                    )
342                });
343                let res = this.adjust_nan(res, &[f1, f2]);
344                this.write_scalar(res, dest)?;
345            }
346            "powf64" => {
347                let [f1, f2] = check_intrinsic_arg_count(args)?;
348                let f1 = this.read_scalar(f1)?.to_f64()?;
349                let f2 = this.read_scalar(f2)?.to_f64()?;
350
351                let res = fixed_float_value(this, intrinsic_name, &[f1, f2]).unwrap_or_else(|| {
352                    // Using host floats (but it's fine, this operation does not have guaranteed precision).
353                    let res = f1.to_host().powf(f2.to_host()).to_soft();
354
355                    // Apply a relative error of 4ULP to introduce some non-determinism
356                    // simulating imprecise implementations and optimizations.
357                    apply_random_float_error_ulp(
358                        this, res, 2, // log2(4)
359                    )
360                });
361                let res = this.adjust_nan(res, &[f1, f2]);
362                this.write_scalar(res, dest)?;
363            }
364
365            "powif32" => {
366                let [f, i] = check_intrinsic_arg_count(args)?;
367                let f = this.read_scalar(f)?.to_f32()?;
368                let i = this.read_scalar(i)?.to_i32()?;
369
370                let res = fixed_powi_float_value(this, f, i).unwrap_or_else(|| {
371                    // Using host floats (but it's fine, this operation does not have guaranteed precision).
372                    let res = f.to_host().powi(i).to_soft();
373
374                    // Apply a relative error of 4ULP to introduce some non-determinism
375                    // simulating imprecise implementations and optimizations.
376                    apply_random_float_error_ulp(
377                        this, res, 2, // log2(4)
378                    )
379                });
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)?;
385                let f = this.read_scalar(f)?.to_f64()?;
386                let i = this.read_scalar(i)?.to_i32()?;
387
388                let res = fixed_powi_float_value(this, f, i).unwrap_or_else(|| {
389                    // Using host floats (but it's fine, this operation does not have guaranteed precision).
390                    let res = f.to_host().powi(i).to_soft();
391
392                    // Apply a relative error of 4ULP to introduce some non-determinism
393                    // simulating imprecise implementations and optimizations.
394                    apply_random_float_error_ulp(
395                        this, res, 2, // log2(4)
396                    )
397                });
398                let res = this.adjust_nan(res, &[f]);
399                this.write_scalar(res, dest)?;
400            }
401
402            #[rustfmt::skip]
403            | "fadd_fast"
404            | "fsub_fast"
405            | "fmul_fast"
406            | "fdiv_fast"
407            | "frem_fast"
408            => {
409                let [a, b] = check_intrinsic_arg_count(args)?;
410                let a = this.read_immediate(a)?;
411                let b = this.read_immediate(b)?;
412                let op = match intrinsic_name {
413                    "fadd_fast" => mir::BinOp::Add,
414                    "fsub_fast" => mir::BinOp::Sub,
415                    "fmul_fast" => mir::BinOp::Mul,
416                    "fdiv_fast" => mir::BinOp::Div,
417                    "frem_fast" => mir::BinOp::Rem,
418                    _ => bug!(),
419                };
420                let float_finite = |x: &ImmTy<'tcx>| -> InterpResult<'tcx, bool> {
421                    let ty::Float(fty) = x.layout.ty.kind() else {
422                        bug!("float_finite: non-float input type {}", x.layout.ty)
423                    };
424                    interp_ok(match fty {
425                        FloatTy::F16 => x.to_scalar().to_f16()?.is_finite(),
426                        FloatTy::F32 => x.to_scalar().to_f32()?.is_finite(),
427                        FloatTy::F64 => x.to_scalar().to_f64()?.is_finite(),
428                        FloatTy::F128 => x.to_scalar().to_f128()?.is_finite(),
429                    })
430                };
431                match (float_finite(&a)?, float_finite(&b)?) {
432                    (false, false) => throw_ub_format!(
433                        "`{intrinsic_name}` intrinsic called with non-finite value as both parameters",
434                    ),
435                    (false, _) => throw_ub_format!(
436                        "`{intrinsic_name}` intrinsic called with non-finite value as first parameter",
437                    ),
438                    (_, false) => throw_ub_format!(
439                        "`{intrinsic_name}` intrinsic called with non-finite value as second parameter",
440                    ),
441                    _ => {}
442                }
443                let res = this.binary_op(op, &a, &b)?;
444                // This cannot be a NaN so we also don't have to apply any non-determinism.
445                // (Also, `binary_op` already called `generate_nan` if needed.)
446                if !float_finite(&res)? {
447                    throw_ub_format!("`{intrinsic_name}` intrinsic produced non-finite value as result");
448                }
449                // Apply a relative error of 4ULP to simulate non-deterministic precision loss
450                // due to optimizations.
451                let res = apply_random_float_error_to_imm(this, res, 2 /* log2(4) */)?;
452                this.write_immediate(*res, dest)?;
453            }
454
455            "float_to_int_unchecked" => {
456                let [val] = check_intrinsic_arg_count(args)?;
457                let val = this.read_immediate(val)?;
458
459                let res = this
460                    .float_to_int_checked(&val, dest.layout, Round::TowardZero)?
461                    .ok_or_else(|| {
462                        err_ub_format!(
463                            "`float_to_int_unchecked` intrinsic called on {val} which cannot be represented in target type `{:?}`",
464                            dest.layout.ty
465                        )
466                    })?;
467
468                this.write_immediate(*res, dest)?;
469            }
470
471            // Other
472            "breakpoint" => {
473                let [] = check_intrinsic_arg_count(args)?;
474                // normally this would raise a SIGTRAP, which aborts if no debugger is connected
475                throw_machine_stop!(TerminationInfo::Abort(format!("trace/breakpoint trap")))
476            }
477
478            "assert_inhabited" | "assert_zero_valid" | "assert_mem_uninitialized_valid" => {
479                // Make these a NOP, so we get the better Miri-native error messages.
480            }
481
482            _ => return interp_ok(EmulateItemResult::NotSupported),
483        }
484
485        interp_ok(EmulateItemResult::NeedsReturn)
486    }
487}
488
489/// Applies a random ULP floating point error to `val` and returns the new value.
490/// So if you want an X ULP error, `ulp_exponent` should be log2(X).
491///
492/// Will fail if `val` is not a floating point number.
493fn apply_random_float_error_to_imm<'tcx>(
494    ecx: &mut MiriInterpCx<'tcx>,
495    val: ImmTy<'tcx>,
496    ulp_exponent: u32,
497) -> InterpResult<'tcx, ImmTy<'tcx>> {
498    let scalar = val.to_scalar_int()?;
499    let res: ScalarInt = match val.layout.ty.kind() {
500        ty::Float(FloatTy::F16) =>
501            apply_random_float_error_ulp(ecx, scalar.to_f16(), ulp_exponent).into(),
502        ty::Float(FloatTy::F32) =>
503            apply_random_float_error_ulp(ecx, scalar.to_f32(), ulp_exponent).into(),
504        ty::Float(FloatTy::F64) =>
505            apply_random_float_error_ulp(ecx, scalar.to_f64(), ulp_exponent).into(),
506        ty::Float(FloatTy::F128) =>
507            apply_random_float_error_ulp(ecx, scalar.to_f128(), ulp_exponent).into(),
508        _ => bug!("intrinsic called with non-float input type"),
509    };
510
511    interp_ok(ImmTy::from_scalar_int(res, val.layout))
512}
513
514/// For the intrinsics:
515/// - sinf32, sinf64
516/// - cosf32, cosf64
517/// - expf32, expf64, exp2f32, exp2f64
518/// - logf32, logf64, log2f32, log2f64, log10f32, log10f64
519/// - powf32, powf64
520///
521/// # Return
522///
523/// Returns `Some(output)` if the `intrinsic` results in a defined fixed `output` specified in the C standard
524/// (specifically, C23 annex F.10)  when given `args` as arguments. Outputs that are unaffected by a relative error
525/// (such as INF and zero) are not handled here, they are assumed to be handled by the underlying
526/// implementation. Returns `None` if no specific value is guaranteed.
527///
528/// # Note
529///
530/// For `powf*` operations of the form:
531///
532/// - `(SNaN)^(±0)`
533/// - `1^(SNaN)`
534///
535/// The result is implementation-defined:
536/// - musl returns for both `1.0`
537/// - glibc returns for both `NaN`
538///
539/// This discrepancy exists because SNaN handling is not consistently defined across platforms,
540/// and the C standard leaves behavior for SNaNs unspecified.
541///
542/// Miri chooses to adhere to both implementations and returns either one of them non-deterministically.
543fn fixed_float_value<S: Semantics>(
544    ecx: &mut MiriInterpCx<'_>,
545    intrinsic_name: &str,
546    args: &[IeeeFloat<S>],
547) -> Option<IeeeFloat<S>> {
548    let one = IeeeFloat::<S>::one();
549    Some(match (intrinsic_name, args) {
550        // cos(+- 0) = 1
551        ("cosf32" | "cosf64", [input]) if input.is_zero() => one,
552
553        // e^0 = 1
554        ("expf32" | "expf64" | "exp2f32" | "exp2f64", [input]) if input.is_zero() => one,
555
556        // (-1)^(±INF) = 1
557        ("powf32" | "powf64", [base, exp]) if *base == -one && exp.is_infinite() => one,
558
559        // 1^y = 1 for any y, even a NaN
560        ("powf32" | "powf64", [base, exp]) if *base == one => {
561            let rng = ecx.machine.rng.get_mut();
562            // SNaN exponents get special treatment: they might return 1, or a NaN.
563            let return_nan = exp.is_signaling() && ecx.machine.float_nondet && rng.random();
564            // Handle both the musl and glibc cases non-deterministically.
565            if return_nan { ecx.generate_nan(args) } else { one }
566        }
567
568        // x^(±0) = 1 for any x, even a NaN
569        ("powf32" | "powf64", [base, exp]) if exp.is_zero() => {
570            let rng = ecx.machine.rng.get_mut();
571            // SNaN bases get special treatment: they might return 1, or a NaN.
572            let return_nan = base.is_signaling() && ecx.machine.float_nondet && rng.random();
573            // Handle both the musl and glibc cases non-deterministically.
574            if return_nan { ecx.generate_nan(args) } else { one }
575        }
576
577        // There are a lot of cases for fixed outputs according to the C Standard, but these are
578        // mainly INF or zero which are not affected by the applied error.
579        _ => return None,
580    })
581}
582
583/// Returns `Some(output)` if `powi` (called `pown` in C) results in a fixed value specified in the
584/// C standard (specifically, C23 annex F.10.4.6) when doing `base^exp`. Otherwise, returns `None`.
585fn fixed_powi_float_value<S: Semantics>(
586    ecx: &mut MiriInterpCx<'_>,
587    base: IeeeFloat<S>,
588    exp: i32,
589) -> Option<IeeeFloat<S>> {
590    Some(match exp {
591        0 => {
592            let one = IeeeFloat::<S>::one();
593            let rng = ecx.machine.rng.get_mut();
594            let return_nan = ecx.machine.float_nondet && rng.random() && base.is_signaling();
595            // For SNaN treatment, we are consistent with `powf`above.
596            // (We wouldn't have two, unlike powf all implementations seem to agree for powi,
597            // but for now we are maximally conservative.)
598            if return_nan { ecx.generate_nan(&[base]) } else { one }
599        }
600
601        _ => return None,
602    })
603}
604
605/// Given an floating-point operation and a floating-point value, clamps the result to the output
606/// range of the given operation.
607fn clamp_float_value<S: Semantics>(intrinsic_name: &str, val: IeeeFloat<S>) -> IeeeFloat<S> {
608    match intrinsic_name {
609        // sin and cos: [-1, 1]
610        "sinf32" | "cosf32" | "sinf64" | "cosf64" =>
611            val.clamp(IeeeFloat::<S>::one().neg(), IeeeFloat::<S>::one()),
612        // exp: [0, +INF]
613        "expf32" | "exp2f32" | "expf64" | "exp2f64" =>
614            IeeeFloat::<S>::maximum(val, IeeeFloat::<S>::ZERO),
615        _ => val,
616    }
617}