Skip to main content

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