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/// AES primitives
256pub(crate) mod aes {
257    /// AES S-box
258    ///
259    /// Source: [NIST Advanced Encryption Standar][1], Figure 7 (page 16)
260    ///
261    /// [1]: https://tsapps.nist.gov/publication/get_pdf.cfm?pub_id=901427
262    const SBOX: [u8; 256] = [
263        0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab,
264        0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4,
265        0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71,
266        0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2,
267        0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6,
268        0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb,
269        0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45,
270        0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5,
271        0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44,
272        0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a,
273        0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49,
274        0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d,
275        0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25,
276        0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e,
277        0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1,
278        0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
279        0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb,
280        0x16,
281    ];
282
283    /// Applies S-box substitution to each byte of a 32-bit word
284    pub(crate) fn sub_word(word: u32) -> u32 {
285        let bytes = word.to_ne_bytes().map(|b| SBOX[usize::from(b)]);
286        u32::from_ne_bytes(bytes)
287    }
288}
289
290// sha256 primitives shared by the x86 and aarch64 intrinsics. Math helpers adapted from RustCrypto soft impl:
291// https://github.com/RustCrypto/hashes/blob/3d2bc57db40fd6aeb25d6c6da98d67e2784c2985/sha2/src/sha256/soft/compact.rs
292pub(crate) mod sha256 {
293    pub(crate) fn sigma0(x: u32) -> u32 {
294        x.rotate_right(7) ^ x.rotate_right(18) ^ (x >> 3)
295    }
296
297    pub(crate) fn sigma1(x: u32) -> u32 {
298        x.rotate_right(17) ^ x.rotate_right(19) ^ (x >> 10)
299    }
300
301    /// One round of the compression; `wk` is the round's `w[i] + k[i]`.
302    pub(crate) fn round(state: [u32; 8], wk: u32) -> [u32; 8] {
303        let [a, b, c, d, e, f, g, h] = state;
304
305        let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
306        let ch = (e & f) ^ ((!e) & g);
307        let t1 = s1.wrapping_add(ch).wrapping_add(wk).wrapping_add(h);
308
309        let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
310        let maj = (a & b) ^ (a & c) ^ (b & c);
311        let t2 = s0.wrapping_add(maj);
312
313        [
314            t1.wrapping_add(t2), // a
315            a,                   // b
316            b,                   // c
317            c,                   // d
318            d.wrapping_add(t1),  // e
319            e,                   // f
320            f,                   // g
321            g,                   // h
322        ]
323    }
324}