Skip to main content

miri/intrinsics/
math.rs

1use rustc_apfloat::ieee::{DoubleS, HalfS, IeeeFloat, Semantics, SingleS};
2use rustc_apfloat::{self, Float, FloatConvert, Round};
3use rustc_middle::mir;
4use rustc_middle::ty::{self, FloatTy};
5
6use self::math::{HostFloatOperation, HostUnaryFloatOp, IeeeExt, host_unary_float_op};
7use super::check_intrinsic_arg_count;
8use crate::*;
9
10fn sqrt<'tcx, F: Float + FloatConvert<F> + Into<Scalar>>(
11    this: &mut MiriInterpCx<'tcx>,
12    args: &[OpTy<'tcx>],
13    dest: &PlaceTy<'tcx>,
14) -> InterpResult<'tcx> {
15    let [f] = check_intrinsic_arg_count(args)?;
16    math::sqrt_op::<F>(this, f, dest)
17}
18
19/// Determine which float operation on which type this is.
20fn is_host_unary_float_op(
21    intrinsic_name: &str,
22    generic_args: ty::GenericArgsRef<'_>,
23) -> Option<(FloatTy, HostUnaryFloatOp)> {
24    let host_float_op = match intrinsic_name {
25        "sin" => HostUnaryFloatOp::Sin,
26        "cos" => HostUnaryFloatOp::Cos,
27        "exp" => HostUnaryFloatOp::Exp,
28        "exp2" => HostUnaryFloatOp::Exp2,
29        "log" => HostUnaryFloatOp::Log,
30        "log10" => HostUnaryFloatOp::Log10,
31        "log2" => HostUnaryFloatOp::Log2,
32        _ => return None,
33    };
34
35    let ty::Float(float_ty) = *generic_args.type_at(0).kind() else {
36        bug!("`{intrinsic_name}` intrinsic called on non-float type");
37    };
38    Some((float_ty, host_float_op))
39}
40
41fn pow_intrinsic<'tcx, S: Semantics>(
42    this: &mut MiriInterpCx<'tcx>,
43    args: &[OpTy<'tcx>],
44    dest: &PlaceTy<'tcx>,
45) -> InterpResult<'tcx, ()>
46where
47    IeeeFloat<S>: HostFloatOperation + IeeeExt + Float + Into<Scalar>,
48{
49    let [f1, f2] = check_intrinsic_arg_count(args)?;
50    let f1: IeeeFloat<S> = this.read_scalar(f1)?.to_float()?;
51    let f2: IeeeFloat<S> = this.read_scalar(f2)?.to_float()?;
52
53    let res = math::fixed_float_value(this, "pow", &[f1, f2]).unwrap_or_else(|| {
54        // Using host floats (but it's fine, this operation does not have guaranteed precision).
55        let res = f1.host_powf(f2);
56
57        // Apply a relative error of 4ULP to introduce some non-determinism
58        // simulating imprecise implementations and optimizations.
59        math::apply_random_float_error_ulp(this, res, 4)
60    });
61    let res = this.adjust_nan(res, &[f1, f2]);
62    this.write_scalar(res, dest)?;
63    interp_ok(())
64}
65fn powi_intrinsic<'tcx, S: Semantics>(
66    this: &mut MiriInterpCx<'tcx>,
67    args: &[OpTy<'tcx>],
68    dest: &PlaceTy<'tcx>,
69) -> InterpResult<'tcx, ()>
70where
71    IeeeFloat<S>: HostFloatOperation + IeeeExt + Float + Into<Scalar>,
72{
73    let [f, i] = check_intrinsic_arg_count(args)?;
74    let f: IeeeFloat<S> = this.read_scalar(f)?.to_float()?;
75    let i = this.read_scalar(i)?.to_i32()?;
76
77    let res = math::fixed_powi_value(this, f, i).unwrap_or_else(|| {
78        // Using host floats (but it's fine, this operation does not have guaranteed precision).
79        let res = f.host_powi(i);
80
81        // Apply a relative error of 4ULP to introduce some non-determinism
82        // simulating imprecise implementations and optimizations.
83        math::apply_random_float_error_ulp(this, res, 4)
84    });
85    let res = this.adjust_nan(res, &[f]);
86    this.write_scalar(res, dest)?;
87    interp_ok(())
88}
89
90impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
91pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
92    fn emulate_math_intrinsic(
93        &mut self,
94        intrinsic_name: &str,
95        generic_args: ty::GenericArgsRef<'tcx>,
96        args: &[OpTy<'tcx>],
97        dest: &PlaceTy<'tcx>,
98    ) -> InterpResult<'tcx, EmulateItemResult> {
99        let this = self.eval_context_mut();
100
101        match intrinsic_name {
102            // Operations we can do with soft-floats.
103            "sqrtf16" => sqrt::<rustc_apfloat::ieee::Half>(this, args, dest)?,
104            "sqrtf32" => sqrt::<rustc_apfloat::ieee::Single>(this, args, dest)?,
105            "sqrtf64" => sqrt::<rustc_apfloat::ieee::Double>(this, args, dest)?,
106            "sqrtf128" => sqrt::<rustc_apfloat::ieee::Quad>(this, args, dest)?,
107
108            #[rustfmt::skip]
109            | "fadd_fast"
110            | "fsub_fast"
111            | "fmul_fast"
112            | "fdiv_fast"
113            | "frem_fast"
114            => {
115                let [a, b] = check_intrinsic_arg_count(args)?;
116                let a = this.read_immediate(a)?;
117                let b = this.read_immediate(b)?;
118                let op = match intrinsic_name {
119                    "fadd_fast" => mir::BinOp::Add,
120                    "fsub_fast" => mir::BinOp::Sub,
121                    "fmul_fast" => mir::BinOp::Mul,
122                    "fdiv_fast" => mir::BinOp::Div,
123                    "frem_fast" => mir::BinOp::Rem,
124                    _ => bug!(),
125                };
126                let float_finite = |x: &ImmTy<'tcx>| -> InterpResult<'tcx, bool> {
127                    let ty::Float(fty) = x.layout.ty.kind() else {
128                        bug!("float_finite: non-float input type {}", x.layout.ty)
129                    };
130                    interp_ok(match fty {
131                        FloatTy::F16 => x.to_scalar().to_f16()?.is_finite(),
132                        FloatTy::F32 => x.to_scalar().to_f32()?.is_finite(),
133                        FloatTy::F64 => x.to_scalar().to_f64()?.is_finite(),
134                        FloatTy::F128 => x.to_scalar().to_f128()?.is_finite(),
135                    })
136                };
137                match (float_finite(&a)?, float_finite(&b)?) {
138                    (false, false) => throw_ub_format!(
139                        "`{intrinsic_name}` intrinsic called with non-finite value as both parameters",
140                    ),
141                    (false, _) => throw_ub_format!(
142                        "`{intrinsic_name}` intrinsic called with non-finite value as first parameter",
143                    ),
144                    (_, false) => throw_ub_format!(
145                        "`{intrinsic_name}` intrinsic called with non-finite value as second parameter",
146                    ),
147                    _ => {}
148                }
149                let res = this.binary_op(op, &a, &b)?;
150                // This cannot be a NaN so we also don't have to apply any non-determinism.
151                // (Also, `binary_op` already called `generate_nan` if needed.)
152                if !float_finite(&res)? {
153                    throw_ub_format!("`{intrinsic_name}` intrinsic produced non-finite value as result");
154                }
155                // Apply a relative error of 4ULP to simulate non-deterministic precision loss
156                // due to optimizations.
157                let res = math::apply_random_float_error_to_imm(this, res, 4)?;
158                this.write_immediate(*res, dest)?;
159            }
160
161            "float_to_int_unchecked" => {
162                let [val] = check_intrinsic_arg_count(args)?;
163                let val = this.read_immediate(val)?;
164
165                let res = this
166                    .float_to_int_checked(&val, dest.layout, Round::TowardZero)?
167                    .ok_or_else(|| {
168                        err_ub_format!(
169                            "`float_to_int_unchecked` intrinsic called on {val} which cannot be represented in target type `{:?}`",
170                            dest.layout.ty
171                        )
172                    })?;
173
174                this.write_immediate(*res, dest)?;
175            }
176
177            // Operations that need host floats.
178            _ if let Some((float_ty, op)) =
179                is_host_unary_float_op(intrinsic_name, generic_args) =>
180            {
181                let [f] = check_intrinsic_arg_count(args)?;
182                match float_ty {
183                    FloatTy::F16 => host_unary_float_op::<HalfS>(this, f, op, dest)?,
184                    FloatTy::F32 => host_unary_float_op::<SingleS>(this, f, op, dest)?,
185                    FloatTy::F64 => host_unary_float_op::<DoubleS>(this, f, op, dest)?,
186                    FloatTy::F128 => todo!("f128"), // FIXME(f128)
187                };
188            }
189
190            "powf16" => pow_intrinsic::<HalfS>(this, args, dest)?,
191            "powf32" => pow_intrinsic::<SingleS>(this, args, dest)?,
192            "powf64" => pow_intrinsic::<DoubleS>(this, args, dest)?,
193            "powf128" => todo!("f128"), // FIXME(f128)
194
195            "powif16" => powi_intrinsic::<HalfS>(this, args, dest)?,
196            "powif32" => powi_intrinsic::<SingleS>(this, args, dest)?,
197            "powif64" => powi_intrinsic::<DoubleS>(this, args, dest)?,
198            "powif128" => todo!("f128"), // FIXME(f128)
199
200            _ => return interp_ok(EmulateItemResult::NotSupported),
201        }
202
203        interp_ok(EmulateItemResult::NeedsReturn)
204    }
205}
206
207/// Compute a CRC32 checksum using the given polynomial.
208///
209/// `bit_size` is the number of relevant data bits (8, 16, 32, or 64).
210/// Only the low `bit_size` bits of `data` are used; higher bits must be zero.
211/// `polynomial` includes the leading 1 bit (e.g. `0x11EDC6F41` for CRC32C).
212///
213/// Following hardware CRC conventions, `crc` and `data` bits are assumed to be reversed,
214/// and output bits will be equally reversed.
215pub(crate) fn compute_crc32(crc: u32, data: u64, bit_size: u32, polynomial: u128) -> u32 {
216    assert!(
217        bit_size == 64 || data < 1u64.strict_shl(bit_size),
218        "crc32: `data` is larger than {bit_size} bits"
219    );
220    // Bit-reverse inputs to match hardware CRC conventions.
221    let crc = u128::from(crc.reverse_bits());
222    // Reverse all 64 bits of `data`, then shift right by `64 - bit_size`. This
223    // discards the (now-reversed) higher bits, leaving only the reversed low
224    // `bit_size` bits in the lowest positions (with zeros above).
225    let v = u128::from(data.reverse_bits() >> (64u32.strict_sub(bit_size)));
226
227    // Perform polynomial division modulo 2.
228    // The algorithm for the division is an adapted version of the
229    // schoolbook division algorithm used for normal integer or polynomial
230    // division. In this context, the quotient is not calculated, since
231    // only the remainder is needed.
232    //
233    // The algorithm works as follows:
234    // 1. Pull down digits until division can be performed. In the context of division
235    //    modulo 2 it means locating the most significant digit of the dividend and shifting
236    //    the divisor such that the position of the divisors most significand digit and the
237    //    dividends most significand digit match.
238    // 2. Perform a division and determine the remainder. Since it is arithmetic modulo 2,
239    //    this operation is a simple bitwise exclusive or.
240    // 3. Repeat steps 1. and 2. until the full remainder is calculated. This is the case
241    //    once the degree of the remainder polynomial is smaller than the degree of the
242    //    divisor polynomial. In other words, the number of leading zeros of the remainder
243    //    is larger than the number of leading zeros of the divisor. It is important to
244    //    note that standard arithmetic comparison is not applicable here:
245    //    0b10011 / 0b11111 = 0b01100 is a valid division, even though the dividend is
246    //    smaller than the divisor.
247    let mut dividend = (crc << bit_size) ^ (v << 32);
248    while dividend.leading_zeros() <= polynomial.leading_zeros() {
249        dividend ^= (polynomial << polynomial.leading_zeros()) >> dividend.leading_zeros();
250    }
251
252    u32::try_from(dividend).unwrap().reverse_bits()
253}
254
255// sha256 primitives shared by the x86 and aarch64 intrinsics. Math helpers adapted from RustCrypto soft impl:
256// https://github.com/RustCrypto/hashes/blob/3d2bc57db40fd6aeb25d6c6da98d67e2784c2985/sha2/src/sha256/soft/compact.rs
257pub(crate) mod sha256 {
258    pub(crate) fn sigma0(x: u32) -> u32 {
259        x.rotate_right(7) ^ x.rotate_right(18) ^ (x >> 3)
260    }
261
262    pub(crate) fn sigma1(x: u32) -> u32 {
263        x.rotate_right(17) ^ x.rotate_right(19) ^ (x >> 10)
264    }
265
266    /// One round of the compression; `wk` is the round's `w[i] + k[i]`.
267    pub(crate) fn round(state: [u32; 8], wk: u32) -> [u32; 8] {
268        let [a, b, c, d, e, f, g, h] = state;
269
270        let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
271        let ch = (e & f) ^ ((!e) & g);
272        let t1 = s1.wrapping_add(ch).wrapping_add(wk).wrapping_add(h);
273
274        let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
275        let maj = (a & b) ^ (a & c) ^ (b & c);
276        let t2 = s0.wrapping_add(maj);
277
278        [
279            t1.wrapping_add(t2), // a
280            a,                   // b
281            b,                   // c
282            c,                   // d
283            d.wrapping_add(t1),  // e
284            e,                   // f
285            f,                   // g
286            g,                   // h
287        ]
288    }
289}