miri/shims/x86/
sse.rs

1use rustc_abi::CanonAbi;
2use rustc_apfloat::ieee::Single;
3use rustc_middle::ty::Ty;
4use rustc_span::Symbol;
5use rustc_target::callconv::FnAbi;
6
7use super::{
8    FloatBinOp, FloatUnaryOp, bin_op_simd_float_all, bin_op_simd_float_first, unary_op_ps,
9    unary_op_ss,
10};
11use crate::*;
12
13impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
14pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
15    fn emulate_x86_sse_intrinsic(
16        &mut self,
17        link_name: Symbol,
18        abi: &FnAbi<'tcx, Ty<'tcx>>,
19        args: &[OpTy<'tcx>],
20        dest: &MPlaceTy<'tcx>,
21    ) -> InterpResult<'tcx, EmulateItemResult> {
22        let this = self.eval_context_mut();
23        this.expect_target_feature_for_intrinsic(link_name, "sse")?;
24        // Prefix should have already been checked.
25        let unprefixed_name = link_name.as_str().strip_prefix("llvm.x86.sse.").unwrap();
26        // All these intrinsics operate on 128-bit (f32x4) SIMD vectors unless stated otherwise.
27        // Many intrinsic names are sufixed with "ps" (packed single) or "ss" (scalar single),
28        // where single means single precision floating point (f32). "ps" means thet the operation
29        // is performed on each element of the vector, while "ss" means that the operation is
30        // performed only on the first element, copying the remaining elements from the input
31        // vector (for binary operations, from the left-hand side).
32        match unprefixed_name {
33            // Used to implement _mm_{min,max}_ss functions.
34            // Performs the operations on the first component of `left` and
35            // `right` and copies the remaining components from `left`.
36            "min.ss" | "max.ss" => {
37                let [left, right] =
38                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
39
40                let which = match unprefixed_name {
41                    "min.ss" => FloatBinOp::Min,
42                    "max.ss" => FloatBinOp::Max,
43                    _ => unreachable!(),
44                };
45
46                bin_op_simd_float_first::<Single>(this, which, left, right, dest)?;
47            }
48            // Used to implement _mm_min_ps and _mm_max_ps functions.
49            // Note that the semantics are a bit different from Rust simd_min
50            // and simd_max intrinsics regarding handling of NaN and -0.0: Rust
51            // matches the IEEE min/max operations, while x86 has different
52            // semantics.
53            "min.ps" | "max.ps" => {
54                let [left, right] =
55                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
56
57                let which = match unprefixed_name {
58                    "min.ps" => FloatBinOp::Min,
59                    "max.ps" => FloatBinOp::Max,
60                    _ => unreachable!(),
61                };
62
63                bin_op_simd_float_all::<Single>(this, which, left, right, dest)?;
64            }
65            // Used to implement _mm_{rcp,rsqrt}_ss functions.
66            // Performs the operations on the first component of `op` and
67            // copies the remaining components from `op`.
68            "rcp.ss" | "rsqrt.ss" => {
69                let [op] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
70
71                let which = match unprefixed_name {
72                    "rcp.ss" => FloatUnaryOp::Rcp,
73                    "rsqrt.ss" => FloatUnaryOp::Rsqrt,
74                    _ => unreachable!(),
75                };
76
77                unary_op_ss(this, which, op, dest)?;
78            }
79            // Used to implement _mm_{sqrt,rcp,rsqrt}_ps functions.
80            // Performs the operations on all components of `op`.
81            "rcp.ps" | "rsqrt.ps" => {
82                let [op] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
83
84                let which = match unprefixed_name {
85                    "rcp.ps" => FloatUnaryOp::Rcp,
86                    "rsqrt.ps" => FloatUnaryOp::Rsqrt,
87                    _ => unreachable!(),
88                };
89
90                unary_op_ps(this, which, op, dest)?;
91            }
92            // Used to implement the _mm_cmp*_ss functions.
93            // Performs a comparison operation on the first component of `left`
94            // and `right`, returning 0 if false or `u32::MAX` if true. The remaining
95            // components are copied from `left`.
96            // _mm_cmp_ss is actually an AVX function where the operation is specified
97            // by a const parameter.
98            // _mm_cmp{eq,lt,le,gt,ge,neq,nlt,nle,ngt,nge,ord,unord}_ss are SSE functions
99            // with hard-coded operations.
100            "cmp.ss" => {
101                let [left, right, imm] =
102                    this.check_shim_sig_lenient(abi, CanonAbi::C, 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_first::<Single>(this, which, left, right, dest)?;
108            }
109            // Used to implement the _mm_cmp*_ps functions.
110            // Performs a comparison operation on each component of `left`
111            // and `right`. For each component, returns 0 if false or u32::MAX
112            // if true.
113            // _mm_cmp_ps is actually an AVX function where the operation is specified
114            // by a const parameter.
115            // _mm_cmp{eq,lt,le,gt,ge,neq,nlt,nle,ngt,nge,ord,unord}_ps are SSE functions
116            // with hard-coded operations.
117            "cmp.ps" => {
118                let [left, right, imm] =
119                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
120
121                let which =
122                    FloatBinOp::cmp_from_imm(this, this.read_scalar(imm)?.to_i8()?, link_name)?;
123
124                bin_op_simd_float_all::<Single>(this, which, left, right, dest)?;
125            }
126            // Used to implement _mm_{,u}comi{eq,lt,le,gt,ge,neq}_ss functions.
127            // Compares the first component of `left` and `right` and returns
128            // a scalar value (0 or 1).
129            "comieq.ss" | "comilt.ss" | "comile.ss" | "comigt.ss" | "comige.ss" | "comineq.ss"
130            | "ucomieq.ss" | "ucomilt.ss" | "ucomile.ss" | "ucomigt.ss" | "ucomige.ss"
131            | "ucomineq.ss" => {
132                let [left, right] =
133                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
134
135                let (left, left_len) = this.project_to_simd(left)?;
136                let (right, right_len) = this.project_to_simd(right)?;
137
138                assert_eq!(left_len, right_len);
139
140                let left = this.read_scalar(&this.project_index(&left, 0)?)?.to_f32()?;
141                let right = this.read_scalar(&this.project_index(&right, 0)?)?.to_f32()?;
142                // The difference between the com* and ucom* variants is signaling
143                // of exceptions when either argument is a quiet NaN. We do not
144                // support accessing the SSE status register from miri (or from Rust,
145                // for that matter), so we treat both variants equally.
146                let res = match unprefixed_name {
147                    "comieq.ss" | "ucomieq.ss" => left == right,
148                    "comilt.ss" | "ucomilt.ss" => left < right,
149                    "comile.ss" | "ucomile.ss" => left <= right,
150                    "comigt.ss" | "ucomigt.ss" => left > right,
151                    "comige.ss" | "ucomige.ss" => left >= right,
152                    "comineq.ss" | "ucomineq.ss" => left != right,
153                    _ => unreachable!(),
154                };
155                this.write_scalar(Scalar::from_i32(i32::from(res)), dest)?;
156            }
157            // Use to implement the _mm_cvtss_si32, _mm_cvttss_si32,
158            // _mm_cvtss_si64 and _mm_cvttss_si64 functions.
159            // Converts the first component of `op` from f32 to i32/i64.
160            "cvtss2si" | "cvttss2si" | "cvtss2si64" | "cvttss2si64" => {
161                let [op] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
162                let (op, _) = this.project_to_simd(op)?;
163
164                let op = this.read_immediate(&this.project_index(&op, 0)?)?;
165
166                let rnd = match unprefixed_name {
167                    // "current SSE rounding mode", assume nearest
168                    // https://www.felixcloutier.com/x86/cvtss2si
169                    "cvtss2si" | "cvtss2si64" => rustc_apfloat::Round::NearestTiesToEven,
170                    // always truncate
171                    // https://www.felixcloutier.com/x86/cvttss2si
172                    "cvttss2si" | "cvttss2si64" => rustc_apfloat::Round::TowardZero,
173                    _ => unreachable!(),
174                };
175
176                let res = this.float_to_int_checked(&op, dest.layout, rnd)?.unwrap_or_else(|| {
177                    // Fallback to minimum according to SSE semantics.
178                    ImmTy::from_int(dest.layout.size.signed_int_min(), dest.layout)
179                });
180
181                this.write_immediate(*res, dest)?;
182            }
183            // Used to implement the _mm_cvtsi32_ss and _mm_cvtsi64_ss functions.
184            // Converts `right` from i32/i64 to f32. Returns a SIMD vector with
185            // the result in the first component and the remaining components
186            // are copied from `left`.
187            // https://www.felixcloutier.com/x86/cvtsi2ss
188            "cvtsi2ss" | "cvtsi642ss" => {
189                let [left, right] =
190                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
191
192                let (left, left_len) = this.project_to_simd(left)?;
193                let (dest, dest_len) = this.project_to_simd(dest)?;
194
195                assert_eq!(dest_len, left_len);
196
197                let right = this.read_immediate(right)?;
198                let dest0 = this.project_index(&dest, 0)?;
199                let res0 = this.int_to_int_or_float(&right, dest0.layout)?;
200                this.write_immediate(*res0, &dest0)?;
201
202                for i in 1..dest_len {
203                    this.copy_op(&this.project_index(&left, i)?, &this.project_index(&dest, i)?)?;
204                }
205            }
206            _ => return interp_ok(EmulateItemResult::NotSupported),
207        }
208        interp_ok(EmulateItemResult::NeedsReturn)
209    }
210}