Skip to main content

miri/intrinsics/
aarch64.rs

1use rustc_middle::mir::BinOp;
2use rustc_span::Symbol;
3
4use crate::intrinsics::math::{compute_crc32, sha256};
5use crate::*;
6
7impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
8pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
9    fn emulate_aarch64_intrinsic(
10        &mut self,
11        link_name: Symbol,
12        args: &[OpTy<'tcx>],
13        dest: &MPlaceTy<'tcx>,
14    ) -> InterpResult<'tcx, EmulateItemResult> {
15        let ecx = self.eval_context_mut();
16        // Prefix should have already been checked.
17        let unprefixed_name = link_name.as_str().strip_prefix("llvm.aarch64.").unwrap();
18        match unprefixed_name {
19            // Used to implement the vpmaxq_u8 function.
20            // Computes the maximum of adjacent pairs; the first half of the output is produced from the
21            // `left` input, the second half of the output from the `right` input.
22            // https://developer.arm.com/architectures/instruction-sets/intrinsics/vpmaxq_u8
23            "neon.umaxp.v16i8" => {
24                let [left, right] = ecx.check_shim_sig_llvm_intrinsic(link_name, args)?;
25
26                let (left, left_len) = ecx.project_to_simd(left)?;
27                let (right, right_len) = ecx.project_to_simd(right)?;
28                let (dest, lane_count) = ecx.project_to_simd(dest)?;
29                assert_eq!(left_len, right_len);
30                assert_eq!(lane_count, left_len);
31
32                for lane_idx in 0..lane_count {
33                    let src = if lane_idx < (lane_count / 2) { &left } else { &right };
34                    let src_idx = lane_idx.strict_rem(lane_count / 2);
35
36                    let lhs_lane =
37                        ecx.read_immediate(&ecx.project_index(src, src_idx.strict_mul(2))?)?;
38                    let rhs_lane = ecx.read_immediate(
39                        &ecx.project_index(src, src_idx.strict_mul(2).strict_add(1))?,
40                    )?;
41
42                    // Compute `if lhs > rhs { lhs } else { rhs }`, i.e., `max`.
43                    let res_lane =
44                        if ecx.binary_op(BinOp::Gt, &lhs_lane, &rhs_lane)?.to_scalar().to_bool()? {
45                            lhs_lane
46                        } else {
47                            rhs_lane
48                        };
49
50                    let dest = ecx.project_index(&dest, lane_idx)?;
51                    ecx.write_immediate(*res_lane, &dest)?;
52                }
53            }
54
55            // Wrapping pairwise addition.
56            //
57            // Concatenates the two input vectors and adds adjacent elements. For input vectors `v`
58            // and `w` this computes `[v0 + v1, v2 + v3, ..., w0 + w1, w2 + w3, ...]`, using
59            // wrapping addition for `+`.
60            //
61            // Used by `vpadd_{s8, u8, s16, u16, s32, u32}`.
62            name if name.starts_with("neon.addp.") => {
63                let [left, right] = ecx.check_shim_sig_llvm_intrinsic(link_name, args)?;
64
65                let (left, left_len) = ecx.project_to_simd(left)?;
66                let (right, right_len) = ecx.project_to_simd(right)?;
67                let (dest, dest_len) = ecx.project_to_simd(dest)?;
68
69                assert_eq!(left_len, right_len);
70                assert_eq!(left_len, dest_len);
71
72                assert_eq!(left.layout, right.layout);
73                assert_eq!(left.layout, dest.layout);
74
75                assert!(dest_len.is_multiple_of(2));
76                let half_len = dest_len.strict_div(2);
77
78                for lane_idx in 0..dest_len {
79                    // The left and right vectors are concatenated.
80                    let (src, src_pair_idx) = if lane_idx < half_len {
81                        (&left, lane_idx)
82                    } else {
83                        (&right, lane_idx.strict_sub(half_len))
84                    };
85                    // Convert "pair index" into "index of first element of the pair".
86                    let i = src_pair_idx.strict_mul(2);
87
88                    let lhs = ecx.read_immediate(&ecx.project_index(src, i)?)?;
89                    let rhs = ecx.read_immediate(&ecx.project_index(src, i.strict_add(1))?)?;
90
91                    // Wrapping addition on the element type.
92                    let sum = ecx.binary_op(BinOp::Add, &lhs, &rhs)?;
93
94                    let dst_lane = ecx.project_index(&dest, lane_idx)?;
95                    ecx.write_immediate(*sum, &dst_lane)?;
96                }
97            }
98
99            // Widening pairwise addition.
100            //
101            // Takes a single input vector, and an output vector with half as many lanes and double
102            // the element width. Takes adjacent pairs of elements, widens both, and then adds them
103            // together.
104            //
105            // Used by `vpaddl_{u8, u16, u32}` and `vpaddlq_{u8, u16, u32}`.
106            name if name.starts_with("neon.uaddlp.") => {
107                let [src] = ecx.check_shim_sig_llvm_intrinsic(link_name, args)?;
108
109                let (src, src_len) = ecx.project_to_simd(src)?;
110                let (dest, dest_len) = ecx.project_to_simd(dest)?;
111
112                // Operates pairwise, so src has twice as many lanes.
113                assert_eq!(src_len, dest_len.strict_mul(2));
114
115                let src_elem_size = src.layout.field(ecx, 0).size;
116                let dest_elem_size = dest.layout.field(ecx, 0).size;
117
118                // Widens, so dest elements must be exactly twice as wide.
119                assert_eq!(dest_elem_size.bytes(), src_elem_size.bytes().strict_mul(2));
120
121                for dest_idx in 0..dest_len {
122                    let src_idx = dest_idx.strict_mul(2);
123
124                    let a_scalar = ecx.read_scalar(&ecx.project_index(&src, src_idx)?)?;
125                    let b_scalar =
126                        ecx.read_scalar(&ecx.project_index(&src, src_idx.strict_add(1))?)?;
127
128                    let a_val = a_scalar.to_uint(src_elem_size)?;
129                    let b_val = b_scalar.to_uint(src_elem_size)?;
130
131                    // Use addition on u128 to simulate widening addition for the destination type.
132                    // This cannot wrap since the element type is at most u64.
133                    let sum = a_val.strict_add(b_val);
134
135                    let dst_lane = ecx.project_index(&dest, dest_idx)?;
136                    ecx.write_scalar(Scalar::from_uint(sum, dest_elem_size), &dst_lane)?;
137                }
138            }
139
140            // Signed saturating doubling multiply returning the high half.
141            //
142            // Used by the `vqdmulh*` functions.
143            //
144            // This LLVM intrinsic multiplies the values of corresponding elements of the two source
145            // vector registers (which are signed integers), doubles the results, places the most significant half of the
146            // final results (using a saturating cast to fit the element type) into a vector, and writes the vector to the destination register.
147            //
148            // https://developer.arm.com/architectures/instruction-sets/intrinsics#f:@navigationhierarchiessimdisa=[Neon]&q=vqdmulh
149            name if name.starts_with("neon.sqdmulh.") => {
150                let [left, right] = ecx.check_shim_sig_llvm_intrinsic(link_name, args)?;
151
152                let (left, left_len) = ecx.project_to_simd(left)?;
153                let (right, right_len) = ecx.project_to_simd(right)?;
154                let (dest, dest_len) = ecx.project_to_simd(dest)?;
155                assert_eq!(left_len, right_len);
156                assert_eq!(left_len, dest_len);
157
158                let elem_size = dest.layout.field(ecx, 0).size;
159                let bits = elem_size.bits();
160                let min = elem_size.signed_int_min();
161                let max = elem_size.signed_int_max();
162
163                for i in 0..dest_len {
164                    let a = ecx.read_scalar(&ecx.project_index(&left, i)?)?.to_int(elem_size)?;
165                    let b = ecx.read_scalar(&ecx.project_index(&right, i)?)?.to_int(elem_size)?;
166
167                    // Uses i128 arithmetic, which cannot overflow because the intrinsic takes at most i32.
168                    let doubled = a.strict_mul(b).strict_mul(2);
169                    let res = (doubled >> bits).clamp(min, max);
170
171                    ecx.write_scalar(
172                        Scalar::from_int(res, elem_size),
173                        &ecx.project_index(&dest, i)?,
174                    )?;
175                }
176            }
177
178            // Vector table lookup: each index selects a byte from the table,
179            // out-of-range -> 0.
180            //
181            // Used to implement the vtblN, vqtblN and vqtblNq set of functions, e.g.:
182            //
183            // - https://developer.arm.com/architectures/instruction-sets/intrinsics/vtbl1_u8
184            // - https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbl1_u8
185            // - https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbl1q_s8
186            // - https://developer.arm.com/architectures/instruction-sets/intrinsics/vtbl2_u8
187            // - https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbl4q_s8
188            //
189            // LLVM does not have a portable shuffle that takes non-const indices
190            // so we need to implement this ourselves.
191            _ if unprefixed_name.starts_with("neon.tbl") => {
192                // The table segments always have 16 elements, the index vector and output vector
193                // have either 8 or 16 elements.
194                let (table_segments, indices) = match unprefixed_name {
195                    "neon.tbl1.v8i8" | "neon.tbl1.v16i8" => {
196                        let [table, indices] =
197                            ecx.check_shim_sig_llvm_intrinsic(link_name, args)?;
198                        let (table, len) = ecx.project_to_simd(table)?;
199                        assert_eq!(len, 16);
200                        (vec![table], indices)
201                    }
202                    "neon.tbl2.v8i8" | "neon.tbl2.v16i8" => {
203                        let [table0, table1, indices] =
204                            ecx.check_shim_sig_llvm_intrinsic(link_name, args)?;
205                        let (table0, len0) = ecx.project_to_simd(table0)?;
206                        let (table1, len1) = ecx.project_to_simd(table1)?;
207                        assert_eq!([len0, len1], [16; 2]);
208                        (vec![table0, table1], indices)
209                    }
210                    "neon.tbl3.v8i8" | "neon.tbl3.v16i8" => {
211                        let [table0, table1, table2, indices] =
212                            ecx.check_shim_sig_llvm_intrinsic(link_name, args)?;
213                        let (table0, len0) = ecx.project_to_simd(table0)?;
214                        let (table1, len1) = ecx.project_to_simd(table1)?;
215                        let (table2, len2) = ecx.project_to_simd(table2)?;
216                        assert_eq!([len0, len1, len2], [16; 3]);
217                        (vec![table0, table1, table2], indices)
218                    }
219                    "neon.tbl4.v8i8" | "neon.tbl4.v16i8" => {
220                        let [table0, table1, table2, table3, indices] =
221                            ecx.check_shim_sig_llvm_intrinsic(link_name, args)?;
222                        let (table0, len0) = ecx.project_to_simd(table0)?;
223                        let (table1, len1) = ecx.project_to_simd(table1)?;
224                        let (table2, len2) = ecx.project_to_simd(table2)?;
225                        let (table3, len3) = ecx.project_to_simd(table3)?;
226                        assert_eq!([len0, len1, len2, len3], [16; 4]);
227                        (vec![table0, table1, table2, table3], indices)
228                    }
229                    _ => unreachable!(),
230                };
231
232                let (indices, idx_len) = ecx.project_to_simd(indices)?;
233                let (dest, dest_len) = ecx.project_to_simd(dest)?;
234                assert_eq!(idx_len, dest_len);
235
236                for i in 0..dest_len {
237                    let idx = ecx.read_immediate(&ecx.project_index(&indices, i)?)?;
238                    let idx = idx.to_scalar().to_u8()?;
239
240                    // The LLVM intrinsic table segments always have 16 elements.
241                    let val = if usize::from(idx) < table_segments.len().strict_mul(16) {
242                        let table = &table_segments[usize::from(idx.strict_div(16))];
243                        let lane = u64::from(idx.strict_rem(16));
244                        let t = ecx.read_immediate(&ecx.project_index(table, lane)?)?;
245                        t.to_scalar()
246                    } else {
247                        Scalar::from_u8(0)
248                    };
249
250                    ecx.write_scalar(val, &ecx.project_index(&dest, i)?)?;
251                }
252            }
253            // Used to implement the __crc32{b,h,w,x} and __crc32c{b,h,w,x} functions.
254            // Polynomial 0x04C11DB7 (standard CRC-32):
255            // https://developer.arm.com/documentation/ddi0602/latest/Base-Instructions/CRC32B--CRC32H--CRC32W--CRC32X--CRC32-checksum-
256            // Polynomial 0x1EDC6F41 (CRC-32C / Castagnoli):
257            // https://developer.arm.com/documentation/ddi0602/latest/Base-Instructions/CRC32CB--CRC32CH--CRC32CW--CRC32CX--CRC32C-checksum-
258            "crc32b" | "crc32h" | "crc32w" | "crc32x" | "crc32cb" | "crc32ch" | "crc32cw"
259            | "crc32cx" => {
260                ecx.expect_target_feature_for_intrinsic(link_name, "crc")?;
261                // The polynomial constants below include the leading 1 bit
262                // (e.g. 0x104C11DB7 instead of 0x04C11DB7) which the ARM docs
263                // omit but the polynomial division algorithm requires.
264                let (bit_size, polynomial): (u32, u128) = match unprefixed_name {
265                    "crc32b" => (8, 0x104C11DB7),
266                    "crc32h" => (16, 0x104C11DB7),
267                    "crc32w" => (32, 0x104C11DB7),
268                    "crc32x" => (64, 0x104C11DB7),
269                    "crc32cb" => (8, 0x11EDC6F41),
270                    "crc32ch" => (16, 0x11EDC6F41),
271                    "crc32cw" => (32, 0x11EDC6F41),
272                    "crc32cx" => (64, 0x11EDC6F41),
273                    _ => unreachable!(),
274                };
275
276                let [crc, data] = ecx.check_shim_sig_llvm_intrinsic(link_name, args)?;
277                let crc = ecx.read_scalar(crc)?;
278                let data = ecx.read_scalar(data)?;
279
280                // The CRC accumulator is always u32. The data argument is u32 for
281                // b/h/w variants and u64 for the x variant, per the LLVM intrinsic
282                // definitions (all b/h/w take i32, only x takes i64).
283                // https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/IR/IntrinsicsAArch64.td
284                // If the higher bits are non-zero, `compute_crc32` will panic. We should probably
285                // raise a proper error instead, but outside stdarch nobody can trigger this anyway.
286                let crc = crc.to_u32()?;
287                let data = if bit_size == 64 { data.to_u64()? } else { u64::from(data.to_u32()?) };
288
289                let result = compute_crc32(crc, data, bit_size, polynomial);
290                ecx.write_scalar(Scalar::from_u32(result), dest)?;
291            }
292            // Polynomial multiply long (64-bit x 64-bit -> 128-bit).
293            //
294            // This is the same as "carryless" multiplication, see
295            // <https://en.wikipedia.org/wiki/Carry-less_product#Multiplication_of_polynomials>.
296            //
297            // Used to implement the vmull_p64 and vmull_high_p64 functions.
298            // https://developer.arm.com/architectures/instruction-sets/intrinsics/vmull_p64
299            "neon.pmull64" => {
300                // LLVM and GCC group pmull with the AES intrinsics.
301                // Also see <https://gcc.gnu.org/pipermail/gcc-patches/2023-February/612088.html>.
302                ecx.expect_target_feature_for_intrinsic(link_name, "aes")?;
303
304                let [left, right] = ecx.check_shim_sig_llvm_intrinsic(link_name, args)?;
305                let left = ecx.read_scalar(left)?.to_u64()?;
306                let right = ecx.read_scalar(right)?.to_u64()?;
307
308                let result = left.widening_carryless_mul(right);
309
310                // dest is int8x16_t, transmute to u128 for the write.
311                let dest = dest.transmute(ecx.machine.layouts.u128, ecx)?;
312                ecx.write_scalar(Scalar::from_u128(result), &dest)?;
313            }
314
315            // Used to implement the vsha256hq_u32 function.
316            // https://developer.arm.com/architectures/instruction-sets/intrinsics/vsha256hq_u32
317            "crypto.sha256h" => {
318                ecx.expect_target_feature_for_intrinsic(link_name, "sha2")?;
319
320                let [abcd, efgh, wk] = ecx.check_shim_sig_llvm_intrinsic(link_name, args)?;
321
322                let (abcd, abcd_len) = ecx.project_to_simd(abcd)?;
323                let (efgh, efgh_len) = ecx.project_to_simd(efgh)?;
324                let (wk, wk_len) = ecx.project_to_simd(wk)?;
325                let (dest, dest_len) = ecx.project_to_simd(dest)?;
326
327                assert_eq!(abcd_len, 4);
328                assert_eq!(efgh_len, 4);
329                assert_eq!(wk_len, 4);
330                assert_eq!(dest_len, 4);
331
332                let abcd: [u32; 4] = read_u32x4(ecx, &abcd)?;
333                let efgh: [u32; 4] = read_u32x4(ecx, &efgh)?;
334                let wk: [u32; 4] = read_u32x4(ecx, &wk)?;
335
336                let result = sha256h(abcd, efgh, wk);
337
338                write_u32x4(ecx, &dest, result)?;
339            }
340            // Used to implement the vsha256h2q_u32 function.
341            // https://developer.arm.com/architectures/instruction-sets/intrinsics/vsha256h2q_u32
342            "crypto.sha256h2" => {
343                ecx.expect_target_feature_for_intrinsic(link_name, "sha2")?;
344
345                let [efgh, abcd, wk] = ecx.check_shim_sig_llvm_intrinsic(link_name, args)?;
346
347                let (efgh, efgh_len) = ecx.project_to_simd(efgh)?;
348                let (abcd, abcd_len) = ecx.project_to_simd(abcd)?;
349                let (wk, wk_len) = ecx.project_to_simd(wk)?;
350                let (dest, dest_len) = ecx.project_to_simd(dest)?;
351
352                assert_eq!(efgh_len, 4);
353                assert_eq!(abcd_len, 4);
354                assert_eq!(wk_len, 4);
355                assert_eq!(dest_len, 4);
356
357                let efgh: [u32; 4] = read_u32x4(ecx, &efgh)?;
358                let abcd: [u32; 4] = read_u32x4(ecx, &abcd)?;
359                let wk: [u32; 4] = read_u32x4(ecx, &wk)?;
360
361                let result = sha256h2(efgh, abcd, wk);
362
363                write_u32x4(ecx, &dest, result)?;
364            }
365            // Used to implement the vsha256su0q_u32 function.
366            // https://developer.arm.com/architectures/instruction-sets/intrinsics/vsha256su0q_u32
367            "crypto.sha256su0" => {
368                ecx.expect_target_feature_for_intrinsic(link_name, "sha2")?;
369
370                let [a, b] = ecx.check_shim_sig_llvm_intrinsic(link_name, args)?;
371
372                let (a, a_len) = ecx.project_to_simd(a)?;
373                let (b, b_len) = ecx.project_to_simd(b)?;
374                let (dest, dest_len) = ecx.project_to_simd(dest)?;
375
376                assert_eq!(a_len, 4);
377                assert_eq!(b_len, 4);
378                assert_eq!(dest_len, 4);
379
380                let a: [u32; 4] = read_u32x4(ecx, &a)?;
381                let b: [u32; 4] = read_u32x4(ecx, &b)?;
382
383                let result = sha256su0(a, b);
384
385                write_u32x4(ecx, &dest, result)?;
386            }
387            // Used to implement the vsha256su1q_u32 function.
388            // https://developer.arm.com/architectures/instruction-sets/intrinsics/vsha256su1q_u32
389            "crypto.sha256su1" => {
390                ecx.expect_target_feature_for_intrinsic(link_name, "sha2")?;
391
392                let [a, b, c] = ecx.check_shim_sig_llvm_intrinsic(link_name, args)?;
393
394                let (a, a_len) = ecx.project_to_simd(a)?;
395                let (b, b_len) = ecx.project_to_simd(b)?;
396                let (c, c_len) = ecx.project_to_simd(c)?;
397                let (dest, dest_len) = ecx.project_to_simd(dest)?;
398
399                assert_eq!(a_len, 4);
400                assert_eq!(b_len, 4);
401                assert_eq!(c_len, 4);
402                assert_eq!(dest_len, 4);
403
404                let a: [u32; 4] = read_u32x4(ecx, &a)?;
405                let b: [u32; 4] = read_u32x4(ecx, &b)?;
406                let c: [u32; 4] = read_u32x4(ecx, &c)?;
407
408                let result = sha256su1(a, b, c);
409
410                write_u32x4(ecx, &dest, result)?;
411            }
412            _ => return interp_ok(EmulateItemResult::NotSupported),
413        }
414        interp_ok(EmulateItemResult::NeedsReturn)
415    }
416}
417
418/// Reads a `[u32; 4]` array.
419fn read_u32x4<'c>(ecx: &mut MiriInterpCx<'c>, vec: &OpTy<'c>) -> InterpResult<'c, [u32; 4]> {
420    let mut res = [0; 4];
421    for (i, dst) in res.iter_mut().enumerate() {
422        let projected = &ecx.project_index(vec, i.try_into().unwrap())?;
423        *dst = ecx.read_scalar(projected)?.to_u32()?;
424    }
425    interp_ok(res)
426}
427
428fn write_u32x4<'c>(
429    ecx: &mut MiriInterpCx<'c>,
430    dest: &MPlaceTy<'c>,
431    val: [u32; 4],
432) -> InterpResult<'c, ()> {
433    for (i, part) in val.into_iter().enumerate() {
434        let projected = &ecx.project_index(dest, i.to_u64())?;
435        ecx.write_scalar(Scalar::from_u32(part), projected)?;
436    }
437    interp_ok(())
438}
439
440fn sha256su0(v0: [u32; 4], v1: [u32; 4]) -> [u32; 4] {
441    [
442        v0[0].wrapping_add(sha256::sigma0(v0[1])),
443        v0[1].wrapping_add(sha256::sigma0(v0[2])),
444        v0[2].wrapping_add(sha256::sigma0(v0[3])),
445        v0[3].wrapping_add(sha256::sigma0(v1[0])),
446    ]
447}
448
449fn sha256su1(v0: [u32; 4], v1: [u32; 4], v2: [u32; 4]) -> [u32; 4] {
450    let r0 = v0[0].wrapping_add(v1[1]).wrapping_add(sha256::sigma1(v2[2]));
451    let r1 = v0[1].wrapping_add(v1[2]).wrapping_add(sha256::sigma1(v2[3]));
452    let r2 = v0[2].wrapping_add(v1[3]).wrapping_add(sha256::sigma1(r0));
453    let r3 = v0[3].wrapping_add(v2[0]).wrapping_add(sha256::sigma1(r1));
454    [r0, r1, r2, r3]
455}
456
457// SHA256H/SHA256H2 do four compression rounds on the abcd/efgh layout.
458// https://developer.arm.com/architectures/instruction-sets/intrinsics/#f:@navigationhierarchiesinstructiongroup=[Cryptography,SHA256]
459fn sha256hash(abcd: [u32; 4], efgh: [u32; 4], wk: [u32; 4]) -> ([u32; 4], [u32; 4]) {
460    let mut state = [abcd[0], abcd[1], abcd[2], abcd[3], efgh[0], efgh[1], efgh[2], efgh[3]];
461    for &wk_i in &wk {
462        state = sha256::round(state, wk_i);
463    }
464    ([state[0], state[1], state[2], state[3]], [state[4], state[5], state[6], state[7]])
465}
466
467fn sha256h(abcd: [u32; 4], efgh: [u32; 4], wk: [u32; 4]) -> [u32; 4] {
468    sha256hash(abcd, efgh, wk).0
469}
470
471// sha256h2 takes efgh as the first argument. abcd and efgh are swapped when calling sha256hash.
472fn sha256h2(efgh: [u32; 4], abcd: [u32; 4], wk: [u32; 4]) -> [u32; 4] {
473    sha256hash(abcd, efgh, wk).1
474}