Rollup merge of #126512 - RalfJung:miri-sync, r=RalfJung

Miri subtree update

r? `@ghost`
This commit is contained in:
Matthias Krüger 2024-06-15 10:56:45 +02:00 committed by GitHub
commit 92ad0b1cd2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 1520 additions and 150 deletions

View File

@ -151,6 +151,21 @@ platform. For example `cargo miri test --target s390x-unknown-linux-gnu`
will run your test suite on a big-endian target, which is useful for testing
endian-sensitive code.
### Testing multiple different executions
Certain parts of the execution are picked randomly by Miri, such as the exact base address
allocations are stored at and the interleaving of concurrently executing threads. Sometimes, it can
be useful to explore multiple different execution, e.g. to make sure that your code does not depend
on incidental "super-alignment" of new allocations and to test different thread interleavings.
This can be done with the `--many-seeds` flag:
```
cargo miri test --many-seeds # tries the seeds in 0..64
cargo miri test --many-seeds=0..16
```
The default of 64 different seeds is quite slow, so you probably want to specify a smaller range.
### Running Miri on CI
When running Miri on CI, use the following snippet to install a nightly toolchain with the Miri
@ -183,23 +198,6 @@ Here is an example job for GitHub Actions:
The explicit `cargo miri setup` helps to keep the output of the actual test step
clean.
### Testing for alignment issues
Miri can sometimes miss misaligned accesses since allocations can "happen to be"
aligned just right. You can use `-Zmiri-symbolic-alignment-check` to definitely
catch all such issues, but that flag will also cause false positives when code
does manual pointer arithmetic to account for alignment. Another alternative is
to call Miri with various values for `-Zmiri-seed`; that will alter the
randomness that is used to determine allocation base addresses. The following
snippet calls Miri in a loop with different values for the seed:
```
for SEED in $(seq 0 255); do
echo "Trying seed: $SEED"
MIRIFLAGS=-Zmiri-seed=$SEED cargo miri test || { echo "Failing seed: $SEED"; break; };
done
```
### Supported targets
Miri does not support all targets supported by Rust. The good news, however, is

View File

@ -1,10 +1,10 @@
//! Implements the various phases of `cargo miri run/test`.
use std::env;
use std::fs::{self, File};
use std::io::BufReader;
use std::io::{BufReader, Write};
use std::path::{Path, PathBuf};
use std::process::Command;
use std::{env, thread};
use rustc_version::VersionMeta;
@ -34,6 +34,8 @@ Examples:
";
const DEFAULT_MANY_SEEDS: &str = "0..64";
fn show_help() {
println!("{CARGO_MIRI_HELP}");
}
@ -119,7 +121,7 @@ pub fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
// <https://github.com/rust-lang/miri/pull/1540#issuecomment-693553191> describes an alternative
// approach that uses `cargo check`, making that part easier but target and binary handling
// harder.
let cargo_miri_path = std::env::current_exe()
let cargo_miri_path = env::current_exe()
.expect("current executable path invalid")
.into_os_string()
.into_string()
@ -163,14 +165,22 @@ pub fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
let target_dir = get_target_dir(&metadata);
cmd.arg("--target-dir").arg(target_dir);
// Store many-seeds argument.
let mut many_seeds = None;
// *After* we set all the flags that need setting, forward everything else. Make sure to skip
// `--target-dir` (which would otherwise be set twice).
// `--target-dir` (which would otherwise be set twice) and `--many-seeds` (which is our flag, not cargo's).
for arg in
ArgSplitFlagValue::from_string_iter(&mut args, "--target-dir").filter_map(Result::err)
{
cmd.arg(arg);
if arg == "--many-seeds" {
many_seeds = Some(DEFAULT_MANY_SEEDS.to_owned());
} else if let Some(val) = arg.strip_prefix("--many-seeds=") {
many_seeds = Some(val.to_owned());
} else {
cmd.arg(arg);
}
}
// Forward all further arguments (not consumed by `ArgSplitFlagValue`) to cargo.
// Forward all further arguments after `--` (not consumed by `ArgSplitFlagValue`) to cargo.
cmd.args(args);
// Set `RUSTC_WRAPPER` to ourselves. Cargo will prepend that binary to its usual invocation,
@ -222,6 +232,9 @@ pub fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
// Forward some crucial information to our own re-invocations.
cmd.env("MIRI_SYSROOT", miri_sysroot);
cmd.env("MIRI_LOCAL_CRATES", local_crates(&metadata));
if let Some(many_seeds) = many_seeds {
cmd.env("MIRI_MANY_SEEDS", many_seeds);
}
if verbose > 0 {
cmd.env("MIRI_VERBOSE", verbose.to_string()); // This makes the other phases verbose.
}
@ -309,7 +322,7 @@ pub fn phase_rustc(mut args: impl Iterator<Item = String>, phase: RustcPhase) {
}
}
let verbose = std::env::var("MIRI_VERBOSE")
let verbose = env::var("MIRI_VERBOSE")
.map_or(0, |verbose| verbose.parse().expect("verbosity flag must be an integer"));
let target_crate = is_target_crate();
@ -489,7 +502,7 @@ pub fn phase_rustc(mut args: impl Iterator<Item = String>, phase: RustcPhase) {
// This is a host crate.
// When we're running `cargo-miri` from `x.py` we need to pass the sysroot explicitly
// due to bootstrap complications.
if let Some(sysroot) = std::env::var_os("MIRI_HOST_SYSROOT") {
if let Some(sysroot) = env::var_os("MIRI_HOST_SYSROOT") {
cmd.arg("--sysroot").arg(sysroot);
}
@ -532,7 +545,7 @@ pub enum RunnerPhase {
}
pub fn phase_runner(mut binary_args: impl Iterator<Item = String>, phase: RunnerPhase) {
let verbose = std::env::var("MIRI_VERBOSE")
let verbose = env::var("MIRI_VERBOSE")
.map_or(0, |verbose| verbose.parse().expect("verbosity flag must be an integer"));
let binary = binary_args.next().unwrap();
@ -541,6 +554,7 @@ pub fn phase_runner(mut binary_args: impl Iterator<Item = String>, phase: Runner
"file {:?} not found or `cargo-miri` invoked incorrectly; please only invoke this binary through `cargo miri`", binary
));
let file = BufReader::new(file);
let binary_args = binary_args.collect::<Vec<_>>();
let info = serde_json::from_reader(file).unwrap_or_else(|_| {
show_error!("file {:?} contains outdated or invalid JSON; try `cargo clean`", binary)
@ -555,84 +569,114 @@ pub fn phase_runner(mut binary_args: impl Iterator<Item = String>, phase: Runner
}
};
let mut cmd = miri();
let many_seeds = env::var("MIRI_MANY_SEEDS");
run_many_seeds(many_seeds.ok(), |seed| {
let mut cmd = miri();
// Set missing env vars. We prefer build-time env vars over run-time ones; see
// <https://github.com/rust-lang/miri/issues/1661> for the kind of issue that fixes.
for (name, val) in info.env {
// `CARGO_MAKEFLAGS` contains information about how to reach the jobserver, but by the time
// the program is being run, that jobserver no longer exists (cargo only runs the jobserver
// for the build portion of `cargo run`/`cargo test`). Hence we shouldn't forward this.
// Also see <https://github.com/rust-lang/rust/pull/113730>.
if name == "CARGO_MAKEFLAGS" {
continue;
}
if let Some(old_val) = env::var_os(&name) {
if old_val == val {
// This one did not actually change, no need to re-set it.
// (This keeps the `debug_cmd` below more manageable.)
// Set missing env vars. We prefer build-time env vars over run-time ones; see
// <https://github.com/rust-lang/miri/issues/1661> for the kind of issue that fixes.
for (name, val) in &info.env {
// `CARGO_MAKEFLAGS` contains information about how to reach the jobserver, but by the time
// the program is being run, that jobserver no longer exists (cargo only runs the jobserver
// for the build portion of `cargo run`/`cargo test`). Hence we shouldn't forward this.
// Also see <https://github.com/rust-lang/rust/pull/113730>.
if name == "CARGO_MAKEFLAGS" {
continue;
} else if verbose > 0 {
eprintln!(
"[cargo-miri runner] Overwriting run-time env var {name:?}={old_val:?} with build-time value {val:?}"
);
}
if let Some(old_val) = env::var_os(name) {
if *old_val == *val {
// This one did not actually change, no need to re-set it.
// (This keeps the `debug_cmd` below more manageable.)
continue;
} else if verbose > 0 {
eprintln!(
"[cargo-miri runner] Overwriting run-time env var {name:?}={old_val:?} with build-time value {val:?}"
);
}
}
cmd.env(name, val);
}
if phase != RunnerPhase::Rustdoc {
// Set the sysroot. Not necessary in rustdoc, where we already set the sysroot in
// `phase_rustdoc`. rustdoc will forward that flag when invoking rustc (i.e., us), so the
// flag is present in `info.args`.
cmd.arg("--sysroot").arg(env::var_os("MIRI_SYSROOT").unwrap());
}
// Forward rustc arguments.
// We need to patch "--extern" filenames because we forced a check-only
// build without cargo knowing about that: replace `.rlib` suffix by
// `.rmeta`.
// We also need to remove `--error-format` as cargo specifies that to be JSON,
// but when we run here, cargo does not interpret the JSON any more. `--json`
// then also needs to be dropped.
let mut args = info.args.iter();
while let Some(arg) = args.next() {
if arg == "--extern" {
forward_patched_extern_arg(&mut (&mut args).cloned(), &mut cmd);
} else if let Some(suffix) = arg.strip_prefix("--error-format") {
assert!(suffix.starts_with('='));
// Drop this argument.
} else if let Some(suffix) = arg.strip_prefix("--json") {
assert!(suffix.starts_with('='));
// Drop this argument.
} else {
cmd.arg(arg);
}
}
cmd.env(name, val);
}
if phase != RunnerPhase::Rustdoc {
// Set the sysroot. Not necessary in rustdoc, where we already set the sysroot in
// `phase_rustdoc`. rustdoc will forward that flag when invoking rustc (i.e., us), so the
// flag is present in `info.args`.
cmd.arg("--sysroot").arg(env::var_os("MIRI_SYSROOT").unwrap());
}
// Forward rustc arguments.
// We need to patch "--extern" filenames because we forced a check-only
// build without cargo knowing about that: replace `.rlib` suffix by
// `.rmeta`.
// We also need to remove `--error-format` as cargo specifies that to be JSON,
// but when we run here, cargo does not interpret the JSON any more. `--json`
// then also needs to be dropped.
let mut args = info.args.into_iter();
while let Some(arg) = args.next() {
if arg == "--extern" {
forward_patched_extern_arg(&mut args, &mut cmd);
} else if let Some(suffix) = arg.strip_prefix("--error-format") {
assert!(suffix.starts_with('='));
// Drop this argument.
} else if let Some(suffix) = arg.strip_prefix("--json") {
assert!(suffix.starts_with('='));
// Drop this argument.
} else {
cmd.arg(arg);
// Respect `MIRIFLAGS`.
if let Ok(a) = env::var("MIRIFLAGS") {
let args = flagsplit(&a);
cmd.args(args);
}
// Set the current seed.
if let Some(seed) = seed {
eprintln!("Trying seed: {seed}");
cmd.arg(format!("-Zmiri-seed={seed}"));
}
}
// Respect `MIRIFLAGS`.
if let Ok(a) = env::var("MIRIFLAGS") {
let args = flagsplit(&a);
cmd.args(args);
}
// Then pass binary arguments.
cmd.arg("--");
cmd.args(binary_args);
// Then pass binary arguments.
cmd.arg("--");
cmd.args(&binary_args);
// Make sure we use the build-time working directory for interpreting Miri/rustc arguments.
// But then we need to switch to the run-time one, which we instruct Miri to do by setting `MIRI_CWD`.
cmd.current_dir(info.current_dir);
cmd.env("MIRI_CWD", env::current_dir().unwrap());
// Make sure we use the build-time working directory for interpreting Miri/rustc arguments.
// But then we need to switch to the run-time one, which we instruct Miri to do by setting `MIRI_CWD`.
cmd.current_dir(&info.current_dir);
cmd.env("MIRI_CWD", env::current_dir().unwrap());
// Run it.
debug_cmd("[cargo-miri runner]", verbose, &cmd);
match phase {
RunnerPhase::Rustdoc => exec_with_pipe(cmd, &info.stdin, format!("{binary}.stdin")),
RunnerPhase::Cargo => exec(cmd),
}
// Run it.
debug_cmd("[cargo-miri runner]", verbose, &cmd);
match phase {
RunnerPhase::Rustdoc => {
cmd.stdin(std::process::Stdio::piped());
let mut child = cmd.spawn().expect("failed to spawn process");
let child_stdin = child.stdin.take().unwrap();
// Write stdin in a background thread, as it may block.
let exit_status = thread::scope(|s| {
s.spawn(|| {
let mut child_stdin = child_stdin;
// Ignore failure, it is most likely due to the process having terminated.
let _ = child_stdin.write_all(&info.stdin);
});
child.wait().expect("failed to run command")
});
if !exit_status.success() {
std::process::exit(exit_status.code().unwrap_or(-1));
}
}
RunnerPhase::Cargo => {
let exit_status = cmd.status().expect("failed to run command");
if !exit_status.success() {
std::process::exit(exit_status.code().unwrap_or(-1));
}
}
}
});
}
pub fn phase_rustdoc(mut args: impl Iterator<Item = String>) {
let verbose = std::env::var("MIRI_VERBOSE")
let verbose = env::var("MIRI_VERBOSE")
.map_or(0, |verbose| verbose.parse().expect("verbosity flag must be an integer"));
// phase_cargo_miri sets the RUSTDOC env var to ourselves, and puts a backup
@ -681,7 +725,7 @@ pub fn phase_rustdoc(mut args: impl Iterator<Item = String>) {
cmd.arg("--cfg").arg("miri");
// Make rustdoc call us back.
let cargo_miri_path = std::env::current_exe().expect("current executable path invalid");
let cargo_miri_path = env::current_exe().expect("current executable path invalid");
cmd.arg("--test-builder").arg(&cargo_miri_path); // invoked by forwarding most arguments
cmd.arg("--runtool").arg(&cargo_miri_path); // invoked with just a single path argument

View File

@ -171,11 +171,16 @@ where
drop(path); // We don't need the path, we can pipe the bytes directly
cmd.stdin(std::process::Stdio::piped());
let mut child = cmd.spawn().expect("failed to spawn process");
{
let stdin = child.stdin.as_mut().expect("failed to open stdin");
stdin.write_all(input).expect("failed to write out test source");
}
let exit_status = child.wait().expect("failed to run command");
let child_stdin = child.stdin.take().unwrap();
// Write stdin in a background thread, as it may block.
let exit_status = std::thread::scope(|s| {
s.spawn(|| {
let mut child_stdin = child_stdin;
// Ignore failure, it is most likely due to the process having terminated.
let _ = child_stdin.write_all(input);
});
child.wait().expect("failed to run command")
});
std::process::exit(exit_status.code().unwrap_or(-1))
}
}
@ -317,3 +322,24 @@ pub fn clean_target_dir(meta: &Metadata) {
remove_dir_all_idem(&target_dir).unwrap_or_else(|err| show_error!("{}", err))
}
/// Run `f` according to the many-seeds argument. In single-seed mode, `f` will only
/// be called once, with `None`.
pub fn run_many_seeds(many_seeds: Option<String>, f: impl Fn(Option<u32>)) {
let Some(many_seeds) = many_seeds else {
return f(None);
};
let (from, to) = many_seeds
.split_once("..")
.unwrap_or_else(|| show_error!("invalid format for `--many-seeds`: expected `from..to`"));
let from: u32 = if from.is_empty() {
0
} else {
from.parse().unwrap_or_else(|_| show_error!("invalid `from` in `--many-seeds=from..to"))
};
let to: u32 =
to.parse().unwrap_or_else(|_| show_error!("invalid `to` in `--many-seeds=from..to"));
for seed in from..to {
f(Some(seed));
}
}

View File

@ -98,7 +98,7 @@ Build miri, set up a sysroot and then run the test suite.
Build miri, set up a sysroot and then run the driver with the given <flags>.
(Also respects MIRIFLAGS environment variable.)
If `--many-seeds` is present, Miri is run many times in parallel with different seeds.
The range defaults to `0..256`.
The range defaults to `0..64`.
./miri fmt <flags>:
Format all sources and tests. <flags> are passed to `rustfmt`.
@ -180,17 +180,16 @@ fn main() -> Result<()> {
dep = true;
} else if args.get_long_flag("verbose")? || args.get_short_flag('v')? {
verbose = true;
} else if let Some(val) = args.get_long_opt_with_default("many-seeds", "0..256")? {
} else if let Some(val) = args.get_long_opt_with_default("many-seeds", "0..64")? {
let (from, to) = val.split_once("..").ok_or_else(|| {
anyhow!("invalid format for `--many-seeds-range`: expected `from..to`")
anyhow!("invalid format for `--many-seeds`: expected `from..to`")
})?;
let from: u32 = if from.is_empty() {
0
} else {
from.parse().context("invalid `from` in `--many-seeds-range=from..to")?
from.parse().context("invalid `from` in `--many-seeds=from..to")?
};
let to: u32 =
to.parse().context("invalid `to` in `--many-seeds-range=from..to")?;
let to: u32 = to.parse().context("invalid `to` in `--many-seeds=from..to")?;
many_seeds = Some(from..to);
} else if let Some(val) = args.get_long_opt("target")? {
target = Some(val);

View File

@ -1 +1 @@
565cadb514d35e7b851540edbc172af0f606014f
f6b4b71ef10307201b52c17b0f9dcf9557cd90ba

View File

@ -45,7 +45,7 @@ macro_rules! declare_id {
// We use 0 as a sentinel value (see the comment above) and,
// therefore, need to shift by one when converting from an index
// into a vector.
let shifted_idx = u32::try_from(idx).unwrap().checked_add(1).unwrap();
let shifted_idx = u32::try_from(idx).unwrap().strict_add(1);
$name(std::num::NonZero::new(shifted_idx).unwrap())
}
fn index(self) -> usize {
@ -350,7 +350,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
} else {
mutex.owner = Some(thread);
}
mutex.lock_count = mutex.lock_count.checked_add(1).unwrap();
mutex.lock_count = mutex.lock_count.strict_add(1);
if let Some(data_race) = &this.machine.data_race {
data_race.acquire_clock(&mutex.clock, &this.machine.threads);
}
@ -370,9 +370,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
return Ok(None);
}
let old_lock_count = mutex.lock_count;
mutex.lock_count = old_lock_count
.checked_sub(1)
.expect("invariant violation: lock_count == 0 iff the thread is unlocked");
mutex.lock_count = old_lock_count.strict_sub(1);
if mutex.lock_count == 0 {
mutex.owner = None;
// The mutex is completely unlocked. Try transferring ownership
@ -450,7 +448,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
trace!("rwlock_reader_lock: {:?} now also held (one more time) by {:?}", id, thread);
let rwlock = &mut this.machine.sync.rwlocks[id];
let count = rwlock.readers.entry(thread).or_insert(0);
*count = count.checked_add(1).expect("the reader counter overflowed");
*count = count.strict_add(1);
if let Some(data_race) = &this.machine.data_race {
data_race.acquire_clock(&rwlock.clock_unlocked, &this.machine.threads);
}

View File

@ -643,10 +643,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
assert_eq!(index_len as u64, dest_len);
for i in 0..dest_len {
let src_index: u64 = index[usize::try_from(i).unwrap()]
.unwrap_leaf()
.to_u32()
.into();
let src_index: u64 =
index[usize::try_from(i).unwrap()].unwrap_leaf().to_u32().into();
let dest = this.project_index(&dest, i)?;
let val = if src_index < left_len {

View File

@ -142,7 +142,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
os_str: &OsStr,
memkind: MemoryKind,
) -> InterpResult<'tcx, Pointer> {
let size = u64::try_from(os_str.len()).unwrap().checked_add(1).unwrap(); // Make space for `0` terminator.
let size = u64::try_from(os_str.len()).unwrap().strict_add(1); // Make space for `0` terminator.
let this = self.eval_context_mut();
let arg_type = Ty::new_array(this.tcx.tcx, this.tcx.types.u8, size);
@ -158,7 +158,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
os_str: &OsStr,
memkind: MemoryKind,
) -> InterpResult<'tcx, Pointer> {
let size = u64::try_from(os_str.len()).unwrap().checked_add(1).unwrap(); // Make space for `0x0000` terminator.
let size = u64::try_from(os_str.len()).unwrap().strict_add(1); // Make space for `0x0000` terminator.
let this = self.eval_context_mut();
let arg_type = Ty::new_array(this.tcx.tcx, this.tcx.types.u16, size);

View File

@ -893,7 +893,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let dirent64_layout = this.libc_ty_layout("dirent64");
let d_name_offset = dirent64_layout.fields.offset(4 /* d_name */).bytes();
let size = d_name_offset.checked_add(name_len).unwrap();
let size = d_name_offset.strict_add(name_len);
let entry = this.allocate_ptr(
Size::from_bytes(size),
@ -994,7 +994,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
name_place.ptr(),
name_place.layout.size.bytes(),
)?;
let file_name_len = file_name_buf_len.checked_sub(1).unwrap();
let file_name_len = file_name_buf_len.strict_sub(1);
if !name_fits {
throw_unsup_format!(
"a directory entry had a name too large to fit in libc::dirent"

View File

@ -57,10 +57,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let flags = this.read_scalar(flags)?.to_i32()?;
let epoll_cloexec = this.eval_libc_i32("EPOLL_CLOEXEC");
if flags == epoll_cloexec {
// Miri does not support exec, so this flag has no effect.
} else if flags != 0 {
throw_unsup_format!("epoll_create1 flags {flags} are not implemented");
// Miri does not support exec, so EPOLL_CLOEXEC flag has no effect.
if flags != epoll_cloexec && flags != 0 {
throw_unsup_format!(
"epoll_create1: flag {:#x} is unsupported, only 0 or EPOLL_CLOEXEC are allowed",
flags
);
}
let fd = this.machine.fds.insert_fd(FileDescriptor::new(Epoll::default()));

View File

@ -1,15 +1,38 @@
use std::cell::RefCell;
use std::collections::VecDeque;
use std::io;
use std::io::{Error, ErrorKind, Read};
use std::rc::{Rc, Weak};
use crate::shims::unix::*;
use crate::*;
use crate::{concurrency::VClock, *};
use self::fd::FileDescriptor;
/// The maximum capacity of the socketpair buffer in bytes.
/// This number is arbitrary as the value can always
/// be configured in the real system.
const MAX_SOCKETPAIR_BUFFER_CAPACITY: usize = 212992;
/// Pair of connected sockets.
///
/// We currently don't allow sending any data through this pair, so this can be just a dummy.
#[derive(Debug)]
struct SocketPair;
struct SocketPair {
// By making the write link weak, a `write` can detect when all readers are
// gone, and trigger EPIPE as appropriate.
writebuf: Weak<RefCell<Buffer>>,
readbuf: Rc<RefCell<Buffer>>,
is_nonblock: bool,
}
#[derive(Debug)]
struct Buffer {
buf: VecDeque<u8>,
clock: VClock,
/// Indicates if there is at least one active writer to this buffer.
/// If all writers of this buffer are dropped, buf_has_writer becomes false and we
/// indicate EOF instead of blocking.
buf_has_writer: bool,
}
impl FileDescription for SocketPair {
fn name(&self) -> &'static str {
@ -20,17 +43,102 @@ impl FileDescription for SocketPair {
self: Box<Self>,
_communicate_allowed: bool,
) -> InterpResult<'tcx, io::Result<()>> {
// This is used to signal socketfd of other side that there is no writer to its readbuf.
// If the upgrade fails, there is no need to update as all read ends have been dropped.
if let Some(writebuf) = self.writebuf.upgrade() {
writebuf.borrow_mut().buf_has_writer = false;
};
Ok(Ok(()))
}
fn read<'tcx>(
&mut self,
_communicate_allowed: bool,
bytes: &mut [u8],
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
let request_byte_size = bytes.len();
let mut readbuf = self.readbuf.borrow_mut();
// Always succeed on read size 0.
if request_byte_size == 0 {
return Ok(Ok(0));
}
if readbuf.buf.is_empty() {
if !readbuf.buf_has_writer {
// Socketpair with no writer and empty buffer.
// 0 bytes successfully read indicates end-of-file.
return Ok(Ok(0));
} else {
if self.is_nonblock {
// Non-blocking socketpair with writer and empty buffer.
// https://linux.die.net/man/2/read
// EAGAIN or EWOULDBLOCK can be returned for socket,
// POSIX.1-2001 allows either error to be returned for this case.
// Since there is no ErrorKind for EAGAIN, WouldBlock is used.
return Ok(Err(Error::from(ErrorKind::WouldBlock)));
} else {
// Blocking socketpair with writer and empty buffer.
// FIXME: blocking is currently not supported
throw_unsup_format!("socketpair read: blocking isn't supported yet");
}
}
}
// Synchronize with all previous writes to this buffer.
// FIXME: this over-synchronizes; a more precise approach would be to
// only sync with the writes whose data we will read.
ecx.acquire_clock(&readbuf.clock);
// Do full read / partial read based on the space available.
// Conveniently, `read` exists on `VecDeque` and has exactly the desired behavior.
let actual_read_size = readbuf.buf.read(bytes).unwrap();
return Ok(Ok(actual_read_size));
}
fn write<'tcx>(
&mut self,
_communicate_allowed: bool,
bytes: &[u8],
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
let write_size = bytes.len();
// Always succeed on write size 0.
// ("If count is zero and fd refers to a file other than a regular file, the results are not specified.")
if write_size == 0 {
return Ok(Ok(0));
}
let Some(writebuf) = self.writebuf.upgrade() else {
// If the upgrade from Weak to Rc fails, it indicates that all read ends have been
// closed.
return Ok(Err(Error::from(ErrorKind::BrokenPipe)));
};
let mut writebuf = writebuf.borrow_mut();
let data_size = writebuf.buf.len();
let available_space = MAX_SOCKETPAIR_BUFFER_CAPACITY.checked_sub(data_size).unwrap();
if available_space == 0 {
if self.is_nonblock {
// Non-blocking socketpair with a full buffer.
return Ok(Err(Error::from(ErrorKind::WouldBlock)));
} else {
// Blocking socketpair with a full buffer.
throw_unsup_format!("socketpair write: blocking isn't supported yet");
}
}
// Remember this clock so `read` can synchronize with us.
if let Some(clock) = &ecx.release_clock() {
writebuf.clock.join(clock);
}
// Do full write / partial write based on the space available.
let actual_write_size = write_size.min(available_space);
writebuf.buf.extend(&bytes[..actual_write_size]);
return Ok(Ok(actual_write_size));
}
}
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
/// Currently this function this function is a stub. Eventually we need to
/// properly implement an FD type for sockets and have this function create
/// two sockets and associated FDs such that writing to one will produce
/// data that can be read from the other.
///
/// For more information on the arguments see the socketpair manpage:
/// <https://linux.die.net/man/2/socketpair>
fn socketpair(
@ -42,17 +150,80 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_mut();
let _domain = this.read_scalar(domain)?.to_i32()?;
let _type_ = this.read_scalar(type_)?.to_i32()?;
let _protocol = this.read_scalar(protocol)?.to_i32()?;
let domain = this.read_scalar(domain)?.to_i32()?;
let mut type_ = this.read_scalar(type_)?.to_i32()?;
let protocol = this.read_scalar(protocol)?.to_i32()?;
let sv = this.deref_pointer(sv)?;
// FIXME: fail on unsupported inputs
let mut is_sock_nonblock = false;
// Parse and remove the type flags that we support. If type != 0 after removing,
// unsupported flags are used.
if type_ & this.eval_libc_i32("SOCK_STREAM") == this.eval_libc_i32("SOCK_STREAM") {
type_ &= !(this.eval_libc_i32("SOCK_STREAM"));
}
// SOCK_NONBLOCK only exists on Linux.
if this.tcx.sess.target.os == "linux" {
if type_ & this.eval_libc_i32("SOCK_NONBLOCK") == this.eval_libc_i32("SOCK_NONBLOCK") {
is_sock_nonblock = true;
type_ &= !(this.eval_libc_i32("SOCK_NONBLOCK"));
}
if type_ & this.eval_libc_i32("SOCK_CLOEXEC") == this.eval_libc_i32("SOCK_CLOEXEC") {
type_ &= !(this.eval_libc_i32("SOCK_CLOEXEC"));
}
}
// Fail on unsupported input.
// AF_UNIX and AF_LOCAL are synonyms, so we accept both in case
// their values differ.
if domain != this.eval_libc_i32("AF_UNIX") && domain != this.eval_libc_i32("AF_LOCAL") {
throw_unsup_format!(
"socketpair: domain {:#x} is unsupported, only AF_UNIX \
and AF_LOCAL are allowed",
domain
);
} else if type_ != 0 {
throw_unsup_format!(
"socketpair: type {:#x} is unsupported, only SOCK_STREAM, \
SOCK_CLOEXEC and SOCK_NONBLOCK are allowed",
type_
);
} else if protocol != 0 {
throw_unsup_format!(
"socketpair: socket protocol {protocol} is unsupported, \
only 0 is allowed",
);
}
let buffer1 = Rc::new(RefCell::new(Buffer {
buf: VecDeque::new(),
clock: VClock::default(),
buf_has_writer: true,
}));
let buffer2 = Rc::new(RefCell::new(Buffer {
buf: VecDeque::new(),
clock: VClock::default(),
buf_has_writer: true,
}));
let socketpair_0 = SocketPair {
writebuf: Rc::downgrade(&buffer1),
readbuf: Rc::clone(&buffer2),
is_nonblock: is_sock_nonblock,
};
let socketpair_1 = SocketPair {
writebuf: Rc::downgrade(&buffer2),
readbuf: Rc::clone(&buffer1),
is_nonblock: is_sock_nonblock,
};
let fds = &mut this.machine.fds;
let sv0 = fds.insert_fd(FileDescriptor::new(SocketPair));
let sv0 = fds.insert_fd(FileDescriptor::new(socketpair_0));
let sv0 = Scalar::from_int(sv0, sv.layout.size);
let sv1 = fds.insert_fd(FileDescriptor::new(SocketPair));
let sv1 = fds.insert_fd(FileDescriptor::new(socketpair_1));
let sv1 = Scalar::from_int(sv1, sv.layout.size);
this.write_scalar(sv0, &sv)?;

View File

@ -176,8 +176,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// of 4.
let chunk_base = i & !0b11;
let src_i = u64::from(this.read_scalar(&control)?.to_u32()? & 0b11)
.checked_add(chunk_base)
.unwrap();
.strict_add(chunk_base);
this.copy_op(
&this.project_index(&data, src_i)?,
@ -210,9 +209,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// second instead of the first, ask Intel). To read the value from the current
// chunk, add the destination index truncated to a multiple of 2.
let chunk_base = i & !1;
let src_i = ((this.read_scalar(&control)?.to_u64()? >> 1) & 1)
.checked_add(chunk_base)
.unwrap();
let src_i =
((this.read_scalar(&control)?.to_u64()? >> 1) & 1).strict_add(chunk_base);
this.copy_op(
&this.project_index(&data, src_i)?,

View File

@ -18,6 +18,7 @@ mod sse;
mod sse2;
mod sse3;
mod sse41;
mod sse42;
mod ssse3;
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
@ -137,6 +138,11 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
this, link_name, abi, args, dest,
);
}
name if name.starts_with("sse42.") => {
return sse42::EvalContextExt::emulate_x86_sse42_intrinsic(
this, link_name, abi, args, dest,
);
}
name if name.starts_with("aesni.") => {
return aesni::EvalContextExt::emulate_x86_aesni_intrinsic(
this, link_name, abi, args, dest,

View File

@ -0,0 +1,500 @@
use rustc_middle::mir;
use rustc_middle::ty::layout::LayoutOf as _;
use rustc_middle::ty::Ty;
use rustc_span::Symbol;
use rustc_target::abi::Size;
use rustc_target::spec::abi::Abi;
use crate::*;
/// A bitmask constant for scrutinizing the immediate byte provided
/// to the string comparison intrinsics. It distinuishes between
/// 16-bit integers and 8-bit integers. See [`compare_strings`]
/// for more details about the immediate byte.
const USE_WORDS: u8 = 1;
/// A bitmask constant for scrutinizing the immediate byte provided
/// to the string comparison intrinsics. It distinuishes between
/// signed integers and unsigned integers. See [`compare_strings`]
/// for more details about the immediate byte.
const USE_SIGNED: u8 = 2;
/// The main worker for the string comparison intrinsics, where the given
/// strings are analyzed according to the given immediate byte.
///
/// # Arguments
///
/// * `str1` - The first string argument. It is always a length 16 array of bytes
/// or a length 8 array of two-byte words.
/// * `str2` - The second string argument. It is always a length 16 array of bytes
/// or a length 8 array of two-byte words.
/// * `len` is the length values of the supplied strings. It is distinct from the operand length
/// in that it describes how much of `str1` and `str2` will be used for the calculation and may
/// be smaller than the array length of `str1` and `str2`. The string length is counted in bytes
/// if using byte operands and in two-byte words when using two-byte word operands.
/// If the value is `None`, the length of a string is determined by the first
/// null value inside the string.
/// * `imm` is the immediate byte argument supplied to the intrinsic. The byte influences
/// the operation as follows:
///
/// ```text
/// 0babccddef
/// || | |||- Use of bytes vs use of two-byte words inside the operation.
/// || | ||
/// || | ||- Use of signed values versus use of unsigned values.
/// || | |
/// || | |- The comparison operation performed. A total of four operations are available.
/// || | * Equal any: Checks which characters of `str2` are inside `str1`.
/// || | * String ranges: Check if characters in `str2` are inside the provided character ranges.
/// || | Adjacent characters in `str1` constitute one range.
/// || | * String comparison: Mark positions where `str1` and `str2` have the same character.
/// || | * Substring search: Mark positions where `str1` is a substring in `str2`.
/// || |
/// || |- Result Polarity. The result bits may be subjected to a bitwise complement
/// || if these bits are set.
/// ||
/// ||- Output selection. This bit has two meanings depending on the instruction.
/// | If the instruction is generating a mask, it distinguishes between a bit mask
/// | and a byte mask. Otherwise it distinguishes between the most significand bit
/// | and the least significand bit when generating an index.
/// |
/// |- This bit is ignored. It is expected that this bit is set to zero, but it is
/// not a requirement.
/// ```
///
/// # Returns
///
/// A result mask. The bit at index `i` inside the mask is set if 'str2' starting at `i`
/// fulfills the test as defined inside the immediate byte.
/// The mask may be negated if negation flags inside the immediate byte are set.
///
/// For more information, see the Intel Software Developer's Manual, Vol. 2b, Chapter 4.1.
#[allow(clippy::arithmetic_side_effects)]
fn compare_strings<'tcx>(
this: &mut MiriInterpCx<'tcx>,
str1: &OpTy<'tcx>,
str2: &OpTy<'tcx>,
len: Option<(u64, u64)>,
imm: u8,
) -> InterpResult<'tcx, i32> {
let default_len = default_len::<u64>(imm);
let (len1, len2) = if let Some(t) = len {
t
} else {
let len1 = implicit_len(this, str1, imm)?.unwrap_or(default_len);
let len2 = implicit_len(this, str2, imm)?.unwrap_or(default_len);
(len1, len2)
};
let mut result = 0;
match (imm >> 2) & 3 {
0 => {
// Equal any: Checks which characters of `str2` are inside `str1`.
for i in 0..len2 {
let ch2 = this.read_immediate(&this.project_index(str2, i)?)?;
for j in 0..len1 {
let ch1 = this.read_immediate(&this.project_index(str1, j)?)?;
let eq = this.binary_op(mir::BinOp::Eq, &ch1, &ch2)?;
if eq.to_scalar().to_bool()? {
result |= 1 << i;
break;
}
}
}
}
1 => {
// String ranges: Check if characters in `str2` are inside the provided character ranges.
// Adjacent characters in `str1` constitute one range.
let len1 = len1 - (len1 & 1);
let get_ch = |ch: Scalar| -> InterpResult<'tcx, i32> {
let result = match (imm & USE_WORDS != 0, imm & USE_SIGNED != 0) {
(true, true) => i32::from(ch.to_i16()?),
(true, false) => i32::from(ch.to_u16()?),
(false, true) => i32::from(ch.to_i8()?),
(false, false) => i32::from(ch.to_u8()?),
};
Ok(result)
};
for i in 0..len2 {
for j in (0..len1).step_by(2) {
let ch2 = get_ch(this.read_scalar(&this.project_index(str2, i)?)?)?;
let ch1_1 = get_ch(this.read_scalar(&this.project_index(str1, j)?)?)?;
let ch1_2 = get_ch(this.read_scalar(&this.project_index(str1, j + 1)?)?)?;
if ch1_1 <= ch2 && ch2 <= ch1_2 {
result |= 1 << i;
}
}
}
}
2 => {
// String comparison: Mark positions where `str1` and `str2` have the same character.
result = (1 << default_len) - 1;
result ^= (1 << len1.max(len2)) - 1;
for i in 0..len1.min(len2) {
let ch1 = this.read_immediate(&this.project_index(str1, i)?)?;
let ch2 = this.read_immediate(&this.project_index(str2, i)?)?;
let eq = this.binary_op(mir::BinOp::Eq, &ch1, &ch2)?;
result |= i32::from(eq.to_scalar().to_bool()?) << i;
}
}
3 => {
// Substring search: Mark positions where `str1` is a substring in `str2`.
if len1 == 0 {
result = (1 << default_len) - 1;
} else if len1 <= len2 {
for i in 0..len2 {
if len1 > len2 - i {
break;
}
result |= 1 << i;
for j in 0..len1 {
let k = i + j;
if k >= default_len {
break;
} else {
let ch1 = this.read_immediate(&this.project_index(str1, j)?)?;
let ch2 = this.read_immediate(&this.project_index(str2, k)?)?;
let ne = this.binary_op(mir::BinOp::Ne, &ch1, &ch2)?;
if ne.to_scalar().to_bool()? {
result &= !(1 << i);
break;
}
}
}
}
}
}
_ => unreachable!(),
}
// Polarity: Possibly perform a bitwise complement on the result.
match (imm >> 4) & 3 {
3 => result ^= (1 << len1) - 1,
1 => result ^= (1 << default_len) - 1,
_ => (),
}
Ok(result)
}
/// Obtain the arguments of the intrinsic based on its name.
/// The result is a tuple with the following values:
/// * The first string argument.
/// * The second string argument.
/// * The string length values, if the intrinsic requires them.
/// * The immediate instruction byte.
///
/// The string arguments will be transmuted into arrays of bytes
/// or two-byte words, depending on the value of the immediate byte.
/// Originally, they are [__m128i](https://doc.rust-lang.org/stable/core/arch/x86_64/struct.__m128i.html) values
/// corresponding to the x86 128-bit integer SIMD type.
fn deconstruct_args<'tcx>(
unprefixed_name: &str,
this: &mut MiriInterpCx<'tcx>,
link_name: Symbol,
abi: Abi,
args: &[OpTy<'tcx>],
) -> InterpResult<'tcx, (OpTy<'tcx>, OpTy<'tcx>, Option<(u64, u64)>, u8)> {
let array_layout_fn = |this: &mut MiriInterpCx<'tcx>, imm: u8| {
if imm & USE_WORDS != 0 {
this.layout_of(Ty::new_array(this.tcx.tcx, this.tcx.types.u16, 8))
} else {
this.layout_of(Ty::new_array(this.tcx.tcx, this.tcx.types.u8, 16))
}
};
// The fourth letter of each string comparison intrinsic is either 'e' for "explicit" or 'i' for "implicit".
// The distinction will correspond to the intrinsics type signature. In this constext, "explicit" and "implicit"
// refer to the way the string length is determined. The length is either passed explicitly in the "explicit"
// case or determined by a null terminator in the "implicit" case.
let is_explicit = match unprefixed_name.as_bytes().get(4) {
Some(&b'e') => true,
Some(&b'i') => false,
_ => unreachable!(),
};
if is_explicit {
let [str1, len1, str2, len2, imm] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let imm = this.read_scalar(imm)?.to_u8()?;
let default_len = default_len::<u32>(imm);
let len1 = u64::from(this.read_scalar(len1)?.to_u32()?.min(default_len));
let len2 = u64::from(this.read_scalar(len2)?.to_u32()?.min(default_len));
let array_layout = array_layout_fn(this, imm)?;
let str1 = str1.transmute(array_layout, this)?;
let str2 = str2.transmute(array_layout, this)?;
Ok((str1, str2, Some((len1, len2)), imm))
} else {
let [str1, str2, imm] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let imm = this.read_scalar(imm)?.to_u8()?;
let array_layout = array_layout_fn(this, imm)?;
let str1 = str1.transmute(array_layout, this)?;
let str2 = str2.transmute(array_layout, this)?;
Ok((str1, str2, None, imm))
}
}
/// Calculate the c-style string length for a given string `str`.
/// The string is either a length 16 array of bytes a length 8 array of two-byte words.
fn implicit_len<'tcx>(
this: &mut MiriInterpCx<'tcx>,
str: &OpTy<'tcx>,
imm: u8,
) -> InterpResult<'tcx, Option<u64>> {
let mut result = None;
let zero = ImmTy::from_int(0, str.layout.field(this, 0));
for i in 0..default_len::<u64>(imm) {
let ch = this.read_immediate(&this.project_index(str, i)?)?;
let is_zero = this.binary_op(mir::BinOp::Eq, &ch, &zero)?;
if is_zero.to_scalar().to_bool()? {
result = Some(i);
break;
}
}
Ok(result)
}
#[inline]
fn default_len<T: From<u8>>(imm: u8) -> T {
if imm & USE_WORDS != 0 { T::from(8u8) } else { T::from(16u8) }
}
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
fn emulate_x86_sse42_intrinsic(
&mut self,
link_name: Symbol,
abi: Abi,
args: &[OpTy<'tcx>],
dest: &MPlaceTy<'tcx>,
) -> InterpResult<'tcx, EmulateItemResult> {
let this = self.eval_context_mut();
this.expect_target_feature_for_intrinsic(link_name, "sse4.2")?;
// Prefix should have already been checked.
let unprefixed_name = link_name.as_str().strip_prefix("llvm.x86.sse42.").unwrap();
match unprefixed_name {
// Used to implement the `_mm_cmpestrm` and the `_mm_cmpistrm` functions.
// These functions compare the input strings and return the resulting mask.
// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#ig_expand=1044,922
"pcmpistrm128" | "pcmpestrm128" => {
let (str1, str2, len, imm) =
deconstruct_args(unprefixed_name, this, link_name, abi, args)?;
let mask = compare_strings(this, &str1, &str2, len, imm)?;
// The sixth bit inside the immediate byte distiguishes
// between a bit mask or a byte mask when generating a mask.
if imm & 0b100_0000 != 0 {
let (array_layout, size) = if imm & USE_WORDS != 0 {
(this.layout_of(Ty::new_array(this.tcx.tcx, this.tcx.types.u16, 8))?, 2)
} else {
(this.layout_of(Ty::new_array(this.tcx.tcx, this.tcx.types.u8, 16))?, 1)
};
let size = Size::from_bytes(size);
let dest = dest.transmute(array_layout, this)?;
for i in 0..default_len::<u64>(imm) {
let result = helpers::bool_to_simd_element(mask & (1 << i) != 0, size);
this.write_scalar(result, &this.project_index(&dest, i)?)?;
}
} else {
let layout = this.layout_of(this.tcx.types.i128)?;
let dest = dest.transmute(layout, this)?;
this.write_scalar(Scalar::from_i128(i128::from(mask)), &dest)?;
}
}
// Used to implement the `_mm_cmpestra` and the `_mm_cmpistra` functions.
// These functions compare the input strings and return `1` if the end of the second
// input string is not reached and the resulting mask is zero, and `0` otherwise.
// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#ig_expand=919,1041
"pcmpistria128" | "pcmpestria128" => {
let (str1, str2, len, imm) =
deconstruct_args(unprefixed_name, this, link_name, abi, args)?;
let result = if compare_strings(this, &str1, &str2, len, imm)? != 0 {
false
} else if let Some((_, len)) = len {
len >= default_len::<u64>(imm)
} else {
implicit_len(this, &str1, imm)?.is_some()
};
this.write_scalar(Scalar::from_i32(i32::from(result)), dest)?;
}
// Used to implement the `_mm_cmpestri` and the `_mm_cmpistri` functions.
// These functions compare the input strings and return the bit index
// for most significant or least significant bit of the resulting mask.
// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#ig_expand=921,1043
"pcmpistri128" | "pcmpestri128" => {
let (str1, str2, len, imm) =
deconstruct_args(unprefixed_name, this, link_name, abi, args)?;
let mask = compare_strings(this, &str1, &str2, len, imm)?;
let len = default_len::<u32>(imm);
// The sixth bit inside the immediate byte distiguishes between the least
// significant bit and the most significant bit when generating an index.
let result = if imm & 0b100_0000 != 0 {
// most significant bit
31u32.wrapping_sub(mask.leading_zeros()).min(len)
} else {
// least significant bit
mask.trailing_zeros().min(len)
};
this.write_scalar(Scalar::from_i32(i32::try_from(result).unwrap()), dest)?;
}
// Used to implement the `_mm_cmpestro` and the `_mm_cmpistro` functions.
// These functions compare the input strings and return the lowest bit of the
// resulting mask.
// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#ig_expand=923,1045
"pcmpistrio128" | "pcmpestrio128" => {
let (str1, str2, len, imm) =
deconstruct_args(unprefixed_name, this, link_name, abi, args)?;
let mask = compare_strings(this, &str1, &str2, len, imm)?;
this.write_scalar(Scalar::from_i32(mask & 1), dest)?;
}
// Used to implement the `_mm_cmpestrc` and the `_mm_cmpistrc` functions.
// These functions compare the input strings and return `1` if the resulting
// mask was non-zero, and `0` otherwise.
// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#ig_expand=920,1042
"pcmpistric128" | "pcmpestric128" => {
let (str1, str2, len, imm) =
deconstruct_args(unprefixed_name, this, link_name, abi, args)?;
let mask = compare_strings(this, &str1, &str2, len, imm)?;
this.write_scalar(Scalar::from_i32(i32::from(mask != 0)), dest)?;
}
// Used to implement the `_mm_cmpistrz` and the `_mm_cmpistrs` functions.
// These functions return `1` if the string end has been reached and `0` otherwise.
// Since these functions define the string length implicitly, it is equal to a
// search for a null terminator (see `deconstruct_args` for more details).
// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#ig_expand=924,925
"pcmpistriz128" | "pcmpistris128" => {
let [str1, str2, imm] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let imm = this.read_scalar(imm)?.to_u8()?;
let str = if unprefixed_name == "pcmpistris128" { str1 } else { str2 };
let array_layout = if imm & USE_WORDS != 0 {
this.layout_of(Ty::new_array(this.tcx.tcx, this.tcx.types.u16, 8))?
} else {
this.layout_of(Ty::new_array(this.tcx.tcx, this.tcx.types.u8, 16))?
};
let str = str.transmute(array_layout, this)?;
let result = implicit_len(this, &str, imm)?.is_some();
this.write_scalar(Scalar::from_i32(i32::from(result)), dest)?;
}
// Used to implement the `_mm_cmpestrz` and the `_mm_cmpestrs` functions.
// These functions return 1 if the explicitly passed string length is smaller
// than 16 for byte-sized operands or 8 for word-sized operands.
// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#ig_expand=1046,1047
"pcmpestriz128" | "pcmpestris128" => {
let [_, len1, _, len2, imm] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let len = if unprefixed_name == "pcmpestris128" { len1 } else { len2 };
let len = this.read_scalar(len)?.to_i32()?;
let imm = this.read_scalar(imm)?.to_u8()?;
this.write_scalar(
Scalar::from_i32(i32::from(len < default_len::<i32>(imm))),
dest,
)?;
}
// Used to implement the `_mm_crc32_u{8, 16, 32, 64}` functions.
// These functions calculate a 32-bit CRC using `0x11EDC6F41`
// as the polynomial, also known as CRC32C.
// https://datatracker.ietf.org/doc/html/rfc3720#section-12.1
"crc32.32.8" | "crc32.32.16" | "crc32.32.32" | "crc32.64.64" => {
let bit_size = match unprefixed_name {
"crc32.32.8" => 8,
"crc32.32.16" => 16,
"crc32.32.32" => 32,
"crc32.64.64" => 64,
_ => unreachable!(),
};
if bit_size == 64 && this.tcx.sess.target.arch != "x86_64" {
return Ok(EmulateItemResult::NotSupported);
}
let [left, right] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let left = this.read_scalar(left)?;
let right = this.read_scalar(right)?;
let crc = if bit_size == 64 {
// The 64-bit version will only consider the lower 32 bits,
// while the upper 32 bits get discarded.
#[allow(clippy::cast_possible_truncation)]
u128::from((left.to_u64()? as u32).reverse_bits())
} else {
u128::from(left.to_u32()?.reverse_bits())
};
let v = match bit_size {
8 => u128::from(right.to_u8()?.reverse_bits()),
16 => u128::from(right.to_u16()?.reverse_bits()),
32 => u128::from(right.to_u32()?.reverse_bits()),
64 => u128::from(right.to_u64()?.reverse_bits()),
_ => unreachable!(),
};
// Perform polynomial division modulo 2.
// The algorithm for the division is an adapted version of the
// schoolbook division algorithm used for normal integer or polynomial
// division. In this context, the quotient is not calculated, since
// only the remainder is needed.
//
// The algorithm works as follows:
// 1. Pull down digits until division can be performed. In the context of division
// modulo 2 it means locating the most significant digit of the dividend and shifting
// the divisor such that the position of the divisors most significand digit and the
// dividends most significand digit match.
// 2. Perform a division and determine the remainder. Since it is arithmetic modulo 2,
// this operation is a simple bitwise exclusive or.
// 3. Repeat steps 1. and 2. until the full remainder is calculated. This is the case
// once the degree of the remainder polynomial is smaller than the degree of the
// divisor polynomial. In other words, the number of leading zeros of the remainder
// is larger than the number of leading zeros of the divisor. It is important to
// note that standard arithmetic comparison is not applicable here:
// 0b10011 / 0b11111 = 0b01100 is a valid division, even though the dividend is
// smaller than the divisor.
let mut dividend = (crc << bit_size) ^ (v << 32);
const POLYNOMIAL: u128 = 0x11EDC6F41;
while dividend.leading_zeros() <= POLYNOMIAL.leading_zeros() {
dividend ^=
(POLYNOMIAL << POLYNOMIAL.leading_zeros()) >> dividend.leading_zeros();
}
let result = u32::try_from(dividend).unwrap().reverse_bits();
let result = if bit_size == 64 {
Scalar::from_u64(u64::from(result))
} else {
Scalar::from_u32(result)
};
this.write_scalar(result, dest)?;
}
_ => return Ok(EmulateItemResult::NotSupported),
}
Ok(EmulateItemResult::NeedsReturn)
}
}

View File

@ -0,0 +1,12 @@
//@ignore-target-windows: no libc socketpair on Windows
// This is temporarily here because blocking on fd is not supported yet.
// When blocking is eventually supported, this will be moved to pass-dep/libc/libc-socketpair
fn main() {
let mut fds = [-1, -1];
let _ = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
// The read below will be blocked because the buffer is empty.
let mut buf: [u8; 3] = [0; 3];
let _res = unsafe { libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) }; //~ERROR: blocking isn't supported
}

View File

@ -0,0 +1,14 @@
error: unsupported operation: socketpair read: blocking isn't supported yet
--> $DIR/socketpair_read_blocking.rs:LL:CC
|
LL | let _res = unsafe { libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ socketpair read: blocking isn't supported yet
|
= help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support
= note: BACKTRACE:
= note: inside `main` at $DIR/socketpair_read_blocking.rs:LL:CC
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to 1 previous error

View File

@ -0,0 +1,16 @@
//@ignore-target-windows: no libc socketpair on Windows
// This is temporarily here because blocking on fd is not supported yet.
// When blocking is eventually supported, this will be moved to pass-dep/libc/libc-socketpair
fn main() {
let mut fds = [-1, -1];
let _ = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
// Write size > buffer capacity
// Used up all the space in the buffer.
let arr1: [u8; 212992] = [1; 212992];
let _ = unsafe { libc::write(fds[0], arr1.as_ptr() as *const libc::c_void, 212992) };
let data = "abc".as_bytes().as_ptr();
// The write below will be blocked as the buffer is full.
let _ = unsafe { libc::write(fds[0], data as *const libc::c_void, 3) }; //~ERROR: blocking isn't supported
let mut buf: [u8; 3] = [0; 3];
let _res = unsafe { libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
}

View File

@ -0,0 +1,14 @@
error: unsupported operation: socketpair write: blocking isn't supported yet
--> $DIR/socketpair_write_blocking.rs:LL:CC
|
LL | let _ = unsafe { libc::write(fds[0], data as *const libc::c_void, 3) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ socketpair write: blocking isn't supported yet
|
= help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support
= note: BACKTRACE:
= note: inside `main` at $DIR/socketpair_write_blocking.rs:LL:CC
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to 1 previous error

View File

@ -3,11 +3,17 @@
#[derive(Copy, Clone)]
#[allow(unused)]
enum E {A, B, C }
enum E {
A,
B,
C,
}
fn cast(ptr: *const E) { unsafe {
let _val = *ptr as u32; //~ERROR: enum value has invalid tag
}}
fn cast(ptr: *const E) {
unsafe {
let _val = *ptr as u32; //~ERROR: enum value has invalid tag
}
}
pub fn main() {
let v = u32::MAX;

View File

@ -1,8 +1,8 @@
error: Undefined Behavior: enum value has invalid tag: 0xff
--> $DIR/invalid_enum_cast.rs:LL:CC
|
LL | let _val = *ptr as u32;
| ^^^^^^^^^^^ enum value has invalid tag: 0xff
LL | let _val = *ptr as u32;
| ^^^^^^^^^^^ enum value has invalid tag: 0xff
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information

View File

@ -0,0 +1,124 @@
//@ignore-target-windows: No libc socketpair on Windows
// test_race depends on a deterministic schedule.
//@compile-flags: -Zmiri-preemption-rate=0
use std::thread;
fn main() {
test_socketpair();
test_socketpair_threaded();
test_race();
}
fn test_socketpair() {
let mut fds = [-1, -1];
let mut res =
unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
assert_eq!(res, 0);
// Read size == data available in buffer.
let data = "abcde".as_bytes().as_ptr();
res = unsafe { libc::write(fds[0], data as *const libc::c_void, 5).try_into().unwrap() };
assert_eq!(res, 5);
let mut buf: [u8; 5] = [0; 5];
res = unsafe {
libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t).try_into().unwrap()
};
assert_eq!(res, 5);
assert_eq!(buf, "abcde".as_bytes());
// Read size > data available in buffer.
let data = "abc".as_bytes().as_ptr();
res = unsafe { libc::write(fds[0], data as *const libc::c_void, 3).try_into().unwrap() };
assert_eq!(res, 3);
let mut buf2: [u8; 5] = [0; 5];
res = unsafe {
libc::read(fds[1], buf2.as_mut_ptr().cast(), buf2.len() as libc::size_t).try_into().unwrap()
};
assert_eq!(res, 3);
assert_eq!(&buf2[0..3], "abc".as_bytes());
// Test read and write from another direction.
// Read size == data available in buffer.
let data = "12345".as_bytes().as_ptr();
res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5).try_into().unwrap() };
assert_eq!(res, 5);
let mut buf3: [u8; 5] = [0; 5];
res = unsafe {
libc::read(fds[0], buf3.as_mut_ptr().cast(), buf3.len() as libc::size_t).try_into().unwrap()
};
assert_eq!(res, 5);
assert_eq!(buf3, "12345".as_bytes());
// Read size > data available in buffer.
let data = "123".as_bytes().as_ptr();
res = unsafe { libc::write(fds[1], data as *const libc::c_void, 3).try_into().unwrap() };
assert_eq!(res, 3);
let mut buf4: [u8; 5] = [0; 5];
res = unsafe {
libc::read(fds[0], buf4.as_mut_ptr().cast(), buf4.len() as libc::size_t).try_into().unwrap()
};
assert_eq!(res, 3);
assert_eq!(&buf4[0..3], "123".as_bytes());
}
fn test_socketpair_threaded() {
let mut fds = [-1, -1];
let mut res =
unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
assert_eq!(res, 0);
let data = "abcde".as_bytes().as_ptr();
res = unsafe { libc::write(fds[0], data as *const libc::c_void, 5).try_into().unwrap() };
assert_eq!(res, 5);
let thread1 = thread::spawn(move || {
let mut buf: [u8; 5] = [0; 5];
let res: i64 = unsafe {
libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
.try_into()
.unwrap()
};
assert_eq!(res, 5);
assert_eq!(buf, "abcde".as_bytes());
});
thread1.join().unwrap();
// Read and write from different direction
let thread2 = thread::spawn(move || {
let data = "12345".as_bytes().as_ptr();
let res: i64 =
unsafe { libc::write(fds[0], data as *const libc::c_void, 5).try_into().unwrap() };
assert_eq!(res, 5);
});
thread2.join().unwrap();
let mut buf: [u8; 5] = [0; 5];
res = unsafe {
libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t).try_into().unwrap()
};
assert_eq!(res, 5);
assert_eq!(buf, "12345".as_bytes());
}
fn test_race() {
static mut VAL: u8 = 0;
let mut fds = [-1, -1];
let mut res =
unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
assert_eq!(res, 0);
let thread1 = thread::spawn(move || {
let mut buf: [u8; 1] = [0; 1];
// write() from the main thread will occur before the read() here
// because preemption is disabled and the main thread yields after write().
let res: i32 = unsafe {
libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
.try_into()
.unwrap()
};
assert_eq!(res, 1);
assert_eq!(buf, "a".as_bytes());
unsafe { assert_eq!(VAL, 1) };
});
unsafe { VAL = 1 };
let data = "a".as_bytes().as_ptr();
res = unsafe { libc::write(fds[0], data as *const libc::c_void, 1).try_into().unwrap() };
assert_eq!(res, 1);
thread::yield_now();
thread1.join().unwrap();
}

View File

@ -0,0 +1,443 @@
// Ignore everything except x86 and x86_64
// Any new targets that are added to CI should be ignored here.
// (We cannot use `cfg`-based tricks here since the `target-feature` flags below only work on x86.)
//@ignore-target-aarch64
//@ignore-target-arm
//@ignore-target-avr
//@ignore-target-s390x
//@ignore-target-thumbv7em
//@ignore-target-wasm32
//@compile-flags: -C target-feature=+sse4.2
#[cfg(target_arch = "x86")]
use std::arch::x86::*;
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;
use std::mem::transmute;
fn main() {
assert!(is_x86_feature_detected!("sse4.2"));
unsafe {
test_sse42();
}
}
#[target_feature(enable = "sse4.2")]
unsafe fn test_sse42() {
// Mostly copied from library/stdarch/crates/core_arch/src/x86/sse42.rs
test_crc();
test_cmp();
test_str();
}
#[target_feature(enable = "sse4.2")]
unsafe fn test_crc() {
#[target_feature(enable = "sse4.2")]
unsafe fn test_mm_crc32_u8() {
let crc = 0x2aa1e72b;
let v = 0x2a;
let i = _mm_crc32_u8(crc, v);
assert_eq!(i, 0xf24122e4);
let crc = 0x61343ec4;
let v = 0xef;
let i = _mm_crc32_u8(crc, v);
assert_eq!(i, 0xb95511db);
let crc = 0xbadeafe;
let v = 0xc0;
let i = _mm_crc32_u8(crc, v);
assert_eq!(i, 0x9c905b7c);
}
test_mm_crc32_u8();
#[target_feature(enable = "sse4.2")]
unsafe fn test_mm_crc32_u16() {
let crc = 0x8ecec3b5;
let v = 0x22b;
let i = _mm_crc32_u16(crc, v);
assert_eq!(i, 0x13bb2fb);
let crc = 0x150bc664;
let v = 0xa6c0;
let i = _mm_crc32_u16(crc, v);
assert_eq!(i, 0xab04fe4e);
let crc = 0xbadeafe;
let v = 0xc0fe;
let i = _mm_crc32_u16(crc, v);
assert_eq!(i, 0x4b5fad4b);
}
test_mm_crc32_u16();
#[target_feature(enable = "sse4.2")]
unsafe fn test_mm_crc32_u32() {
let crc = 0xae2912c8;
let v = 0x845fed;
let i = _mm_crc32_u32(crc, v);
assert_eq!(i, 0xffae2ed1);
let crc = 0x1a198fe3;
let v = 0x885585c2;
let i = _mm_crc32_u32(crc, v);
assert_eq!(i, 0x22443a7b);
let crc = 0xbadeafe;
let v = 0xc0febeef;
let i = _mm_crc32_u32(crc, v);
assert_eq!(i, 0xb309502f);
}
test_mm_crc32_u32();
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "sse4.2")]
unsafe fn test_mm_crc32_u64() {
let crc = 0x7819dccd3e824;
let v = 0x2a22b845fed;
let i = _mm_crc32_u64(crc, v);
assert_eq!(i, 0xbb6cdc6c);
let crc = 0x6dd960387fe13819;
let v = 0x1a7ea8fb571746b0;
let i = _mm_crc32_u64(crc, v);
assert_eq!(i, 0x315b4f6);
let crc = 0xbadeafe;
let v = 0xc0febeefdadafefe;
let i = _mm_crc32_u64(crc, v);
assert_eq!(i, 0x5b44f54f);
}
#[cfg(not(target_arch = "x86_64"))]
unsafe fn test_mm_crc32_u64() {}
test_mm_crc32_u64();
}
#[target_feature(enable = "sse4.2")]
unsafe fn test_cmp() {
let a = _mm_set_epi64x(0x2a, 0);
let b = _mm_set1_epi64x(0x00);
let i = _mm_cmpgt_epi64(a, b);
assert_eq_m128i(i, _mm_set_epi64x(0xffffffffffffffffu64 as i64, 0x00));
}
#[target_feature(enable = "sse4.2")]
unsafe fn test_str() {
#[target_feature(enable = "sse4.2")]
unsafe fn str_to_m128i(s: &[u8]) -> __m128i {
assert!(s.len() <= 16);
let slice = &mut [0u8; 16];
std::ptr::copy_nonoverlapping(s.as_ptr(), slice.as_mut_ptr(), s.len());
_mm_loadu_si128(slice.as_ptr() as *const _)
}
// Test the `_mm_cmpistrm` intrinsic.
#[target_feature(enable = "sse4.2")]
unsafe fn test_mm_cmpistrm() {
let a = str_to_m128i(b"Hello! Good-Bye!");
let b = str_to_m128i(b"hello! good-bye!");
let i = _mm_cmpistrm::<_SIDD_UNIT_MASK>(a, b);
#[rustfmt::skip]
let res = _mm_setr_epi8(
0x00, !0, !0, !0, !0, !0, !0, 0x00,
!0, !0, !0, !0, 0x00, !0, !0, !0,
);
assert_eq_m128i(i, res);
}
test_mm_cmpistrm();
// Test the `_mm_cmpistri` intrinsic.
#[target_feature(enable = "sse4.2")]
unsafe fn test_mm_cmpistri() {
let a = str_to_m128i(b"Hello");
let b = str_to_m128i(b" Hello ");
let i = _mm_cmpistri::<_SIDD_CMP_EQUAL_ORDERED>(a, b);
assert_eq!(3, i);
}
test_mm_cmpistri();
// Test the `_mm_cmpistrz` intrinsic.
#[target_feature(enable = "sse4.2")]
unsafe fn test_mm_cmpistrz() {
let a = str_to_m128i(b"");
let b = str_to_m128i(b"Hello");
let i = _mm_cmpistrz::<_SIDD_CMP_EQUAL_ORDERED>(a, b);
assert_eq!(1, i);
}
test_mm_cmpistrz();
// Test the `_mm_cmpistrc` intrinsic.
#[target_feature(enable = "sse4.2")]
unsafe fn test_mm_cmpistrc() {
let a = str_to_m128i(b" ");
let b = str_to_m128i(b" ! ");
let i = _mm_cmpistrc::<_SIDD_UNIT_MASK>(a, b);
assert_eq!(1, i);
}
test_mm_cmpistrc();
// Test the `_mm_cmpistrs` intrinsic.
#[target_feature(enable = "sse4.2")]
unsafe fn test_mm_cmpistrs() {
let a = str_to_m128i(b"Hello");
let b = str_to_m128i(b"");
let i = _mm_cmpistrs::<_SIDD_CMP_EQUAL_ORDERED>(a, b);
assert_eq!(1, i);
}
test_mm_cmpistrs();
// Test the `_mm_cmpistro` intrinsic.
#[target_feature(enable = "sse4.2")]
unsafe fn test_mm_cmpistro() {
#[rustfmt::skip]
let a_bytes = _mm_setr_epi8(
0x00, 0x47, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c,
0x00, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
);
#[rustfmt::skip]
let b_bytes = _mm_setr_epi8(
0x00, 0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c,
0x00, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
);
let a = a_bytes;
let b = b_bytes;
let i = _mm_cmpistro::<{ _SIDD_UWORD_OPS | _SIDD_UNIT_MASK }>(a, b);
assert_eq!(0, i);
}
test_mm_cmpistro();
// Test the `_mm_cmpistra` intrinsic.
#[target_feature(enable = "sse4.2")]
unsafe fn test_mm_cmpistra() {
let a = str_to_m128i(b"");
let b = str_to_m128i(b"Hello!!!!!!!!!!!");
let i = _mm_cmpistra::<_SIDD_UNIT_MASK>(a, b);
assert_eq!(1, i);
}
test_mm_cmpistra();
// Test the `_mm_cmpestrm` intrinsic.
#[target_feature(enable = "sse4.2")]
unsafe fn test_mm_cmpestrm() {
let a = str_to_m128i(b"Hello!");
let b = str_to_m128i(b"Hello.");
let i = _mm_cmpestrm::<_SIDD_UNIT_MASK>(a, 5, b, 5);
#[rustfmt::skip]
let r = _mm_setr_epi8(
!0, !0, !0, !0, !0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
);
assert_eq_m128i(i, r);
}
test_mm_cmpestrm();
// Test the `_mm_cmpestri` intrinsic.
#[target_feature(enable = "sse4.2")]
unsafe fn test_mm_cmpestri() {
let a = str_to_m128i(b"bar - garbage");
let b = str_to_m128i(b"foobar");
let i = _mm_cmpestri::<_SIDD_CMP_EQUAL_ORDERED>(a, 3, b, 6);
assert_eq!(3, i);
}
test_mm_cmpestri();
// Test the `_mm_cmpestrz` intrinsic.
#[target_feature(enable = "sse4.2")]
unsafe fn test_mm_cmpestrz() {
let a = str_to_m128i(b"");
let b = str_to_m128i(b"Hello");
let i = _mm_cmpestrz::<_SIDD_CMP_EQUAL_ORDERED>(a, 16, b, 6);
assert_eq!(1, i);
}
test_mm_cmpestrz();
// Test the `_mm_cmpestrs` intrinsic.
#[target_feature(enable = "sse4.2")]
unsafe fn test_mm_cmpestrc() {
let va = str_to_m128i(b"!!!!!!!!");
let vb = str_to_m128i(b" ");
let i = _mm_cmpestrc::<_SIDD_UNIT_MASK>(va, 7, vb, 7);
assert_eq!(0, i);
}
test_mm_cmpestrc();
// Test the `_mm_cmpestrs` intrinsic.
#[target_feature(enable = "sse4.2")]
unsafe fn test_mm_cmpestrs() {
#[rustfmt::skip]
let a_bytes = _mm_setr_epi8(
0x00, 0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c,
0x00, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
);
let a = a_bytes;
let b = _mm_set1_epi8(0x00);
let i = _mm_cmpestrs::<_SIDD_UWORD_OPS>(a, 8, b, 0);
assert_eq!(0, i);
}
test_mm_cmpestrs();
// Test the `_mm_cmpestro` intrinsic.
#[target_feature(enable = "sse4.2")]
unsafe fn test_mm_cmpestro() {
let a = str_to_m128i(b"Hello");
let b = str_to_m128i(b"World");
let i = _mm_cmpestro::<_SIDD_UBYTE_OPS>(a, 5, b, 5);
assert_eq!(0, i);
}
test_mm_cmpestro();
// Test the `_mm_cmpestra` intrinsic.
#[target_feature(enable = "sse4.2")]
unsafe fn test_mm_cmpestra() {
let a = str_to_m128i(b"Cannot match a");
let b = str_to_m128i(b"Null after 14");
let i = _mm_cmpestra::<{ _SIDD_CMP_EQUAL_EACH | _SIDD_UNIT_MASK }>(a, 14, b, 16);
assert_eq!(1, i);
}
test_mm_cmpestra();
// Additional tests not inside the standard library.
// Test the subset functionality of the intrinsic.
unsafe fn test_subset() {
let a = str_to_m128i(b"ABCDEFG");
let b = str_to_m128i(b"ABC UVW XYZ EFG");
let i = _mm_cmpistrm::<{ _SIDD_CMP_EQUAL_ANY | _SIDD_UNIT_MASK }>(a, b);
#[rustfmt::skip]
let res = _mm_setr_epi8(
!0, !0, !0, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, !0, !0, !0, 0x00,
);
assert_eq_m128i(i, res);
}
test_subset();
// Properly test index generation.
unsafe fn test_index() {
let a = str_to_m128i(b"Hello");
let b = str_to_m128i(b"Hello Hello H");
let i = _mm_cmpistri::<{ _SIDD_CMP_EQUAL_EACH | _SIDD_LEAST_SIGNIFICANT }>(a, b);
assert_eq!(i, 0);
let i = _mm_cmpistri::<{ _SIDD_CMP_EQUAL_EACH | _SIDD_MOST_SIGNIFICANT }>(a, b);
assert_eq!(i, 15);
let a = str_to_m128i(b"Hello");
let b = str_to_m128i(b" ");
let i = _mm_cmpistri::<{ _SIDD_CMP_EQUAL_EACH | _SIDD_MOST_SIGNIFICANT }>(a, b);
assert_eq!(i, 16);
}
test_index();
// Properly test the substring functionality of the intrinsics.
#[target_feature(enable = "sse4.2")]
unsafe fn test_substring() {
let a = str_to_m128i(b"Hello");
let b = str_to_m128i(b"Hello Hello H");
let i = _mm_cmpistrm::<{ _SIDD_CMP_EQUAL_ORDERED | _SIDD_UNIT_MASK }>(a, b);
#[rustfmt::skip]
let res = _mm_setr_epi8(
!0, 0x00, 0x00, 0x00, 0x00, 0x00, !0, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
);
assert_eq_m128i(i, res);
}
test_substring();
// Test the range functionality of the intrinsics.
// Will also test signed values and word-sized values.
#[target_feature(enable = "sse4.2")]
unsafe fn test_ranges() {
let a = _mm_setr_epi16(0, 1, 7, 8, 0, 0, -100, 100);
let b = _mm_setr_epi16(1, 2, 3, 4, 5, 6, 7, 8);
let i =
_mm_cmpestrm::<{ _SIDD_SWORD_OPS | _SIDD_CMP_RANGES | _SIDD_UNIT_MASK }>(a, 2, b, 8);
let res = _mm_setr_epi16(!0, 0, 0, 0, 0, 0, 0, 0);
assert_eq_m128i(i, res);
let i =
_mm_cmpestrm::<{ _SIDD_SWORD_OPS | _SIDD_CMP_RANGES | _SIDD_UNIT_MASK }>(a, 3, b, 8);
let res = _mm_setr_epi16(!0, 0, 0, 0, 0, 0, 0, 0);
assert_eq_m128i(i, res);
let i =
_mm_cmpestrm::<{ _SIDD_SWORD_OPS | _SIDD_CMP_RANGES | _SIDD_UNIT_MASK }>(a, 4, b, 8);
let res = _mm_setr_epi16(!0, 0, 0, 0, 0, 0, !0, !0);
assert_eq_m128i(i, res);
let i =
_mm_cmpestrm::<{ _SIDD_SWORD_OPS | _SIDD_CMP_RANGES | _SIDD_UNIT_MASK }>(a, 6, b, 8);
let res = _mm_setr_epi16(!0, 0, 0, 0, 0, 0, !0, !0);
assert_eq_m128i(i, res);
let i =
_mm_cmpestrm::<{ _SIDD_SWORD_OPS | _SIDD_CMP_RANGES | _SIDD_UNIT_MASK }>(a, 8, b, 8);
let res = _mm_setr_epi16(!0, !0, !0, !0, !0, !0, !0, !0);
assert_eq_m128i(i, res);
}
test_ranges();
// Confirm that the polarity bits work as indended.
#[target_feature(enable = "sse4.2")]
unsafe fn test_polarity() {
let a = str_to_m128i(b"Hello!");
let b = str_to_m128i(b"hello?");
let i = _mm_cmpistrm::<
{ (_SIDD_MASKED_NEGATIVE_POLARITY ^ _SIDD_NEGATIVE_POLARITY) | _SIDD_UNIT_MASK },
>(a, b);
#[rustfmt::skip]
let res = _mm_setr_epi8(
0x00, !0, !0, !0, !0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
);
assert_eq_m128i(i, res);
let i = _mm_cmpistrm::<{ _SIDD_MASKED_NEGATIVE_POLARITY | _SIDD_UNIT_MASK }>(a, b);
#[rustfmt::skip]
let res = _mm_setr_epi8(
!0, 0x00, 0x00, 0x00, 0x00, !0, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
);
assert_eq_m128i(i, res);
let i = _mm_cmpistrm::<{ _SIDD_NEGATIVE_POLARITY | _SIDD_UNIT_MASK }>(a, b);
#[rustfmt::skip]
let res = _mm_setr_epi8(
!0, 0x00, 0x00, 0x00, 0x00, !0, !0, !0,
!0, !0, !0, !0, !0, !0, !0, !0,
);
assert_eq_m128i(i, res);
}
test_polarity();
// Test the code path in which the intrinsic is supposed to
// return a bit mask instead of a byte mask.
#[target_feature(enable = "sse4.2")]
unsafe fn test_bitmask() {
let a = str_to_m128i(b"Hello! Good-Bye!");
let b = str_to_m128i(b"hello! good-bye!");
let i = _mm_cmpistrm::<0>(a, b);
#[rustfmt::skip]
let res = _mm_setr_epi32(0b11101111_01111110, 0, 0, 0);
assert_eq_m128i(i, res);
let i = _mm_cmpistrm::<_SIDD_MASKED_NEGATIVE_POLARITY>(a, b);
#[rustfmt::skip]
let res = _mm_setr_epi32(0b00010000_10000001, 0, 0, 0);
assert_eq_m128i(i, res);
}
test_bitmask();
}
#[track_caller]
#[target_feature(enable = "sse2")]
pub unsafe fn assert_eq_m128i(a: __m128i, b: __m128i) {
assert_eq!(transmute::<_, [u64; 2]>(a), transmute::<_, [u64; 2]>(b))
}