miri/intrinsics/x86/sse42.rs
1use rustc_abi::Size;
2use rustc_middle::mir;
3use rustc_middle::ty::Ty;
4use rustc_span::Symbol;
5use rustc_target::spec::Arch;
6
7use crate::intrinsics::math::compute_crc32;
8use crate::*;
9
10/// A bitmask constant for scrutinizing the immediate byte provided
11/// to the string comparison intrinsics. It distinguishes between
12/// 16-bit integers and 8-bit integers. See [`compare_strings`]
13/// for more details about the immediate byte.
14const USE_WORDS: u8 = 1;
15
16/// A bitmask constant for scrutinizing the immediate byte provided
17/// to the string comparison intrinsics. It distinguishes between
18/// signed integers and unsigned integers. See [`compare_strings`]
19/// for more details about the immediate byte.
20const USE_SIGNED: u8 = 2;
21
22/// The main worker for the string comparison intrinsics, where the given
23/// strings are analyzed according to the given immediate byte.
24///
25/// # Arguments
26///
27/// * `str1` - The first string argument. It is always a length 16 array of bytes
28/// or a length 8 array of two-byte words.
29/// * `str2` - The second string argument. It is always a length 16 array of bytes
30/// or a length 8 array of two-byte words.
31/// * `len` is the length values of the supplied strings. It is distinct from the operand length
32/// in that it describes how much of `str1` and `str2` will be used for the calculation and may
33/// be smaller than the array length of `str1` and `str2`. The string length is counted in bytes
34/// if using byte operands and in two-byte words when using two-byte word operands.
35/// If the value is `None`, the length of a string is determined by the first
36/// null value inside the string.
37/// * `imm` is the immediate byte argument supplied to the intrinsic. The byte influences
38/// the operation as follows:
39///
40/// ```text
41/// 0babccddef
42/// || | |||- Use of bytes vs use of two-byte words inside the operation.
43/// || | ||
44/// || | ||- Use of signed values versus use of unsigned values.
45/// || | |
46/// || | |- The comparison operation performed. A total of four operations are available.
47/// || | * Equal any: Checks which characters of `str2` are inside `str1`.
48/// || | * String ranges: Check if characters in `str2` are inside the provided character ranges.
49/// || | Adjacent characters in `str1` constitute one range.
50/// || | * String comparison: Mark positions where `str1` and `str2` have the same character.
51/// || | * Substring search: Mark positions where `str1` is a substring in `str2`.
52/// || |
53/// || |- Result Polarity. The result bits may be subjected to a bitwise complement
54/// || if these bits are set.
55/// ||
56/// ||- Output selection. This bit has two meanings depending on the instruction.
57/// | If the instruction is generating a mask, it distinguishes between a bit mask
58/// | and a byte mask. Otherwise it distinguishes between the most significant bit
59/// | and the least significant bit when generating an index.
60/// |
61/// |- This bit is ignored. It is expected that this bit is set to zero, but it is
62/// not a requirement.
63/// ```
64///
65/// # Returns
66///
67/// A result mask. The bit at index `i` inside the mask is set if 'str2' starting at `i`
68/// fulfills the test as defined inside the immediate byte.
69/// The mask may be negated if negation flags inside the immediate byte are set.
70///
71/// For more information, see the Intel Software Developer's Manual, Vol. 2b, Chapter 4.1,
72/// as well as the online SDM reference for [PCMPxSTRx_Decode_Immediate], [PCMPxSTRx_Compare], and [PCMPxSTRx_Update_Flags].
73///
74/// [PCMPxSTRx_Decode_Immediate]: https://intel.github.io/SDM/definition/PCMPxSTRx_Decode_Immediate.html
75/// [PCMPxSTRx_Compare]: https://intel.github.io/SDM/definition/PCMPxSTRx_Compare.html
76/// [PCMPxSTRx_Update_Flags]: https://intel.github.io/SDM/definition/PCMPxSTRx_Update_Flags.html
77#[expect(clippy::arithmetic_side_effects)]
78fn compare_strings<'tcx>(
79 ecx: &mut MiriInterpCx<'tcx>,
80 str1: &OpTy<'tcx>,
81 str2: &OpTy<'tcx>,
82 len: Option<(u64, u64)>,
83 imm: u8,
84) -> InterpResult<'tcx, i32> {
85 let default_len = default_len::<u64>(imm);
86 let (len1, len2) = if let Some(t) = len {
87 t
88 } else {
89 let len1 = implicit_len(ecx, str1, imm)?.unwrap_or(default_len);
90 let len2 = implicit_len(ecx, str2, imm)?.unwrap_or(default_len);
91 (len1, len2)
92 };
93
94 let mut result = 0;
95 match (imm >> 2) & 3 {
96 0 => {
97 // Equal any: Checks which characters of `str2` are inside `str1`.
98 for i in 0..len2 {
99 let ch2 = ecx.read_immediate(&ecx.project_index(str2, i)?)?;
100
101 for j in 0..len1 {
102 let ch1 = ecx.read_immediate(&ecx.project_index(str1, j)?)?;
103
104 let eq = ecx.binary_op(mir::BinOp::Eq, &ch1, &ch2)?;
105 if eq.to_scalar().to_bool()? {
106 result |= 1 << i;
107 break;
108 }
109 }
110 }
111 }
112 1 => {
113 // String ranges: Check if characters in `str2` are inside the provided character ranges.
114 // Adjacent characters in `str1` constitute one range.
115 let len1 = len1 - (len1 & 1);
116 let get_ch = |ch: Scalar| -> InterpResult<'tcx, i32> {
117 let result = match (imm & USE_WORDS != 0, imm & USE_SIGNED != 0) {
118 (true, true) => i32::from(ch.to_i16()?),
119 (true, false) => i32::from(ch.to_u16()?),
120 (false, true) => i32::from(ch.to_i8()?),
121 (false, false) => i32::from(ch.to_u8()?),
122 };
123 interp_ok(result)
124 };
125
126 for i in 0..len2 {
127 for j in (0..len1).step_by(2) {
128 let ch2 = get_ch(ecx.read_scalar(&ecx.project_index(str2, i)?)?)?;
129 let ch1_1 = get_ch(ecx.read_scalar(&ecx.project_index(str1, j)?)?)?;
130 let ch1_2 = get_ch(ecx.read_scalar(&ecx.project_index(str1, j + 1)?)?)?;
131
132 if ch1_1 <= ch2 && ch2 <= ch1_2 {
133 result |= 1 << i;
134 }
135 }
136 }
137 }
138 2 => {
139 // String comparison: Mark positions where `str1` and `str2` have the same character.
140 result = (1 << default_len) - 1;
141 result ^= (1 << len1.max(len2)) - 1;
142
143 for i in 0..len1.min(len2) {
144 let ch1 = ecx.read_immediate(&ecx.project_index(str1, i)?)?;
145 let ch2 = ecx.read_immediate(&ecx.project_index(str2, i)?)?;
146 let eq = ecx.binary_op(mir::BinOp::Eq, &ch1, &ch2)?;
147 result |= i32::from(eq.to_scalar().to_bool()?) << i;
148 }
149 }
150 3 => {
151 // Substring search: Mark positions where `str1` is a substring in `str2`.
152 if len1 == 0 {
153 result = (1 << default_len) - 1;
154 } else if len1 <= len2 {
155 for i in 0..len2 {
156 // By default, assume that there is a match.
157 let mut has_match = true;
158
159 for j in 0..len1 {
160 let k = i + j;
161
162 if k >= default_len {
163 // The string ends before the match is completed, but the end
164 // is defined by the register size, not the string.
165 // This situation counts as a match.
166 break;
167 } else if k >= len2 {
168 // The string ends before the match is completed.
169 // This means that there is no match.
170 has_match = false;
171 break;
172 } else {
173 let ch1 = ecx.read_immediate(&ecx.project_index(str1, j)?)?;
174 let ch2 = ecx.read_immediate(&ecx.project_index(str2, k)?)?;
175 let ne = ecx.binary_op(mir::BinOp::Ne, &ch1, &ch2)?;
176
177 if ne.to_scalar().to_bool()? {
178 // Two characters are not equal, so there can't be a match.
179 has_match = false;
180 break;
181 }
182 }
183 }
184
185 // Accumulate the match result inside the bit mask.
186 if has_match {
187 result |= 1 << i;
188 }
189 }
190 }
191 }
192 _ => unreachable!(),
193 }
194
195 // Polarity: Possibly perform a bitwise complement on the result.
196 match (imm >> 4) & 3 {
197 3 => result ^= (1 << len2) - 1,
198 1 => result ^= (1 << default_len) - 1,
199 _ => (),
200 }
201
202 interp_ok(result)
203}
204
205/// Obtain the arguments of the intrinsic based on its name.
206/// The result is a tuple with the following values:
207/// * The first string argument.
208/// * The second string argument.
209/// * The string length values, if the intrinsic requires them.
210/// * The immediate instruction byte.
211///
212/// The string arguments will be transmuted into arrays of bytes
213/// or two-byte words, depending on the value of the immediate byte.
214/// Originally, they are [__m128i](https://doc.rust-lang.org/stable/core/arch/x86_64/struct.__m128i.html) values
215/// corresponding to the x86 128-bit integer SIMD type.
216fn deconstruct_args<'tcx>(
217 unprefixed_name: &str,
218 ecx: &mut MiriInterpCx<'tcx>,
219 link_name: Symbol,
220 args: &[OpTy<'tcx>],
221) -> InterpResult<'tcx, (OpTy<'tcx>, OpTy<'tcx>, Option<(u64, u64)>, u8)> {
222 let array_layout_fn = |ecx: &mut MiriInterpCx<'tcx>, imm: u8| {
223 if imm & USE_WORDS != 0 {
224 ecx.layout_of(Ty::new_array(ecx.tcx.tcx, ecx.tcx.types.u16, 8))
225 } else {
226 ecx.layout_of(Ty::new_array(ecx.tcx.tcx, ecx.tcx.types.u8, 16))
227 }
228 };
229
230 // The fourth letter of each string comparison intrinsic is either 'e' for "explicit" or 'i' for "implicit".
231 // The distinction will correspond to the intrinsics type signature. In this context, "explicit" and "implicit"
232 // refer to the way the string length is determined. The length is either passed explicitly in the "explicit"
233 // case or determined by a null terminator in the "implicit" case.
234 let is_explicit = match unprefixed_name.as_bytes().get(4) {
235 Some(&b'e') => true,
236 Some(&b'i') => false,
237 _ => unreachable!(),
238 };
239
240 if is_explicit {
241 let [str1, len1, str2, len2, imm] = ecx.check_shim_sig_llvm_intrinsic(link_name, args)?;
242 let imm = ecx.read_scalar(imm)?.to_u8()?;
243
244 let default_len = default_len::<u32>(imm);
245 // According to https://intel.github.io/SDM/instruction/PCMPESTRM.html, the length argument
246 // is initially signed and turned into an unsigned integer by taking its absolute value.
247 let len1 = u64::from(ecx.read_scalar(len1)?.to_i32()?.unsigned_abs().min(default_len));
248 let len2 = u64::from(ecx.read_scalar(len2)?.to_i32()?.unsigned_abs().min(default_len));
249
250 let array_layout = array_layout_fn(ecx, imm)?;
251 let str1 = str1.transmute(array_layout, ecx)?;
252 let str2 = str2.transmute(array_layout, ecx)?;
253
254 interp_ok((str1, str2, Some((len1, len2)), imm))
255 } else {
256 let [str1, str2, imm] = ecx.check_shim_sig_llvm_intrinsic(link_name, args)?;
257 let imm = ecx.read_scalar(imm)?.to_u8()?;
258
259 let array_layout = array_layout_fn(ecx, imm)?;
260 let str1 = str1.transmute(array_layout, ecx)?;
261 let str2 = str2.transmute(array_layout, ecx)?;
262
263 interp_ok((str1, str2, None, imm))
264 }
265}
266
267/// Calculate the c-style string length for a given string `str`.
268/// The string is either a length 16 array of bytes a length 8 array of two-byte words.
269fn implicit_len<'tcx>(
270 ecx: &mut MiriInterpCx<'tcx>,
271 str: &OpTy<'tcx>,
272 imm: u8,
273) -> InterpResult<'tcx, Option<u64>> {
274 let mut result = None;
275 let zero = ImmTy::from_int(0, str.layout.field(ecx, 0));
276
277 for i in 0..default_len::<u64>(imm) {
278 let ch = ecx.read_immediate(&ecx.project_index(str, i)?)?;
279 let is_zero = ecx.binary_op(mir::BinOp::Eq, &ch, &zero)?;
280 if is_zero.to_scalar().to_bool()? {
281 result = Some(i);
282 break;
283 }
284 }
285 interp_ok(result)
286}
287
288#[inline]
289fn default_len<T: From<u8>>(imm: u8) -> T {
290 if imm & USE_WORDS != 0 { T::from(8u8) } else { T::from(16u8) }
291}
292
293impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
294pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
295 fn emulate_x86_sse42_intrinsic(
296 &mut self,
297 link_name: Symbol,
298 args: &[OpTy<'tcx>],
299 dest: &MPlaceTy<'tcx>,
300 ) -> InterpResult<'tcx, EmulateItemResult> {
301 let this = self.eval_context_mut();
302 this.expect_target_feature_for_intrinsic(link_name, "sse4.2")?;
303 // Prefix should have already been checked.
304 let unprefixed_name = link_name.as_str().strip_prefix("llvm.x86.sse42.").unwrap();
305
306 match unprefixed_name {
307 // Used to implement the `_mm_cmpestrm` and the `_mm_cmpistrm` functions.
308 // These functions compare the input strings and return the resulting mask.
309 // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#ig_expand=1044,922
310 "pcmpistrm128" | "pcmpestrm128" => {
311 let (str1, str2, len, imm) =
312 deconstruct_args(unprefixed_name, this, link_name, args)?;
313 let mask = compare_strings(this, &str1, &str2, len, imm)?;
314
315 // The sixth bit inside the immediate byte distinguishes
316 // between a bit mask or a byte mask when generating a mask.
317 if imm & 0b100_0000 != 0 {
318 let (array_layout, size) = if imm & USE_WORDS != 0 {
319 (this.layout_of(Ty::new_array(this.tcx.tcx, this.tcx.types.u16, 8))?, 2)
320 } else {
321 (this.layout_of(Ty::new_array(this.tcx.tcx, this.tcx.types.u8, 16))?, 1)
322 };
323 let size = Size::from_bytes(size);
324 let dest = dest.transmute(array_layout, this)?;
325
326 for i in 0..default_len::<u64>(imm) {
327 let result = helpers::bool_to_simd_element(mask & (1 << i) != 0, size);
328 this.write_scalar(result, &this.project_index(&dest, i)?)?;
329 }
330 } else {
331 let layout = this.layout_of(this.tcx.types.i128)?;
332 let dest = dest.transmute(layout, this)?;
333 this.write_scalar(Scalar::from_i128(i128::from(mask)), &dest)?;
334 }
335 }
336
337 // Used to implement the `_mm_cmpestra` and the `_mm_cmpistra` functions.
338 // These functions compare the input strings and return `1` if the end of the second
339 // input string is not reached and the resulting mask is zero, and `0` otherwise.
340 // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#ig_expand=919,1041
341 "pcmpistria128" | "pcmpestria128" => {
342 let (str1, str2, len, imm) =
343 deconstruct_args(unprefixed_name, this, link_name, args)?;
344 let result = if compare_strings(this, &str1, &str2, len, imm)? != 0 {
345 false
346 } else if let Some((_, len)) = len {
347 len >= default_len::<u64>(imm)
348 } else {
349 // Test if the second string has no null-terminator.
350 implicit_len(this, &str2, imm)?.is_none()
351 };
352
353 this.write_scalar(Scalar::from_i32(i32::from(result)), dest)?;
354 }
355
356 // Used to implement the `_mm_cmpestri` and the `_mm_cmpistri` functions.
357 // These functions compare the input strings and return the bit index
358 // for most significant or least significant bit of the resulting mask.
359 // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#ig_expand=921,1043
360 "pcmpistri128" | "pcmpestri128" => {
361 let (str1, str2, len, imm) =
362 deconstruct_args(unprefixed_name, this, link_name, args)?;
363 let mask = compare_strings(this, &str1, &str2, len, imm)?;
364
365 let len = default_len::<u32>(imm);
366 // The sixth bit inside the immediate byte distinguishes between the least
367 // significant bit and the most significant bit when generating an index.
368 let result = if imm & 0b100_0000 != 0 {
369 // most significant bit
370 31u32.wrapping_sub(mask.leading_zeros()).min(len)
371 } else {
372 // least significant bit
373 mask.trailing_zeros().min(len)
374 };
375 this.write_scalar(Scalar::from_i32(i32::try_from(result).unwrap()), dest)?;
376 }
377
378 // Used to implement the `_mm_cmpestro` and the `_mm_cmpistro` functions.
379 // These functions compare the input strings and return the lowest bit of the
380 // resulting mask.
381 // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#ig_expand=923,1045
382 "pcmpistrio128" | "pcmpestrio128" => {
383 let (str1, str2, len, imm) =
384 deconstruct_args(unprefixed_name, this, link_name, args)?;
385 let mask = compare_strings(this, &str1, &str2, len, imm)?;
386 this.write_scalar(Scalar::from_i32(mask & 1), dest)?;
387 }
388
389 // Used to implement the `_mm_cmpestrc` and the `_mm_cmpistrc` functions.
390 // These functions compare the input strings and return `1` if the resulting
391 // mask was non-zero, and `0` otherwise.
392 // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#ig_expand=920,1042
393 "pcmpistric128" | "pcmpestric128" => {
394 let (str1, str2, len, imm) =
395 deconstruct_args(unprefixed_name, this, link_name, args)?;
396 let mask = compare_strings(this, &str1, &str2, len, imm)?;
397 this.write_scalar(Scalar::from_i32(i32::from(mask != 0)), dest)?;
398 }
399
400 // Used to implement the `_mm_cmpistrz` and the `_mm_cmpistrs` functions.
401 // These functions return `1` if the string end has been reached and `0` otherwise.
402 // Since these functions define the string length implicitly, it is equal to a
403 // search for a null terminator (see `deconstruct_args` for more details).
404 // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#ig_expand=924,925
405 "pcmpistriz128" | "pcmpistris128" => {
406 let [str1, str2, imm] = this.check_shim_sig_llvm_intrinsic(link_name, args)?;
407 let imm = this.read_scalar(imm)?.to_u8()?;
408
409 let str = if unprefixed_name == "pcmpistris128" { str1 } else { str2 };
410 let array_layout = if imm & USE_WORDS != 0 {
411 this.layout_of(Ty::new_array(this.tcx.tcx, this.tcx.types.u16, 8))?
412 } else {
413 this.layout_of(Ty::new_array(this.tcx.tcx, this.tcx.types.u8, 16))?
414 };
415 let str = str.transmute(array_layout, this)?;
416 let result = implicit_len(this, &str, imm)?.is_some();
417
418 this.write_scalar(Scalar::from_i32(i32::from(result)), dest)?;
419 }
420
421 // Used to implement the `_mm_cmpestrz` and the `_mm_cmpestrs` functions.
422 // These functions return 1 if the explicitly passed string length is smaller
423 // than 16 for byte-sized operands or 8 for word-sized operands.
424 // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#ig_expand=1046,1047
425 "pcmpestriz128" | "pcmpestris128" => {
426 let [_, len1, _, len2, imm] =
427 this.check_shim_sig_llvm_intrinsic(link_name, args)?;
428 let len = if unprefixed_name == "pcmpestris128" { len1 } else { len2 };
429 // According to https://intel.github.io/SDM/instruction/PCMPESTRM.html, the length argument
430 // is initially signed and turned into an unsigned integer by taking its absolute value.
431 let len = this.read_scalar(len)?.to_i32()?.unsigned_abs();
432 let imm = this.read_scalar(imm)?.to_u8()?;
433 this.write_scalar(
434 Scalar::from_i32(i32::from(len < default_len::<u32>(imm))),
435 dest,
436 )?;
437 }
438
439 // Used to implement the `_mm_crc32_u{8, 16, 32, 64}` functions.
440 // These functions calculate a 32-bit CRC using `0x11EDC6F41`
441 // as the polynomial, also known as CRC32C.
442 // https://datatracker.ietf.org/doc/html/rfc3720#section-12.1
443 "crc32.32.8" | "crc32.32.16" | "crc32.32.32" | "crc32.64.64" => {
444 let bit_size = match unprefixed_name {
445 "crc32.32.8" => 8,
446 "crc32.32.16" => 16,
447 "crc32.32.32" => 32,
448 "crc32.64.64" => 64,
449 _ => unreachable!(),
450 };
451
452 if bit_size == 64 && this.tcx.sess.target.arch != Arch::X86_64 {
453 return interp_ok(EmulateItemResult::NotSupported);
454 }
455
456 let [left, right] = this.check_shim_sig_llvm_intrinsic(link_name, args)?;
457 let left = this.read_scalar(left)?;
458 let right = this.read_scalar(right)?;
459
460 let crc = if bit_size == 64 {
461 // The 64-bit version will only consider the lower 32 bits,
462 // while the upper 32 bits get discarded.
463 #[expect(clippy::as_conversions)]
464 (left.to_u64()? as u32)
465 } else {
466 left.to_u32()?
467 };
468 let data = match bit_size {
469 8 => u64::from(right.to_u8()?),
470 16 => u64::from(right.to_u16()?),
471 32 => u64::from(right.to_u32()?),
472 64 => right.to_u64()?,
473 _ => unreachable!(),
474 };
475
476 let result = compute_crc32(crc, data, bit_size, 0x11EDC6F41);
477 let result = if bit_size == 64 {
478 Scalar::from_u64(u64::from(result))
479 } else {
480 Scalar::from_u32(result)
481 };
482
483 this.write_scalar(result, dest)?;
484 }
485 _ => return interp_ok(EmulateItemResult::NotSupported),
486 }
487 interp_ok(EmulateItemResult::NeedsReturn)
488 }
489}