Skip to main content

miri/intrinsics/x86/
sse2.rs

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