Auto merge of #124368 - RalfJung:miri, r=RalfJung

Miri subtree update

r? `@ghost`
This commit is contained in:
bors 2024-04-25 10:59:13 +00:00
commit 1c84675e1f
46 changed files with 3266 additions and 517 deletions

View file

@ -491,9 +491,9 @@ checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
[[package]]
name = "chrono"
version = "0.4.37"
version = "0.4.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e"
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
dependencies = [
"android-tzdata",
"iana-time-zone",
@ -2493,8 +2493,10 @@ name = "miri"
version = "0.1.0"
dependencies = [
"aes",
"chrono",
"colored",
"ctrlc",
"directories",
"getrandom",
"jemalloc-sys",
"lazy_static",

View file

@ -32,7 +32,7 @@ jobs:
env:
HOST_TARGET: ${{ matrix.host_target }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Show Rust version (stable toolchain)
run: |
@ -57,12 +57,12 @@ jobs:
~/.cargo/bin
~/.cargo/.crates.toml
~/.cargo/.crates2.json
key: cargo-${{ runner.os }}-reset20240331-${{ hashFiles('**/Cargo.lock') }}
restore-keys: cargo-${{ runner.os }}-reset20240331
key: cargo-${{ runner.os }}-reset20240425-${{ hashFiles('**/Cargo.lock') }}
restore-keys: cargo-${{ runner.os }}-reset20240425
- name: Install rustup-toolchain-install-master
- name: Install tools
if: ${{ steps.cache.outputs.cache-hit != 'true' }}
run: cargo install -f rustup-toolchain-install-master
run: cargo install -f rustup-toolchain-install-master hyperfine
- name: Install miri toolchain
run: |
@ -85,7 +85,7 @@ jobs:
name: style checks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
# This is exactly duplicated from above. GHA is pretty terrible when it comes
# to avoiding code duplication.
@ -165,7 +165,7 @@ jobs:
name: cronjob failure notification
runs-on: ubuntu-latest
needs: [build, style]
if: github.event_name == 'schedule' && (failure() || cancelled())
if: github.event_name == 'schedule' && failure()
steps:
# Send a Zulip notification
- name: Install zulip-send
@ -191,7 +191,7 @@ jobs:
The Miri Cronjobs Bot'
# Attempt to auto-sync with rustc
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 256 # get a bit more of the history
- name: install josh-proxy

View file

@ -37,6 +37,21 @@ dependencies = [
"memchr",
]
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "annotate-snippets"
version = "0.9.2"
@ -106,6 +121,12 @@ dependencies = [
"serde",
]
[[package]]
name = "bumpalo"
version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
name = "camino"
version = "1.1.6"
@ -150,6 +171,18 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
dependencies = [
"android-tzdata",
"iana-time-zone",
"num-traits",
"windows-targets 0.52.3",
]
[[package]]
name = "cipher"
version = "0.4.4"
@ -216,6 +249,12 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
[[package]]
name = "cpufeatures"
version = "0.2.12"
@ -260,6 +299,27 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "directories"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.48.0",
]
[[package]]
name = "encode_unicode"
version = "0.3.6"
@ -319,6 +379,29 @@ version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]]
name = "iana-time-zone"
version = "0.1.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "indenter"
version = "0.3.3"
@ -372,6 +455,15 @@ dependencies = [
"libc",
]
[[package]]
name = "js-sys"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -419,6 +511,16 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "libredox"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
"bitflags 2.4.2",
"libc",
]
[[package]]
name = "linux-raw-sys"
version = "0.4.13"
@ -484,8 +586,10 @@ name = "miri"
version = "0.1.0"
dependencies = [
"aes",
"chrono",
"colored",
"ctrlc",
"directories",
"getrandom",
"jemalloc-sys",
"lazy_static",
@ -512,6 +616,15 @@ dependencies = [
"libc",
]
[[package]]
name = "num-traits"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
dependencies = [
"autocfg",
]
[[package]]
name = "number_prefix"
version = "0.4.0"
@ -533,6 +646,12 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "option-ext"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "owo-colors"
version = "3.5.0"
@ -665,6 +784,17 @@ dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_users"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891"
dependencies = [
"getrandom",
"libredox",
"thiserror",
]
[[package]]
name = "regex"
version = "1.10.3"
@ -964,6 +1094,60 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
[[package]]
name = "winapi"
version = "0.3.9"
@ -986,6 +1170,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-core"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
"windows-targets 0.52.3",
]
[[package]]
name = "windows-sys"
version = "0.48.0"

View file

@ -24,6 +24,8 @@ smallvec = "1.7"
aes = { version = "0.8.3", features = ["hazmat"] }
measureme = "11"
ctrlc = "3.2.5"
chrono = { version = "0.4.38", default-features = false, features = ["clock"] }
directories = "5"
# Copied from `compiler/rustc/Cargo.toml`.
# But only for some targets, it fails for others. Rustc configures this in its CI, but we can't

View file

@ -321,6 +321,10 @@ environment variable. We first document the most relevant and most commonly used
* `-Zmiri-env-forward=<var>` forwards the `var` environment variable to the interpreted program. Can
be used multiple times to forward several variables. Execution will still be deterministic if the
value of forwarded variables stays the same. Has no effect if `-Zmiri-disable-isolation` is set.
* `-Zmiri-env-set=<var>=<value>` sets the `var` environment variable to `value` in the interpreted program.
It can be used to pass environment variables without needing to alter the host environment. It can
be used multiple times to set several variables. If `-Zmiri-disable-isolation` or `-Zmiri-env-forward`
is set, values set with this option will have priority over values from the host environment.
* `-Zmiri-ignore-leaks` disables the memory leak checker, and also allows some
remaining threads to exist when the main thread exits.
* `-Zmiri-isolation-error=<action>` configures Miri's response to operations
@ -560,7 +564,8 @@ used according to their aliasing restrictions.
## Bugs found by Miri
Miri has already found a number of bugs in the Rust standard library and beyond, which we collect here.
Miri has already found a number of bugs in the Rust standard library and beyond, some of which we collect here.
If Miri helped you find a subtle UB bug in your code, we'd appreciate a PR adding it to the list!
Definite bugs found:
@ -595,6 +600,7 @@ Definite bugs found:
* [Deallocating with the wrong layout in new specializations for in-place `Iterator::collect`](https://github.com/rust-lang/rust/pull/118460)
* [Incorrect offset computation for highly-aligned types in `portable-atomic-util`](https://github.com/taiki-e/portable-atomic/pull/138)
* [Occasional memory leak in `std::mpsc` channels](https://github.com/rust-lang/rust/issues/121582) (original code in [crossbeam](https://github.com/crossbeam-rs/crossbeam/pull/1084))
* [Weak-memory-induced memory leak in Windows thread-local storage](https://github.com/rust-lang/rust/pull/124281)
Violations of [Stacked Borrows] found that are likely bugs (but Stacked Borrows is currently just an experiment):

View file

@ -78,8 +78,8 @@ function run_tests {
done
fi
if [ -n "${TEST_BENCH-}" ]; then
# Check that the benchmarks build and run, but without actually benchmarking.
time HYPERFINE="'$BASH' -c" ./miri bench
# Check that the benchmarks build and run, but only once.
time HYPERFINE="hyperfine -w0 -r1" ./miri bench
fi
## test-cargo-miri
@ -128,16 +128,18 @@ function run_tests_minimal {
## Main Testing Logic ##
# In particular, fully cover all tier 1 targets.
# We also want to run the many-seeds tests on all tier 1 targets.
case $HOST_TARGET in
x86_64-unknown-linux-gnu)
# Host
GC_STRESS=1 MIR_OPT=1 MANY_SEEDS=64 TEST_BENCH=1 CARGO_MIRI_ENV=1 run_tests
# Extra tier 1
MIRI_TEST_TARGET=i686-unknown-linux-gnu run_tests
MIRI_TEST_TARGET=aarch64-unknown-linux-gnu run_tests
MIRI_TEST_TARGET=x86_64-apple-darwin run_tests
MIRI_TEST_TARGET=i686-pc-windows-gnu run_tests
MIRI_TEST_TARGET=x86_64-pc-windows-gnu run_tests
# With reduced many-seed count to avoid spending too much time on that.
# (All OSes are run with 64 seeds at least once though via the macOS runner.)
MANY_SEEDS=16 MIRI_TEST_TARGET=i686-unknown-linux-gnu run_tests
MANY_SEEDS=16 MIRI_TEST_TARGET=aarch64-unknown-linux-gnu run_tests
MANY_SEEDS=16 MIRI_TEST_TARGET=x86_64-apple-darwin run_tests
MANY_SEEDS=16 MIRI_TEST_TARGET=x86_64-pc-windows-gnu run_tests
# Extra tier 2
MIRI_TEST_TARGET=aarch64-apple-darwin run_tests
MIRI_TEST_TARGET=arm-unknown-linux-gnueabi run_tests
@ -155,13 +157,15 @@ case $HOST_TARGET in
# Host (tier 2)
GC_STRESS=1 MIR_OPT=1 MANY_SEEDS=64 TEST_BENCH=1 CARGO_MIRI_ENV=1 run_tests
# Extra tier 1
MIRI_TEST_TARGET=x86_64-pc-windows-msvc CARGO_MIRI_ENV=1 run_tests
MANY_SEEDS=64 MIRI_TEST_TARGET=i686-pc-windows-gnu run_tests
MANY_SEEDS=64 MIRI_TEST_TARGET=x86_64-pc-windows-msvc CARGO_MIRI_ENV=1 run_tests
# Extra tier 2
MIRI_TEST_TARGET=s390x-unknown-linux-gnu run_tests # big-endian architecture
;;
i686-pc-windows-msvc)
# Host
# Only smoke-test `many-seeds`; 64 runs take 15min here!
# Only smoke-test `many-seeds`; 64 runs of just the scoped-thread-leak test take 15min here!
# See <https://github.com/rust-lang/miri/issues/3509>.
GC_STRESS=1 MIR_OPT=1 MANY_SEEDS=1 TEST_BENCH=1 run_tests
# Extra tier 1
# We really want to ensure a Linux target works on a Windows host,

View file

@ -0,0 +1 @@
arithmetic-side-effects-allowed = ["rustc_target::abi::Size"]

View file

@ -8,7 +8,9 @@ version = "0.1.0"
default-run = "miri-script"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[workspace]
# We make this a workspace root so that cargo does not go looking in ../Cargo.toml for the workspace root.
# This is needed to make this package build on stable when the parent package uses unstable cargo features.
[dependencies]
which = "4.4"

View file

@ -1 +1 @@
c8d19a92aa9022eb690899cf6d54fd23cb6877e5
cb3752d20e0f5d24348062211102a08d46fbecff

View file

@ -506,6 +506,11 @@ fn main() {
);
} else if let Some(param) = arg.strip_prefix("-Zmiri-env-forward=") {
miri_config.forwarded_env_vars.push(param.to_owned());
} else if let Some(param) = arg.strip_prefix("-Zmiri-env-set=") {
let Some((name, value)) = param.split_once('=') else {
show_error!("-Zmiri-env-set requires an argument of the form <name>=<value>");
};
miri_config.set_env_vars.insert(name.to_owned(), value.to_owned());
} else if let Some(param) = arg.strip_prefix("-Zmiri-track-pointer-tag=") {
let ids: Vec<u64> = parse_comma_list(param).unwrap_or_else(|err| {
show_error!("-Zmiri-track-pointer-tag requires a comma separated list of valid `u64` arguments: {err}")

View file

@ -248,7 +248,7 @@ impl<'tcx> Stack {
#[cfg(feature = "stack-cache")]
fn find_granting_cache(&mut self, access: AccessKind, tag: BorTag) -> Option<usize> {
// This looks like a common-sense optimization; we're going to do a linear search of the
// cache or the borrow stack to scan the shorter of the two. This optimization is miniscule
// cache or the borrow stack to scan the shorter of the two. This optimization is minuscule
// and this check actually ensures we do not access an invalid cache.
// When a stack is created and when items are removed from the top of the borrow stack, we
// need some valid value to populate the cache. In both cases, we try to use the bottom

View file

@ -847,6 +847,7 @@ impl VClockAlloc {
kind: MemoryKind,
current_span: Span,
) -> VClockAlloc {
// Determine the thread that did the allocation, and when it did it.
let (alloc_timestamp, alloc_index) = match kind {
// User allocated and stack memory should track allocation.
MemoryKind::Machine(
@ -858,13 +859,13 @@ impl VClockAlloc {
| MiriMemoryKind::Mmap,
)
| MemoryKind::Stack => {
let (alloc_index, clocks) = global.current_thread_state(thread_mgr);
let (alloc_index, clocks) = global.active_thread_state(thread_mgr);
let mut alloc_timestamp = clocks.clock[alloc_index];
alloc_timestamp.span = current_span;
(alloc_timestamp, alloc_index)
}
// Other global memory should trace races but be allocated at the 0 timestamp
// (conceptually they are allocated before everything).
// (conceptually they are allocated on the main thread before everything).
MemoryKind::Machine(
MiriMemoryKind::Global
| MiriMemoryKind::Machine
@ -872,7 +873,8 @@ impl VClockAlloc {
| MiriMemoryKind::ExternStatic
| MiriMemoryKind::Tls,
)
| MemoryKind::CallerLocation => (VTimestamp::ZERO, VectorIdx::MAX_INDEX),
| MemoryKind::CallerLocation =>
(VTimestamp::ZERO, global.thread_index(ThreadId::MAIN_THREAD)),
};
VClockAlloc {
alloc_ranges: RefCell::new(RangeMap::new(
@ -930,7 +932,7 @@ impl VClockAlloc {
ptr_dbg: Pointer<AllocId>,
ty: Option<Ty<'_>>,
) -> InterpResult<'tcx> {
let (current_index, current_clocks) = global.current_thread_state(thread_mgr);
let (active_index, active_clocks) = global.active_thread_state(thread_mgr);
let mut other_size = None; // if `Some`, this was a size-mismatch race
let write_clock;
let (other_access, other_thread, other_clock) =
@ -939,30 +941,30 @@ impl VClockAlloc {
// we are reporting races between two non-atomic reads.
if !access.is_atomic() &&
let Some(atomic) = mem_clocks.atomic() &&
let Some(idx) = Self::find_gt_index(&atomic.write_vector, &current_clocks.clock)
let Some(idx) = Self::find_gt_index(&atomic.write_vector, &active_clocks.clock)
{
(AccessType::AtomicStore, idx, &atomic.write_vector)
} else if !access.is_atomic() &&
let Some(atomic) = mem_clocks.atomic() &&
let Some(idx) = Self::find_gt_index(&atomic.read_vector, &current_clocks.clock)
let Some(idx) = Self::find_gt_index(&atomic.read_vector, &active_clocks.clock)
{
(AccessType::AtomicLoad, idx, &atomic.read_vector)
// Then check races with non-atomic writes/reads.
} else if mem_clocks.write.1 > current_clocks.clock[mem_clocks.write.0] {
} else if mem_clocks.write.1 > active_clocks.clock[mem_clocks.write.0] {
write_clock = mem_clocks.write();
(AccessType::NaWrite(mem_clocks.write_type), mem_clocks.write.0, &write_clock)
} else if let Some(idx) = Self::find_gt_index(&mem_clocks.read, &current_clocks.clock) {
} else if let Some(idx) = Self::find_gt_index(&mem_clocks.read, &active_clocks.clock) {
(AccessType::NaRead(mem_clocks.read[idx].read_type()), idx, &mem_clocks.read)
// Finally, mixed-size races.
} else if access.is_atomic() && let Some(atomic) = mem_clocks.atomic() && atomic.size != access_size {
// This is only a race if we are not synchronized with all atomic accesses, so find
// the one we are not synchronized with.
other_size = Some(atomic.size);
if let Some(idx) = Self::find_gt_index(&atomic.write_vector, &current_clocks.clock)
if let Some(idx) = Self::find_gt_index(&atomic.write_vector, &active_clocks.clock)
{
(AccessType::AtomicStore, idx, &atomic.write_vector)
} else if let Some(idx) =
Self::find_gt_index(&atomic.read_vector, &current_clocks.clock)
Self::find_gt_index(&atomic.read_vector, &active_clocks.clock)
{
(AccessType::AtomicLoad, idx, &atomic.read_vector)
} else {
@ -975,7 +977,7 @@ impl VClockAlloc {
};
// Load elaborated thread information about the racing thread actions.
let current_thread_info = global.print_thread_metadata(thread_mgr, current_index);
let active_thread_info = global.print_thread_metadata(thread_mgr, active_index);
let other_thread_info = global.print_thread_metadata(thread_mgr, other_thread);
let involves_non_atomic = !access.is_atomic() || !other_access.is_atomic();
@ -1003,8 +1005,8 @@ impl VClockAlloc {
},
op2: RacingOp {
action: access.description(ty, other_size.map(|_| access_size)),
thread_info: current_thread_info,
span: current_clocks.clock.as_slice()[current_index.index()].span_data(),
thread_info: active_thread_info,
span: active_clocks.clock.as_slice()[active_index.index()].span_data(),
},
}))?
}
@ -1026,7 +1028,7 @@ impl VClockAlloc {
let current_span = machine.current_span();
let global = machine.data_race.as_ref().unwrap();
if global.race_detecting() {
let (index, mut thread_clocks) = global.current_thread_state_mut(&machine.threads);
let (index, mut thread_clocks) = global.active_thread_state_mut(&machine.threads);
let mut alloc_ranges = self.alloc_ranges.borrow_mut();
for (mem_clocks_range, mem_clocks) in
alloc_ranges.iter_mut(access_range.start, access_range.size)
@ -1069,7 +1071,7 @@ impl VClockAlloc {
let current_span = machine.current_span();
let global = machine.data_race.as_mut().unwrap();
if global.race_detecting() {
let (index, mut thread_clocks) = global.current_thread_state_mut(&machine.threads);
let (index, mut thread_clocks) = global.active_thread_state_mut(&machine.threads);
for (mem_clocks_range, mem_clocks) in
self.alloc_ranges.get_mut().iter_mut(access_range.start, access_range.size)
{
@ -1454,7 +1456,7 @@ impl GlobalState {
// Setup the main-thread since it is not explicitly created:
// uses vector index and thread-id 0.
let index = global_state.vector_clocks.get_mut().push(ThreadClockSet::default());
global_state.vector_info.get_mut().push(ThreadId::new(0));
global_state.vector_info.get_mut().push(ThreadId::MAIN_THREAD);
global_state
.thread_info
.get_mut()
@ -1518,7 +1520,7 @@ impl GlobalState {
thread: ThreadId,
current_span: Span,
) {
let current_index = self.current_index(thread_mgr);
let current_index = self.active_thread_index(thread_mgr);
// Enable multi-threaded execution, there are now at least two threads
// so data-races are now possible.
@ -1642,7 +1644,7 @@ impl GlobalState {
/// `thread_joined`.
#[inline]
pub fn thread_terminated(&mut self, thread_mgr: &ThreadManager<'_, '_>, current_span: Span) {
let current_index = self.current_index(thread_mgr);
let current_index = self.active_thread_index(thread_mgr);
// Increment the clock to a unique termination timestamp.
let vector_clocks = self.vector_clocks.get_mut();
@ -1680,9 +1682,9 @@ impl GlobalState {
op: impl FnOnce(VectorIdx, RefMut<'_, ThreadClockSet>) -> InterpResult<'tcx, bool>,
) -> InterpResult<'tcx> {
if self.multi_threaded.get() {
let (index, clocks) = self.current_thread_state_mut(thread_mgr);
let (index, clocks) = self.active_thread_state_mut(thread_mgr);
if op(index, clocks)? {
let (_, mut clocks) = self.current_thread_state_mut(thread_mgr);
let (_, mut clocks) = self.active_thread_state_mut(thread_mgr);
clocks.increment_clock(index, current_span);
}
}
@ -1725,13 +1727,15 @@ impl GlobalState {
Ref::map(clocks, |c| &c.clock)
}
fn thread_index(&self, thread: ThreadId) -> VectorIdx {
self.thread_info.borrow()[thread].vector_index.expect("thread has no assigned vector")
}
/// Load the vector index used by the given thread as well as the set of vector clocks
/// used by the thread.
#[inline]
fn thread_state_mut(&self, thread: ThreadId) -> (VectorIdx, RefMut<'_, ThreadClockSet>) {
let index = self.thread_info.borrow()[thread]
.vector_index
.expect("Loading thread state for thread with no assigned vector");
let index = self.thread_index(thread);
let ref_vector = self.vector_clocks.borrow_mut();
let clocks = RefMut::map(ref_vector, |vec| &mut vec[index]);
(index, clocks)
@ -1741,9 +1745,7 @@ impl GlobalState {
/// used by the thread.
#[inline]
fn thread_state(&self, thread: ThreadId) -> (VectorIdx, Ref<'_, ThreadClockSet>) {
let index = self.thread_info.borrow()[thread]
.vector_index
.expect("Loading thread state for thread with no assigned vector");
let index = self.thread_index(thread);
let ref_vector = self.vector_clocks.borrow();
let clocks = Ref::map(ref_vector, |vec| &vec[index]);
(index, clocks)
@ -1752,7 +1754,7 @@ impl GlobalState {
/// Load the current vector clock in use and the current set of thread clocks
/// in use for the vector.
#[inline]
pub(super) fn current_thread_state(
pub(super) fn active_thread_state(
&self,
thread_mgr: &ThreadManager<'_, '_>,
) -> (VectorIdx, Ref<'_, ThreadClockSet>) {
@ -1762,7 +1764,7 @@ impl GlobalState {
/// Load the current vector clock in use and the current set of thread clocks
/// in use for the vector mutably for modification.
#[inline]
pub(super) fn current_thread_state_mut(
pub(super) fn active_thread_state_mut(
&self,
thread_mgr: &ThreadManager<'_, '_>,
) -> (VectorIdx, RefMut<'_, ThreadClockSet>) {
@ -1772,22 +1774,20 @@ impl GlobalState {
/// Return the current thread, should be the same
/// as the data-race active thread.
#[inline]
fn current_index(&self, thread_mgr: &ThreadManager<'_, '_>) -> VectorIdx {
fn active_thread_index(&self, thread_mgr: &ThreadManager<'_, '_>) -> VectorIdx {
let active_thread_id = thread_mgr.get_active_thread_id();
self.thread_info.borrow()[active_thread_id]
.vector_index
.expect("active thread has no assigned vector")
self.thread_index(active_thread_id)
}
// SC ATOMIC STORE rule in the paper.
pub(super) fn sc_write(&self, thread_mgr: &ThreadManager<'_, '_>) {
let (index, clocks) = self.current_thread_state(thread_mgr);
let (index, clocks) = self.active_thread_state(thread_mgr);
self.last_sc_write.borrow_mut().set_at_index(&clocks.clock, index);
}
// SC ATOMIC READ rule in the paper.
pub(super) fn sc_read(&self, thread_mgr: &ThreadManager<'_, '_>) {
let (.., mut clocks) = self.current_thread_state_mut(thread_mgr);
let (.., mut clocks) = self.active_thread_state_mut(thread_mgr);
clocks.read_seqcst.join(&self.last_sc_fence.borrow());
}
}

View file

@ -57,6 +57,8 @@ impl ThreadId {
pub fn to_u32(self) -> u32 {
self.0
}
pub const MAIN_THREAD: ThreadId = ThreadId(0);
}
impl Idx for ThreadId {
@ -401,7 +403,7 @@ impl<'mir, 'tcx> Default for ThreadManager<'mir, 'tcx> {
// Create the main thread and add it to the list of threads.
threads.push(Thread::new(Some("main"), None));
Self {
active_thread: ThreadId::new(0),
active_thread: ThreadId::MAIN_THREAD,
threads,
sync: SynchronizationState::default(),
thread_local_alloc_ids: Default::default(),
@ -416,10 +418,12 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
ecx: &mut MiriInterpCx<'mir, 'tcx>,
on_main_stack_empty: StackEmptyCallback<'mir, 'tcx>,
) {
ecx.machine.threads.threads[ThreadId::new(0)].on_stack_empty = Some(on_main_stack_empty);
ecx.machine.threads.threads[ThreadId::MAIN_THREAD].on_stack_empty =
Some(on_main_stack_empty);
if ecx.tcx.sess.target.os.as_ref() != "windows" {
// The main thread can *not* be joined on except on windows.
ecx.machine.threads.threads[ThreadId::new(0)].join_status = ThreadJoinStatus::Detached;
ecx.machine.threads.threads[ThreadId::MAIN_THREAD].join_status =
ThreadJoinStatus::Detached;
}
}

View file

@ -13,15 +13,13 @@ use super::data_race::NaReadType;
/// but in some cases one vector index may be shared with
/// multiple thread ids if it's safe to do so.
#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
pub struct VectorIdx(u32);
pub(super) struct VectorIdx(u32);
impl VectorIdx {
#[inline(always)]
pub fn to_u32(self) -> u32 {
fn to_u32(self) -> u32 {
self.0
}
pub const MAX_INDEX: VectorIdx = VectorIdx(u32::MAX);
}
impl Idx for VectorIdx {
@ -51,7 +49,7 @@ const SMALL_VECTOR: usize = 4;
/// a 32-bit unsigned integer which is the actual timestamp, and a `Span`
/// so that diagnostics can report what code was responsible for an operation.
#[derive(Clone, Copy, Debug)]
pub struct VTimestamp {
pub(super) struct VTimestamp {
/// The lowest bit indicates read type, the rest is the time.
/// `1` indicates a retag read, `0` a regular read.
time_and_read_type: u32,
@ -87,7 +85,7 @@ impl VTimestamp {
}
#[inline]
pub fn read_type(&self) -> NaReadType {
pub(super) fn read_type(&self) -> NaReadType {
if self.time_and_read_type & 1 == 0 { NaReadType::Read } else { NaReadType::Retag }
}
@ -97,7 +95,7 @@ impl VTimestamp {
}
#[inline]
pub fn span_data(&self) -> SpanData {
pub(super) fn span_data(&self) -> SpanData {
self.span.data()
}
}

View file

@ -270,7 +270,7 @@ impl<'mir, 'tcx: 'mir> StoreBuffer {
) {
let store_elem = self.buffer.back();
if let Some(store_elem) = store_elem {
let (index, clocks) = global.current_thread_state(thread_mgr);
let (index, clocks) = global.active_thread_state(thread_mgr);
store_elem.load_impl(index, &clocks, is_seqcst);
}
}
@ -289,7 +289,7 @@ impl<'mir, 'tcx: 'mir> StoreBuffer {
let (store_elem, recency) = {
// The `clocks` we got here must be dropped before calling validate_atomic_load
// as the race detector will update it
let (.., clocks) = global.current_thread_state(thread_mgr);
let (.., clocks) = global.active_thread_state(thread_mgr);
// Load from a valid entry in the store buffer
self.fetch_store(is_seqcst, &clocks, &mut *rng)
};
@ -300,7 +300,7 @@ impl<'mir, 'tcx: 'mir> StoreBuffer {
// requires access to ThreadClockSet.clock, which is updated by the race detector
validate()?;
let (index, clocks) = global.current_thread_state(thread_mgr);
let (index, clocks) = global.active_thread_state(thread_mgr);
let loaded = store_elem.load_impl(index, &clocks, is_seqcst);
Ok((loaded, recency))
}
@ -312,7 +312,7 @@ impl<'mir, 'tcx: 'mir> StoreBuffer {
thread_mgr: &ThreadManager<'_, '_>,
is_seqcst: bool,
) -> InterpResult<'tcx> {
let (index, clocks) = global.current_thread_state(thread_mgr);
let (index, clocks) = global.active_thread_state(thread_mgr);
self.store_impl(val, index, &clocks.clock, is_seqcst);
Ok(())
@ -520,7 +520,9 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
validate,
)?;
if global.track_outdated_loads && recency == LoadRecency::Outdated {
this.emit_diagnostic(NonHaltingDiagnostic::WeakMemoryOutdatedLoad);
this.emit_diagnostic(NonHaltingDiagnostic::WeakMemoryOutdatedLoad {
ptr: place.ptr(),
});
}
return Ok(loaded);

View file

@ -125,7 +125,9 @@ pub enum NonHaltingDiagnostic {
Int2Ptr {
details: bool,
},
WeakMemoryOutdatedLoad,
WeakMemoryOutdatedLoad {
ptr: Pointer<Option<Provenance>>,
},
}
/// Level of Miri specific diagnostics
@ -583,7 +585,8 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
| AccessedAlloc(..)
| FreedAlloc(..)
| ProgressReport { .. }
| WeakMemoryOutdatedLoad => ("tracking was triggered".to_string(), DiagLevel::Note),
| WeakMemoryOutdatedLoad { .. } =>
("tracking was triggered".to_string(), DiagLevel::Note),
};
let msg = match &e {
@ -610,8 +613,8 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
ProgressReport { .. } =>
format!("progress report: current operation being executed is here"),
Int2Ptr { .. } => format!("integer-to-pointer cast"),
WeakMemoryOutdatedLoad =>
format!("weak memory emulation: outdated value returned from load"),
WeakMemoryOutdatedLoad { ptr } =>
format!("weak memory emulation: outdated value returned from load at {ptr}"),
};
let notes = match &e {

View file

@ -9,7 +9,7 @@ use std::thread;
use crate::concurrency::thread::TlsAllocAction;
use crate::diagnostics::report_leaks;
use rustc_data_structures::fx::FxHashSet;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir::def::Namespace;
use rustc_hir::def_id::DefId;
use rustc_middle::ty::{
@ -100,6 +100,8 @@ pub struct MiriConfig {
pub ignore_leaks: bool,
/// Environment variables that should always be forwarded from the host.
pub forwarded_env_vars: Vec<String>,
/// Additional environment variables that should be set in the interpreted program.
pub set_env_vars: FxHashMap<String, String>,
/// Command-line arguments passed to the interpreted program.
pub args: Vec<String>,
/// The seed to use when non-determinism or randomness are required (e.g. ptr-to-int cast, `getrandom()`).
@ -167,6 +169,7 @@ impl Default for MiriConfig {
isolated_op: IsolatedOp::Reject(RejectOpWith::Abort),
ignore_leaks: false,
forwarded_env_vars: vec![],
set_env_vars: FxHashMap::default(),
args: vec![],
seed: None,
tracked_pointer_tags: FxHashSet::default(),
@ -383,10 +386,9 @@ pub fn create_ecx<'mir, 'tcx: 'mir>(
let main_ptr = ecx.fn_ptr(FnVal::Instance(entry_instance));
// Inlining of `DEFAULT` from
// https://github.com/rust-lang/rust/blob/master/compiler/rustc_session/src/config/sigpipe.rs.
// Always using DEFAULT is okay since we don't support signals in Miri anyway.
let sigpipe = 2;
// (This means we are effectively ignoring `#[unix_sigpipe]`.)
let sigpipe = rustc_session::config::sigpipe::DEFAULT;
ecx.call_function(
start_instance,

View file

@ -44,21 +44,15 @@ impl<'tcx> EnvVars<'tcx> {
let forward = ecx.machine.communicate()
|| config.forwarded_env_vars.iter().any(|v| **v == *name);
if forward {
let var_ptr = match ecx.tcx.sess.target.os.as_ref() {
_ if ecx.target_os_is_unix() =>
alloc_env_var_as_c_str(name.as_ref(), value.as_ref(), ecx)?,
"windows" => alloc_env_var_as_wide_str(name.as_ref(), value.as_ref(), ecx)?,
unsupported =>
throw_unsup_format!(
"environment support for target OS `{}` not yet available",
unsupported
),
};
ecx.machine.env_vars.map.insert(name.clone(), var_ptr);
add_env_var(ecx, name, value)?;
}
}
}
for (name, value) in &config.set_env_vars {
add_env_var(ecx, OsStr::new(name), OsStr::new(value))?;
}
// Initialize the `environ` pointer when needed.
if ecx.target_os_is_unix() {
// This is memory backing an extern static, hence `ExternStatic`, not `Env`.
@ -89,6 +83,24 @@ impl<'tcx> EnvVars<'tcx> {
}
}
fn add_env_var<'mir, 'tcx>(
ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
name: &OsStr,
value: &OsStr,
) -> InterpResult<'tcx, ()> {
let var_ptr = match ecx.tcx.sess.target.os.as_ref() {
_ if ecx.target_os_is_unix() => alloc_env_var_as_c_str(name, value, ecx)?,
"windows" => alloc_env_var_as_wide_str(name, value, ecx)?,
unsupported =>
throw_unsup_format!(
"environment support for target OS `{}` not yet available",
unsupported
),
};
ecx.machine.env_vars.map.insert(name.to_os_string(), var_ptr);
Ok(())
}
fn alloc_env_var_as_c_str<'mir, 'tcx>(
name: &OsStr,
value: &OsStr,
@ -148,10 +160,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
this.assert_target_os("windows", "GetEnvironmentVariableW");
let name_ptr = this.read_pointer(name_op)?;
let buf_ptr = this.read_pointer(buf_op)?;
let buf_size = this.read_scalar(size_op)?.to_u32()?; // in characters
let name = this.read_os_str_from_wide_str(name_ptr)?;
Ok(match this.machine.env_vars.map.get(&name) {
Some(&var_ptr) => {
this.set_last_error(Scalar::from_u32(0))?; // make sure this is unambiguously not an error
// The offset is used to strip the "{name}=" part of the string.
#[rustfmt::skip]
let name_offset_bytes = u64::try_from(name.len()).unwrap()
@ -160,14 +174,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let var_ptr = var_ptr.offset(Size::from_bytes(name_offset_bytes), this)?;
let var = this.read_os_str_from_wide_str(var_ptr)?;
let buf_ptr = this.read_pointer(buf_op)?;
// `buf_size` represents the size in characters.
let buf_size = u64::from(this.read_scalar(size_op)?.to_u32()?);
Scalar::from_u32(windows_check_buffer_size(
this.write_os_str_to_wide_str(
&var, buf_ptr, buf_size, /*truncate*/ false,
)?,
))
Scalar::from_u32(windows_check_buffer_size(this.write_os_str_to_wide_str(
&var,
buf_ptr,
buf_size.into(),
)?))
// This can in fact return 0. It is up to the caller to set last_error to 0
// beforehand and check it afterwards to exclude that case.
}
None => {
let envvar_not_found = this.eval_windows("c", "ERROR_ENVVAR_NOT_FOUND");
@ -363,9 +376,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
// If we cannot get the current directory, we return 0
match env::current_dir() {
Ok(cwd) => {
this.set_last_error(Scalar::from_u32(0))?; // make sure this is unambiguously not an error
// This can in fact return 0. It is up to the caller to set last_error to 0
// beforehand and check it afterwards to exclude that case.
return Ok(Scalar::from_u32(windows_check_buffer_size(
this.write_path_to_wide_str(&cwd, buf, size, /*truncate*/ false)?,
this.write_path_to_wide_str(&cwd, buf, size)?,
)));
}
Err(e) => this.set_last_error_from_io_error(e.kind())?,
@ -482,9 +496,60 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn GetCurrentProcessId(&mut self) -> InterpResult<'tcx, u32> {
let this = self.eval_context_mut();
this.assert_target_os("windows", "GetCurrentProcessId");
this.check_no_isolation("`GetCurrentProcessId`")?;
Ok(std::process::id())
}
#[allow(non_snake_case)]
fn GetUserProfileDirectoryW(
&mut self,
token: &OpTy<'tcx, Provenance>, // HANDLE
buf: &OpTy<'tcx, Provenance>, // LPWSTR
size: &OpTy<'tcx, Provenance>, // LPDWORD
) -> InterpResult<'tcx, Scalar<Provenance>> // returns BOOL
{
let this = self.eval_context_mut();
this.assert_target_os("windows", "GetUserProfileDirectoryW");
this.check_no_isolation("`GetUserProfileDirectoryW`")?;
let token = this.read_target_isize(token)?;
let buf = this.read_pointer(buf)?;
let size = this.deref_pointer(size)?;
if token != -4 {
throw_unsup_format!(
"GetUserProfileDirectoryW: only CURRENT_PROCESS_TOKEN is supported"
);
}
// See <https://learn.microsoft.com/en-us/windows/win32/api/userenv/nf-userenv-getuserprofiledirectoryw> for docs.
Ok(match directories::UserDirs::new() {
Some(dirs) => {
let home = dirs.home_dir();
let size_avail = if this.ptr_is_null(size.ptr())? {
0 // if the buf pointer is null, we can't write to it; `size` will be updated to the required length
} else {
this.read_scalar(&size)?.to_u32()?
};
// Of course we cannot use `windows_check_buffer_size` here since this uses
// a different method for dealing with a too-small buffer than the other functions...
let (success, len) = this.write_path_to_wide_str(home, buf, size_avail.into())?;
// The Windows docs just say that this is written on failure. But std
// seems to rely on it always being written.
this.write_scalar(Scalar::from_u32(len.try_into().unwrap()), &size)?;
if success {
Scalar::from_i32(1) // return TRUE
} else {
this.set_last_error(this.eval_windows("c", "ERROR_INSUFFICIENT_BUFFER"))?;
Scalar::from_i32(0) // return FALSE
}
}
None => {
// We have to pick some error code.
this.set_last_error(this.eval_windows("c", "ERROR_BAD_USER_PROFILE"))?;
Scalar::from_i32(0) // return FALSE
}
})
}
}

View file

@ -72,11 +72,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
u16vec_to_osstring(u16_vec)
}
/// Helper function to write an OsStr as a null-terminated sequence of bytes, which is what
/// the Unix APIs usually handle. This function returns `Ok((false, length))` without trying
/// to write if `size` is not large enough to fit the contents of `os_string` plus a null
/// terminator. It returns `Ok((true, length))` if the writing process was successful. The
/// string length returned does include the null terminator.
/// Helper function to write an OsStr as a null-terminated sequence of bytes, which is what the
/// Unix APIs usually handle. Returns `(success, full_len)`, where length includes the null
/// terminator. On failure, nothing is written.
fn write_os_str_to_c_str(
&mut self,
os_str: &OsStr,
@ -87,19 +85,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
self.eval_context_mut().write_c_str(bytes, ptr, size)
}
/// Helper function to write an OsStr as a 0x0000-terminated u16-sequence, which is what the
/// Windows APIs usually handle.
///
/// If `truncate == false` (the usual mode of operation), this function returns `Ok((false,
/// length))` without trying to write if `size` is not large enough to fit the contents of
/// `os_string` plus a null terminator. It returns `Ok((true, length))` if the writing process
/// was successful. The string length returned does include the null terminator. Length is
/// measured in units of `u16.`
///
/// If `truncate == true`, then in case `size` is not large enough it *will* write the first
/// `size.saturating_sub(1)` many items, followed by a null terminator (if `size > 0`).
/// The return value is still `(false, length)` in that case.
fn write_os_str_to_wide_str(
/// Internal helper to share code between `write_os_str_to_wide_str` and
/// `write_os_str_to_wide_str_truncated`.
fn write_os_str_to_wide_str_helper(
&mut self,
os_str: &OsStr,
ptr: Pointer<Option<Provenance>>,
@ -133,6 +121,29 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
Ok((written, size_needed))
}
/// Helper function to write an OsStr as a 0x0000-terminated u16-sequence, which is what the
/// Windows APIs usually handle. Returns `(success, full_len)`, where length is measured
/// in units of `u16` and includes the null terminator. On failure, nothing is written.
fn write_os_str_to_wide_str(
&mut self,
os_str: &OsStr,
ptr: Pointer<Option<Provenance>>,
size: u64,
) -> InterpResult<'tcx, (bool, u64)> {
self.write_os_str_to_wide_str_helper(os_str, ptr, size, /*truncate*/ false)
}
/// Like `write_os_str_to_wide_str`, but on failure as much as possible is written into
/// the buffer (always with a null terminator).
fn write_os_str_to_wide_str_truncated(
&mut self,
os_str: &OsStr,
ptr: Pointer<Option<Provenance>>,
size: u64,
) -> InterpResult<'tcx, (bool, u64)> {
self.write_os_str_to_wide_str_helper(os_str, ptr, size, /*truncate*/ true)
}
/// Allocate enough memory to store the given `OsStr` as a null-terminated sequence of bytes.
fn alloc_os_str_as_c_str(
&mut self,
@ -160,9 +171,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let arg_type = Ty::new_array(this.tcx.tcx, this.tcx.types.u16, size);
let arg_place = this.allocate(this.layout_of(arg_type).unwrap(), memkind)?;
let (written, _) = self
.write_os_str_to_wide_str(os_str, arg_place.ptr(), size, /*truncate*/ false)
.unwrap();
let (written, _) = self.write_os_str_to_wide_str(os_str, arg_place.ptr(), size).unwrap();
assert!(written);
Ok(arg_place.ptr())
}
@ -217,12 +226,25 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
path: &Path,
ptr: Pointer<Option<Provenance>>,
size: u64,
truncate: bool,
) -> InterpResult<'tcx, (bool, u64)> {
let this = self.eval_context_mut();
let os_str =
this.convert_path(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget);
this.write_os_str_to_wide_str(&os_str, ptr, size, truncate)
this.write_os_str_to_wide_str(&os_str, ptr, size)
}
/// Write a Path to the machine memory (as a null-terminated sequence of `u16`s),
/// adjusting path separators if needed.
fn write_path_to_wide_str_truncated(
&mut self,
path: &Path,
ptr: Pointer<Option<Provenance>>,
size: u64,
) -> InterpResult<'tcx, (bool, u64)> {
let this = self.eval_context_mut();
let os_str =
this.convert_path(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget);
this.write_os_str_to_wide_str_truncated(&os_str, ptr, size)
}
/// Allocate enough memory to store a Path as a null-terminated sequence of bytes,

View file

@ -1,5 +1,9 @@
use std::ffi::OsString;
use std::fmt::Write;
use std::time::{Duration, SystemTime};
use chrono::{DateTime, Datelike, Local, Timelike, Utc};
use crate::concurrency::thread::MachineCallback;
use crate::*;
@ -107,6 +111,80 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
Ok(0)
}
// The localtime() function shall convert the time in seconds since the Epoch pointed to by
// timer into a broken-down time, expressed as a local time.
// https://linux.die.net/man/3/localtime_r
fn localtime_r(
&mut self,
timep: &OpTy<'tcx, Provenance>,
result_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
let this = self.eval_context_mut();
this.assert_target_os_is_unix("localtime_r");
this.check_no_isolation("`localtime_r`")?;
let timep = this.deref_pointer(timep)?;
let result = this.deref_pointer_as(result_op, this.libc_ty_layout("tm"))?;
// The input "represents the number of seconds elapsed since the Epoch,
// 1970-01-01 00:00:00 +0000 (UTC)".
let sec_since_epoch: i64 = this
.read_scalar(&timep)?
.to_int(this.libc_ty_layout("time_t").size)?
.try_into()
.unwrap();
let dt_utc: DateTime<Utc> =
DateTime::from_timestamp(sec_since_epoch, 0).expect("Invalid timestamp");
// Convert that to local time, then return the broken-down time value.
let dt: DateTime<Local> = DateTime::from(dt_utc);
// This value is always set to -1, because there is no way to know if dst is in effect with
// chrono crate yet.
// This may not be consistent with libc::localtime_r's result.
let tm_isdst = -1;
// tm_zone represents the timezone value in the form of: +0730, +08, -0730 or -08.
// This may not be consistent with libc::localtime_r's result.
let offset_in_second = Local::now().offset().local_minus_utc();
let tm_gmtoff = offset_in_second;
let mut tm_zone = String::new();
if offset_in_second < 0 {
tm_zone.push('-');
} else {
tm_zone.push('+');
}
let offset_hour = offset_in_second.abs() / 3600;
write!(tm_zone, "{:02}", offset_hour).unwrap();
let offset_min = (offset_in_second.abs() % 3600) / 60;
if offset_min != 0 {
write!(tm_zone, "{:02}", offset_min).unwrap();
}
// FIXME: String de-duplication is needed so that we only allocate this string only once
// even when there are multiple calls to this function.
let tm_zone_ptr =
this.alloc_os_str_as_c_str(&OsString::from(tm_zone), MiriMemoryKind::Machine.into())?;
this.write_pointer(tm_zone_ptr, &this.project_field_named(&result, "tm_zone")?)?;
this.write_int_fields_named(
&[
("tm_sec", dt.second().into()),
("tm_min", dt.minute().into()),
("tm_hour", dt.hour().into()),
("tm_mday", dt.day().into()),
("tm_mon", dt.month0().into()),
("tm_year", dt.year().checked_sub(1900).unwrap().into()),
("tm_wday", dt.weekday().num_days_from_sunday().into()),
("tm_yday", dt.ordinal0().into()),
("tm_isdst", tm_isdst),
("tm_gmtoff", tm_gmtoff.into()),
],
&result,
)?;
Ok(result.ptr())
}
#[allow(non_snake_case, clippy::arithmetic_side_effects)]
fn GetSystemTimeAsFileTime(
&mut self,

View file

@ -234,6 +234,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let result = this.gettimeofday(tv, tz)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"localtime_r" => {
let [timep, result_op] = this.check_shim(abi, Abi::C {unwind: false}, link_name, args)?;
let result = this.localtime_r(timep, result_op)?;
this.write_pointer(result, dest)?;
}
"clock_gettime" => {
let [clk_id, tp] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;

View file

@ -23,7 +23,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
// old_address must be a multiple of the page size
#[allow(clippy::arithmetic_side_effects)] // PAGE_SIZE is nonzero
if old_address.addr().bytes() % this.machine.page_size != 0 || new_size == 0 {
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?;
this.set_last_error(this.eval_libc("EINVAL"))?;
return Ok(this.eval_libc("MAP_FAILED"));
}
@ -37,7 +37,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
if flags & this.eval_libc_i32("MREMAP_MAYMOVE") == 0 {
// We only support MREMAP_MAYMOVE, so not passing the flag is just a failure
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?;
this.set_last_error(this.eval_libc("EINVAL"))?;
return Ok(this.eval_libc("MAP_FAILED"));
}

View file

@ -53,11 +53,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
// First, we do some basic argument validation as required by mmap
if (flags & (map_private | map_shared)).count_ones() != 1 {
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?;
this.set_last_error(this.eval_libc("EINVAL"))?;
return Ok(this.eval_libc("MAP_FAILED"));
}
if length == 0 {
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?;
this.set_last_error(this.eval_libc("EINVAL"))?;
return Ok(this.eval_libc("MAP_FAILED"));
}
@ -77,7 +77,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
//
// Miri doesn't support MAP_FIXED or any any protections other than PROT_READ|PROT_WRITE.
if flags & map_fixed != 0 || prot != prot_read | prot_write {
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("ENOTSUP")))?;
this.set_last_error(this.eval_libc("ENOTSUP"))?;
return Ok(this.eval_libc("MAP_FAILED"));
}
@ -96,11 +96,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let align = this.machine.page_align();
let Some(map_length) = length.checked_next_multiple_of(this.machine.page_size) else {
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?;
this.set_last_error(this.eval_libc("EINVAL"))?;
return Ok(this.eval_libc("MAP_FAILED"));
};
if map_length > this.target_usize_max() {
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?;
this.set_last_error(this.eval_libc("EINVAL"))?;
return Ok(this.eval_libc("MAP_FAILED"));
}
@ -131,16 +131,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
// as a dealloc.
#[allow(clippy::arithmetic_side_effects)] // PAGE_SIZE is nonzero
if addr.addr().bytes() % this.machine.page_size != 0 {
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?;
this.set_last_error(this.eval_libc("EINVAL"))?;
return Ok(Scalar::from_i32(-1));
}
let Some(length) = length.checked_next_multiple_of(this.machine.page_size) else {
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?;
this.set_last_error(this.eval_libc("EINVAL"))?;
return Ok(Scalar::from_i32(-1));
};
if length > this.target_usize_max() {
this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?;
this.set_last_error(this.eval_libc("EINVAL"))?;
return Ok(this.eval_libc("MAP_FAILED"));
}

View file

@ -135,6 +135,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let result = this.SetCurrentDirectoryW(path)?;
this.write_scalar(result, dest)?;
}
"GetUserProfileDirectoryW" => {
let [token, buf, size] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
let result = this.GetUserProfileDirectoryW(token, buf, size)?;
this.write_scalar(result, dest)?;
}
// File related shims
"NtWriteFile" => {
@ -225,15 +231,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
Scalar::from_u32(0) // return zero upon failure
}
Ok(abs_filename) => {
this.set_last_error(Scalar::from_u32(0))?; // make sure this is unambiguously not an error
Scalar::from_u32(helpers::windows_check_buffer_size(
this.write_path_to_wide_str(
&abs_filename,
buffer,
size.into(),
/*truncate*/ false,
)?,
this.write_path_to_wide_str(&abs_filename, buffer, size.into())?,
))
// This can in fact return 0. It is up to the caller to set last_error to 0
// beforehand and check it afterwards to exclude that case.
}
};
this.write_scalar(result, dest)?;
@ -601,15 +603,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
// Using the host current_exe is a bit off, but consistent with Linux
// (where stdlib reads /proc/self/exe).
// Unfortunately this Windows function has a crazy behavior so we can't just use
// `write_path_to_wide_str`...
let path = std::env::current_exe().unwrap();
let (all_written, size_needed) = this.write_path_to_wide_str(
&path,
filename,
size.into(),
/*truncate*/ true,
)?;
let (all_written, size_needed) =
this.write_path_to_wide_str_truncated(&path, filename, size.into())?;
if all_written {
// If the function succeeds, the return value is the length of the string that
@ -649,12 +645,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
Some(err) => format!("{err}"),
None => format!("<unknown error in FormatMessageW: {message_id}>"),
};
let (complete, length) = this.write_os_str_to_wide_str(
OsStr::new(&formatted),
buffer,
size.into(),
/*trunacte*/ false,
)?;
let (complete, length) =
this.write_os_str_to_wide_str(OsStr::new(&formatted), buffer, size.into())?;
if !complete {
// The API docs don't say what happens when the buffer is not big enough...
// Let's just bail.

View file

@ -7,7 +7,8 @@ use rustc_target::spec::abi::Abi;
use super::{
bin_op_simd_float_all, conditional_dot_product, convert_float_to_int, horizontal_bin_op,
round_all, test_bits_masked, test_high_bits_masked, unary_op_ps, FloatBinOp, FloatUnaryOp,
mask_load, mask_store, round_all, test_bits_masked, test_high_bits_masked, unary_op_ps,
FloatBinOp, FloatUnaryOp,
};
use crate::*;
use shims::foreign_items::EmulateForeignItemResult;
@ -347,71 +348,3 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
Ok(EmulateForeignItemResult::NeedsJumping)
}
}
/// Conditionally loads from `ptr` according the high bit of each
/// element of `mask`. `ptr` does not need to be aligned.
fn mask_load<'tcx>(
this: &mut crate::MiriInterpCx<'_, 'tcx>,
ptr: &OpTy<'tcx, Provenance>,
mask: &OpTy<'tcx, Provenance>,
dest: &MPlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx, ()> {
let (mask, mask_len) = this.operand_to_simd(mask)?;
let (dest, dest_len) = this.mplace_to_simd(dest)?;
assert_eq!(dest_len, mask_len);
let mask_item_size = mask.layout.field(this, 0).size;
let high_bit_offset = mask_item_size.bits().checked_sub(1).unwrap();
let ptr = this.read_pointer(ptr)?;
for i in 0..dest_len {
let mask = this.project_index(&mask, i)?;
let dest = this.project_index(&dest, i)?;
if this.read_scalar(&mask)?.to_uint(mask_item_size)? >> high_bit_offset != 0 {
// Size * u64 is implemented as always checked
#[allow(clippy::arithmetic_side_effects)]
let ptr = ptr.wrapping_offset(dest.layout.size * i, &this.tcx);
// Unaligned copy, which is what we want.
this.mem_copy(ptr, dest.ptr(), dest.layout.size, /*nonoverlapping*/ true)?;
} else {
this.write_scalar(Scalar::from_int(0, dest.layout.size), &dest)?;
}
}
Ok(())
}
/// Conditionally stores into `ptr` according the high bit of each
/// element of `mask`. `ptr` does not need to be aligned.
fn mask_store<'tcx>(
this: &mut crate::MiriInterpCx<'_, 'tcx>,
ptr: &OpTy<'tcx, Provenance>,
mask: &OpTy<'tcx, Provenance>,
value: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, ()> {
let (mask, mask_len) = this.operand_to_simd(mask)?;
let (value, value_len) = this.operand_to_simd(value)?;
assert_eq!(value_len, mask_len);
let mask_item_size = mask.layout.field(this, 0).size;
let high_bit_offset = mask_item_size.bits().checked_sub(1).unwrap();
let ptr = this.read_pointer(ptr)?;
for i in 0..value_len {
let mask = this.project_index(&mask, i)?;
let value = this.project_index(&value, i)?;
if this.read_scalar(&mask)?.to_uint(mask_item_size)? >> high_bit_offset != 0 {
// Size * u64 is implemented as always checked
#[allow(clippy::arithmetic_side_effects)]
let ptr = ptr.wrapping_offset(value.layout.size * i, &this.tcx);
// Unaligned copy, which is what we want.
this.mem_copy(value.ptr(), ptr, value.layout.size, /*nonoverlapping*/ true)?;
}
}
Ok(())
}

View file

@ -0,0 +1,444 @@
use crate::rustc_middle::ty::layout::LayoutOf as _;
use rustc_middle::mir;
use rustc_middle::ty::Ty;
use rustc_span::Symbol;
use rustc_target::spec::abi::Abi;
use super::{
horizontal_bin_op, int_abs, mask_load, mask_store, mpsadbw, packssdw, packsswb, packusdw,
packuswb, pmulhrsw, psign, shift_simd_by_scalar, shift_simd_by_simd, ShiftOp,
};
use crate::*;
use shims::foreign_items::EmulateForeignItemResult;
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
crate::MiriInterpCxExt<'mir, 'tcx>
{
fn emulate_x86_avx2_intrinsic(
&mut self,
link_name: Symbol,
abi: Abi,
args: &[OpTy<'tcx, Provenance>],
dest: &MPlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx, EmulateForeignItemResult> {
let this = self.eval_context_mut();
this.expect_target_feature_for_intrinsic(link_name, "avx2")?;
// Prefix should have already been checked.
let unprefixed_name = link_name.as_str().strip_prefix("llvm.x86.avx2.").unwrap();
match unprefixed_name {
// Used to implement the _mm256_abs_epi{8,16,32} functions.
// Calculates the absolute value of packed 8/16/32-bit integers.
"pabs.b" | "pabs.w" | "pabs.d" => {
let [op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
int_abs(this, op, dest)?;
}
// Used to implement the _mm256_h{add,adds,sub}_epi{16,32} functions.
// Horizontally add / add with saturation / subtract adjacent 16/32-bit
// integer values in `left` and `right`.
"phadd.w" | "phadd.sw" | "phadd.d" | "phsub.w" | "phsub.sw" | "phsub.d" => {
let [left, right] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let (which, saturating) = match unprefixed_name {
"phadd.w" | "phadd.d" => (mir::BinOp::Add, false),
"phadd.sw" => (mir::BinOp::Add, true),
"phsub.w" | "phsub.d" => (mir::BinOp::Sub, false),
"phsub.sw" => (mir::BinOp::Sub, true),
_ => unreachable!(),
};
horizontal_bin_op(this, which, saturating, left, right, dest)?;
}
// Used to implement `_mm{,_mask}_{i32,i64}gather_{epi32,epi64,pd,ps}` functions
// Gathers elements from `slice` using `offsets * scale` as indices.
// When the highest bit of the corresponding element of `mask` is 0,
// the value is copied from `src` instead.
"gather.d.d" | "gather.d.d.256" | "gather.d.q" | "gather.d.q.256" | "gather.q.d"
| "gather.q.d.256" | "gather.q.q" | "gather.q.q.256" | "gather.d.pd"
| "gather.d.pd.256" | "gather.q.pd" | "gather.q.pd.256" | "gather.d.ps"
| "gather.d.ps.256" | "gather.q.ps" | "gather.q.ps.256" => {
let [src, slice, offsets, mask, scale] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
assert_eq!(dest.layout, src.layout);
let (src, _) = this.operand_to_simd(src)?;
let (offsets, offsets_len) = this.operand_to_simd(offsets)?;
let (mask, mask_len) = this.operand_to_simd(mask)?;
let (dest, dest_len) = this.mplace_to_simd(dest)?;
// There are cases like dest: i32x4, offsets: i64x2
let actual_len = dest_len.min(offsets_len);
assert_eq!(dest_len, mask_len);
let mask_item_size = mask.layout.field(this, 0).size;
let high_bit_offset = mask_item_size.bits().checked_sub(1).unwrap();
let scale = this.read_scalar(scale)?.to_i8()?;
if !matches!(scale, 1 | 2 | 4 | 8) {
throw_unsup_format!("invalid gather scale {scale}");
}
let scale = i64::from(scale);
let slice = this.read_pointer(slice)?;
for i in 0..actual_len {
let mask = this.project_index(&mask, i)?;
let dest = this.project_index(&dest, i)?;
if this.read_scalar(&mask)?.to_uint(mask_item_size)? >> high_bit_offset != 0 {
let offset = this.project_index(&offsets, i)?;
let offset =
i64::try_from(this.read_scalar(&offset)?.to_int(offset.layout.size)?)
.unwrap();
let ptr = slice
.wrapping_signed_offset(offset.checked_mul(scale).unwrap(), &this.tcx);
// Unaligned copy, which is what we want.
this.mem_copy(
ptr,
dest.ptr(),
dest.layout.size,
/*nonoverlapping*/ true,
)?;
} else {
this.copy_op(&this.project_index(&src, i)?, &dest)?;
}
}
for i in actual_len..dest_len {
let dest = this.project_index(&dest, i)?;
this.write_scalar(Scalar::from_int(0, dest.layout.size), &dest)?;
}
}
// Used to implement the _mm256_madd_epi16 function.
// Multiplies packed signed 16-bit integers in `left` and `right`, producing
// intermediate signed 32-bit integers. Horizontally add adjacent pairs of
// intermediate 32-bit integers, and pack the results in `dest`.
"pmadd.wd" => {
let [left, right] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let (left, left_len) = this.operand_to_simd(left)?;
let (right, right_len) = this.operand_to_simd(right)?;
let (dest, dest_len) = this.mplace_to_simd(dest)?;
assert_eq!(left_len, right_len);
assert_eq!(dest_len.checked_mul(2).unwrap(), left_len);
for i in 0..dest_len {
let j1 = i.checked_mul(2).unwrap();
let left1 = this.read_scalar(&this.project_index(&left, j1)?)?.to_i16()?;
let right1 = this.read_scalar(&this.project_index(&right, j1)?)?.to_i16()?;
let j2 = j1.checked_add(1).unwrap();
let left2 = this.read_scalar(&this.project_index(&left, j2)?)?.to_i16()?;
let right2 = this.read_scalar(&this.project_index(&right, j2)?)?.to_i16()?;
let dest = this.project_index(&dest, i)?;
// Multiplications are i16*i16->i32, which will not overflow.
let mul1 = i32::from(left1).checked_mul(right1.into()).unwrap();
let mul2 = i32::from(left2).checked_mul(right2.into()).unwrap();
// However, this addition can overflow in the most extreme case
// (-0x8000)*(-0x8000)+(-0x8000)*(-0x8000) = 0x80000000
let res = mul1.wrapping_add(mul2);
this.write_scalar(Scalar::from_i32(res), &dest)?;
}
}
// Used to implement the _mm256_maddubs_epi16 function.
// Multiplies packed 8-bit unsigned integers from `left` and packed
// signed 8-bit integers from `right` into 16-bit signed integers. Then,
// the saturating sum of the products with indices `2*i` and `2*i+1`
// produces the output at index `i`.
"pmadd.ub.sw" => {
let [left, right] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let (left, left_len) = this.operand_to_simd(left)?;
let (right, right_len) = this.operand_to_simd(right)?;
let (dest, dest_len) = this.mplace_to_simd(dest)?;
assert_eq!(left_len, right_len);
assert_eq!(dest_len.checked_mul(2).unwrap(), left_len);
for i in 0..dest_len {
let j1 = i.checked_mul(2).unwrap();
let left1 = this.read_scalar(&this.project_index(&left, j1)?)?.to_u8()?;
let right1 = this.read_scalar(&this.project_index(&right, j1)?)?.to_i8()?;
let j2 = j1.checked_add(1).unwrap();
let left2 = this.read_scalar(&this.project_index(&left, j2)?)?.to_u8()?;
let right2 = this.read_scalar(&this.project_index(&right, j2)?)?.to_i8()?;
let dest = this.project_index(&dest, i)?;
// Multiplication of a u8 and an i8 into an i16 cannot overflow.
let mul1 = i16::from(left1).checked_mul(right1.into()).unwrap();
let mul2 = i16::from(left2).checked_mul(right2.into()).unwrap();
let res = mul1.saturating_add(mul2);
this.write_scalar(Scalar::from_i16(res), &dest)?;
}
}
// Used to implement the _mm_maskload_epi32, _mm_maskload_epi64,
// _mm256_maskload_epi32 and _mm256_maskload_epi64 functions.
// For the element `i`, if the high bit of the `i`-th element of `mask`
// is one, it is loaded from `ptr.wrapping_add(i)`, otherwise zero is
// loaded.
"maskload.d" | "maskload.q" | "maskload.d.256" | "maskload.q.256" => {
let [ptr, mask] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
mask_load(this, ptr, mask, dest)?;
}
// Used to implement the _mm_maskstore_epi32, _mm_maskstore_epi64,
// _mm256_maskstore_epi32 and _mm256_maskstore_epi64 functions.
// For the element `i`, if the high bit of the element `i`-th of `mask`
// is one, it is stored into `ptr.wapping_add(i)`.
// Unlike SSE2's _mm_maskmoveu_si128, these are not non-temporal stores.
"maskstore.d" | "maskstore.q" | "maskstore.d.256" | "maskstore.q.256" => {
let [ptr, mask, value] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
mask_store(this, ptr, mask, value)?;
}
// Used to implement the _mm256_mpsadbw_epu8 function.
// Compute the sum of absolute differences of quadruplets of unsigned
// 8-bit integers in `left` and `right`, and store the 16-bit results
// in `right`. Quadruplets are selected from `left` and `right` with
// offsets specified in `imm`.
// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm256_mpsadbw_epu8
"mpsadbw" => {
let [left, right, imm] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
mpsadbw(this, left, right, imm, dest)?;
}
// Used to implement the _mm256_mulhrs_epi16 function.
// Multiplies packed 16-bit signed integer values, truncates the 32-bit
// product to the 18 most significant bits by right-shifting, and then
// divides the 18-bit value by 2 (rounding to nearest) by first adding
// 1 and then taking the bits `1..=16`.
// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm256_mulhrs_epi16
"pmul.hr.sw" => {
let [left, right] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
pmulhrsw(this, left, right, dest)?;
}
// Used to implement the _mm256_packs_epi16 function.
// Converts two 16-bit integer vectors to a single 8-bit integer
// vector with signed saturation.
"packsswb" => {
let [left, right] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
packsswb(this, left, right, dest)?;
}
// Used to implement the _mm256_packs_epi32 function.
// Converts two 32-bit integer vectors to a single 16-bit integer
// vector with signed saturation.
"packssdw" => {
let [left, right] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
packssdw(this, left, right, dest)?;
}
// Used to implement the _mm256_packus_epi16 function.
// Converts two 16-bit signed integer vectors to a single 8-bit
// unsigned integer vector with saturation.
"packuswb" => {
let [left, right] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
packuswb(this, left, right, dest)?;
}
// Used to implement the _mm256_packus_epi32 function.
// Concatenates two 32-bit signed integer vectors and converts
// the result to a 16-bit unsigned integer vector with saturation.
"packusdw" => {
let [left, right] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
packusdw(this, left, right, dest)?;
}
// Used to implement the _mm256_permutevar8x32_epi32 and
// _mm256_permutevar8x32_ps function.
// Shuffles `left` using the three low bits of each element of `right`
// as indices.
"permd" | "permps" => {
let [left, right] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let (left, left_len) = this.operand_to_simd(left)?;
let (right, right_len) = this.operand_to_simd(right)?;
let (dest, dest_len) = this.mplace_to_simd(dest)?;
assert_eq!(dest_len, left_len);
assert_eq!(dest_len, right_len);
for i in 0..dest_len {
let dest = this.project_index(&dest, i)?;
let right = this.read_scalar(&this.project_index(&right, i)?)?.to_u32()?;
let left = this.project_index(&left, (right & 0b111).into())?;
this.copy_op(&left, &dest)?;
}
}
// Used to implement the _mm256_permute2x128_si256 function.
// Shuffles 128-bit blocks of `a` and `b` using `imm` as pattern.
"vperm2i128" => {
let [left, right, imm] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
assert_eq!(left.layout.size.bits(), 256);
assert_eq!(right.layout.size.bits(), 256);
assert_eq!(dest.layout.size.bits(), 256);
// Transmute to `[i128; 2]`
let array_layout =
this.layout_of(Ty::new_array(this.tcx.tcx, this.tcx.types.i128, 2))?;
let left = left.transmute(array_layout, this)?;
let right = right.transmute(array_layout, this)?;
let dest = dest.transmute(array_layout, this)?;
let imm = this.read_scalar(imm)?.to_u8()?;
for i in 0..2 {
let dest = this.project_index(&dest, i)?;
let src = match (imm >> i.checked_mul(4).unwrap()) & 0b11 {
0 => this.project_index(&left, 0)?,
1 => this.project_index(&left, 1)?,
2 => this.project_index(&right, 0)?,
3 => this.project_index(&right, 1)?,
_ => unreachable!(),
};
this.copy_op(&src, &dest)?;
}
}
// Used to implement the _mm256_sad_epu8 function.
// Compute the absolute differences of packed unsigned 8-bit integers
// in `left` and `right`, then horizontally sum each consecutive 8
// differences to produce four unsigned 16-bit integers, and pack
// these unsigned 16-bit integers in the low 16 bits of 64-bit elements
// in `dest`.
// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm256_sad_epu8
"psad.bw" => {
let [left, right] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let (left, left_len) = this.operand_to_simd(left)?;
let (right, right_len) = this.operand_to_simd(right)?;
let (dest, dest_len) = this.mplace_to_simd(dest)?;
assert_eq!(left_len, right_len);
assert_eq!(left_len, dest_len.checked_mul(8).unwrap());
for i in 0..dest_len {
let dest = this.project_index(&dest, i)?;
let mut acc: u16 = 0;
for j in 0..8 {
let src_index = i.checked_mul(8).unwrap().checked_add(j).unwrap();
let left = this.project_index(&left, src_index)?;
let left = this.read_scalar(&left)?.to_u8()?;
let right = this.project_index(&right, src_index)?;
let right = this.read_scalar(&right)?.to_u8()?;
acc = acc.checked_add(left.abs_diff(right).into()).unwrap();
}
this.write_scalar(Scalar::from_u64(acc.into()), &dest)?;
}
}
// Used to implement the _mm256_shuffle_epi8 intrinsic.
// Shuffles bytes from `left` using `right` as pattern.
// Each 128-bit block is shuffled independently.
"pshuf.b" => {
let [left, right] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let (left, left_len) = this.operand_to_simd(left)?;
let (right, right_len) = this.operand_to_simd(right)?;
let (dest, dest_len) = this.mplace_to_simd(dest)?;
assert_eq!(dest_len, left_len);
assert_eq!(dest_len, right_len);
for i in 0..dest_len {
let right = this.read_scalar(&this.project_index(&right, i)?)?.to_u8()?;
let dest = this.project_index(&dest, i)?;
let res = if right & 0x80 == 0 {
// Shuffle each 128-bit (16-byte) block independently.
let j = u64::from(right % 16).checked_add(i & !15).unwrap();
this.read_scalar(&this.project_index(&left, j)?)?
} else {
// If the highest bit in `right` is 1, write zero.
Scalar::from_u8(0)
};
this.write_scalar(res, &dest)?;
}
}
// Used to implement the _mm256_sign_epi{8,16,32} functions.
// Negates elements from `left` when the corresponding element in
// `right` is negative. If an element from `right` is zero, zero
// is writen to the corresponding output element.
// Basically, we multiply `left` with `right.signum()`.
"psign.b" | "psign.w" | "psign.d" => {
let [left, right] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
psign(this, left, right, dest)?;
}
// Used to implement the _mm256_{sll,srl,sra}_epi{16,32,64} functions
// (except _mm256_sra_epi64, which is not available in AVX2).
// Shifts N-bit packed integers in left by the amount in right.
// `right` is as 128-bit vector. but it is interpreted as a single
// 64-bit integer (remaining bits are ignored).
// For logic shifts, when right is larger than N - 1, zero is produced.
// For arithmetic shifts, when right is larger than N - 1, the sign bit
// is copied to remaining bits.
"psll.w" | "psrl.w" | "psra.w" | "psll.d" | "psrl.d" | "psra.d" | "psll.q"
| "psrl.q" => {
let [left, right] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let which = match unprefixed_name {
"psll.w" | "psll.d" | "psll.q" => ShiftOp::Left,
"psrl.w" | "psrl.d" | "psrl.q" => ShiftOp::RightLogic,
"psra.w" | "psra.d" => ShiftOp::RightArith,
_ => unreachable!(),
};
shift_simd_by_scalar(this, left, right, which, dest)?;
}
// Used to implement the _mm{,256}_{sllv,srlv,srav}_epi{32,64} functions
// (except _mm{,256}_srav_epi64, which are not available in AVX2).
"psllv.d" | "psllv.d.256" | "psllv.q" | "psllv.q.256" | "psrlv.d" | "psrlv.d.256"
| "psrlv.q" | "psrlv.q.256" | "psrav.d" | "psrav.d.256" => {
let [left, right] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let which = match unprefixed_name {
"psllv.d" | "psllv.d.256" | "psllv.q" | "psllv.q.256" => ShiftOp::Left,
"psrlv.d" | "psrlv.d.256" | "psrlv.q" | "psrlv.q.256" => ShiftOp::RightLogic,
"psrav.d" | "psrav.d.256" => ShiftOp::RightArith,
_ => unreachable!(),
};
shift_simd_by_simd(this, left, right, which, dest)?;
}
_ => return Ok(EmulateForeignItemResult::NotSupported),
}
Ok(EmulateForeignItemResult::NeedsJumping)
}
}

View file

@ -14,6 +14,7 @@ use shims::foreign_items::EmulateForeignItemResult;
mod aesni;
mod avx;
mod avx2;
mod sse;
mod sse2;
mod sse3;
@ -136,6 +137,11 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
this, link_name, abi, args, dest,
);
}
name if name.starts_with("avx2.") => {
return avx2::EvalContextExt::emulate_x86_avx2_intrinsic(
this, link_name, abi, args, dest,
);
}
_ => return Ok(EmulateForeignItemResult::NotSupported),
}
@ -482,7 +488,7 @@ enum ShiftOp {
///
/// For logic shifts, when right is larger than BITS - 1, zero is produced.
/// For arithmetic right-shifts, when right is larger than BITS - 1, the sign
/// bit is copied to remaining bits.
/// bit is copied to all bits.
fn shift_simd_by_scalar<'tcx>(
this: &mut crate::MiriInterpCx<'_, 'tcx>,
left: &OpTy<'tcx, Provenance>,
@ -534,6 +540,61 @@ fn shift_simd_by_scalar<'tcx>(
Ok(())
}
/// Shifts each element of `left` by the corresponding element of `right`.
///
/// For logic shifts, when right is larger than BITS - 1, zero is produced.
/// For arithmetic right-shifts, when right is larger than BITS - 1, the sign
/// bit is copied to all bits.
fn shift_simd_by_simd<'tcx>(
this: &mut crate::MiriInterpCx<'_, 'tcx>,
left: &OpTy<'tcx, Provenance>,
right: &OpTy<'tcx, Provenance>,
which: ShiftOp,
dest: &MPlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx, ()> {
let (left, left_len) = this.operand_to_simd(left)?;
let (right, right_len) = this.operand_to_simd(right)?;
let (dest, dest_len) = this.mplace_to_simd(dest)?;
assert_eq!(dest_len, left_len);
assert_eq!(dest_len, right_len);
for i in 0..dest_len {
let left = this.read_scalar(&this.project_index(&left, i)?)?;
let right = this.read_scalar(&this.project_index(&right, i)?)?;
let dest = this.project_index(&dest, i)?;
// It is ok to saturate the value to u32::MAX because any value
// above BITS - 1 will produce the same result.
let shift = u32::try_from(right.to_uint(dest.layout.size)?).unwrap_or(u32::MAX);
let res = match which {
ShiftOp::Left => {
let left = left.to_uint(dest.layout.size)?;
let res = left.checked_shl(shift).unwrap_or(0);
// `truncate` is needed as left-shift can make the absolute value larger.
Scalar::from_uint(dest.layout.size.truncate(res), dest.layout.size)
}
ShiftOp::RightLogic => {
let left = left.to_uint(dest.layout.size)?;
let res = left.checked_shr(shift).unwrap_or(0);
// No `truncate` needed as right-shift can only make the absolute value smaller.
Scalar::from_uint(res, dest.layout.size)
}
ShiftOp::RightArith => {
let left = left.to_int(dest.layout.size)?;
// On overflow, copy the sign bit to the remaining bits
let res = left.checked_shr(shift).unwrap_or(left >> 127);
// No `truncate` needed as right-shift can only make the absolute value smaller.
Scalar::from_int(res, dest.layout.size)
}
};
this.write_scalar(res, &dest)?;
}
Ok(())
}
/// Takes a 128-bit vector, transmutes it to `[u64; 2]` and extracts
/// the first value.
fn extract_first_u64<'tcx>(
@ -650,7 +711,7 @@ fn convert_float_to_int<'tcx>(
let dest = this.project_index(&dest, i)?;
let res = this.float_to_int_checked(&op, dest.layout, rnd)?.unwrap_or_else(|| {
// Fallback to minimum acording to SSE/AVX semantics.
// Fallback to minimum according to SSE/AVX semantics.
ImmTy::from_int(dest.layout.size.signed_int_min(), dest.layout)
});
this.write_immediate(*res, &dest)?;
@ -664,6 +725,33 @@ fn convert_float_to_int<'tcx>(
Ok(())
}
/// Calculates absolute value of integers in `op` and stores the result in `dest`.
///
/// In case of overflow (when the operand is the minimum value), the operation
/// will wrap around.
fn int_abs<'tcx>(
this: &mut crate::MiriInterpCx<'_, 'tcx>,
op: &OpTy<'tcx, Provenance>,
dest: &MPlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx, ()> {
let (op, op_len) = this.operand_to_simd(op)?;
let (dest, dest_len) = this.mplace_to_simd(dest)?;
assert_eq!(op_len, dest_len);
for i in 0..dest_len {
let op = this.read_scalar(&this.project_index(&op, i)?)?;
let dest = this.project_index(&dest, i)?;
// Converting to a host "i128" works since the input is always signed.
let res = op.to_int(dest.layout.size)?.unsigned_abs();
this.write_scalar(Scalar::from_uint(res, dest.layout.size), &dest)?;
}
Ok(())
}
/// Splits `op` (which must be a SIMD vector) into 128-bit chuncks.
///
/// Returns a tuple where:
@ -874,3 +962,316 @@ fn test_high_bits_masked<'tcx>(
Ok((direct, negated))
}
/// Conditionally loads from `ptr` according the high bit of each
/// element of `mask`. `ptr` does not need to be aligned.
fn mask_load<'tcx>(
this: &mut crate::MiriInterpCx<'_, 'tcx>,
ptr: &OpTy<'tcx, Provenance>,
mask: &OpTy<'tcx, Provenance>,
dest: &MPlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx, ()> {
let (mask, mask_len) = this.operand_to_simd(mask)?;
let (dest, dest_len) = this.mplace_to_simd(dest)?;
assert_eq!(dest_len, mask_len);
let mask_item_size = mask.layout.field(this, 0).size;
let high_bit_offset = mask_item_size.bits().checked_sub(1).unwrap();
let ptr = this.read_pointer(ptr)?;
for i in 0..dest_len {
let mask = this.project_index(&mask, i)?;
let dest = this.project_index(&dest, i)?;
if this.read_scalar(&mask)?.to_uint(mask_item_size)? >> high_bit_offset != 0 {
let ptr = ptr.wrapping_offset(dest.layout.size * i, &this.tcx);
// Unaligned copy, which is what we want.
this.mem_copy(ptr, dest.ptr(), dest.layout.size, /*nonoverlapping*/ true)?;
} else {
this.write_scalar(Scalar::from_int(0, dest.layout.size), &dest)?;
}
}
Ok(())
}
/// Conditionally stores into `ptr` according the high bit of each
/// element of `mask`. `ptr` does not need to be aligned.
fn mask_store<'tcx>(
this: &mut crate::MiriInterpCx<'_, 'tcx>,
ptr: &OpTy<'tcx, Provenance>,
mask: &OpTy<'tcx, Provenance>,
value: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, ()> {
let (mask, mask_len) = this.operand_to_simd(mask)?;
let (value, value_len) = this.operand_to_simd(value)?;
assert_eq!(value_len, mask_len);
let mask_item_size = mask.layout.field(this, 0).size;
let high_bit_offset = mask_item_size.bits().checked_sub(1).unwrap();
let ptr = this.read_pointer(ptr)?;
for i in 0..value_len {
let mask = this.project_index(&mask, i)?;
let value = this.project_index(&value, i)?;
if this.read_scalar(&mask)?.to_uint(mask_item_size)? >> high_bit_offset != 0 {
let ptr = ptr.wrapping_offset(value.layout.size * i, &this.tcx);
// Unaligned copy, which is what we want.
this.mem_copy(value.ptr(), ptr, value.layout.size, /*nonoverlapping*/ true)?;
}
}
Ok(())
}
/// Compute the sum of absolute differences of quadruplets of unsigned
/// 8-bit integers in `left` and `right`, and store the 16-bit results
/// in `right`. Quadruplets are selected from `left` and `right` with
/// offsets specified in `imm`.
///
/// <https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_maddubs_epi16>
/// <https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm256_mpsadbw_epu8>
///
/// Each 128-bit chunk is treated independently (i.e., the value for
/// the is i-th 128-bit chunk of `dest` is calculated with the i-th
/// 128-bit chunks of `left` and `right`).
fn mpsadbw<'tcx>(
this: &mut crate::MiriInterpCx<'_, 'tcx>,
left: &OpTy<'tcx, Provenance>,
right: &OpTy<'tcx, Provenance>,
imm: &OpTy<'tcx, Provenance>,
dest: &MPlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx, ()> {
assert_eq!(left.layout, right.layout);
assert_eq!(left.layout.size, dest.layout.size);
let (num_chunks, op_items_per_chunk, left) = split_simd_to_128bit_chunks(this, left)?;
let (_, _, right) = split_simd_to_128bit_chunks(this, right)?;
let (_, dest_items_per_chunk, dest) = split_simd_to_128bit_chunks(this, dest)?;
assert_eq!(op_items_per_chunk, dest_items_per_chunk.checked_mul(2).unwrap());
let imm = this.read_scalar(imm)?.to_uint(imm.layout.size)?;
// Bit 2 of `imm` specifies the offset for indices of `left`.
// The offset is 0 when the bit is 0 or 4 when the bit is 1.
let left_offset = u64::try_from((imm >> 2) & 1).unwrap().checked_mul(4).unwrap();
// Bits 0..=1 of `imm` specify the offset for indices of
// `right` in blocks of 4 elements.
let right_offset = u64::try_from(imm & 0b11).unwrap().checked_mul(4).unwrap();
for i in 0..num_chunks {
let left = this.project_index(&left, i)?;
let right = this.project_index(&right, i)?;
let dest = this.project_index(&dest, i)?;
for j in 0..dest_items_per_chunk {
let left_offset = left_offset.checked_add(j).unwrap();
let mut res: u16 = 0;
for k in 0..4 {
let left = this
.read_scalar(&this.project_index(&left, left_offset.checked_add(k).unwrap())?)?
.to_u8()?;
let right = this
.read_scalar(
&this.project_index(&right, right_offset.checked_add(k).unwrap())?,
)?
.to_u8()?;
res = res.checked_add(left.abs_diff(right).into()).unwrap();
}
this.write_scalar(Scalar::from_u16(res), &this.project_index(&dest, j)?)?;
}
}
Ok(())
}
/// Multiplies packed 16-bit signed integer values, truncates the 32-bit
/// product to the 18 most significant bits by right-shifting, and then
/// divides the 18-bit value by 2 (rounding to nearest) by first adding
/// 1 and then taking the bits `1..=16`.
///
/// <https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_mulhrs_epi16>
/// <https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm256_mulhrs_epi16>
fn pmulhrsw<'tcx>(
this: &mut crate::MiriInterpCx<'_, 'tcx>,
left: &OpTy<'tcx, Provenance>,
right: &OpTy<'tcx, Provenance>,
dest: &MPlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx, ()> {
let (left, left_len) = this.operand_to_simd(left)?;
let (right, right_len) = this.operand_to_simd(right)?;
let (dest, dest_len) = this.mplace_to_simd(dest)?;
assert_eq!(dest_len, left_len);
assert_eq!(dest_len, right_len);
for i in 0..dest_len {
let left = this.read_scalar(&this.project_index(&left, i)?)?.to_i16()?;
let right = this.read_scalar(&this.project_index(&right, i)?)?.to_i16()?;
let dest = this.project_index(&dest, i)?;
let res =
(i32::from(left).checked_mul(right.into()).unwrap() >> 14).checked_add(1).unwrap() >> 1;
// The result of this operation can overflow a signed 16-bit integer.
// When `left` and `right` are -0x8000, the result is 0x8000.
#[allow(clippy::cast_possible_truncation)]
let res = res as i16;
this.write_scalar(Scalar::from_i16(res), &dest)?;
}
Ok(())
}
fn pack_generic<'tcx>(
this: &mut crate::MiriInterpCx<'_, 'tcx>,
left: &OpTy<'tcx, Provenance>,
right: &OpTy<'tcx, Provenance>,
dest: &MPlaceTy<'tcx, Provenance>,
f: impl Fn(Scalar<Provenance>) -> InterpResult<'tcx, Scalar<Provenance>>,
) -> InterpResult<'tcx, ()> {
assert_eq!(left.layout, right.layout);
assert_eq!(left.layout.size, dest.layout.size);
let (num_chunks, op_items_per_chunk, left) = split_simd_to_128bit_chunks(this, left)?;
let (_, _, right) = split_simd_to_128bit_chunks(this, right)?;
let (_, dest_items_per_chunk, dest) = split_simd_to_128bit_chunks(this, dest)?;
assert_eq!(dest_items_per_chunk, op_items_per_chunk.checked_mul(2).unwrap());
for i in 0..num_chunks {
let left = this.project_index(&left, i)?;
let right = this.project_index(&right, i)?;
let dest = this.project_index(&dest, i)?;
for j in 0..op_items_per_chunk {
let left = this.read_scalar(&this.project_index(&left, j)?)?;
let right = this.read_scalar(&this.project_index(&right, j)?)?;
let left_dest = this.project_index(&dest, j)?;
let right_dest =
this.project_index(&dest, j.checked_add(op_items_per_chunk).unwrap())?;
let left_res = f(left)?;
let right_res = f(right)?;
this.write_scalar(left_res, &left_dest)?;
this.write_scalar(right_res, &right_dest)?;
}
}
Ok(())
}
/// Converts two 16-bit integer vectors to a single 8-bit integer
/// vector with signed saturation.
///
/// Each 128-bit chunk is treated independently (i.e., the value for
/// the is i-th 128-bit chunk of `dest` is calculated with the i-th
/// 128-bit chunks of `left` and `right`).
fn packsswb<'tcx>(
this: &mut crate::MiriInterpCx<'_, 'tcx>,
left: &OpTy<'tcx, Provenance>,
right: &OpTy<'tcx, Provenance>,
dest: &MPlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx, ()> {
pack_generic(this, left, right, dest, |op| {
let op = op.to_i16()?;
let res = i8::try_from(op).unwrap_or(if op < 0 { i8::MIN } else { i8::MAX });
Ok(Scalar::from_i8(res))
})
}
/// Converts two 16-bit signed integer vectors to a single 8-bit
/// unsigned integer vector with saturation.
///
/// Each 128-bit chunk is treated independently (i.e., the value for
/// the is i-th 128-bit chunk of `dest` is calculated with the i-th
/// 128-bit chunks of `left` and `right`).
fn packuswb<'tcx>(
this: &mut crate::MiriInterpCx<'_, 'tcx>,
left: &OpTy<'tcx, Provenance>,
right: &OpTy<'tcx, Provenance>,
dest: &MPlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx, ()> {
pack_generic(this, left, right, dest, |op| {
let op = op.to_i16()?;
let res = u8::try_from(op).unwrap_or(if op < 0 { 0 } else { u8::MAX });
Ok(Scalar::from_u8(res))
})
}
/// Converts two 32-bit integer vectors to a single 16-bit integer
/// vector with signed saturation.
///
/// Each 128-bit chunk is treated independently (i.e., the value for
/// the is i-th 128-bit chunk of `dest` is calculated with the i-th
/// 128-bit chunks of `left` and `right`).
fn packssdw<'tcx>(
this: &mut crate::MiriInterpCx<'_, 'tcx>,
left: &OpTy<'tcx, Provenance>,
right: &OpTy<'tcx, Provenance>,
dest: &MPlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx, ()> {
pack_generic(this, left, right, dest, |op| {
let op = op.to_i32()?;
let res = i16::try_from(op).unwrap_or(if op < 0 { i16::MIN } else { i16::MAX });
Ok(Scalar::from_i16(res))
})
}
/// Converts two 32-bit integer vectors to a single 16-bit integer
/// vector with unsigned saturation.
///
/// Each 128-bit chunk is treated independently (i.e., the value for
/// the is i-th 128-bit chunk of `dest` is calculated with the i-th
/// 128-bit chunks of `left` and `right`).
fn packusdw<'tcx>(
this: &mut crate::MiriInterpCx<'_, 'tcx>,
left: &OpTy<'tcx, Provenance>,
right: &OpTy<'tcx, Provenance>,
dest: &MPlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx, ()> {
pack_generic(this, left, right, dest, |op| {
let op = op.to_i32()?;
let res = u16::try_from(op).unwrap_or(if op < 0 { 0 } else { u16::MAX });
Ok(Scalar::from_u16(res))
})
}
/// Negates elements from `left` when the corresponding element in
/// `right` is negative. If an element from `right` is zero, zero
/// is writen to the corresponding output element.
/// In other words, multiplies `left` with `right.signum()`.
fn psign<'tcx>(
this: &mut crate::MiriInterpCx<'_, 'tcx>,
left: &OpTy<'tcx, Provenance>,
right: &OpTy<'tcx, Provenance>,
dest: &MPlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx, ()> {
let (left, left_len) = this.operand_to_simd(left)?;
let (right, right_len) = this.operand_to_simd(right)?;
let (dest, dest_len) = this.mplace_to_simd(dest)?;
assert_eq!(dest_len, left_len);
assert_eq!(dest_len, right_len);
for i in 0..dest_len {
let dest = this.project_index(&dest, i)?;
let left = this.read_immediate(&this.project_index(&left, i)?)?;
let right = this.read_scalar(&this.project_index(&right, i)?)?.to_int(dest.layout.size)?;
let res = this.wrapping_binary_op(
mir::BinOp::Mul,
&left,
&ImmTy::from_int(right.signum(), dest.layout),
)?;
this.write_immediate(*res, &dest)?;
}
Ok(())
}

View file

@ -182,7 +182,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
};
let res = this.float_to_int_checked(&op, dest.layout, rnd)?.unwrap_or_else(|| {
// Fallback to minimum acording to SSE semantics.
// Fallback to minimum according to SSE semantics.
ImmTy::from_int(dest.layout.size.signed_int_min(), dest.layout)
});

View file

@ -3,8 +3,8 @@ use rustc_span::Symbol;
use rustc_target::spec::abi::Abi;
use super::{
bin_op_simd_float_all, bin_op_simd_float_first, convert_float_to_int, shift_simd_by_scalar,
FloatBinOp, ShiftOp,
bin_op_simd_float_all, bin_op_simd_float_first, convert_float_to_int, packssdw, packsswb,
packuswb, shift_simd_by_scalar, FloatBinOp, ShiftOp,
};
use crate::*;
use shims::foreign_items::EmulateForeignItemResult;
@ -176,29 +176,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
let [left, right] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let (left, left_len) = this.operand_to_simd(left)?;
let (right, right_len) = this.operand_to_simd(right)?;
let (dest, dest_len) = this.mplace_to_simd(dest)?;
// left and right are i16x8, dest is i8x16
assert_eq!(left_len, 8);
assert_eq!(right_len, 8);
assert_eq!(dest_len, 16);
for i in 0..left_len {
let left = this.read_scalar(&this.project_index(&left, i)?)?.to_i16()?;
let right = this.read_scalar(&this.project_index(&right, i)?)?.to_i16()?;
let left_dest = this.project_index(&dest, i)?;
let right_dest = this.project_index(&dest, i.checked_add(left_len).unwrap())?;
let left_res =
i8::try_from(left).unwrap_or(if left < 0 { i8::MIN } else { i8::MAX });
let right_res =
i8::try_from(right).unwrap_or(if right < 0 { i8::MIN } else { i8::MAX });
this.write_scalar(Scalar::from_i8(left_res), &left_dest)?;
this.write_scalar(Scalar::from_i8(right_res), &right_dest)?;
}
packsswb(this, left, right, dest)?;
}
// Used to implement the _mm_packus_epi16 function.
// Converts two 16-bit signed integer vectors to a single 8-bit
@ -207,28 +185,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
let [left, right] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let (left, left_len) = this.operand_to_simd(left)?;
let (right, right_len) = this.operand_to_simd(right)?;
let (dest, dest_len) = this.mplace_to_simd(dest)?;
// left and right are i16x8, dest is u8x16
assert_eq!(left_len, 8);
assert_eq!(right_len, 8);
assert_eq!(dest_len, 16);
for i in 0..left_len {
let left = this.read_scalar(&this.project_index(&left, i)?)?.to_i16()?;
let right = this.read_scalar(&this.project_index(&right, i)?)?.to_i16()?;
let left_dest = this.project_index(&dest, i)?;
let right_dest = this.project_index(&dest, i.checked_add(left_len).unwrap())?;
let left_res = u8::try_from(left).unwrap_or(if left < 0 { 0 } else { u8::MAX });
let right_res =
u8::try_from(right).unwrap_or(if right < 0 { 0 } else { u8::MAX });
this.write_scalar(Scalar::from_u8(left_res), &left_dest)?;
this.write_scalar(Scalar::from_u8(right_res), &right_dest)?;
}
packuswb(this, left, right, dest)?;
}
// Used to implement the _mm_packs_epi32 function.
// Converts two 32-bit integer vectors to a single 16-bit integer
@ -237,29 +194,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
let [left, right] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let (left, left_len) = this.operand_to_simd(left)?;
let (right, right_len) = this.operand_to_simd(right)?;
let (dest, dest_len) = this.mplace_to_simd(dest)?;
// left and right are i32x4, dest is i16x8
assert_eq!(left_len, 4);
assert_eq!(right_len, 4);
assert_eq!(dest_len, 8);
for i in 0..left_len {
let left = this.read_scalar(&this.project_index(&left, i)?)?.to_i32()?;
let right = this.read_scalar(&this.project_index(&right, i)?)?.to_i32()?;
let left_dest = this.project_index(&dest, i)?;
let right_dest = this.project_index(&dest, i.checked_add(left_len).unwrap())?;
let left_res =
i16::try_from(left).unwrap_or(if left < 0 { i16::MIN } else { i16::MAX });
let right_res =
i16::try_from(right).unwrap_or(if right < 0 { i16::MIN } else { i16::MAX });
this.write_scalar(Scalar::from_i16(left_res), &left_dest)?;
this.write_scalar(Scalar::from_i16(right_res), &right_dest)?;
}
packssdw(this, left, right, dest)?;
}
// Used to implement _mm_min_sd and _mm_max_sd functions.
// Note that the semantics are a bit different from Rust simd_min
@ -420,7 +355,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
};
let res = this.float_to_int_checked(&op, dest.layout, rnd)?.unwrap_or_else(|| {
// Fallback to minimum acording to SSE semantics.
// Fallback to minimum according to SSE semantics.
ImmTy::from_int(dest.layout.size.signed_int_min(), dest.layout)
});
@ -447,7 +382,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
let res0 = this.float_to_float_or_int(&right0, dest0.layout)?;
this.write_immediate(*res0, &dest0)?;
// Copy remianing from `left`
// Copy remaining from `left`
for i in 1..dest_len {
this.copy_op(&this.project_index(&left, i)?, &this.project_index(&dest, i)?)?;
}

View file

@ -1,7 +1,7 @@
use rustc_span::Symbol;
use rustc_target::spec::abi::Abi;
use super::{conditional_dot_product, round_all, round_first, test_bits_masked};
use super::{conditional_dot_product, mpsadbw, packusdw, round_all, round_first, test_bits_masked};
use crate::*;
use shims::foreign_items::EmulateForeignItemResult;
@ -68,27 +68,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
let [left, right] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let (left, left_len) = this.operand_to_simd(left)?;
let (right, right_len) = this.operand_to_simd(right)?;
let (dest, dest_len) = this.mplace_to_simd(dest)?;
assert_eq!(left_len, right_len);
assert_eq!(dest_len, left_len.checked_mul(2).unwrap());
for i in 0..left_len {
let left = this.read_scalar(&this.project_index(&left, i)?)?.to_i32()?;
let right = this.read_scalar(&this.project_index(&right, i)?)?.to_i32()?;
let left_dest = this.project_index(&dest, i)?;
let right_dest = this.project_index(&dest, i.checked_add(left_len).unwrap())?;
let left_res =
u16::try_from(left).unwrap_or(if left < 0 { 0 } else { u16::MAX });
let right_res =
u16::try_from(right).unwrap_or(if right < 0 { 0 } else { u16::MAX });
this.write_scalar(Scalar::from_u16(left_res), &left_dest)?;
this.write_scalar(Scalar::from_u16(right_res), &right_dest)?;
}
packusdw(this, left, right, dest)?;
}
// Used to implement the _mm_dp_ps and _mm_dp_pd functions.
// Conditionally multiplies the packed floating-point elements in
@ -176,40 +156,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
let [left, right, imm] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let (left, left_len) = this.operand_to_simd(left)?;
let (right, right_len) = this.operand_to_simd(right)?;
let (dest, dest_len) = this.mplace_to_simd(dest)?;
assert_eq!(left_len, right_len);
assert_eq!(left_len, dest_len.checked_mul(2).unwrap());
let imm = this.read_scalar(imm)?.to_u8()?;
// Bit 2 of `imm` specifies the offset for indices of `left`.
// The offset is 0 when the bit is 0 or 4 when the bit is 1.
let left_offset = u64::from((imm >> 2) & 1).checked_mul(4).unwrap();
// Bits 0..=1 of `imm` specify the offset for indices of
// `right` in blocks of 4 elements.
let right_offset = u64::from(imm & 0b11).checked_mul(4).unwrap();
for i in 0..dest_len {
let left_offset = left_offset.checked_add(i).unwrap();
let mut res: u16 = 0;
for j in 0..4 {
let left = this
.read_scalar(
&this.project_index(&left, left_offset.checked_add(j).unwrap())?,
)?
.to_u8()?;
let right = this
.read_scalar(
&this
.project_index(&right, right_offset.checked_add(j).unwrap())?,
)?
.to_u8()?;
res = res.checked_add(left.abs_diff(right).into()).unwrap();
}
this.write_scalar(Scalar::from_u16(res), &this.project_index(&dest, i)?)?;
}
mpsadbw(this, left, right, imm, dest)?;
}
// Used to implement the _mm_testz_si128, _mm_testc_si128
// and _mm_testnzc_si128 functions.

View file

@ -2,7 +2,7 @@ use rustc_middle::mir;
use rustc_span::Symbol;
use rustc_target::spec::abi::Abi;
use super::horizontal_bin_op;
use super::{horizontal_bin_op, int_abs, pmulhrsw, psign};
use crate::*;
use shims::foreign_items::EmulateForeignItemResult;
@ -28,20 +28,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
"pabs.b.128" | "pabs.w.128" | "pabs.d.128" => {
let [op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let (op, op_len) = this.operand_to_simd(op)?;
let (dest, dest_len) = this.mplace_to_simd(dest)?;
assert_eq!(op_len, dest_len);
for i in 0..dest_len {
let op = this.read_scalar(&this.project_index(&op, i)?)?;
let dest = this.project_index(&dest, i)?;
// Converting to a host "i128" works since the input is always signed.
let res = op.to_int(dest.layout.size)?.unsigned_abs();
this.write_scalar(Scalar::from_uint(res, dest.layout.size), &dest)?;
}
int_abs(this, op, dest)?;
}
// Used to implement the _mm_shuffle_epi8 intrinsic.
// Shuffles bytes from `left` using `right` as pattern.
@ -136,30 +123,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
let [left, right] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let (left, left_len) = this.operand_to_simd(left)?;
let (right, right_len) = this.operand_to_simd(right)?;
let (dest, dest_len) = this.mplace_to_simd(dest)?;
assert_eq!(dest_len, left_len);
assert_eq!(dest_len, right_len);
for i in 0..dest_len {
let left = this.read_scalar(&this.project_index(&left, i)?)?.to_i16()?;
let right = this.read_scalar(&this.project_index(&right, i)?)?.to_i16()?;
let dest = this.project_index(&dest, i)?;
let res = (i32::from(left).checked_mul(right.into()).unwrap() >> 14)
.checked_add(1)
.unwrap()
>> 1;
// The result of this operation can overflow a signed 16-bit integer.
// When `left` and `right` are -0x8000, the result is 0x8000.
#[allow(clippy::cast_possible_truncation)]
let res = res as i16;
this.write_scalar(Scalar::from_i16(res), &dest)?;
}
pmulhrsw(this, left, right, dest)?;
}
// Used to implement the _mm_sign_epi{8,16,32} functions.
// Negates elements from `left` when the corresponding element in
@ -170,28 +134,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
let [left, right] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let (left, left_len) = this.operand_to_simd(left)?;
let (right, right_len) = this.operand_to_simd(right)?;
let (dest, dest_len) = this.mplace_to_simd(dest)?;
assert_eq!(dest_len, left_len);
assert_eq!(dest_len, right_len);
for i in 0..dest_len {
let dest = this.project_index(&dest, i)?;
let left = this.read_immediate(&this.project_index(&left, i)?)?;
let right = this
.read_scalar(&this.project_index(&right, i)?)?
.to_int(dest.layout.size)?;
let res = this.wrapping_binary_op(
mir::BinOp::Mul,
&left,
&ImmTy::from_int(right.signum(), dest.layout),
)?;
this.write_immediate(*res, &dest)?;
}
psign(this, left, right, dest)?;
}
_ => return Ok(EmulateForeignItemResult::NotSupported),
}

View file

@ -8,7 +8,7 @@ use std::mem;
pub fn safe(x: &i32, y: &mut Cell<i32>) {
//~[stack]^ ERROR: protect
y.set(1);
let _ = *x;
let _load = *x;
}
fn main() {

View file

@ -7,7 +7,8 @@ use std::{
};
fn firstn() -> impl Coroutine<Yield = u64, Return = ()> {
#[coroutine] static move || {
#[coroutine]
static move || {
let mut num = 0;
let num = &mut num;
*num += 0;

View file

@ -10,7 +10,7 @@ fn fill(v: &mut i32) {
}
fn evil() {
let _ = unsafe { &mut *(LEAK as *mut i32) }; //~ ERROR: is a dangling pointer
let _ref = unsafe { &mut *(LEAK as *mut i32) }; //~ ERROR: is a dangling pointer
}
fn main() {

View file

@ -1,8 +1,8 @@
error: Undefined Behavior: out-of-bounds pointer use: $HEX[noalloc] is a dangling pointer (it has no provenance)
--> $DIR/storage_dead_dangling.rs:LL:CC
|
LL | let _ = unsafe { &mut *(LEAK as *mut i32) };
| ^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds pointer use: $HEX[noalloc] is a dangling pointer (it has no provenance)
LL | let _ref = unsafe { &mut *(LEAK as *mut i32) };
| ^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds pointer use: $HEX[noalloc] is a dangling pointer (it has no provenance)
|
= 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,26 @@
//! Regression test for <https://github.com/rust-lang/rust/issues/123583>.
use std::thread;
fn with_thread_local1() {
thread_local! { static X: Box<u8> = Box::new(0); }
X.with(|_x| {})
}
fn with_thread_local2() {
thread_local! { static Y: Box<u8> = Box::new(0); }
Y.with(|_y| {})
}
fn main() {
// Here we have two threads racing on initializing the thread-local and adding it to the global
// dtor list (on targets that have such a list, i.e., targets without target_thread_local).
let t = thread::spawn(with_thread_local1);
with_thread_local1();
t.join().unwrap();
// Here we have one thread running the destructors racing with another thread initializing a
// thread-local. The second thread adds a destructor that could be picked up by the first.
let t = thread::spawn(|| { /* immediately just run destructors */ });
with_thread_local2(); // initialize thread-local
t.join().unwrap();
}

View file

@ -213,6 +213,50 @@ fn test_posix_gettimeofday() {
assert_eq!(is_error, -1);
}
fn test_localtime_r() {
use std::ffi::CStr;
use std::{env, ptr};
// Set timezone to GMT.
let key = "TZ";
env::set_var(key, "GMT");
const TIME_SINCE_EPOCH: libc::time_t = 1712475836;
let custom_time_ptr = &TIME_SINCE_EPOCH;
let mut tm = libc::tm {
tm_sec: 0,
tm_min: 0,
tm_hour: 0,
tm_mday: 0,
tm_mon: 0,
tm_year: 0,
tm_wday: 0,
tm_yday: 0,
tm_isdst: 0,
tm_gmtoff: 0,
tm_zone: std::ptr::null_mut::<libc::c_char>(),
};
let res = unsafe { libc::localtime_r(custom_time_ptr, &mut tm) };
assert_eq!(tm.tm_sec, 56);
assert_eq!(tm.tm_min, 43);
assert_eq!(tm.tm_hour, 7);
assert_eq!(tm.tm_mday, 7);
assert_eq!(tm.tm_mon, 3);
assert_eq!(tm.tm_year, 124);
assert_eq!(tm.tm_wday, 0);
assert_eq!(tm.tm_yday, 97);
assert_eq!(tm.tm_isdst, -1);
assert_eq!(tm.tm_gmtoff, 0);
unsafe { assert_eq!(CStr::from_ptr(tm.tm_zone).to_str().unwrap(), "+00") };
// The returned value is the pointer passed in.
assert!(ptr::eq(res, &mut tm));
//Remove timezone setting.
env::remove_var(key);
}
fn test_isatty() {
// Testing whether our isatty shim returns the right value would require controlling whether
// these streams are actually TTYs, which is hard.
@ -365,6 +409,7 @@ fn main() {
test_posix_realpath_errors();
test_thread_local_errno();
test_localtime_r();
test_isatty();

View file

@ -30,7 +30,7 @@ fn test1() {
// See https://github.com/rust-lang/miri/issues/1866#issuecomment-985770125
{
let m = 0u64;
let _ = &m as *const u64;
let _ptr = &m as *const u64;
}
let iptr = ptr as usize;

View file

@ -4,7 +4,7 @@
// deallocated.
// In Miri we explicitly store previously-assigned AllocIds for each const and ensure
// that we only hand out a finite number of AllocIds per const.
// MIR inlining will put every evaluation of the const we're repeatedly evaluting into the same
// MIR inlining will put every evaluation of the const we're repeatedly evaluating into the same
// stack frame, breaking this test.
//@compile-flags: -Zinline-mir=no
#![feature(strict_provenance)]

View file

@ -43,94 +43,144 @@ fn basic() {
panic!()
}
finish(1, false, #[coroutine] || yield 1);
finish(
1,
false,
#[coroutine]
|| yield 1,
);
finish(3, false, #[coroutine] || {
let mut x = 0;
yield 1;
x += 1;
yield 1;
x += 1;
yield 1;
assert_eq!(x, 2);
});
finish(7 * 8 / 2, false, #[coroutine] || {
for i in 0..8 {
yield i;
}
});
finish(1, false, #[coroutine] || {
if true {
finish(
3,
false,
#[coroutine]
|| {
let mut x = 0;
yield 1;
} else {
}
});
x += 1;
yield 1;
x += 1;
yield 1;
assert_eq!(x, 2);
},
);
finish(1, false, #[coroutine] || {
if false {
} else {
yield 1;
}
});
finish(
7 * 8 / 2,
false,
#[coroutine]
|| {
for i in 0..8 {
yield i;
}
},
);
finish(2, false, #[coroutine] || {
if {
finish(
1,
false,
#[coroutine]
|| {
if true {
yield 1;
} else {
}
},
);
finish(
1,
false,
#[coroutine]
|| {
if false {
} else {
yield 1;
}
},
);
finish(
2,
false,
#[coroutine]
|| {
if {
yield 1;
false
} {
yield 1;
panic!()
}
yield 1;
false
} {
yield 1;
panic!()
}
yield 1;
});
},
);
// also test self-referential coroutines
assert_eq!(
finish(5, true, #[coroutine] static || {
let mut x = 5;
let y = &mut x;
*y = 5;
yield *y;
*y = 10;
x
}),
finish(
5,
true,
#[coroutine]
static || {
let mut x = 5;
let y = &mut x;
*y = 5;
yield *y;
*y = 10;
x
}
),
10
);
assert_eq!(
finish(5, true, #[coroutine] || {
let mut x = Box::new(5);
let y = &mut *x;
*y = 5;
yield *y;
*y = 10;
*x
}),
finish(
5,
true,
#[coroutine]
|| {
let mut x = Box::new(5);
let y = &mut *x;
*y = 5;
yield *y;
*y = 10;
*x
}
),
10
);
let b = true;
finish(1, false, #[coroutine] || {
yield 1;
if b {
return;
}
#[allow(unused)]
let x = never();
#[allow(unreachable_code)]
yield 2;
drop(x);
});
finish(3, false, #[coroutine] || {
yield 1;
#[allow(unreachable_code)]
let _x: (String, !) = (String::new(), {
finish(
1,
false,
#[coroutine]
|| {
yield 1;
if b {
return;
}
#[allow(unused)]
let x = never();
#[allow(unreachable_code)]
yield 2;
return;
});
});
drop(x);
},
);
finish(
3,
false,
#[coroutine]
|| {
yield 1;
#[allow(unreachable_code)]
let _x: (String, !) = (String::new(), {
yield 2;
return;
});
},
);
}
fn smoke_resume_arg() {
@ -172,7 +222,8 @@ fn smoke_resume_arg() {
}
drain(
&mut #[coroutine] |mut b| {
&mut #[coroutine]
|mut b| {
while b != 0 {
b = yield (b + 1);
}
@ -181,21 +232,35 @@ fn smoke_resume_arg() {
vec![(1, Yielded(2)), (-45, Yielded(-44)), (500, Yielded(501)), (0, Complete(-1))],
);
expect_drops(2, || drain(&mut #[coroutine] |a| yield a, vec![(DropMe, Yielded(DropMe))]));
expect_drops(2, || {
drain(
&mut #[coroutine]
|a| yield a,
vec![(DropMe, Yielded(DropMe))],
)
});
expect_drops(6, || {
drain(
&mut #[coroutine] |a| yield yield a,
&mut #[coroutine]
|a| yield yield a,
vec![(DropMe, Yielded(DropMe)), (DropMe, Yielded(DropMe)), (DropMe, Complete(DropMe))],
)
});
#[allow(unreachable_code)]
expect_drops(2, || drain(&mut #[coroutine] |a| yield return a, vec![(DropMe, Complete(DropMe))]));
expect_drops(2, || {
drain(
&mut #[coroutine]
|a| yield return a,
vec![(DropMe, Complete(DropMe))],
)
});
expect_drops(2, || {
drain(
&mut #[coroutine] |a: DropMe| {
&mut #[coroutine]
|a: DropMe| {
if false { yield () } else { a }
},
vec![(DropMe, Complete(DropMe))],
@ -205,7 +270,8 @@ fn smoke_resume_arg() {
expect_drops(4, || {
drain(
#[allow(unused_assignments, unused_variables)]
&mut #[coroutine] |mut a: DropMe| {
&mut #[coroutine]
|mut a: DropMe| {
a = yield;
a = yield;
a = yield;
@ -228,7 +294,8 @@ fn uninit_fields() {
}
fn run<T>(x: bool, y: bool) {
let mut c = #[coroutine] || {
let mut c = #[coroutine]
|| {
if x {
let _a: T;
if y {

View file

@ -69,7 +69,7 @@ fn basic() {
}
let baz: &dyn Baz = &1;
let _: &dyn fmt::Debug = baz;
let _up: &dyn fmt::Debug = baz;
assert_eq!(*baz, 1);
assert_eq!(baz.a(), 100);
assert_eq!(baz.b(), 200);
@ -79,7 +79,7 @@ fn basic() {
assert_eq!(baz.w(), 21);
let bar: &dyn Bar = baz;
let _: &dyn fmt::Debug = bar;
let _up: &dyn fmt::Debug = bar;
assert_eq!(*bar, 1);
assert_eq!(bar.a(), 100);
assert_eq!(bar.b(), 200);
@ -88,14 +88,14 @@ fn basic() {
assert_eq!(bar.w(), 21);
let foo: &dyn Foo = baz;
let _: &dyn fmt::Debug = foo;
let _up: &dyn fmt::Debug = foo;
assert_eq!(*foo, 1);
assert_eq!(foo.a(), 100);
assert_eq!(foo.z(), 11);
assert_eq!(foo.y(), 12);
let foo: &dyn Foo = bar;
let _: &dyn fmt::Debug = foo;
let _up: &dyn fmt::Debug = foo;
assert_eq!(*foo, 1);
assert_eq!(foo.a(), 100);
assert_eq!(foo.z(), 11);
@ -168,7 +168,7 @@ fn diamond() {
}
let baz: &dyn Baz = &1;
let _: &dyn fmt::Debug = baz;
let _up: &dyn fmt::Debug = baz;
assert_eq!(*baz, 1);
assert_eq!(baz.a(), 100);
assert_eq!(baz.b(), 200);
@ -180,7 +180,7 @@ fn diamond() {
assert_eq!(baz.v(), 31);
let bar1: &dyn Bar1 = baz;
let _: &dyn fmt::Debug = bar1;
let _up: &dyn fmt::Debug = bar1;
assert_eq!(*bar1, 1);
assert_eq!(bar1.a(), 100);
assert_eq!(bar1.b(), 200);
@ -189,7 +189,7 @@ fn diamond() {
assert_eq!(bar1.w(), 21);
let bar2: &dyn Bar2 = baz;
let _: &dyn fmt::Debug = bar2;
let _up: &dyn fmt::Debug = bar2;
assert_eq!(*bar2, 1);
assert_eq!(bar2.a(), 100);
assert_eq!(bar2.c(), 300);
@ -198,17 +198,17 @@ fn diamond() {
assert_eq!(bar2.v(), 31);
let foo: &dyn Foo = baz;
let _: &dyn fmt::Debug = foo;
let _up: &dyn fmt::Debug = foo;
assert_eq!(*foo, 1);
assert_eq!(foo.a(), 100);
let foo: &dyn Foo = bar1;
let _: &dyn fmt::Debug = foo;
let _up: &dyn fmt::Debug = foo;
assert_eq!(*foo, 1);
assert_eq!(foo.a(), 100);
let foo: &dyn Foo = bar2;
let _: &dyn fmt::Debug = foo;
let _up: &dyn fmt::Debug = foo;
assert_eq!(*foo, 1);
assert_eq!(foo.a(), 100);
}

File diff suppressed because it is too large Load diff

View file

@ -9,7 +9,7 @@ use std::alloc::System;
/// `ptr` must be valid for writes of `len` bytes
unsafe fn volatile_write_zeroize_mem(ptr: *mut u8, len: usize) {
for i in 0..len {
// ptr as usize + i can't overlow because `ptr` is valid for writes of `len`
// ptr as usize + i can't overflow because `ptr` is valid for writes of `len`
let ptr_new: *mut u8 = ((ptr as usize) + i) as *mut u8;
// SAFETY: `ptr` is valid for writes of `len` bytes, so `ptr_new` is valid for a
// byte write

View file

@ -1,9 +1,9 @@
//@ignore-target-windows: home_dir is not supported on Windows
//@compile-flags: -Zmiri-disable-isolation
use std::env;
fn main() {
env::remove_var("HOME"); // make sure we enter the interesting codepath
env::remove_var("USERPROFILE"); // Windows also looks as this env var
#[allow(deprecated)]
env::home_dir().unwrap();
}

View file

@ -0,0 +1,7 @@
// Test a value set on the host (MIRI_ENV_VAR_TEST) and one that is not.
//@compile-flags: -Zmiri-env-set=MIRI_ENV_VAR_TEST=test_value_1 -Zmiri-env-set=TEST_VAR_2=test_value_2
fn main() {
assert_eq!(std::env::var("MIRI_ENV_VAR_TEST"), Ok("test_value_1".to_owned()));
assert_eq!(std::env::var("TEST_VAR_2"), Ok("test_value_2".to_owned()));
}

View file

@ -8,7 +8,8 @@ use std::{
};
fn firstn() -> impl Coroutine<Yield = u64, Return = ()> {
#[coroutine] static move || {
#[coroutine]
static move || {
let mut num = 0;
let num = &mut num;