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 this = self.eval_context_mut();
19 this.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] = this.check_shim_sig_unadjusted(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>(this, 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] = this.check_shim_sig_unadjusted(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>(this, 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] = this.check_shim_sig_unadjusted(link_name, args)?;
56
57 round_all::<rustc_apfloat::ieee::Single>(this, 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] = this.check_shim_sig_unadjusted(link_name, args)?;
63
64 round_all::<rustc_apfloat::ieee::Double>(this, 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] = this.check_shim_sig_unadjusted(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(this, which, op, dest)?;
78 }
79 // Used to implement the _mm256_dp_ps function.
80 "dp.ps.256" => {
81 let [left, right, imm] = this.check_shim_sig_unadjusted(link_name, args)?;
82
83 conditional_dot_product(this, 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] = this.check_shim_sig_unadjusted(link_name, args)?;
91
92 let which =
93 FloatBinOp::cmp_from_imm(this, this.read_scalar(imm)?.to_i8()?, link_name)?;
94
95 bin_op_simd_float_all::<Single>(this, 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] = this.check_shim_sig_unadjusted(link_name, args)?;
103
104 let which =
105 FloatBinOp::cmp_from_imm(this, this.read_scalar(imm)?.to_i8()?, link_name)?;
106
107 bin_op_simd_float_all::<Double>(this, 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] = this.check_shim_sig_unadjusted(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(this, 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] = this.check_shim_sig_unadjusted(link_name, args)?;
132
133 let (data, data_len) = this.project_to_simd(data)?;
134 let (control, control_len) = this.project_to_simd(control)?;
135 let (dest, dest_len) = this.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 = this.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(this.read_scalar(&control)?.to_u32()? & 0b11)
149 .strict_add(chunk_base);
150
151 this.copy_op(
152 &this.project_index(&data, src_i)?,
153 &this.project_index(&dest, i)?,
154 )?;
155 }
156 }
157 // Used to implement the _mm_permutevar_pd and _mm256_permutevar_pd functions.
158 // Shuffles 64-bit floats from `left` using `right` as control. Each 128-bit
159 // chunk is shuffled independently: this means that we view the vector as
160 // a sequence of 2-element arrays, and we shuffle each of these arrays,
161 // where `right` determines which element of the current `left` array is
162 // written.
163 "vpermilvar.pd" | "vpermilvar.pd.256" => {
164 let [data, control] = this.check_shim_sig_unadjusted(link_name, args)?;
165
166 let (data, data_len) = this.project_to_simd(data)?;
167 let (control, control_len) = this.project_to_simd(control)?;
168 let (dest, dest_len) = this.project_to_simd(dest)?;
169
170 assert_eq!(dest_len, data_len);
171 assert_eq!(dest_len, control_len);
172
173 for i in 0..dest_len {
174 let control = this.project_index(&control, i)?;
175
176 // Each 128-bit chunk is shuffled independently. Since each chunk contains
177 // two 64-bit elements, only the second bit from `control` is used (yes, the
178 // second instead of the first, ask Intel). To read the value from the current
179 // chunk, add the destination index truncated to a multiple of 2.
180 let chunk_base = i & !1;
181 let src_i =
182 ((this.read_scalar(&control)?.to_u64()? >> 1) & 1).strict_add(chunk_base);
183
184 this.copy_op(
185 &this.project_index(&data, src_i)?,
186 &this.project_index(&dest, i)?,
187 )?;
188 }
189 }
190 // Used to implement the _mm256_lddqu_si256 function.
191 // Reads a 256-bit vector from an unaligned pointer. This intrinsic
192 // is expected to perform better than a regular unaligned read when
193 // the data crosses a cache line, but for Miri this is just a regular
194 // unaligned read.
195 "ldu.dq.256" => {
196 let [src_ptr] = this.check_shim_sig_unadjusted(link_name, args)?;
197 let src_ptr = this.read_pointer(src_ptr)?;
198 let dest = dest.force_mplace(this)?;
199
200 // Unaligned copy, which is what we want.
201 this.mem_copy(src_ptr, dest.ptr(), dest.layout.size, /*nonoverlapping*/ true)?;
202 }
203 // Used to implement the _mm256_testnzc_si256 function.
204 // Tests `op & mask != 0 && op & mask != mask`
205 "ptestnzc.256" => {
206 let [op, mask] = this.check_shim_sig_unadjusted(link_name, args)?;
207
208 let (all_zero, masked_set) = test_bits_masked(this, op, mask)?;
209 let res = !all_zero && !masked_set;
210
211 this.write_scalar(Scalar::from_i32(res.into()), dest)?;
212 }
213 // Used to implement the _mm256_testz_pd, _mm256_testc_pd, _mm256_testnzc_pd
214 // _mm_testnzc_pd, _mm256_testz_ps, _mm256_testc_ps, _mm256_testnzc_ps and
215 // _mm_testnzc_ps functions.
216 // Calculates two booleans:
217 // `direct`, which is true when the highest bit of each element of `op & mask` is zero.
218 // `negated`, which is true when the highest bit of each element of `!op & mask` is zero.
219 // Return `direct` (testz), `negated` (testc) or `!direct & !negated` (testnzc)
220 "vtestz.pd.256" | "vtestc.pd.256" | "vtestnzc.pd.256" | "vtestnzc.pd"
221 | "vtestz.ps.256" | "vtestc.ps.256" | "vtestnzc.ps.256" | "vtestnzc.ps" => {
222 let [op, mask] = this.check_shim_sig_unadjusted(link_name, args)?;
223
224 let (direct, negated) = test_high_bits_masked(this, op, mask)?;
225 let res = match unprefixed_name {
226 "vtestz.pd.256" | "vtestz.ps.256" => direct,
227 "vtestc.pd.256" | "vtestc.ps.256" => negated,
228 "vtestnzc.pd.256" | "vtestnzc.pd" | "vtestnzc.ps.256" | "vtestnzc.ps" =>
229 !direct && !negated,
230 _ => unreachable!(),
231 };
232
233 this.write_scalar(Scalar::from_i32(res.into()), dest)?;
234 }
235 // Used to implement the `_mm256_zeroupper` and `_mm256_zeroall` functions.
236 // These function clear out the upper 128 bits of all avx registers or
237 // zero out all avx registers respectively.
238 "vzeroupper" | "vzeroall" => {
239 // These functions are purely a performance hint for the CPU.
240 // Any registers currently in use will be saved beforehand by the
241 // compiler, making these functions no-ops.
242
243 // The only thing that needs to be ensured is the correct calling convention.
244 let [] = this.check_shim_sig_unadjusted(link_name, args)?;
245 }
246 _ => return interp_ok(EmulateItemResult::NotSupported),
247 }
248 interp_ok(EmulateItemResult::NeedsReturn)
249 }
250}