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