Skip to main content

miri/intrinsics/x86/
avx512.rs

1use rustc_span::Symbol;
2
3use super::{
4    ShiftOp, packssdw, packsswb, packusdw, packuswb, pavg, permute, permute2, pmaddbw, pmaddwd,
5    psadbw, pshufb, shift_simd_by_simd,
6};
7use crate::*;
8
9impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
10pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
11    fn emulate_x86_avx512_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.x86.avx512.").unwrap();
20
21        match unprefixed_name {
22            // Used by _mm512_{sllv,srlv}_epi64.
23            "psllv.q.512" | "psrlv.q.512" => {
24                this.expect_target_feature_for_intrinsic(link_name, "avx512f")?;
25                let [left, right] = this.check_shim_sig_llvm_intrinsic(link_name, args)?;
26                let which = match unprefixed_name {
27                    "psllv.q.512" => ShiftOp::Left,
28                    "psrlv.q.512" => ShiftOp::RightLogic,
29                    _ => unreachable!(),
30                };
31                shift_simd_by_simd(this, left, right, which, dest)?;
32            }
33            // Used by the ternarylogic functions.
34            "pternlog.d.128" | "pternlog.d.256" | "pternlog.d.512" | "pternlog.q.128"
35            | "pternlog.q.256" | "pternlog.q.512" => {
36                this.expect_target_feature_for_intrinsic(link_name, "avx512f")?;
37                if !unprefixed_name.ends_with(".512") {
38                    this.expect_target_feature_for_intrinsic(link_name, "avx512vl")?;
39                }
40
41                let [a, b, c, imm8] = this.check_shim_sig_llvm_intrinsic(link_name, args)?;
42
43                assert_eq!(dest.layout, a.layout);
44                assert_eq!(dest.layout, b.layout);
45                assert_eq!(dest.layout, c.layout);
46
47                // The signatures of these operations are:
48                //
49                // ```
50                // fn vpternlogd(a: i32x16, b: i32x16, c: i32x16, imm8: i32) -> i32x16;
51                // fn vpternlogd256(a: i32x8, b: i32x8, c: i32x8, imm8: i32) -> i32x8;
52                // fn vpternlogd128(a: i32x4, b: i32x4, c: i32x4, imm8: i32) -> i32x4;
53                // fn vpternlogq(a: i64x8, b: i64x8, c: i64x8, imm8: i32) -> i64x8;
54                // fn vpternlogq256(a: i64x4, b: i64x4, c: i64x4, imm8: i32) -> i64x4;
55                // fn vpternlogq128(a: i64x2, b: i64x2, c: i64x2, imm8: i32) -> i64x2;
56                // ```
57                //
58                // The element type is a 32- or 64-bit integer, the width varies. The operation
59                // is bitwise, so the element size only matters to the masked forms, which stdarch
60                // builds from this and a select.
61
62                let (a, _a_len) = this.project_to_simd(a)?;
63                let (b, _b_len) = this.project_to_simd(b)?;
64                let (c, _c_len) = this.project_to_simd(c)?;
65                let (dest, dest_len) = this.project_to_simd(dest)?;
66
67                // Compute one lane with ternary table.
68                let tern = |xa: u128, xb: u128, xc: u128, imm: u128, bits: u64| -> u128 {
69                    let mut out = 0u128;
70                    // At each bit position, select bit from imm8 at index = (a << 2) | (b << 1) | c
71                    for bit in 0..bits {
72                        let ia = (xa >> bit) & 1;
73                        let ib = (xb >> bit) & 1;
74                        let ic = (xc >> bit) & 1;
75                        let idx = (ia << 2) | (ib << 1) | ic;
76                        let v = (imm >> idx) & 1;
77                        out |= v << bit;
78                    }
79                    out
80                };
81
82                let imm8 = u128::from(this.read_scalar(imm8)?.to_u32()? & 0xFF);
83                for i in 0..dest_len {
84                    let a_lane = this.project_index(&a, i)?;
85                    let b_lane = this.project_index(&b, i)?;
86                    let c_lane = this.project_index(&c, i)?;
87                    let d_lane = this.project_index(&dest, i)?;
88
89                    let size = d_lane.layout.size;
90                    let va = this.read_scalar(&a_lane)?.to_bits(size)?;
91                    let vb = this.read_scalar(&b_lane)?.to_bits(size)?;
92                    let vc = this.read_scalar(&c_lane)?.to_bits(size)?;
93
94                    let r = tern(va, vb, vc, imm8, size.bits());
95                    this.write_scalar(Scalar::from_uint(r, size), &d_lane)?;
96                }
97            }
98            // Used to implement the _mm512_sad_epu8 function.
99            "psad.bw.512" => {
100                this.expect_target_feature_for_intrinsic(link_name, "avx512bw")?;
101
102                let [left, right] = this.check_shim_sig_llvm_intrinsic(link_name, args)?;
103
104                psadbw(this, left, right, dest)?
105            }
106            // Used to implement the _mm512_madd_epi16 function.
107            "pmaddw.d.512" => {
108                this.expect_target_feature_for_intrinsic(link_name, "avx512bw")?;
109
110                let [left, right] = this.check_shim_sig_llvm_intrinsic(link_name, args)?;
111
112                pmaddwd(this, left, right, dest)?;
113            }
114            // Used to implement the _mm512_maddubs_epi16 function.
115            "pmaddubs.w.512" => {
116                let [left, right] = this.check_shim_sig_llvm_intrinsic(link_name, args)?;
117
118                pmaddbw(this, left, right, dest)?;
119            }
120            // Used to implement the _mm512_permutexvar_epi32/_mm512_permutexvar_epi64 functions.
121            "permvar.si.512" | "permvar.di.512" => {
122                let [left, right] = this.check_shim_sig_llvm_intrinsic(link_name, args)?;
123
124                permute(this, left, right, dest)?;
125            }
126            "permvar.qi.512" | "permvar.qi.256" | "permvar.qi.128" => {
127                this.expect_target_feature_for_intrinsic(link_name, "avx512vbmi")?;
128                if !unprefixed_name.ends_with("512") {
129                    this.expect_target_feature_for_intrinsic(link_name, "avx512vl")?;
130                }
131
132                let [left, right] = this.check_shim_sig_llvm_intrinsic(link_name, args)?;
133
134                permute(this, left, right, dest)?;
135            }
136            // Used to implement the _mm512_permutex2var_epi{32,64} intrinsics.
137            "vpermi2var.d.512" | "vpermi2var.q.512" => {
138                this.expect_target_feature_for_intrinsic(link_name, "avx512f")?;
139                let [left, indices, right] = this.check_shim_sig_llvm_intrinsic(link_name, args)?;
140
141                permute2(this, left, indices, right, dest)?;
142            }
143            // Used to implement the _mm{256,512}_permutex2var_epi8 intrinsics.
144            "vpermi2var.qi.256" | "vpermi2var.qi.512" => {
145                this.expect_target_feature_for_intrinsic(link_name, "avx512vbmi")?;
146                if unprefixed_name.ends_with(".256") {
147                    this.expect_target_feature_for_intrinsic(link_name, "avx512vl")?;
148                }
149
150                let [left, indices, right] = this.check_shim_sig_llvm_intrinsic(link_name, args)?;
151
152                permute2(this, left, indices, right, dest)?;
153            }
154            // Used to implement the _mm512_shuffle_epi8 intrinsic.
155            "pshuf.b.512" => {
156                let [left, right] = this.check_shim_sig_llvm_intrinsic(link_name, args)?;
157
158                pshufb(this, left, right, dest)?;
159            }
160
161            // Used to implement the _mm512_dpbusd_epi32 function.
162            "vpdpbusd.512" | "vpdpbusd.256" | "vpdpbusd.128" => {
163                this.expect_target_feature_for_intrinsic(link_name, "avx512vnni")?;
164                if matches!(unprefixed_name, "vpdpbusd.128" | "vpdpbusd.256") {
165                    this.expect_target_feature_for_intrinsic(link_name, "avx512vl")?;
166                }
167
168                let [src, a, b] = this.check_shim_sig_llvm_intrinsic(link_name, args)?;
169
170                vpdpbusd(this, src, a, b, dest)?;
171            }
172            // Used to implement the _mm512_packs_epi16 function
173            "packsswb.512" => {
174                this.expect_target_feature_for_intrinsic(link_name, "avx512bw")?;
175
176                let [a, b] = this.check_shim_sig_llvm_intrinsic(link_name, args)?;
177
178                packsswb(this, a, b, dest)?;
179            }
180            // Used to implement the _mm512_packus_epi16 function
181            "packuswb.512" => {
182                this.expect_target_feature_for_intrinsic(link_name, "avx512bw")?;
183
184                let [a, b] = this.check_shim_sig_llvm_intrinsic(link_name, args)?;
185
186                packuswb(this, a, b, dest)?;
187            }
188            // Used to implement the _mm512_packs_epi32 function
189            "packssdw.512" => {
190                this.expect_target_feature_for_intrinsic(link_name, "avx512bw")?;
191
192                let [a, b] = this.check_shim_sig_llvm_intrinsic(link_name, args)?;
193
194                packssdw(this, a, b, dest)?;
195            }
196            // Used to implement the _mm512_packus_epi32 function
197            "packusdw.512" => {
198                this.expect_target_feature_for_intrinsic(link_name, "avx512bw")?;
199
200                let [a, b] = this.check_shim_sig_llvm_intrinsic(link_name, args)?;
201
202                packusdw(this, a, b, dest)?;
203            }
204            // Used to implement the _mm512_avg_epu8 and _mm512_avg_epu16 functions.
205            // Averages packed unsigned 8/16-bit integers in `left` and `right`.
206            "pavg.b.512" | "pavg.w.512" => {
207                this.expect_target_feature_for_intrinsic(link_name, "avx512bw")?;
208
209                let [left, right] = this.check_shim_sig_llvm_intrinsic(link_name, args)?;
210
211                pavg(this, left, right, dest)?;
212            }
213            _ => return interp_ok(EmulateItemResult::NotSupported),
214        }
215        interp_ok(EmulateItemResult::NeedsReturn)
216    }
217}
218
219/// Multiply groups of 4 adjacent pairs of unsigned 8-bit integers in `a` with corresponding signed
220/// 8-bit integers in `b`, producing 4 intermediate signed 16-bit results. Sum these 4 results with
221/// the corresponding 32-bit integer in `src` (using wrapping arighmetic), and store the packed
222/// 32-bit results in `dst`.
223///
224/// <https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_dpbusd_epi32>
225/// <https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm256_dpbusd_epi32>
226/// <https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm512_dpbusd_epi32>
227fn vpdpbusd<'tcx>(
228    ecx: &mut crate::MiriInterpCx<'tcx>,
229    src: &OpTy<'tcx>,
230    a: &OpTy<'tcx>,
231    b: &OpTy<'tcx>,
232    dest: &MPlaceTy<'tcx>,
233) -> InterpResult<'tcx, ()> {
234    let (src, src_len) = ecx.project_to_simd(src)?;
235    let (a, a_len) = ecx.project_to_simd(a)?;
236    let (b, b_len) = ecx.project_to_simd(b)?;
237    let (dest, dest_len) = ecx.project_to_simd(dest)?;
238
239    // fn vpdpbusd(src: i32x16, a: u8x64, b: i8x64) -> i32x16;
240    // fn vpdpbusd256(src: i32x8, a: u8x32, b: i8x32) -> i32x8;
241    // fn vpdpbusd128(src: i32x4, a: u8x16, b: i8x16) -> i32x4;
242    assert_eq!(src_len, dest_len);
243    assert_eq!(a_len, dest_len.strict_mul(4));
244    assert_eq!(b_len, a_len);
245
246    for i in 0..dest_len {
247        let src = ecx.read_scalar(&ecx.project_index(&src, i)?)?.to_i32()?;
248        let dest = ecx.project_index(&dest, i)?;
249
250        let mut intermediate_sum: i32 = 0;
251        for j in 0..4 {
252            let idx = i.strict_mul(4).strict_add(j);
253            let a = ecx.read_scalar(&ecx.project_index(&a, idx)?)?.to_u8()?;
254            let b = ecx.read_scalar(&ecx.project_index(&b, idx)?)?.to_i8()?;
255
256            let product = i32::from(a).strict_mul(i32::from(b));
257            intermediate_sum = intermediate_sum.strict_add(product);
258        }
259
260        // Use `wrapping_add` because `src` is an arbitrary i32 and the addition can overflow.
261        let res = Scalar::from_i32(intermediate_sum.wrapping_add(src));
262        ecx.write_scalar(res, &dest)?;
263    }
264
265    interp_ok(())
266}