Skip to main content

miri/intrinsics/
aarch64.rs

1use std::assert_matches;
2
3use rustc_middle::mir::BinOp;
4use rustc_span::Symbol;
5
6use crate::intrinsics::math::compute_crc32;
7use crate::*;
8
9impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
10pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
11    fn emulate_aarch64_intrinsic(
12        &mut self,
13        link_name: Symbol,
14        args: &[OpTy<'tcx>],
15        dest: &MPlaceTy<'tcx>,
16    ) -> InterpResult<'tcx, EmulateItemResult> {
17        let this = self.eval_context_mut();
18        // Prefix should have already been checked.
19        let unprefixed_name = link_name.as_str().strip_prefix("llvm.aarch64.").unwrap();
20        match unprefixed_name {
21            // Used to implement the vpmaxq_u8 function.
22            // Computes the maximum of adjacent pairs; the first half of the output is produced from the
23            // `left` input, the second half of the output from the `right` input.
24            // https://developer.arm.com/architectures/instruction-sets/intrinsics/vpmaxq_u8
25            "neon.umaxp.v16i8" => {
26                let [left, right] = this.check_shim_sig_unadjusted(link_name, args)?;
27
28                let (left, left_len) = this.project_to_simd(left)?;
29                let (right, right_len) = this.project_to_simd(right)?;
30                let (dest, lane_count) = this.project_to_simd(dest)?;
31                assert_eq!(left_len, right_len);
32                assert_eq!(lane_count, left_len);
33
34                for lane_idx in 0..lane_count {
35                    let src = if lane_idx < (lane_count / 2) { &left } else { &right };
36                    let src_idx = lane_idx.strict_rem(lane_count / 2);
37
38                    let lhs_lane =
39                        this.read_immediate(&this.project_index(src, src_idx.strict_mul(2))?)?;
40                    let rhs_lane = this.read_immediate(
41                        &this.project_index(src, src_idx.strict_mul(2).strict_add(1))?,
42                    )?;
43
44                    // Compute `if lhs > rhs { lhs } else { rhs }`, i.e., `max`.
45                    let res_lane = if this
46                        .binary_op(BinOp::Gt, &lhs_lane, &rhs_lane)?
47                        .to_scalar()
48                        .to_bool()?
49                    {
50                        lhs_lane
51                    } else {
52                        rhs_lane
53                    };
54
55                    let dest = this.project_index(&dest, lane_idx)?;
56                    this.write_immediate(*res_lane, &dest)?;
57                }
58            }
59
60            // Wrapping pairwise addition.
61            //
62            // Concatenates the two input vectors and adds adjacent elements. For input vectors `v`
63            // and `w` this computes `[v0 + v1, v2 + v3, ..., w0 + w1, w2 + w3, ...]`, using
64            // wrapping addition for `+`.
65            //
66            // Used by `vpadd_{s8, u8, s16, u16, s32, u32}`.
67            name if name.starts_with("neon.addp.") => {
68                let [left, right] = this.check_shim_sig_unadjusted(link_name, args)?;
69
70                let (left, left_len) = this.project_to_simd(left)?;
71                let (right, right_len) = this.project_to_simd(right)?;
72                let (dest, dest_len) = this.project_to_simd(dest)?;
73
74                assert_eq!(left_len, right_len);
75                assert_eq!(left_len, dest_len);
76
77                assert_eq!(left.layout, right.layout);
78                assert_eq!(left.layout, dest.layout);
79
80                assert!(dest_len.is_multiple_of(2));
81                let half_len = dest_len.strict_div(2);
82
83                for lane_idx in 0..dest_len {
84                    // The left and right vectors are concatenated.
85                    let (src, src_pair_idx) = if lane_idx < half_len {
86                        (&left, lane_idx)
87                    } else {
88                        (&right, lane_idx.strict_sub(half_len))
89                    };
90                    // Convert "pair index" into "index of first element of the pair".
91                    let i = src_pair_idx.strict_mul(2);
92
93                    let lhs = this.read_immediate(&this.project_index(src, i)?)?;
94                    let rhs = this.read_immediate(&this.project_index(src, i.strict_add(1))?)?;
95
96                    // Wrapping addition on the element type.
97                    let sum = this.binary_op(BinOp::Add, &lhs, &rhs)?;
98
99                    let dst_lane = this.project_index(&dest, lane_idx)?;
100                    this.write_immediate(*sum, &dst_lane)?;
101                }
102            }
103
104            // Widening pairwise addition.
105            //
106            // Takes a single input vector, and an output vector with half as many lanes and double
107            // the element width. Takes adjacent pairs of elements, widens both, and then adds them
108            // together.
109            //
110            // Used by `vpaddl_{u8, u16, u32}` and `vpaddlq_{u8, u16, u32}`.
111            name if name.starts_with("neon.uaddlp.") => {
112                let [src] = this.check_shim_sig_unadjusted(link_name, args)?;
113
114                let (src, src_len) = this.project_to_simd(src)?;
115                let (dest, dest_len) = this.project_to_simd(dest)?;
116
117                // Operates pairwise, so src has twice as many lanes.
118                assert_eq!(src_len, dest_len.strict_mul(2));
119
120                let src_elem_size = src.layout.field(this, 0).size;
121                let dest_elem_size = dest.layout.field(this, 0).size;
122
123                // Widens, so dest elements must be exactly twice as wide.
124                assert_eq!(dest_elem_size.bytes(), src_elem_size.bytes().strict_mul(2));
125
126                for dest_idx in 0..dest_len {
127                    let src_idx = dest_idx.strict_mul(2);
128
129                    let a_scalar = this.read_scalar(&this.project_index(&src, src_idx)?)?;
130                    let b_scalar =
131                        this.read_scalar(&this.project_index(&src, src_idx.strict_add(1))?)?;
132
133                    let a_val = a_scalar.to_uint(src_elem_size)?;
134                    let b_val = b_scalar.to_uint(src_elem_size)?;
135
136                    // Use addition on u128 to simulate widening addition for the destination type.
137                    // This cannot wrap since the element type is at most u64.
138                    let sum = a_val.strict_add(b_val);
139
140                    let dst_lane = this.project_index(&dest, dest_idx)?;
141                    this.write_scalar(Scalar::from_uint(sum, dest_elem_size), &dst_lane)?;
142                }
143            }
144
145            // Signed saturating doubling multiply returning the high half.
146            //
147            // Used by the `vqdmulh*` functions.
148            //
149            // This LLVM intrinsic multiplies the values of corresponding elements of the two source
150            // vector registers (which are signed integers), doubles the results, places the most significant half of the
151            // final results (using a saturating cast to fit the element type) into a vector, and writes the vector to the destination register.
152            //
153            // https://developer.arm.com/architectures/instruction-sets/intrinsics#f:@navigationhierarchiessimdisa=[Neon]&q=vqdmulh
154            name if name.starts_with("neon.sqdmulh.") => {
155                let [left, right] = this.check_shim_sig_unadjusted(link_name, args)?;
156
157                let (left, left_len) = this.project_to_simd(left)?;
158                let (right, right_len) = this.project_to_simd(right)?;
159                let (dest, dest_len) = this.project_to_simd(dest)?;
160                assert_eq!(left_len, right_len);
161                assert_eq!(left_len, dest_len);
162
163                let elem_size = dest.layout.field(this, 0).size;
164                let bits = elem_size.bits();
165                let min = elem_size.signed_int_min();
166                let max = elem_size.signed_int_max();
167
168                for i in 0..dest_len {
169                    let a = this.read_scalar(&this.project_index(&left, i)?)?.to_int(elem_size)?;
170                    let b = this.read_scalar(&this.project_index(&right, i)?)?.to_int(elem_size)?;
171
172                    // Uses i128 arithmetic, which cannot overflow because the intrinsic takes at most i32.
173                    let doubled = a.strict_mul(b).strict_mul(2);
174                    let res = (doubled >> bits).clamp(min, max);
175
176                    this.write_scalar(
177                        Scalar::from_int(res, elem_size),
178                        &this.project_index(&dest, i)?,
179                    )?;
180                }
181            }
182
183            // Vector table lookup: each index selects a byte from the 8 or 16-byte table,
184            // out-of-range -> 0.
185            //
186            // Used to implement the vqtbl1 and vqtbl1q set of functions, e.g.:
187            //
188            // - https://developer.arm.com/architectures/instruction-sets/intrinsics/vtbl1_u8
189            // - https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbl1q_s8
190            //
191            // LLVM does not have a portable shuffle that takes non-const indices
192            // so we need to implement this ourselves.
193            "neon.tbl1.v8i8" | "neon.tbl1.v16i8" => {
194                let [table, indices] = this.check_shim_sig_unadjusted(link_name, args)?;
195
196                let (table, table_len) = this.project_to_simd(table)?;
197                let (indices, idx_len) = this.project_to_simd(indices)?;
198                let (dest, dest_len) = this.project_to_simd(dest)?;
199                assert_matches!(table_len, 8 | 16);
200                assert_eq!(idx_len, dest_len);
201
202                for i in 0..dest_len {
203                    let idx = this.read_immediate(&this.project_index(&indices, i)?)?;
204                    let idx_u = idx.to_scalar().to_u8()?;
205                    let val = if u64::from(idx_u) < table_len {
206                        let t = this.read_immediate(&this.project_index(&table, idx_u.into())?)?;
207                        t.to_scalar()
208                    } else {
209                        Scalar::from_u8(0)
210                    };
211                    this.write_scalar(val, &this.project_index(&dest, i)?)?;
212                }
213            }
214            // Used to implement the __crc32{b,h,w,x} and __crc32c{b,h,w,x} functions.
215            // Polynomial 0x04C11DB7 (standard CRC-32):
216            // https://developer.arm.com/documentation/ddi0602/latest/Base-Instructions/CRC32B--CRC32H--CRC32W--CRC32X--CRC32-checksum-
217            // Polynomial 0x1EDC6F41 (CRC-32C / Castagnoli):
218            // https://developer.arm.com/documentation/ddi0602/latest/Base-Instructions/CRC32CB--CRC32CH--CRC32CW--CRC32CX--CRC32C-checksum-
219            "crc32b" | "crc32h" | "crc32w" | "crc32x" | "crc32cb" | "crc32ch" | "crc32cw"
220            | "crc32cx" => {
221                this.expect_target_feature_for_intrinsic(link_name, "crc")?;
222                // The polynomial constants below include the leading 1 bit
223                // (e.g. 0x104C11DB7 instead of 0x04C11DB7) which the ARM docs
224                // omit but the polynomial division algorithm requires.
225                let (bit_size, polynomial): (u32, u128) = match unprefixed_name {
226                    "crc32b" => (8, 0x104C11DB7),
227                    "crc32h" => (16, 0x104C11DB7),
228                    "crc32w" => (32, 0x104C11DB7),
229                    "crc32x" => (64, 0x104C11DB7),
230                    "crc32cb" => (8, 0x11EDC6F41),
231                    "crc32ch" => (16, 0x11EDC6F41),
232                    "crc32cw" => (32, 0x11EDC6F41),
233                    "crc32cx" => (64, 0x11EDC6F41),
234                    _ => unreachable!(),
235                };
236
237                let [crc, data] = this.check_shim_sig_unadjusted(link_name, args)?;
238                let crc = this.read_scalar(crc)?;
239                let data = this.read_scalar(data)?;
240
241                // The CRC accumulator is always u32. The data argument is u32 for
242                // b/h/w variants and u64 for the x variant, per the LLVM intrinsic
243                // definitions (all b/h/w take i32, only x takes i64).
244                // https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/IR/IntrinsicsAArch64.td
245                // If the higher bits are non-zero, `compute_crc32` will panic. We should probably
246                // raise a proper error instead, but outside stdarch nobody can trigger this anyway.
247                let crc = crc.to_u32()?;
248                let data = if bit_size == 64 { data.to_u64()? } else { u64::from(data.to_u32()?) };
249
250                let result = compute_crc32(crc, data, bit_size, polynomial);
251                this.write_scalar(Scalar::from_u32(result), dest)?;
252            }
253            // Polynomial multiply long (64-bit x 64-bit -> 128-bit).
254            //
255            // This is the same as "carryless" multiplication, see
256            // <https://en.wikipedia.org/wiki/Carry-less_product#Multiplication_of_polynomials>.
257            //
258            // Used to implement the vmull_p64 and vmull_high_p64 functions.
259            // https://developer.arm.com/architectures/instruction-sets/intrinsics/vmull_p64
260            "neon.pmull64" => {
261                // LLVM and GCC group pmull with the AES intrinsics.
262                // Also see <https://gcc.gnu.org/pipermail/gcc-patches/2023-February/612088.html>.
263                this.expect_target_feature_for_intrinsic(link_name, "aes")?;
264
265                let [left, right] = this.check_shim_sig_unadjusted(link_name, args)?;
266                let left = this.read_scalar(left)?.to_u64()?;
267                let right = this.read_scalar(right)?.to_u64()?;
268
269                let result = left.widening_carryless_mul(right);
270
271                // dest is int8x16_t, transmute to u128 for the write.
272                let dest = dest.transmute(this.machine.layouts.u128, this)?;
273                this.write_scalar(Scalar::from_u128(result), &dest)?;
274            }
275
276            _ => return interp_ok(EmulateItemResult::NotSupported),
277        }
278        interp_ok(EmulateItemResult::NeedsReturn)
279    }
280}