miri/intrinsics/x86/sse2.rs
1use rustc_apfloat::ieee::Double;
2use rustc_span::Symbol;
3
4use super::{
5 FloatBinOp, ShiftOp, bin_op_simd_float_all, bin_op_simd_float_first, convert_float_to_int,
6 packssdw, packsswb, packuswb, pmaddwd, psadbw, shift_simd_by_scalar,
7};
8use crate::*;
9
10impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
11pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
12 fn emulate_x86_sse2_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, "sse2")?;
20 // Prefix should have already been checked.
21 let unprefixed_name = link_name.as_str().strip_prefix("llvm.x86.sse2.").unwrap();
22
23 // These intrinsics operate on 128-bit (f32x4, f64x2, i8x16, i16x8, i32x4, i64x2) SIMD
24 // vectors unless stated otherwise.
25 // Many intrinsic names are suffixed with "ps" (packed single), "ss" (scalar single),
26 // "pd" (packed double) or "sd" (scalar double), where single means single precision
27 // floating point (f32) and double means double precision floating point (f64). "ps"
28 // and "pd" means that the operation is performed on each element of the vector, while
29 // "ss" and "sd" means that the operation is performed only on the first element, copying
30 // the remaining elements from the input vector (for binary operations, from the left-hand
31 // side).
32 // Intrinsics suffixed with "epiX" or "epuX" operate with X-bit signed or unsigned
33 // vectors.
34 match unprefixed_name {
35 // Used to implement the _mm_sad_epu8 function.
36 "psad.bw" => {
37 let [left, right] = this.check_shim_sig_unadjusted(link_name, args)?;
38
39 psadbw(this, left, right, dest)?
40 }
41 // Used to implement the _mm_{sll,srl,sra}_epi{16,32,64} functions
42 // (except _mm_sra_epi64, which is not available in SSE2).
43 // Shifts N-bit packed integers in left by the amount in right.
44 // Both operands are 128-bit vectors. However, right is interpreted as
45 // a single 64-bit integer (remaining bits are ignored).
46 // For logic shifts, when right is larger than N - 1, zero is produced.
47 // For arithmetic shifts, when right is larger than N - 1, the sign bit
48 // is copied to remaining bits.
49 "psll.w" | "psrl.w" | "psra.w" | "psll.d" | "psrl.d" | "psra.d" | "psll.q"
50 | "psrl.q" => {
51 let [left, right] = this.check_shim_sig_unadjusted(link_name, args)?;
52
53 let which = match unprefixed_name {
54 "psll.w" | "psll.d" | "psll.q" => ShiftOp::Left,
55 "psrl.w" | "psrl.d" | "psrl.q" => ShiftOp::RightLogic,
56 "psra.w" | "psra.d" => ShiftOp::RightArith,
57 _ => unreachable!(),
58 };
59
60 shift_simd_by_scalar(this, left, right, which, dest)?;
61 }
62 // Used to implement the _mm_cvtps_epi32, _mm_cvttps_epi32, _mm_cvtpd_epi32
63 // and _mm_cvttpd_epi32 functions.
64 // Converts packed f32/f64 to packed i32.
65 "cvtps2dq" | "cvttps2dq" | "cvtpd2dq" | "cvttpd2dq" => {
66 let [op] = this.check_shim_sig_unadjusted(link_name, args)?;
67
68 let (op_len, _) = op.layout.ty.simd_size_and_type(*this.tcx);
69 let (dest_len, _) = dest.layout.ty.simd_size_and_type(*this.tcx);
70 match unprefixed_name {
71 "cvtps2dq" | "cvttps2dq" => {
72 // f32x4 to i32x4 conversion
73 assert_eq!(op_len, 4);
74 assert_eq!(dest_len, op_len);
75 }
76 "cvtpd2dq" | "cvttpd2dq" => {
77 // f64x2 to i32x4 conversion
78 // the last two values are filled with zeros
79 assert_eq!(op_len, 2);
80 assert_eq!(dest_len, 4);
81 }
82 _ => unreachable!(),
83 }
84
85 let rnd = match unprefixed_name {
86 // "current SSE rounding mode", assume nearest
87 // https://www.felixcloutier.com/x86/cvtps2dq
88 // https://www.felixcloutier.com/x86/cvtpd2dq
89 "cvtps2dq" | "cvtpd2dq" => rustc_apfloat::Round::NearestTiesToEven,
90 // always truncate
91 // https://www.felixcloutier.com/x86/cvttps2dq
92 // https://www.felixcloutier.com/x86/cvttpd2dq
93 "cvttps2dq" | "cvttpd2dq" => rustc_apfloat::Round::TowardZero,
94 _ => unreachable!(),
95 };
96
97 convert_float_to_int(this, op, rnd, dest)?;
98 }
99 // Used to implement the _mm_packs_epi16 function.
100 // Converts two 16-bit integer vectors to a single 8-bit integer
101 // vector with signed saturation.
102 "packsswb.128" => {
103 let [left, right] = this.check_shim_sig_unadjusted(link_name, args)?;
104
105 packsswb(this, left, right, dest)?;
106 }
107 // Used to implement the _mm_packus_epi16 function.
108 // Converts two 16-bit signed integer vectors to a single 8-bit
109 // unsigned integer vector with saturation.
110 "packuswb.128" => {
111 let [left, right] = this.check_shim_sig_unadjusted(link_name, args)?;
112
113 packuswb(this, left, right, dest)?;
114 }
115 // Used to implement the _mm_packs_epi32 function.
116 // Converts two 32-bit integer vectors to a single 16-bit integer
117 // vector with signed saturation.
118 "packssdw.128" => {
119 let [left, right] = this.check_shim_sig_unadjusted(link_name, args)?;
120
121 packssdw(this, left, right, dest)?;
122 }
123 // Used to implement _mm_min_sd and _mm_max_sd functions.
124 // Note that the semantics are a bit different from Rust simd_min
125 // and simd_max intrinsics regarding handling of NaN and -0.0: Rust
126 // matches the IEEE min/max operations, while x86 has different
127 // semantics.
128 "min.sd" | "max.sd" => {
129 let [left, right] = this.check_shim_sig_unadjusted(link_name, args)?;
130
131 let which = match unprefixed_name {
132 "min.sd" => FloatBinOp::Min,
133 "max.sd" => FloatBinOp::Max,
134 _ => unreachable!(),
135 };
136
137 bin_op_simd_float_first::<Double>(this, which, left, right, dest)?;
138 }
139 // Used to implement _mm_min_pd and _mm_max_pd functions.
140 // Note that the semantics are a bit different from Rust simd_min
141 // and simd_max intrinsics regarding handling of NaN and -0.0: Rust
142 // matches the IEEE min/max operations, while x86 has different
143 // semantics.
144 "min.pd" | "max.pd" => {
145 let [left, right] = this.check_shim_sig_unadjusted(link_name, args)?;
146
147 let which = match unprefixed_name {
148 "min.pd" => FloatBinOp::Min,
149 "max.pd" => FloatBinOp::Max,
150 _ => unreachable!(),
151 };
152
153 bin_op_simd_float_all::<Double>(this, which, left, right, dest)?;
154 }
155 // Used to implement the _mm_cmp*_sd functions.
156 // Performs a comparison operation on the first component of `left`
157 // and `right`, returning 0 if false or `u64::MAX` if true. The remaining
158 // components are copied from `left`.
159 // _mm_cmp_sd is actually an AVX function where the operation is specified
160 // by a const parameter.
161 // _mm_cmp{eq,lt,le,gt,ge,neq,nlt,nle,ngt,nge,ord,unord}_sd are SSE2 functions
162 // with hard-coded operations.
163 "cmp.sd" => {
164 let [left, right, imm] = this.check_shim_sig_unadjusted(link_name, args)?;
165
166 let which =
167 FloatBinOp::cmp_from_imm(this, this.read_scalar(imm)?.to_i8()?, link_name)?;
168
169 bin_op_simd_float_first::<Double>(this, which, left, right, dest)?;
170 }
171 // Used to implement the _mm_cmp*_pd functions.
172 // Performs a comparison operation on each component of `left`
173 // and `right`. For each component, returns 0 if false or `u64::MAX`
174 // if true.
175 // _mm_cmp_pd is actually an AVX function where the operation is specified
176 // by a const parameter.
177 // _mm_cmp{eq,lt,le,gt,ge,neq,nlt,nle,ngt,nge,ord,unord}_pd are SSE2 functions
178 // with hard-coded operations.
179 "cmp.pd" => {
180 let [left, right, imm] = this.check_shim_sig_unadjusted(link_name, args)?;
181
182 let which =
183 FloatBinOp::cmp_from_imm(this, this.read_scalar(imm)?.to_i8()?, link_name)?;
184
185 bin_op_simd_float_all::<Double>(this, which, left, right, dest)?;
186 }
187 // Used to implement _mm_{,u}comi{eq,lt,le,gt,ge,neq}_sd functions.
188 // Compares the first component of `left` and `right` and returns
189 // a scalar value (0 or 1).
190 "comieq.sd" | "comilt.sd" | "comile.sd" | "comigt.sd" | "comige.sd" | "comineq.sd"
191 | "ucomieq.sd" | "ucomilt.sd" | "ucomile.sd" | "ucomigt.sd" | "ucomige.sd"
192 | "ucomineq.sd" => {
193 let [left, right] = this.check_shim_sig_unadjusted(link_name, args)?;
194
195 let (left, left_len) = this.project_to_simd(left)?;
196 let (right, right_len) = this.project_to_simd(right)?;
197
198 assert_eq!(left_len, right_len);
199
200 let left = this.read_scalar(&this.project_index(&left, 0)?)?.to_f64()?;
201 let right = this.read_scalar(&this.project_index(&right, 0)?)?.to_f64()?;
202 // The difference between the com* and ucom* variants is signaling
203 // of exceptions when either argument is a quiet NaN. We do not
204 // support accessing the SSE status register from miri (or from Rust,
205 // for that matter), so we treat both variants equally.
206 let res = match unprefixed_name {
207 "comieq.sd" | "ucomieq.sd" => left == right,
208 "comilt.sd" | "ucomilt.sd" => left < right,
209 "comile.sd" | "ucomile.sd" => left <= right,
210 "comigt.sd" | "ucomigt.sd" => left > right,
211 "comige.sd" | "ucomige.sd" => left >= right,
212 "comineq.sd" | "ucomineq.sd" => left != right,
213 _ => unreachable!(),
214 };
215 this.write_scalar(Scalar::from_i32(i32::from(res)), dest)?;
216 }
217 // Use to implement the _mm_cvtsd_si32, _mm_cvttsd_si32,
218 // _mm_cvtsd_si64 and _mm_cvttsd_si64 functions.
219 // Converts the first component of `op` from f64 to i32/i64.
220 "cvtsd2si" | "cvttsd2si" | "cvtsd2si64" | "cvttsd2si64" => {
221 let [op] = this.check_shim_sig_unadjusted(link_name, args)?;
222 let (op, _) = this.project_to_simd(op)?;
223
224 let op = this.read_immediate(&this.project_index(&op, 0)?)?;
225
226 let rnd = match unprefixed_name {
227 // "current SSE rounding mode", assume nearest
228 // https://www.felixcloutier.com/x86/cvtsd2si
229 "cvtsd2si" | "cvtsd2si64" => rustc_apfloat::Round::NearestTiesToEven,
230 // always truncate
231 // https://www.felixcloutier.com/x86/cvttsd2si
232 "cvttsd2si" | "cvttsd2si64" => rustc_apfloat::Round::TowardZero,
233 _ => unreachable!(),
234 };
235
236 let res = this.float_to_int_checked(&op, dest.layout, rnd)?.unwrap_or_else(|| {
237 // Fallback to minimum according to SSE semantics.
238 ImmTy::from_int(dest.layout.size.signed_int_min(), dest.layout)
239 });
240
241 this.write_immediate(*res, dest)?;
242 }
243 // Used to implement the _mm_cvtsd_ss function.
244 // Converts the first f64 from `right` to f32 and copies the remaining
245 // elements from `left`
246 "cvtsd2ss" => {
247 let [left, right] = this.check_shim_sig_unadjusted(link_name, args)?;
248
249 let (left, left_len) = this.project_to_simd(left)?;
250 let (right, _) = this.project_to_simd(right)?;
251 let (dest, dest_len) = this.project_to_simd(dest)?;
252
253 assert_eq!(dest_len, left_len);
254
255 // Convert first element of `right`
256 let right0 = this.read_immediate(&this.project_index(&right, 0)?)?;
257 let dest0 = this.project_index(&dest, 0)?;
258 let res0 = this.float_to_float_or_int(&right0, dest0.layout)?;
259 this.write_immediate(*res0, &dest0)?;
260
261 // Copy remaining from `left`
262 for i in 1..dest_len {
263 this.copy_op(&this.project_index(&left, i)?, &this.project_index(&dest, i)?)?;
264 }
265 }
266 // Used to implement the _mm_madd_epi16 function.
267 // Multiplies packed signed 16-bit integers in `left` and `right`, producing
268 // intermediate signed 32-bit integers. Horizontally add adjacent pairs of
269 // intermediate 32-bit integers, and pack the results in `dest`.
270 "pmadd.wd" => {
271 let [left, right] = this.check_shim_sig_unadjusted(link_name, args)?;
272
273 pmaddwd(this, left, right, dest)?;
274 }
275 _ => return interp_ok(EmulateItemResult::NotSupported),
276 }
277 interp_ok(EmulateItemResult::NeedsReturn)
278 }
279}