Skip to main content

miri/intrinsics/x86/
avx.rs

1use rustc_apfloat::ieee::{Double, Single};
2use rustc_span::Symbol;
3
4use super::{
5    FloatBinOp, FloatUnaryOp, bin_op_simd_float_all, conditional_dot_product, convert_float_to_int,
6    round_all, test_bits_masked, test_high_bits_masked, unary_op_ps,
7};
8use crate::*;
9
10impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
11pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
12    fn emulate_x86_avx_intrinsic(
13        &mut self,
14        link_name: Symbol,
15        args: &[OpTy<'tcx>],
16        dest: &MPlaceTy<'tcx>,
17    ) -> InterpResult<'tcx, EmulateItemResult> {
18        let ecx = self.eval_context_mut();
19        ecx.expect_target_feature_for_intrinsic(link_name, "avx")?;
20        // Prefix should have already been checked.
21        let unprefixed_name = link_name.as_str().strip_prefix("llvm.x86.avx.").unwrap();
22
23        match unprefixed_name {
24            // Used to implement _mm256_min_ps and _mm256_max_ps functions.
25            // Note that the semantics are a bit different from Rust simd_min
26            // and simd_max intrinsics regarding handling of NaN and -0.0: Rust
27            // matches the IEEE min/max operations, while x86 has different
28            // semantics.
29            "min.ps.256" | "max.ps.256" => {
30                let [left, right] = ecx.check_shim_sig_llvm_intrinsic(link_name, args)?;
31
32                let which = match unprefixed_name {
33                    "min.ps.256" => FloatBinOp::Min,
34                    "max.ps.256" => FloatBinOp::Max,
35                    _ => unreachable!(),
36                };
37
38                bin_op_simd_float_all::<Single>(ecx, which, left, right, dest)?;
39            }
40            // Used to implement _mm256_min_pd and _mm256_max_pd functions.
41            "min.pd.256" | "max.pd.256" => {
42                let [left, right] = ecx.check_shim_sig_llvm_intrinsic(link_name, args)?;
43
44                let which = match unprefixed_name {
45                    "min.pd.256" => FloatBinOp::Min,
46                    "max.pd.256" => FloatBinOp::Max,
47                    _ => unreachable!(),
48                };
49
50                bin_op_simd_float_all::<Double>(ecx, which, left, right, dest)?;
51            }
52            // Used to implement the _mm256_round_ps function.
53            // Rounds the elements of `op` according to `rounding`.
54            "round.ps.256" => {
55                let [op, rounding] = ecx.check_shim_sig_llvm_intrinsic(link_name, args)?;
56
57                round_all::<rustc_apfloat::ieee::Single>(ecx, op, rounding, dest)?;
58            }
59            // Used to implement the _mm256_round_pd function.
60            // Rounds the elements of `op` according to `rounding`.
61            "round.pd.256" => {
62                let [op, rounding] = ecx.check_shim_sig_llvm_intrinsic(link_name, args)?;
63
64                round_all::<rustc_apfloat::ieee::Double>(ecx, op, rounding, dest)?;
65            }
66            // Used to implement _mm256_{rcp,rsqrt}_ps functions.
67            // Performs the operations on all components of `op`.
68            "rcp.ps.256" | "rsqrt.ps.256" => {
69                let [op] = ecx.check_shim_sig_llvm_intrinsic(link_name, args)?;
70
71                let which = match unprefixed_name {
72                    "rcp.ps.256" => FloatUnaryOp::Rcp,
73                    "rsqrt.ps.256" => FloatUnaryOp::Rsqrt,
74                    _ => unreachable!(),
75                };
76
77                unary_op_ps(ecx, which, op, dest)?;
78            }
79            // Used to implement the _mm256_dp_ps function.
80            "dp.ps.256" => {
81                let [left, right, imm] = ecx.check_shim_sig_llvm_intrinsic(link_name, args)?;
82
83                conditional_dot_product(ecx, left, right, imm, dest)?;
84            }
85            // Used to implement the _mm256_cmp_ps function.
86            // Performs a comparison operation on each component of `left`
87            // and `right`. For each component, returns 0 if false or u32::MAX
88            // if true.
89            "cmp.ps.256" => {
90                let [left, right, imm] = ecx.check_shim_sig_llvm_intrinsic(link_name, args)?;
91
92                let which =
93                    FloatBinOp::cmp_from_imm(ecx, ecx.read_scalar(imm)?.to_i8()?, link_name)?;
94
95                bin_op_simd_float_all::<Single>(ecx, which, left, right, dest)?;
96            }
97            // Used to implement the _mm256_cmp_pd function.
98            // Performs a comparison operation on each component of `left`
99            // and `right`. For each component, returns 0 if false or u64::MAX
100            // if true.
101            "cmp.pd.256" => {
102                let [left, right, imm] = ecx.check_shim_sig_llvm_intrinsic(link_name, args)?;
103
104                let which =
105                    FloatBinOp::cmp_from_imm(ecx, ecx.read_scalar(imm)?.to_i8()?, link_name)?;
106
107                bin_op_simd_float_all::<Double>(ecx, which, left, right, dest)?;
108            }
109            // Used to implement the _mm256_cvtps_epi32, _mm256_cvttps_epi32, _mm256_cvtpd_epi32
110            // and _mm256_cvttpd_epi32 functions.
111            // Converts packed f32/f64 to packed i32.
112            "cvt.ps2dq.256" | "cvtt.ps2dq.256" | "cvt.pd2dq.256" | "cvtt.pd2dq.256" => {
113                let [op] = ecx.check_shim_sig_llvm_intrinsic(link_name, args)?;
114
115                let rnd = match unprefixed_name {
116                    // "current SSE rounding mode", assume nearest
117                    "cvt.ps2dq.256" | "cvt.pd2dq.256" => rustc_apfloat::Round::NearestTiesToEven,
118                    // always truncate
119                    "cvtt.ps2dq.256" | "cvtt.pd2dq.256" => rustc_apfloat::Round::TowardZero,
120                    _ => unreachable!(),
121                };
122
123                convert_float_to_int(ecx, op, rnd, dest)?;
124            }
125            // Used to implement the _mm_permutevar_ps and _mm256_permutevar_ps functions.
126            // Shuffles 32-bit floats from `data` using `control` as control. Each 128-bit
127            // chunk is shuffled independently: this means that we view the vector as a
128            // sequence of 4-element arrays, and we shuffle each of these arrays, where
129            // `control` determines which element of the current `data` array is written.
130            "vpermilvar.ps" | "vpermilvar.ps.256" => {
131                let [data, control] = ecx.check_shim_sig_llvm_intrinsic(link_name, args)?;
132
133                let (data, data_len) = ecx.project_to_simd(data)?;
134                let (control, control_len) = ecx.project_to_simd(control)?;
135                let (dest, dest_len) = ecx.project_to_simd(dest)?;
136
137                assert_eq!(dest_len, data_len);
138                assert_eq!(dest_len, control_len);
139
140                for i in 0..dest_len {
141                    let control = ecx.project_index(&control, i)?;
142
143                    // Each 128-bit chunk is shuffled independently. Since each chunk contains
144                    // four 32-bit elements, only two bits from `control` are used. To read the
145                    // value from the current chunk, add the destination index truncated to a multiple
146                    // of 4.
147                    let chunk_base = i & !0b11;
148                    let src_i = u64::from(ecx.read_scalar(&control)?.to_u32()? & 0b11)
149                        .strict_add(chunk_base);
150
151                    ecx.copy_op(&ecx.project_index(&data, src_i)?, &ecx.project_index(&dest, i)?)?;
152                }
153            }
154            // Used to implement the _mm_permutevar_pd and _mm256_permutevar_pd functions.
155            // Shuffles 64-bit floats from `left` using `right` as control. Each 128-bit
156            // chunk is shuffled independently: this means that we view the vector as
157            // a sequence of 2-element arrays, and we shuffle each of these arrays,
158            // where `right` determines which element of the current `left` array is
159            // written.
160            "vpermilvar.pd" | "vpermilvar.pd.256" => {
161                let [data, control] = ecx.check_shim_sig_llvm_intrinsic(link_name, args)?;
162
163                let (data, data_len) = ecx.project_to_simd(data)?;
164                let (control, control_len) = ecx.project_to_simd(control)?;
165                let (dest, dest_len) = ecx.project_to_simd(dest)?;
166
167                assert_eq!(dest_len, data_len);
168                assert_eq!(dest_len, control_len);
169
170                for i in 0..dest_len {
171                    let control = ecx.project_index(&control, i)?;
172
173                    // Each 128-bit chunk is shuffled independently. Since each chunk contains
174                    // two 64-bit elements, only the second bit from `control` is used (yes, the
175                    // second instead of the first, ask Intel). To read the value from the current
176                    // chunk, add the destination index truncated to a multiple of 2.
177                    let chunk_base = i & !1;
178                    let src_i =
179                        ((ecx.read_scalar(&control)?.to_u64()? >> 1) & 1).strict_add(chunk_base);
180
181                    ecx.copy_op(&ecx.project_index(&data, src_i)?, &ecx.project_index(&dest, i)?)?;
182                }
183            }
184            // Used to implement the _mm256_lddqu_si256 function.
185            // Reads a 256-bit vector from an unaligned pointer. This intrinsic
186            // is expected to perform better than a regular unaligned read when
187            // the data crosses a cache line, but for Miri this is just a regular
188            // unaligned read.
189            "ldu.dq.256" => {
190                let [src_ptr] = ecx.check_shim_sig_llvm_intrinsic(link_name, args)?;
191                let src_ptr = ecx.read_pointer(src_ptr)?;
192                let dest = dest.force_mplace(ecx)?;
193
194                // Unaligned copy, which is what we want.
195                ecx.mem_copy(src_ptr, dest.ptr(), dest.layout.size, /*nonoverlapping*/ true)?;
196            }
197            // Used to implement the _mm256_testnzc_si256 function.
198            // Tests `op & mask != 0 && op & mask != mask`
199            "ptestnzc.256" => {
200                let [op, mask] = ecx.check_shim_sig_llvm_intrinsic(link_name, args)?;
201
202                let (all_zero, masked_set) = test_bits_masked(ecx, op, mask)?;
203                let res = !all_zero && !masked_set;
204
205                ecx.write_scalar(Scalar::from_i32(res.into()), dest)?;
206            }
207            // Used to implement the _mm256_testz_pd, _mm256_testc_pd, _mm256_testnzc_pd
208            // _mm_testnzc_pd, _mm256_testz_ps, _mm256_testc_ps, _mm256_testnzc_ps and
209            // _mm_testnzc_ps functions.
210            // Calculates two booleans:
211            // `direct`, which is true when the highest bit of each element of `op & mask` is zero.
212            // `negated`, which is true when the highest bit of each element of `!op & mask` is zero.
213            // Return `direct` (testz), `negated` (testc) or `!direct & !negated` (testnzc)
214            "vtestz.pd.256" | "vtestc.pd.256" | "vtestnzc.pd.256" | "vtestnzc.pd"
215            | "vtestz.ps.256" | "vtestc.ps.256" | "vtestnzc.ps.256" | "vtestnzc.ps" => {
216                let [op, mask] = ecx.check_shim_sig_llvm_intrinsic(link_name, args)?;
217
218                let (direct, negated) = test_high_bits_masked(ecx, op, mask)?;
219                let res = match unprefixed_name {
220                    "vtestz.pd.256" | "vtestz.ps.256" => direct,
221                    "vtestc.pd.256" | "vtestc.ps.256" => negated,
222                    "vtestnzc.pd.256" | "vtestnzc.pd" | "vtestnzc.ps.256" | "vtestnzc.ps" =>
223                        !direct && !negated,
224                    _ => unreachable!(),
225                };
226
227                ecx.write_scalar(Scalar::from_i32(res.into()), dest)?;
228            }
229            // Used to implement the `_mm256_zeroupper` and `_mm256_zeroall` functions.
230            // These function clear out the upper 128 bits of all avx registers or
231            // zero out all avx registers respectively.
232            "vzeroupper" | "vzeroall" => {
233                // These functions are purely a performance hint for the CPU.
234                // Any registers currently in use will be saved beforehand by the
235                // compiler, making these functions no-ops.
236
237                // The only thing that needs to be ensured is the correct calling convention.
238                let [] = ecx.check_shim_sig_llvm_intrinsic(link_name, args)?;
239            }
240            _ => return interp_ok(EmulateItemResult::NotSupported),
241        }
242        interp_ok(EmulateItemResult::NeedsReturn)
243    }
244}