Rollup merge of #143478 - RalfJung:miri-sync, r=RalfJung
Miri subtree update r? `@ghost`
This commit is contained in:
commit
7ad67e6b54
54 changed files with 1474 additions and 480 deletions
|
|
@ -2394,6 +2394,7 @@ dependencies = [
|
|||
"regex",
|
||||
"rustc_version",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smallvec",
|
||||
"tempfile",
|
||||
"tikv-jemalloc-sys",
|
||||
|
|
|
|||
79
src/tools/miri/.github/workflows/ci.yml
vendored
79
src/tools/miri/.github/workflows/ci.yml
vendored
|
|
@ -13,35 +13,82 @@ defaults:
|
|||
shell: bash
|
||||
|
||||
jobs:
|
||||
build:
|
||||
test:
|
||||
name: test (${{ matrix.host_target }})
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
host_target: x86_64-unknown-linux-gnu
|
||||
- os: macos-14
|
||||
host_target: aarch64-apple-darwin
|
||||
- os: windows-latest
|
||||
host_target: i686-pc-windows-msvc
|
||||
- host_target: x86_64-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
- host_target: i686-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
multiarch: i386
|
||||
gcc_cross: i686-linux-gnu
|
||||
- host_target: aarch64-unknown-linux-gnu
|
||||
os: ubuntu-24.04-arm
|
||||
- host_target: armv7-unknown-linux-gnueabihf
|
||||
os: ubuntu-24.04-arm
|
||||
multiarch: armhf
|
||||
gcc_cross: arm-linux-gnueabihf
|
||||
- host_target: riscv64gc-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
multiarch: riscv64
|
||||
gcc_cross: riscv64-linux-gnu
|
||||
qemu: true
|
||||
- host_target: s390x-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
multiarch: s390x
|
||||
gcc_cross: s390x-linux-gnu
|
||||
qemu: true
|
||||
- host_target: aarch64-apple-darwin
|
||||
os: macos-latest
|
||||
- host_target: i686-pc-windows-msvc
|
||||
os: windows-latest
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
HOST_TARGET: ${{ matrix.host_target }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: install qemu
|
||||
if: ${{ matrix.qemu }}
|
||||
run: sudo apt install qemu-user qemu-user-binfmt
|
||||
- name: install multiarch
|
||||
if: ${{ matrix.multiarch != '' }}
|
||||
run: |
|
||||
# s390x, ppc64el need Ubuntu Ports to be in the mirror list
|
||||
sudo bash -c "echo 'https://ports.ubuntu.com/ priority:4' >> /etc/apt/apt-mirrors.txt"
|
||||
# Add architecture
|
||||
sudo dpkg --add-architecture ${{ matrix.multiarch }}
|
||||
sudo apt update
|
||||
# Install needed packages
|
||||
sudo apt install $(echo "libatomic1: zlib1g-dev:" | sed 's/:/:${{ matrix.multiarch }}/g')
|
||||
- uses: ./.github/workflows/setup
|
||||
with:
|
||||
toolchain_flags: "--host ${{ matrix.host_target }}"
|
||||
|
||||
# The `style` job only runs on Linux; this makes sure the Windows-host-specific
|
||||
# code is also covered by clippy.
|
||||
- name: Check clippy
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
run: ./miri clippy -- -D warnings
|
||||
# We set up the cross-compiler *after* the basic setup as setting CC would otherwise
|
||||
# cause confusion.
|
||||
- name: install gcc-cross
|
||||
if: ${{ matrix.gcc_cross != '' }}
|
||||
run: |
|
||||
sudo apt install gcc-${{ matrix.gcc_cross }}
|
||||
echo "Setting environment variables:"
|
||||
echo "CC_${{ matrix.host_target }}=${{ matrix.gcc_cross }}-gcc" | tee -a $GITHUB_ENV
|
||||
TARGET_UPPERCASE=$(echo ${{ matrix.host_target }} | tr '[:lower:]-' '[:upper:]_')
|
||||
echo "CARGO_TARGET_${TARGET_UPPERCASE}_LINKER=${{ matrix.gcc_cross }}-gcc" | tee -a $GITHUB_ENV
|
||||
|
||||
- name: Test Miri
|
||||
# The main test job! We don't run this in qemu as that is quite slow,
|
||||
# so those targets only get the clippy check below.
|
||||
- name: test Miri
|
||||
if: ${{ !matrix.qemu }}
|
||||
run: ./ci/ci.sh
|
||||
|
||||
# The `style` job only runs on Linux; this makes sure the host-specific
|
||||
# code is also covered by clippy.
|
||||
- name: clippy
|
||||
run: ./miri clippy -- -D warnings
|
||||
|
||||
style:
|
||||
name: style checks
|
||||
runs-on: ubuntu-latest
|
||||
|
|
@ -51,8 +98,6 @@ jobs:
|
|||
|
||||
- name: rustfmt
|
||||
run: ./miri fmt --check
|
||||
- name: clippy
|
||||
run: ./miri clippy -- -D warnings
|
||||
- name: clippy (no features)
|
||||
run: ./miri clippy --no-default-features -- -D warnings
|
||||
- name: clippy (all features)
|
||||
|
|
@ -73,7 +118,7 @@ jobs:
|
|||
# ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB!
|
||||
# And they should be added below in `cron-fail-notify` as well.
|
||||
conclusion:
|
||||
needs: [build, style, coverage]
|
||||
needs: [test, style, coverage]
|
||||
# We need to ensure this job does *not* get skipped if its dependencies fail,
|
||||
# because a skipped job is considered a success by GitHub. So we have to
|
||||
# overwrite `if:`. We use `!cancelled()` to ensure the job does still not get run
|
||||
|
|
@ -135,7 +180,7 @@ jobs:
|
|||
cron-fail-notify:
|
||||
name: cronjob failure notification
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build, style, coverage]
|
||||
needs: [test, style, coverage]
|
||||
if: ${{ github.event_name == 'schedule' && failure() }}
|
||||
steps:
|
||||
# Send a Zulip notification
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ name: "Miri CI setup"
|
|||
description: "Sets up Miri CI"
|
||||
inputs:
|
||||
toolchain_flags:
|
||||
description: extra flags to pass to rustup-toolchain-install-master
|
||||
required: false
|
||||
default: ''
|
||||
runs:
|
||||
|
|
@ -31,18 +32,15 @@ runs:
|
|||
~/.cargo/bin
|
||||
~/.cargo/.crates.toml
|
||||
~/.cargo/.crates2.json
|
||||
key: cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.lock', '.github/workflows/**/*.yml') }}
|
||||
restore-keys: cargo-${{ runner.os }}
|
||||
# Bump the version when something here changes that needs a cache reset.
|
||||
key: cargo-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/Cargo.lock') }}-v1
|
||||
restore-keys: cargo-${{ runner.os }}-${{ runner.arch }}
|
||||
|
||||
- name: Install rustup-toolchain-install-master
|
||||
- name: Install the tools we need
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: cargo install -f rustup-toolchain-install-master hyperfine
|
||||
shell: bash
|
||||
|
||||
- name: Install nightly toolchain
|
||||
run: rustup toolchain install nightly --profile minimal
|
||||
shell: bash
|
||||
|
||||
- name: Install "master" toolchain
|
||||
run: |
|
||||
if [[ ${{ github.event_name }} == 'schedule' ]]; then
|
||||
|
|
|
|||
|
|
@ -158,6 +158,15 @@ compiler that has `debug=true` set in `bootstrap.toml`.
|
|||
You can set `MIRI_BACKTRACE=1` to get a backtrace of where an
|
||||
evaluation error was originally raised.
|
||||
|
||||
#### Tracing
|
||||
|
||||
You can generate a Chrome trace file from a Miri execution by passing `--features=tracing` during the
|
||||
build and then setting `MIRI_TRACING=1` when running Miri. This will generate a `.json` file that
|
||||
you can visualize in [Perfetto](https://ui.perfetto.dev/). For example:
|
||||
|
||||
```sh
|
||||
MIRI_TRACING=1 ./miri run --features=tracing tests/pass/hello.rs
|
||||
```
|
||||
|
||||
### UI testing
|
||||
|
||||
|
|
|
|||
|
|
@ -627,6 +627,7 @@ dependencies = [
|
|||
"regex",
|
||||
"rustc_version",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smallvec",
|
||||
"tempfile",
|
||||
"tikv-jemalloc-sys",
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ chrono = { version = "0.4.38", default-features = false }
|
|||
chrono-tz = "0.10"
|
||||
directories = "6"
|
||||
bitflags = "2.6"
|
||||
serde_json = { version = "1.0", optional = true }
|
||||
|
||||
# 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
|
||||
|
|
@ -67,6 +68,7 @@ default = ["stack-cache"]
|
|||
genmc = []
|
||||
stack-cache = []
|
||||
stack-cache-consistency-check = ["stack-cache"]
|
||||
tracing = ["serde_json"]
|
||||
|
||||
[lints.rust.unexpected_cfgs]
|
||||
level = "warn"
|
||||
|
|
|
|||
|
|
@ -11,12 +11,12 @@ instance:
|
|||
* Not sufficiently aligned memory accesses and references
|
||||
* Violation of basic type invariants (a `bool` that is not 0 or 1, for example,
|
||||
or an invalid enum discriminant)
|
||||
* Data races and emulation of *some* weak memory effects, i.e.,
|
||||
atomic reads can return outdated values
|
||||
* **Experimental**: Violations of the [Stacked Borrows] rules governing aliasing
|
||||
for reference types
|
||||
* **Experimental**: Violations of the [Tree Borrows] aliasing rules, as an optional
|
||||
alternative to [Stacked Borrows]
|
||||
* **Experimental**: Data races and emulation of weak memory effects, i.e.,
|
||||
atomic reads can return outdated values.
|
||||
|
||||
On top of that, Miri will also tell you about memory leaks: when there is memory
|
||||
still allocated at the end of the execution, and that memory is not reachable
|
||||
|
|
|
|||
|
|
@ -143,28 +143,13 @@ case $HOST_TARGET in
|
|||
GC_STRESS=1 MIR_OPT=1 MANY_SEEDS=64 TEST_BENCH=1 CARGO_MIRI_ENV=1 run_tests
|
||||
# Extra tier 1
|
||||
MANY_SEEDS=64 TEST_TARGET=i686-unknown-linux-gnu run_tests
|
||||
MANY_SEEDS=64 TEST_TARGET=aarch64-unknown-linux-gnu run_tests
|
||||
MANY_SEEDS=64 TEST_TARGET=x86_64-apple-darwin run_tests
|
||||
MANY_SEEDS=64 TEST_TARGET=x86_64-pc-windows-gnu run_tests
|
||||
# Extra tier 1 candidate
|
||||
MANY_SEEDS=64 TEST_TARGET=aarch64-pc-windows-msvc run_tests
|
||||
;;
|
||||
aarch64-apple-darwin)
|
||||
i686-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
|
||||
MANY_SEEDS=64 TEST_TARGET=i686-pc-windows-gnu run_tests
|
||||
MANY_SEEDS=64 TEST_TARGET=x86_64-pc-windows-msvc CARGO_MIRI_ENV=1 run_tests
|
||||
# Extra tier 2
|
||||
MANY_SEEDS=16 TEST_TARGET=arm-unknown-linux-gnueabi run_tests # 32bit ARM
|
||||
MANY_SEEDS=16 TEST_TARGET=aarch64-pc-windows-gnullvm run_tests # gnullvm ABI
|
||||
MANY_SEEDS=16 TEST_TARGET=s390x-unknown-linux-gnu run_tests # big-endian architecture of choice
|
||||
# Not officially supported tier 2
|
||||
MANY_SEEDS=16 TEST_TARGET=mips-unknown-linux-gnu run_tests # a 32bit big-endian target, and also a target without 64bit atomics
|
||||
MANY_SEEDS=16 TEST_TARGET=x86_64-unknown-illumos run_tests
|
||||
MANY_SEEDS=16 TEST_TARGET=x86_64-pc-solaris run_tests
|
||||
MANY_SEEDS=16 TEST_TARGET=x86_64-unknown-freebsd run_tests
|
||||
MANY_SEEDS=16 TEST_TARGET=i686-unknown-freebsd run_tests
|
||||
# Without GC_STRESS as this is a slow runner.
|
||||
MIR_OPT=1 MANY_SEEDS=64 TEST_BENCH=1 CARGO_MIRI_ENV=1 run_tests
|
||||
# Partially supported targets (tier 2)
|
||||
BASIC="empty_main integer heap_alloc libc-mem vec string btreemap" # ensures we have the basics: pre-main code, system allocator
|
||||
UNIX="hello panic/panic panic/unwind concurrency/simple atomic libc-mem libc-misc libc-random env num_cpus" # the things that are very similar across all Unixes, and hence easily supported there
|
||||
|
|
@ -172,13 +157,39 @@ case $HOST_TARGET in
|
|||
TEST_TARGET=wasm32-wasip2 run_tests_minimal $BASIC wasm
|
||||
TEST_TARGET=wasm32-unknown-unknown run_tests_minimal no_std empty_main wasm # this target doesn't really have std
|
||||
TEST_TARGET=thumbv7em-none-eabihf run_tests_minimal no_std
|
||||
;;
|
||||
aarch64-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 candidate
|
||||
MANY_SEEDS=64 TEST_TARGET=aarch64-pc-windows-msvc run_tests
|
||||
# Extra tier 2
|
||||
MANY_SEEDS=16 TEST_TARGET=arm-unknown-linux-gnueabi run_tests # 32bit ARM
|
||||
MANY_SEEDS=16 TEST_TARGET=aarch64-pc-windows-gnullvm run_tests # gnullvm ABI
|
||||
MANY_SEEDS=16 TEST_TARGET=s390x-unknown-linux-gnu run_tests # big-endian architecture of choice
|
||||
# Custom target JSON file
|
||||
TEST_TARGET=tests/x86_64-unknown-kernel.json MIRI_NO_STD=1 run_tests_minimal no_std
|
||||
;;
|
||||
armv7-unknown-linux-gnueabihf)
|
||||
# Host
|
||||
GC_STRESS=1 MIR_OPT=1 MANY_SEEDS=64 TEST_BENCH=1 CARGO_MIRI_ENV=1 run_tests
|
||||
;;
|
||||
aarch64-apple-darwin)
|
||||
# Host
|
||||
GC_STRESS=1 MIR_OPT=1 MANY_SEEDS=64 TEST_BENCH=1 CARGO_MIRI_ENV=1 run_tests
|
||||
# Extra tier 1
|
||||
MANY_SEEDS=64 TEST_TARGET=i686-pc-windows-gnu run_tests
|
||||
MANY_SEEDS=64 TEST_TARGET=x86_64-pc-windows-msvc CARGO_MIRI_ENV=1 run_tests
|
||||
# Not officially supported tier 2
|
||||
MANY_SEEDS=16 TEST_TARGET=mips-unknown-linux-gnu run_tests # a 32bit big-endian target, and also a target without 64bit atomics
|
||||
MANY_SEEDS=16 TEST_TARGET=x86_64-unknown-illumos run_tests
|
||||
MANY_SEEDS=16 TEST_TARGET=x86_64-pc-solaris run_tests
|
||||
MANY_SEEDS=16 TEST_TARGET=x86_64-unknown-freebsd run_tests
|
||||
MANY_SEEDS=16 TEST_TARGET=i686-unknown-freebsd run_tests
|
||||
;;
|
||||
i686-pc-windows-msvc)
|
||||
# Host
|
||||
# Without GC_STRESS and with reduced many-seeds count as this is the slowest runner.
|
||||
# (The macOS runner checks windows-msvc with full many-seeds count.)
|
||||
# Without GC_STRESS as this is the slowest runner.
|
||||
MIR_OPT=1 MANY_SEEDS=64 TEST_BENCH=1 run_tests
|
||||
# Extra tier 1
|
||||
# We really want to ensure a Linux target works on a Windows host,
|
||||
|
|
|
|||
|
|
@ -9,23 +9,21 @@ linkedProjects = [
|
|||
]
|
||||
|
||||
[language-server.rust-analyzer.config.check]
|
||||
invocationLocation = "root"
|
||||
invocationStrategy = "once"
|
||||
overrideCommand = [
|
||||
"env",
|
||||
"MIRI_AUTO_OPS=no",
|
||||
"./miri",
|
||||
"clippy", # make this `check` when working with a locally built rustc
|
||||
"--message-format=json",
|
||||
]
|
||||
|
||||
[language-server.rust-analyzer.config.cargo.extraEnv]
|
||||
MIRI_AUTO_OPS = "no"
|
||||
MIRI_IN_RA = "1"
|
||||
|
||||
# Contrary to what the name suggests, this also affects proc macros.
|
||||
[language-server.rust-analyzer.config.buildScripts]
|
||||
invocationLocation = "root"
|
||||
[language-server.rust-analyzer.config.cargo.buildScripts]
|
||||
invocationStrategy = "once"
|
||||
overrideCommand = [
|
||||
"env",
|
||||
"MIRI_AUTO_OPS=no",
|
||||
"./miri",
|
||||
"check",
|
||||
"--message-format=json",
|
||||
|
|
|
|||
|
|
@ -2,20 +2,24 @@
|
|||
set -e
|
||||
# We want to call the binary directly, so we need to know where it ends up.
|
||||
ROOT_DIR="$(dirname "$0")"
|
||||
MIRI_SCRIPT_TARGET_DIR="$ROOT_DIR"/miri-script/target
|
||||
TOOLCHAIN="+nightly"
|
||||
TARGET_DIR="$ROOT_DIR"/miri-script/target
|
||||
# Prepare cargo invocation.
|
||||
# We have to overwrite the toolchain since `+miri` might be activated and not installed.
|
||||
TOOLCHAIN="+stable"
|
||||
CARGO_FLAGS=("-q")
|
||||
# If we are being invoked for RA, use JSON output and the default toolchain (to make proc-macros
|
||||
# work in RA). This needs a different target dir to avoid mixing up the builds.
|
||||
# Also set `-Zroot-dir` so that RA can identify where the error occurred.
|
||||
if [ -n "$MIRI_IN_RA" ]; then
|
||||
MESSAGE_FORMAT="--message-format=json"
|
||||
TOOLCHAIN=""
|
||||
MIRI_SCRIPT_TARGET_DIR="$MIRI_SCRIPT_TARGET_DIR"/ra
|
||||
CARGO_FLAGS+=("--message-format=json" "-Zroot-dir=$ROOT_DIR")
|
||||
TARGET_DIR="$ROOT_DIR"/target
|
||||
fi
|
||||
# We need a nightly toolchain, for `-Zroot-dir`.
|
||||
cargo $TOOLCHAIN build $CARGO_EXTRA_FLAGS --manifest-path "$ROOT_DIR"/miri-script/Cargo.toml \
|
||||
-Zroot-dir="$ROOT_DIR" \
|
||||
-q --target-dir "$MIRI_SCRIPT_TARGET_DIR" $MESSAGE_FORMAT || \
|
||||
( echo "Failed to build miri-script. Is the 'nightly' toolchain installed?"; exit 1 )
|
||||
# Instead of doing just `cargo run --manifest-path .. $@`, we invoke miri-script binary directly. Invoking `cargo run` goes through
|
||||
# rustup (that sets it's own environmental variables), which is undesirable.
|
||||
"$MIRI_SCRIPT_TARGET_DIR"/debug/miri-script "$@"
|
||||
# Run cargo.
|
||||
cargo $TOOLCHAIN build --manifest-path "$ROOT_DIR"/miri-script/Cargo.toml \
|
||||
--target-dir "$TARGET_DIR" "${CARGO_FLAGS[@]}" || \
|
||||
( echo "Failed to build miri-script. Is the 'stable' toolchain installed?"; exit 1 )
|
||||
# Instead of doing just `cargo run --manifest-path .. $@`, we invoke miri-script binary directly.
|
||||
# Invoking `cargo run` goes through rustup (that sets it's own environmental variables), which is
|
||||
# undesirable.
|
||||
"$TARGET_DIR"/debug/miri-script "$@"
|
||||
|
|
|
|||
|
|
@ -231,11 +231,6 @@ impl Command {
|
|||
cmd!(sh, "rustup override set miri").run()?;
|
||||
// Cleanup.
|
||||
cmd!(sh, "cargo clean").run()?;
|
||||
// Call `cargo metadata` on the sources in case that changes the lockfile
|
||||
// (which fails under some setups when it is done from inside vscode).
|
||||
let sysroot = cmd!(sh, "rustc --print sysroot").read()?;
|
||||
let sysroot = sysroot.trim();
|
||||
cmd!(sh, "cargo metadata --format-version 1 --manifest-path {sysroot}/lib/rustlib/rustc-src/rust/compiler/rustc/Cargo.toml").ignore_stdout().run()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -707,9 +702,12 @@ impl Command {
|
|||
let mut early_flags = Vec::<OsString>::new();
|
||||
|
||||
// In `dep` mode, the target is already passed via `MIRI_TEST_TARGET`
|
||||
if !dep && let Some(target) = &target {
|
||||
early_flags.push("--target".into());
|
||||
early_flags.push(target.into());
|
||||
#[expect(clippy::collapsible_if)] // we need to wait until this is stable
|
||||
if !dep {
|
||||
if let Some(target) = &target {
|
||||
early_flags.push("--target".into());
|
||||
early_flags.push(target.into());
|
||||
}
|
||||
}
|
||||
early_flags.push("--edition".into());
|
||||
early_flags.push(edition.as_deref().unwrap_or("2021").into());
|
||||
|
|
@ -737,8 +735,11 @@ impl Command {
|
|||
// Add Miri flags
|
||||
let mut cmd = cmd.args(&miri_flags).args(&early_flags).args(&flags);
|
||||
// For `--dep` we also need to set the target in the env var.
|
||||
if dep && let Some(target) = &target {
|
||||
cmd = cmd.env("MIRI_TEST_TARGET", target);
|
||||
#[expect(clippy::collapsible_if)] // we need to wait until this is stable
|
||||
if dep {
|
||||
if let Some(target) = &target {
|
||||
cmd = cmd.env("MIRI_TEST_TARGET", target);
|
||||
}
|
||||
}
|
||||
// Finally, run the thing.
|
||||
Ok(cmd.run()?)
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ set MIRI_SCRIPT_TARGET_DIR=%0\..\miri-script\target
|
|||
|
||||
:: If any other steps are added, the "|| exit /b" must be appended to early
|
||||
:: return from the script. If not, it will continue execution.
|
||||
cargo +nightly build %CARGO_EXTRA_FLAGS% -q --target-dir %MIRI_SCRIPT_TARGET_DIR% --manifest-path %0\..\miri-script\Cargo.toml ^
|
||||
|| (echo Failed to build miri-script. Is the 'nightly' toolchain installed? & exit /b)
|
||||
cargo +stable build %CARGO_EXTRA_FLAGS% -q --target-dir %MIRI_SCRIPT_TARGET_DIR% --manifest-path %0\..\miri-script\Cargo.toml ^
|
||||
|| (echo Failed to build miri-script. Is the 'stable' toolchain installed? & exit /b)
|
||||
|
||||
:: Forwards all arguments to this file to the executable.
|
||||
:: We invoke the binary directly to avoid going through rustup, which would set some extra
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
d41e12f1f4e4884c356f319b881921aa37040de5
|
||||
733b47ea4b1b86216f14ef56e49440c33933f230
|
||||
|
|
|
|||
|
|
@ -8,3 +8,8 @@ imports_granularity = "Module"
|
|||
force_multiline_blocks = true
|
||||
match_arm_blocks = false
|
||||
match_arm_leading_pipes = "Preserve"
|
||||
|
||||
ignore = [
|
||||
# This file is copy-pasted from the tracing_chrome crate and should remain like the original.
|
||||
"src/bin/log/tracing_chrome.rs"
|
||||
]
|
||||
|
|
|
|||
2
src/tools/miri/src/bin/log/mod.rs
Normal file
2
src/tools/miri/src/bin/log/mod.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
pub mod setup;
|
||||
mod tracing_chrome;
|
||||
126
src/tools/miri/src/bin/log/setup.rs
Normal file
126
src/tools/miri/src/bin/log/setup.rs
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
use std::env::{self, VarError};
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_session::{CtfeBacktrace, EarlyDiagCtxt};
|
||||
|
||||
/// The tracing layer from `tracing-chrome` starts a thread in the background that saves data to
|
||||
/// file and closes the file when stopped. If the thread is not stopped properly, the file will be
|
||||
/// missing end terminators (`]` for JSON arrays) and other data may also not be flushed. Therefore
|
||||
/// we need to keep a guard that, when [Drop]ped, will send a signal to stop the thread. Make sure
|
||||
/// to manually drop this guard using [deinit_loggers], if you are exiting the program with
|
||||
/// [std::process::exit]!
|
||||
#[must_use]
|
||||
struct TracingGuard {
|
||||
#[cfg(feature = "tracing")]
|
||||
_chrome: super::tracing_chrome::FlushGuard,
|
||||
_no_construct: (),
|
||||
}
|
||||
|
||||
// This ensures TracingGuard is always a drop-type, even when the `_chrome` field is disabled.
|
||||
impl Drop for TracingGuard {
|
||||
fn drop(&mut self) {}
|
||||
}
|
||||
|
||||
fn rustc_logger_config() -> rustc_log::LoggerConfig {
|
||||
// Start with the usual env vars.
|
||||
let mut cfg = rustc_log::LoggerConfig::from_env("RUSTC_LOG");
|
||||
|
||||
// Overwrite if MIRI_LOG is set.
|
||||
if let Ok(var) = env::var("MIRI_LOG") {
|
||||
// MIRI_LOG serves as default for RUSTC_LOG, if that is not set.
|
||||
if matches!(cfg.filter, Err(VarError::NotPresent)) {
|
||||
// We try to be a bit clever here: if `MIRI_LOG` is just a single level
|
||||
// used for everything, we only apply it to the parts of rustc that are
|
||||
// CTFE-related. Otherwise, we use it verbatim for `RUSTC_LOG`.
|
||||
// This way, if you set `MIRI_LOG=trace`, you get only the right parts of
|
||||
// rustc traced, but you can also do `MIRI_LOG=miri=trace,rustc_const_eval::interpret=debug`.
|
||||
if tracing::Level::from_str(&var).is_ok() {
|
||||
cfg.filter = Ok(format!(
|
||||
"rustc_middle::mir::interpret={var},rustc_const_eval::interpret={var},miri={var}"
|
||||
));
|
||||
} else {
|
||||
cfg.filter = Ok(var);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfg
|
||||
}
|
||||
|
||||
/// The global logger can only be set once per process, so track whether that already happened and
|
||||
/// keep a [TracingGuard] so it can be [Drop]ped later using [deinit_loggers].
|
||||
static LOGGER_INITED: OnceLock<Mutex<Option<TracingGuard>>> = OnceLock::new();
|
||||
|
||||
fn init_logger_once(early_dcx: &EarlyDiagCtxt) {
|
||||
// If the logger is not yet initialized, initialize it.
|
||||
LOGGER_INITED.get_or_init(|| {
|
||||
let guard = if env::var_os("MIRI_TRACING").is_some() {
|
||||
#[cfg(not(feature = "tracing"))]
|
||||
{
|
||||
crate::fatal_error!(
|
||||
"fatal error: cannot enable MIRI_TRACING since Miri was not built with the \"tracing\" feature"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
{
|
||||
let (chrome_layer, chrome_guard) =
|
||||
super::tracing_chrome::ChromeLayerBuilder::new().include_args(true).build();
|
||||
rustc_driver::init_logger_with_additional_layer(
|
||||
early_dcx,
|
||||
rustc_logger_config(),
|
||||
|| {
|
||||
tracing_subscriber::layer::SubscriberExt::with(
|
||||
tracing_subscriber::Registry::default(),
|
||||
chrome_layer,
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
Some(TracingGuard { _chrome: chrome_guard, _no_construct: () })
|
||||
}
|
||||
} else {
|
||||
// initialize the logger without any tracing enabled
|
||||
rustc_driver::init_logger(early_dcx, rustc_logger_config());
|
||||
None
|
||||
};
|
||||
Mutex::new(guard)
|
||||
});
|
||||
}
|
||||
|
||||
pub fn init_early_loggers(early_dcx: &EarlyDiagCtxt) {
|
||||
// We only initialize `rustc` if the env var is set (so the user asked for it).
|
||||
// If it is not set, we avoid initializing now so that we can initialize later with our custom
|
||||
// settings, and *not* log anything for what happens before `miri` starts interpreting.
|
||||
if env::var_os("RUSTC_LOG").is_some() {
|
||||
init_logger_once(early_dcx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_late_loggers(early_dcx: &EarlyDiagCtxt, tcx: TyCtxt<'_>) {
|
||||
// If the logger is not yet initialized, initialize it.
|
||||
init_logger_once(early_dcx);
|
||||
|
||||
// If `MIRI_BACKTRACE` is set and `RUSTC_CTFE_BACKTRACE` is not, set `RUSTC_CTFE_BACKTRACE`.
|
||||
// Do this late, so we ideally only apply this to Miri's errors.
|
||||
if let Some(val) = env::var_os("MIRI_BACKTRACE") {
|
||||
let ctfe_backtrace = match &*val.to_string_lossy() {
|
||||
"immediate" => CtfeBacktrace::Immediate,
|
||||
"0" => CtfeBacktrace::Disabled,
|
||||
_ => CtfeBacktrace::Capture,
|
||||
};
|
||||
*tcx.sess.ctfe_backtrace.borrow_mut() = ctfe_backtrace;
|
||||
}
|
||||
}
|
||||
|
||||
/// Must be called before the program terminates to ensure the trace file is closed correctly. Not
|
||||
/// doing so will result in invalid trace files. Also see [TracingGuard].
|
||||
pub fn deinit_loggers() {
|
||||
if let Some(guard) = LOGGER_INITED.get()
|
||||
&& let Ok(mut guard) = guard.lock()
|
||||
{
|
||||
std::mem::drop(guard.take());
|
||||
}
|
||||
}
|
||||
625
src/tools/miri/src/bin/log/tracing_chrome.rs
Normal file
625
src/tools/miri/src/bin/log/tracing_chrome.rs
Normal file
|
|
@ -0,0 +1,625 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: Copyright (c) 2020 Thoren Paulson
|
||||
//! This file is taken unmodified from the following link, except for file attributes and
|
||||
//! `extern crate` at the top.
|
||||
//! https://github.com/thoren-d/tracing-chrome/blob/7e2625ab4aeeef2f0ef9bde9d6258dd181c04472/src/lib.rs
|
||||
//! Depending on the tracing-chrome crate from crates.io is unfortunately not possible, since it
|
||||
//! depends on `tracing_core` which conflicts with rustc_private's `tracing_core` (meaning it would
|
||||
//! not be possible to use the [ChromeLayer] in a context that expects a [Layer] from
|
||||
//! rustc_private's `tracing_core` version).
|
||||
#![allow(warnings)]
|
||||
#![cfg(feature = "tracing")]
|
||||
|
||||
// This is here and not in src/lib.rs since it is a direct dependency of tracing_chrome.rs and
|
||||
// should not be included if the "tracing" feature is disabled.
|
||||
extern crate tracing_core;
|
||||
|
||||
use tracing_core::{field::Field, span, Event, Subscriber};
|
||||
use tracing_subscriber::{
|
||||
layer::Context,
|
||||
registry::{LookupSpan, SpanRef},
|
||||
Layer,
|
||||
};
|
||||
|
||||
use serde_json::{json, Value as JsonValue};
|
||||
use std::{
|
||||
marker::PhantomData,
|
||||
path::Path,
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc, Mutex,
|
||||
},
|
||||
};
|
||||
|
||||
use std::io::{BufWriter, Write};
|
||||
use std::sync::mpsc;
|
||||
use std::sync::mpsc::Sender;
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
thread::JoinHandle,
|
||||
};
|
||||
|
||||
thread_local! {
|
||||
static OUT: RefCell<Option<Sender<Message>>> = const { RefCell::new(None) };
|
||||
static TID: RefCell<Option<usize>> = const { RefCell::new(None) };
|
||||
}
|
||||
|
||||
type NameFn<S> = Box<dyn Fn(&EventOrSpan<'_, '_, S>) -> String + Send + Sync>;
|
||||
type Object = serde_json::Map<String, JsonValue>;
|
||||
|
||||
/// A [`Layer`](tracing_subscriber::Layer) that writes a Chrome trace file.
|
||||
pub struct ChromeLayer<S>
|
||||
where
|
||||
S: Subscriber + for<'span> LookupSpan<'span> + Send + Sync,
|
||||
{
|
||||
out: Arc<Mutex<Sender<Message>>>,
|
||||
start: std::time::Instant,
|
||||
max_tid: AtomicUsize,
|
||||
include_args: bool,
|
||||
include_locations: bool,
|
||||
trace_style: TraceStyle,
|
||||
name_fn: Option<NameFn<S>>,
|
||||
cat_fn: Option<NameFn<S>>,
|
||||
_inner: PhantomData<S>,
|
||||
}
|
||||
|
||||
/// A builder for [`ChromeLayer`](crate::ChromeLayer).
|
||||
#[derive(Default)]
|
||||
pub struct ChromeLayerBuilder<S>
|
||||
where
|
||||
S: Subscriber + for<'span> LookupSpan<'span> + Send + Sync,
|
||||
{
|
||||
out_writer: Option<Box<dyn Write + Send>>,
|
||||
name_fn: Option<NameFn<S>>,
|
||||
cat_fn: Option<NameFn<S>>,
|
||||
include_args: bool,
|
||||
include_locations: bool,
|
||||
trace_style: TraceStyle,
|
||||
_inner: PhantomData<S>,
|
||||
}
|
||||
|
||||
/// Decides how traces will be recorded.
|
||||
#[derive(Default)]
|
||||
pub enum TraceStyle {
|
||||
/// Traces will be recorded as a group of threads.
|
||||
/// In this style, spans should be entered and exited on the same thread.
|
||||
#[default]
|
||||
Threaded,
|
||||
|
||||
/// Traces will recorded as a group of asynchronous operations.
|
||||
Async,
|
||||
}
|
||||
|
||||
impl<S> ChromeLayerBuilder<S>
|
||||
where
|
||||
S: Subscriber + for<'span> LookupSpan<'span> + Send + Sync,
|
||||
{
|
||||
pub fn new() -> Self {
|
||||
ChromeLayerBuilder {
|
||||
out_writer: None,
|
||||
name_fn: None,
|
||||
cat_fn: None,
|
||||
include_args: false,
|
||||
include_locations: true,
|
||||
trace_style: TraceStyle::Threaded,
|
||||
_inner: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the file to which to output the trace.
|
||||
///
|
||||
/// Defaults to `./trace-{unix epoch in micros}.json`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If `file` could not be opened/created. To handle errors,
|
||||
/// open a file and pass it to [`writer`](crate::ChromeLayerBuilder::writer) instead.
|
||||
pub fn file<P: AsRef<Path>>(self, file: P) -> Self {
|
||||
self.writer(std::fs::File::create(file).expect("Failed to create trace file."))
|
||||
}
|
||||
|
||||
/// Supply an arbitrary writer to which to write trace contents.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use tracing_chrome::ChromeLayerBuilder;
|
||||
/// # use tracing_subscriber::prelude::*;
|
||||
/// let (layer, guard) = ChromeLayerBuilder::new().writer(std::io::sink()).build();
|
||||
/// # tracing_subscriber::registry().with(layer).init();
|
||||
/// ```
|
||||
pub fn writer<W: Write + Send + 'static>(mut self, writer: W) -> Self {
|
||||
self.out_writer = Some(Box::new(writer));
|
||||
self
|
||||
}
|
||||
|
||||
/// Include arguments in each trace entry.
|
||||
///
|
||||
/// Defaults to `false`.
|
||||
///
|
||||
/// Includes the arguments used when creating a span/event
|
||||
/// in the "args" section of the trace entry.
|
||||
pub fn include_args(mut self, include: bool) -> Self {
|
||||
self.include_args = include;
|
||||
self
|
||||
}
|
||||
|
||||
/// Include file+line with each trace entry.
|
||||
///
|
||||
/// Defaults to `true`.
|
||||
///
|
||||
/// This can add quite a bit of data to the output so turning
|
||||
/// it off might be helpful when collecting larger traces.
|
||||
pub fn include_locations(mut self, include: bool) -> Self {
|
||||
self.include_locations = include;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the style used when recording trace events.
|
||||
///
|
||||
/// See [`TraceStyle`](crate::TraceStyle) for details.
|
||||
pub fn trace_style(mut self, style: TraceStyle) -> Self {
|
||||
self.trace_style = style;
|
||||
self
|
||||
}
|
||||
|
||||
/// Allows supplying a function that derives a name from
|
||||
/// an Event or Span. The result is used as the "name" field
|
||||
/// on trace entries.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use tracing_chrome::{ChromeLayerBuilder, EventOrSpan};
|
||||
/// use tracing_subscriber::{registry::Registry, prelude::*};
|
||||
///
|
||||
/// let (chrome_layer, _guard) = ChromeLayerBuilder::new().name_fn(Box::new(|event_or_span| {
|
||||
/// match event_or_span {
|
||||
/// EventOrSpan::Event(ev) => { ev.metadata().name().into() },
|
||||
/// EventOrSpan::Span(_s) => { "span".into() },
|
||||
/// }
|
||||
/// })).build();
|
||||
/// tracing_subscriber::registry().with(chrome_layer).init()
|
||||
/// ```
|
||||
pub fn name_fn(mut self, name_fn: NameFn<S>) -> Self {
|
||||
self.name_fn = Some(name_fn);
|
||||
self
|
||||
}
|
||||
|
||||
/// Allows supplying a function that derives a category from
|
||||
/// an Event or Span. The result is used as the "cat" field on
|
||||
/// trace entries.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use tracing_chrome::{ChromeLayerBuilder, EventOrSpan};
|
||||
/// use tracing_subscriber::{registry::Registry, prelude::*};
|
||||
///
|
||||
/// let (chrome_layer, _guard) = ChromeLayerBuilder::new().category_fn(Box::new(|_| {
|
||||
/// "my_module".into()
|
||||
/// })).build();
|
||||
/// tracing_subscriber::registry().with(chrome_layer).init()
|
||||
/// ```
|
||||
pub fn category_fn(mut self, cat_fn: NameFn<S>) -> Self {
|
||||
self.cat_fn = Some(cat_fn);
|
||||
self
|
||||
}
|
||||
|
||||
/// Creates a [`ChromeLayer`](crate::ChromeLayer) and associated [`FlushGuard`](crate::FlushGuard).
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If no file or writer was specified and the default trace file could not be opened/created.
|
||||
pub fn build(self) -> (ChromeLayer<S>, FlushGuard) {
|
||||
ChromeLayer::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// This guard will signal the thread writing the trace file to stop and join it when dropped.
|
||||
pub struct FlushGuard {
|
||||
sender: Sender<Message>,
|
||||
handle: Cell<Option<JoinHandle<()>>>,
|
||||
}
|
||||
|
||||
impl FlushGuard {
|
||||
/// Signals the trace writing thread to flush to disk.
|
||||
pub fn flush(&self) {
|
||||
if let Some(handle) = self.handle.take() {
|
||||
let _ignored = self.sender.send(Message::Flush);
|
||||
self.handle.set(Some(handle));
|
||||
}
|
||||
}
|
||||
|
||||
/// Finishes the current trace and starts a new one.
|
||||
///
|
||||
/// If a [`Write`](std::io::Write) implementation is supplied,
|
||||
/// the new trace is written to it. Otherwise, the new trace
|
||||
/// goes to `./trace-{unix epoc in micros}.json`.
|
||||
pub fn start_new(&self, writer: Option<Box<dyn Write + Send>>) {
|
||||
if let Some(handle) = self.handle.take() {
|
||||
let _ignored = self.sender.send(Message::StartNew(writer));
|
||||
self.handle.set(Some(handle));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FlushGuard {
|
||||
fn drop(&mut self) {
|
||||
if let Some(handle) = self.handle.take() {
|
||||
let _ignored = self.sender.send(Message::Drop);
|
||||
if handle.join().is_err() {
|
||||
eprintln!("tracing_chrome: Trace writing thread panicked.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Callsite {
|
||||
tid: usize,
|
||||
name: String,
|
||||
target: String,
|
||||
file: Option<&'static str>,
|
||||
line: Option<u32>,
|
||||
args: Option<Arc<Object>>,
|
||||
}
|
||||
|
||||
enum Message {
|
||||
Enter(f64, Callsite, Option<u64>),
|
||||
Event(f64, Callsite),
|
||||
Exit(f64, Callsite, Option<u64>),
|
||||
NewThread(usize, String),
|
||||
Flush,
|
||||
Drop,
|
||||
StartNew(Option<Box<dyn Write + Send>>),
|
||||
}
|
||||
|
||||
/// Represents either an [`Event`](tracing_core::Event) or [`SpanRef`](tracing_subscriber::registry::SpanRef).
|
||||
pub enum EventOrSpan<'a, 'b, S>
|
||||
where
|
||||
S: Subscriber + for<'span> LookupSpan<'span> + Send + Sync,
|
||||
{
|
||||
Event(&'a Event<'b>),
|
||||
Span(&'a SpanRef<'b, S>),
|
||||
}
|
||||
|
||||
fn create_default_writer() -> Box<dyn Write + Send> {
|
||||
Box::new(
|
||||
std::fs::File::create(format!(
|
||||
"./trace-{}.json",
|
||||
std::time::SystemTime::UNIX_EPOCH
|
||||
.elapsed()
|
||||
.unwrap()
|
||||
.as_micros()
|
||||
))
|
||||
.expect("Failed to create trace file."),
|
||||
)
|
||||
}
|
||||
|
||||
impl<S> ChromeLayer<S>
|
||||
where
|
||||
S: Subscriber + for<'span> LookupSpan<'span> + Send + Sync,
|
||||
{
|
||||
fn new(mut builder: ChromeLayerBuilder<S>) -> (ChromeLayer<S>, FlushGuard) {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
OUT.with(|val| val.replace(Some(tx.clone())));
|
||||
|
||||
let out_writer = builder
|
||||
.out_writer
|
||||
.unwrap_or_else(|| create_default_writer());
|
||||
|
||||
let handle = std::thread::spawn(move || {
|
||||
let mut write = BufWriter::new(out_writer);
|
||||
write.write_all(b"[\n").unwrap();
|
||||
|
||||
let mut has_started = false;
|
||||
let mut thread_names: Vec<(usize, String)> = Vec::new();
|
||||
for msg in rx {
|
||||
if let Message::Flush = &msg {
|
||||
write.flush().unwrap();
|
||||
continue;
|
||||
} else if let Message::Drop = &msg {
|
||||
break;
|
||||
} else if let Message::StartNew(writer) = msg {
|
||||
// Finish off current file
|
||||
write.write_all(b"\n]").unwrap();
|
||||
write.flush().unwrap();
|
||||
|
||||
// Get or create new writer
|
||||
let out_writer = writer.unwrap_or_else(|| create_default_writer());
|
||||
write = BufWriter::new(out_writer);
|
||||
write.write_all(b"[\n").unwrap();
|
||||
has_started = false;
|
||||
|
||||
// Write saved thread names
|
||||
for (tid, name) in thread_names.iter() {
|
||||
let entry = json!({
|
||||
"ph": "M",
|
||||
"pid": 1,
|
||||
"name": "thread_name",
|
||||
"tid": *tid,
|
||||
"args": {
|
||||
"name": name,
|
||||
},
|
||||
});
|
||||
|
||||
if has_started {
|
||||
write.write_all(b",\n").unwrap();
|
||||
}
|
||||
serde_json::to_writer(&mut write, &entry).unwrap();
|
||||
has_started = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
let (ph, ts, callsite, id) = match &msg {
|
||||
Message::Enter(ts, callsite, None) => ("B", Some(ts), Some(callsite), None),
|
||||
Message::Enter(ts, callsite, Some(root_id)) => {
|
||||
("b", Some(ts), Some(callsite), Some(root_id))
|
||||
}
|
||||
Message::Event(ts, callsite) => ("i", Some(ts), Some(callsite), None),
|
||||
Message::Exit(ts, callsite, None) => ("E", Some(ts), Some(callsite), None),
|
||||
Message::Exit(ts, callsite, Some(root_id)) => {
|
||||
("e", Some(ts), Some(callsite), Some(root_id))
|
||||
}
|
||||
Message::NewThread(_tid, _name) => ("M", None, None, None),
|
||||
Message::Flush | Message::Drop | Message::StartNew(_) => {
|
||||
panic!("Was supposed to break by now.")
|
||||
}
|
||||
};
|
||||
let mut entry = json!({
|
||||
"ph": ph,
|
||||
"pid": 1,
|
||||
});
|
||||
|
||||
if let Message::NewThread(tid, name) = msg {
|
||||
thread_names.push((tid, name.clone()));
|
||||
entry["name"] = "thread_name".into();
|
||||
entry["tid"] = tid.into();
|
||||
entry["args"] = json!({ "name": name });
|
||||
} else {
|
||||
let ts = ts.unwrap();
|
||||
let callsite = callsite.unwrap();
|
||||
entry["ts"] = (*ts).into();
|
||||
entry["name"] = callsite.name.clone().into();
|
||||
entry["cat"] = callsite.target.clone().into();
|
||||
entry["tid"] = callsite.tid.into();
|
||||
|
||||
if let Some(&id) = id {
|
||||
entry["id"] = id.into();
|
||||
}
|
||||
|
||||
if ph == "i" {
|
||||
entry["s"] = "t".into();
|
||||
}
|
||||
|
||||
if let (Some(file), Some(line)) = (callsite.file, callsite.line) {
|
||||
entry[".file"] = file.into();
|
||||
entry[".line"] = line.into();
|
||||
}
|
||||
|
||||
if let Some(call_args) = &callsite.args {
|
||||
if !call_args.is_empty() {
|
||||
entry["args"] = (**call_args).clone().into();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if has_started {
|
||||
write.write_all(b",\n").unwrap();
|
||||
}
|
||||
serde_json::to_writer(&mut write, &entry).unwrap();
|
||||
has_started = true;
|
||||
}
|
||||
|
||||
write.write_all(b"\n]").unwrap();
|
||||
write.flush().unwrap();
|
||||
});
|
||||
|
||||
let guard = FlushGuard {
|
||||
sender: tx.clone(),
|
||||
handle: Cell::new(Some(handle)),
|
||||
};
|
||||
let layer = ChromeLayer {
|
||||
out: Arc::new(Mutex::new(tx)),
|
||||
start: std::time::Instant::now(),
|
||||
max_tid: AtomicUsize::new(0),
|
||||
name_fn: builder.name_fn.take(),
|
||||
cat_fn: builder.cat_fn.take(),
|
||||
include_args: builder.include_args,
|
||||
include_locations: builder.include_locations,
|
||||
trace_style: builder.trace_style,
|
||||
_inner: PhantomData,
|
||||
};
|
||||
|
||||
(layer, guard)
|
||||
}
|
||||
|
||||
fn get_tid(&self) -> (usize, bool) {
|
||||
TID.with(|value| {
|
||||
let tid = *value.borrow();
|
||||
match tid {
|
||||
Some(tid) => (tid, false),
|
||||
None => {
|
||||
let tid = self.max_tid.fetch_add(1, Ordering::SeqCst);
|
||||
value.replace(Some(tid));
|
||||
(tid, true)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn get_callsite(&self, data: EventOrSpan<S>) -> Callsite {
|
||||
let (tid, new_thread) = self.get_tid();
|
||||
let name = self.name_fn.as_ref().map(|name_fn| name_fn(&data));
|
||||
let target = self.cat_fn.as_ref().map(|cat_fn| cat_fn(&data));
|
||||
let meta = match data {
|
||||
EventOrSpan::Event(e) => e.metadata(),
|
||||
EventOrSpan::Span(s) => s.metadata(),
|
||||
};
|
||||
let args = match data {
|
||||
EventOrSpan::Event(e) => {
|
||||
if self.include_args {
|
||||
let mut args = Object::new();
|
||||
e.record(&mut JsonVisitor { object: &mut args });
|
||||
Some(Arc::new(args))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
EventOrSpan::Span(s) => s
|
||||
.extensions()
|
||||
.get::<ArgsWrapper>()
|
||||
.map(|e| &e.args)
|
||||
.cloned(),
|
||||
};
|
||||
let name = name.unwrap_or_else(|| meta.name().into());
|
||||
let target = target.unwrap_or_else(|| meta.target().into());
|
||||
let (file, line) = if self.include_locations {
|
||||
(meta.file(), meta.line())
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
if new_thread {
|
||||
let name = match std::thread::current().name() {
|
||||
Some(name) => name.to_owned(),
|
||||
None => tid.to_string(),
|
||||
};
|
||||
self.send_message(Message::NewThread(tid, name));
|
||||
}
|
||||
|
||||
Callsite {
|
||||
tid,
|
||||
name,
|
||||
target,
|
||||
file,
|
||||
line,
|
||||
args,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_root_id(span: SpanRef<S>) -> u64 {
|
||||
span.scope()
|
||||
.from_root()
|
||||
.take(1)
|
||||
.next()
|
||||
.unwrap_or(span)
|
||||
.id()
|
||||
.into_u64()
|
||||
}
|
||||
|
||||
fn enter_span(&self, span: SpanRef<S>, ts: f64) {
|
||||
let callsite = self.get_callsite(EventOrSpan::Span(&span));
|
||||
let root_id = match self.trace_style {
|
||||
TraceStyle::Async => Some(ChromeLayer::get_root_id(span)),
|
||||
_ => None,
|
||||
};
|
||||
self.send_message(Message::Enter(ts, callsite, root_id));
|
||||
}
|
||||
|
||||
fn exit_span(&self, span: SpanRef<S>, ts: f64) {
|
||||
let callsite = self.get_callsite(EventOrSpan::Span(&span));
|
||||
let root_id = match self.trace_style {
|
||||
TraceStyle::Async => Some(ChromeLayer::get_root_id(span)),
|
||||
_ => None,
|
||||
};
|
||||
self.send_message(Message::Exit(ts, callsite, root_id));
|
||||
}
|
||||
|
||||
fn get_ts(&self) -> f64 {
|
||||
self.start.elapsed().as_nanos() as f64 / 1000.0
|
||||
}
|
||||
|
||||
fn send_message(&self, message: Message) {
|
||||
OUT.with(move |val| {
|
||||
if val.borrow().is_some() {
|
||||
let _ignored = val.borrow().as_ref().unwrap().send(message);
|
||||
} else {
|
||||
let out = self.out.lock().unwrap().clone();
|
||||
let _ignored = out.send(message);
|
||||
val.replace(Some(out));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Layer<S> for ChromeLayer<S>
|
||||
where
|
||||
S: Subscriber + for<'span> LookupSpan<'span> + Send + Sync,
|
||||
{
|
||||
fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) {
|
||||
if let TraceStyle::Async = self.trace_style {
|
||||
return;
|
||||
}
|
||||
|
||||
let ts = self.get_ts();
|
||||
self.enter_span(ctx.span(id).expect("Span not found."), ts);
|
||||
}
|
||||
|
||||
fn on_record(&self, id: &span::Id, values: &span::Record<'_>, ctx: Context<'_, S>) {
|
||||
if self.include_args {
|
||||
let span = ctx.span(id).unwrap();
|
||||
let mut exts = span.extensions_mut();
|
||||
|
||||
let args = exts.get_mut::<ArgsWrapper>();
|
||||
|
||||
if let Some(args) = args {
|
||||
let args = Arc::make_mut(&mut args.args);
|
||||
values.record(&mut JsonVisitor { object: args });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) {
|
||||
let ts = self.get_ts();
|
||||
let callsite = self.get_callsite(EventOrSpan::Event(event));
|
||||
self.send_message(Message::Event(ts, callsite));
|
||||
}
|
||||
|
||||
fn on_exit(&self, id: &span::Id, ctx: Context<'_, S>) {
|
||||
if let TraceStyle::Async = self.trace_style {
|
||||
return;
|
||||
}
|
||||
let ts = self.get_ts();
|
||||
self.exit_span(ctx.span(id).expect("Span not found."), ts);
|
||||
}
|
||||
|
||||
fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) {
|
||||
if self.include_args {
|
||||
let mut args = Object::new();
|
||||
attrs.record(&mut JsonVisitor { object: &mut args });
|
||||
ctx.span(id).unwrap().extensions_mut().insert(ArgsWrapper {
|
||||
args: Arc::new(args),
|
||||
});
|
||||
}
|
||||
if let TraceStyle::Threaded = self.trace_style {
|
||||
return;
|
||||
}
|
||||
|
||||
let ts = self.get_ts();
|
||||
self.enter_span(ctx.span(id).expect("Span not found."), ts);
|
||||
}
|
||||
|
||||
fn on_close(&self, id: span::Id, ctx: Context<'_, S>) {
|
||||
if let TraceStyle::Threaded = self.trace_style {
|
||||
return;
|
||||
}
|
||||
|
||||
let ts = self.get_ts();
|
||||
self.exit_span(ctx.span(&id).expect("Span not found."), ts);
|
||||
}
|
||||
}
|
||||
|
||||
struct JsonVisitor<'a> {
|
||||
object: &'a mut Object,
|
||||
}
|
||||
|
||||
impl<'a> tracing_subscriber::field::Visit for JsonVisitor<'a> {
|
||||
fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
|
||||
self.object
|
||||
.insert(field.name().to_owned(), format!("{value:?}").into());
|
||||
}
|
||||
}
|
||||
|
||||
struct ArgsWrapper {
|
||||
args: Arc<Object>,
|
||||
}
|
||||
|
|
@ -10,6 +10,8 @@
|
|||
|
||||
// Some "regular" crates we want to share with rustc
|
||||
extern crate tracing;
|
||||
#[cfg(feature = "tracing")]
|
||||
extern crate tracing_subscriber;
|
||||
|
||||
// The rustc crates we need
|
||||
extern crate rustc_abi;
|
||||
|
|
@ -24,14 +26,16 @@ extern crate rustc_middle;
|
|||
extern crate rustc_session;
|
||||
extern crate rustc_span;
|
||||
|
||||
use std::env::{self, VarError};
|
||||
mod log;
|
||||
|
||||
use std::env;
|
||||
use std::num::NonZero;
|
||||
use std::ops::Range;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicI32, AtomicU32, Ordering};
|
||||
use std::sync::{Arc, Once};
|
||||
|
||||
use miri::{
|
||||
BacktraceStyle, BorrowTrackerMethod, GenmcConfig, GenmcCtx, MiriConfig, MiriEntryFnType,
|
||||
|
|
@ -52,12 +56,14 @@ use rustc_middle::query::LocalCrate;
|
|||
use rustc_middle::traits::{ObligationCause, ObligationCauseCode};
|
||||
use rustc_middle::ty::{self, Ty, TyCtxt};
|
||||
use rustc_middle::util::Providers;
|
||||
use rustc_session::EarlyDiagCtxt;
|
||||
use rustc_session::config::{CrateType, ErrorOutputType, OptLevel};
|
||||
use rustc_session::search_paths::PathKind;
|
||||
use rustc_session::{CtfeBacktrace, EarlyDiagCtxt};
|
||||
use rustc_span::def_id::DefId;
|
||||
use tracing::debug;
|
||||
|
||||
use crate::log::setup::{deinit_loggers, init_early_loggers, init_late_loggers};
|
||||
|
||||
struct MiriCompilerCalls {
|
||||
miri_config: Option<MiriConfig>,
|
||||
many_seeds: Option<ManySeedsConfig>,
|
||||
|
|
@ -154,13 +160,13 @@ impl rustc_driver::Callbacks for MiriCompilerCalls {
|
|||
if tcx.sess.dcx().has_errors_or_delayed_bugs().is_some() {
|
||||
tcx.dcx().fatal("miri cannot be run on programs that fail compilation");
|
||||
}
|
||||
|
||||
let early_dcx = EarlyDiagCtxt::new(tcx.sess.opts.error_format);
|
||||
init_late_loggers(&early_dcx, tcx);
|
||||
if !tcx.crate_types().contains(&CrateType::Executable) {
|
||||
tcx.dcx().fatal("miri only makes sense on bin crates");
|
||||
}
|
||||
|
||||
let early_dcx = EarlyDiagCtxt::new(tcx.sess.opts.error_format);
|
||||
init_late_loggers(&early_dcx, tcx);
|
||||
|
||||
let (entry_def_id, entry_type) = entry_fn(tcx);
|
||||
let mut config = self.miri_config.take().expect("after_analysis must only be called once");
|
||||
|
||||
|
|
@ -213,7 +219,7 @@ impl rustc_driver::Callbacks for MiriCompilerCalls {
|
|||
if !many_seeds.keep_going {
|
||||
// `abort_if_errors` would actually not stop, since `par_for_each` waits for the
|
||||
// rest of the to finish, so we just exit immediately.
|
||||
std::process::exit(return_code);
|
||||
exit(return_code);
|
||||
}
|
||||
exit_code.store(return_code, Ordering::Relaxed);
|
||||
num_failed.fetch_add(1, Ordering::Relaxed);
|
||||
|
|
@ -223,7 +229,7 @@ impl rustc_driver::Callbacks for MiriCompilerCalls {
|
|||
if num_failed > 0 {
|
||||
eprintln!("{num_failed}/{total} SEEDS FAILED", total = many_seeds.seeds.count());
|
||||
}
|
||||
std::process::exit(exit_code.0.into_inner());
|
||||
exit(exit_code.0.into_inner());
|
||||
} else {
|
||||
let return_code = miri::eval_entry(tcx, entry_def_id, entry_type, &config, None)
|
||||
.unwrap_or_else(|| {
|
||||
|
|
@ -232,7 +238,7 @@ impl rustc_driver::Callbacks for MiriCompilerCalls {
|
|||
tcx.dcx().abort_if_errors();
|
||||
rustc_driver::EXIT_FAILURE
|
||||
});
|
||||
std::process::exit(return_code);
|
||||
exit(return_code);
|
||||
}
|
||||
|
||||
// Unreachable.
|
||||
|
|
@ -328,83 +334,31 @@ impl rustc_driver::Callbacks for MiriBeRustCompilerCalls {
|
|||
}
|
||||
}
|
||||
|
||||
fn show_error(msg: &impl std::fmt::Display) -> ! {
|
||||
fn exit(exit_code: i32) -> ! {
|
||||
// Drop the tracing guard before exiting, so tracing calls are flushed correctly.
|
||||
deinit_loggers();
|
||||
std::process::exit(exit_code);
|
||||
}
|
||||
|
||||
fn fatal_error_(msg: &impl std::fmt::Display) -> ! {
|
||||
eprintln!("fatal error: {msg}");
|
||||
std::process::exit(1)
|
||||
exit(1)
|
||||
}
|
||||
|
||||
macro_rules! show_error {
|
||||
($($tt:tt)*) => { show_error(&format_args!($($tt)*)) };
|
||||
}
|
||||
|
||||
fn rustc_logger_config() -> rustc_log::LoggerConfig {
|
||||
// Start with the usual env vars.
|
||||
let mut cfg = rustc_log::LoggerConfig::from_env("RUSTC_LOG");
|
||||
|
||||
// Overwrite if MIRI_LOG is set.
|
||||
if let Ok(var) = env::var("MIRI_LOG") {
|
||||
// MIRI_LOG serves as default for RUSTC_LOG, if that is not set.
|
||||
if matches!(cfg.filter, Err(VarError::NotPresent)) {
|
||||
// We try to be a bit clever here: if `MIRI_LOG` is just a single level
|
||||
// used for everything, we only apply it to the parts of rustc that are
|
||||
// CTFE-related. Otherwise, we use it verbatim for `RUSTC_LOG`.
|
||||
// This way, if you set `MIRI_LOG=trace`, you get only the right parts of
|
||||
// rustc traced, but you can also do `MIRI_LOG=miri=trace,rustc_const_eval::interpret=debug`.
|
||||
if tracing::Level::from_str(&var).is_ok() {
|
||||
cfg.filter = Ok(format!(
|
||||
"rustc_middle::mir::interpret={var},rustc_const_eval::interpret={var},miri={var}"
|
||||
));
|
||||
} else {
|
||||
cfg.filter = Ok(var);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfg
|
||||
}
|
||||
|
||||
/// The global logger can only be set once per process, so track
|
||||
/// whether that already happened.
|
||||
static LOGGER_INITED: Once = Once::new();
|
||||
|
||||
fn init_early_loggers(early_dcx: &EarlyDiagCtxt) {
|
||||
// We only initialize `rustc` if the env var is set (so the user asked for it).
|
||||
// If it is not set, we avoid initializing now so that we can initialize later with our custom
|
||||
// settings, and *not* log anything for what happens before `miri` starts interpreting.
|
||||
if env::var_os("RUSTC_LOG").is_some() {
|
||||
LOGGER_INITED.call_once(|| {
|
||||
rustc_driver::init_logger(early_dcx, rustc_logger_config());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn init_late_loggers(early_dcx: &EarlyDiagCtxt, tcx: TyCtxt<'_>) {
|
||||
// If the logger is not yet initialized, initialize it.
|
||||
LOGGER_INITED.call_once(|| {
|
||||
rustc_driver::init_logger(early_dcx, rustc_logger_config());
|
||||
});
|
||||
|
||||
// If `MIRI_BACKTRACE` is set and `RUSTC_CTFE_BACKTRACE` is not, set `RUSTC_CTFE_BACKTRACE`.
|
||||
// Do this late, so we ideally only apply this to Miri's errors.
|
||||
if let Some(val) = env::var_os("MIRI_BACKTRACE") {
|
||||
let ctfe_backtrace = match &*val.to_string_lossy() {
|
||||
"immediate" => CtfeBacktrace::Immediate,
|
||||
"0" => CtfeBacktrace::Disabled,
|
||||
_ => CtfeBacktrace::Capture,
|
||||
};
|
||||
*tcx.sess.ctfe_backtrace.borrow_mut() = ctfe_backtrace;
|
||||
}
|
||||
macro_rules! fatal_error {
|
||||
($($tt:tt)*) => { $crate::fatal_error_(&format_args!($($tt)*)) };
|
||||
}
|
||||
use fatal_error;
|
||||
|
||||
/// Execute a compiler with the given CLI arguments and callbacks.
|
||||
fn run_compiler_and_exit(
|
||||
args: &[String],
|
||||
callbacks: &mut (dyn rustc_driver::Callbacks + Send),
|
||||
) -> ! {
|
||||
// Invoke compiler, and handle return code.
|
||||
// Invoke compiler, catch any unwinding panics and handle return code.
|
||||
let exit_code =
|
||||
rustc_driver::catch_with_exit_code(move || rustc_driver::run_compiler(args, callbacks));
|
||||
std::process::exit(exit_code)
|
||||
exit(exit_code)
|
||||
}
|
||||
|
||||
/// Parses a comma separated list of `T` from the given string:
|
||||
|
|
@ -567,7 +521,7 @@ fn main() {
|
|||
params.precise_interior_mut = false;
|
||||
}
|
||||
_ =>
|
||||
show_error!(
|
||||
fatal_error!(
|
||||
"`-Zmiri-tree-borrows` is required before `-Zmiri-tree-borrows-no-precise-interior-mut`"
|
||||
),
|
||||
};
|
||||
|
|
@ -594,7 +548,7 @@ fn main() {
|
|||
"warn-nobacktrace" =>
|
||||
miri::IsolatedOp::Reject(miri::RejectOpWith::WarningWithoutBacktrace),
|
||||
_ =>
|
||||
show_error!(
|
||||
fatal_error!(
|
||||
"-Zmiri-isolation-error must be `abort`, `hide`, `warn`, or `warn-nobacktrace`"
|
||||
),
|
||||
};
|
||||
|
|
@ -625,16 +579,16 @@ fn main() {
|
|||
"all" => RetagFields::Yes,
|
||||
"none" => RetagFields::No,
|
||||
"scalar" => RetagFields::OnlyScalar,
|
||||
_ => show_error!("`-Zmiri-retag-fields` can only be `all`, `none`, or `scalar`"),
|
||||
_ => fatal_error!("`-Zmiri-retag-fields` can only be `all`, `none`, or `scalar`"),
|
||||
};
|
||||
} else if let Some(param) = arg.strip_prefix("-Zmiri-seed=") {
|
||||
let seed = param.parse::<u64>().unwrap_or_else(|_| {
|
||||
show_error!("-Zmiri-seed must be an integer that fits into u64")
|
||||
fatal_error!("-Zmiri-seed must be an integer that fits into u64")
|
||||
});
|
||||
miri_config.seed = Some(seed);
|
||||
} else if let Some(param) = arg.strip_prefix("-Zmiri-many-seeds=") {
|
||||
let range = parse_range(param).unwrap_or_else(|err| {
|
||||
show_error!(
|
||||
fatal_error!(
|
||||
"-Zmiri-many-seeds requires a range in the form `from..to` or `..to`: {err}"
|
||||
)
|
||||
});
|
||||
|
|
@ -651,51 +605,51 @@ fn main() {
|
|||
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>");
|
||||
fatal_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}")
|
||||
fatal_error!("-Zmiri-track-pointer-tag requires a comma separated list of valid `u64` arguments: {err}")
|
||||
});
|
||||
for id in ids.into_iter().map(miri::BorTag::new) {
|
||||
if let Some(id) = id {
|
||||
miri_config.tracked_pointer_tags.insert(id);
|
||||
} else {
|
||||
show_error!("-Zmiri-track-pointer-tag requires nonzero arguments");
|
||||
fatal_error!("-Zmiri-track-pointer-tag requires nonzero arguments");
|
||||
}
|
||||
}
|
||||
} else if let Some(param) = arg.strip_prefix("-Zmiri-track-alloc-id=") {
|
||||
let ids = parse_comma_list::<NonZero<u64>>(param).unwrap_or_else(|err| {
|
||||
show_error!("-Zmiri-track-alloc-id requires a comma separated list of valid non-zero `u64` arguments: {err}")
|
||||
fatal_error!("-Zmiri-track-alloc-id requires a comma separated list of valid non-zero `u64` arguments: {err}")
|
||||
});
|
||||
miri_config.tracked_alloc_ids.extend(ids.into_iter().map(miri::AllocId));
|
||||
} else if arg == "-Zmiri-track-alloc-accesses" {
|
||||
miri_config.track_alloc_accesses = true;
|
||||
} else if let Some(param) = arg.strip_prefix("-Zmiri-address-reuse-rate=") {
|
||||
miri_config.address_reuse_rate = parse_rate(param)
|
||||
.unwrap_or_else(|err| show_error!("-Zmiri-address-reuse-rate {err}"));
|
||||
.unwrap_or_else(|err| fatal_error!("-Zmiri-address-reuse-rate {err}"));
|
||||
} else if let Some(param) = arg.strip_prefix("-Zmiri-address-reuse-cross-thread-rate=") {
|
||||
miri_config.address_reuse_cross_thread_rate = parse_rate(param)
|
||||
.unwrap_or_else(|err| show_error!("-Zmiri-address-reuse-cross-thread-rate {err}"));
|
||||
.unwrap_or_else(|err| fatal_error!("-Zmiri-address-reuse-cross-thread-rate {err}"));
|
||||
} else if let Some(param) = arg.strip_prefix("-Zmiri-compare-exchange-weak-failure-rate=") {
|
||||
miri_config.cmpxchg_weak_failure_rate = parse_rate(param).unwrap_or_else(|err| {
|
||||
show_error!("-Zmiri-compare-exchange-weak-failure-rate {err}")
|
||||
fatal_error!("-Zmiri-compare-exchange-weak-failure-rate {err}")
|
||||
});
|
||||
} else if let Some(param) = arg.strip_prefix("-Zmiri-preemption-rate=") {
|
||||
miri_config.preemption_rate =
|
||||
parse_rate(param).unwrap_or_else(|err| show_error!("-Zmiri-preemption-rate {err}"));
|
||||
miri_config.preemption_rate = parse_rate(param)
|
||||
.unwrap_or_else(|err| fatal_error!("-Zmiri-preemption-rate {err}"));
|
||||
} else if arg == "-Zmiri-report-progress" {
|
||||
// This makes it take a few seconds between progress reports on my laptop.
|
||||
miri_config.report_progress = Some(1_000_000);
|
||||
} else if let Some(param) = arg.strip_prefix("-Zmiri-report-progress=") {
|
||||
let interval = param.parse::<u32>().unwrap_or_else(|err| {
|
||||
show_error!("-Zmiri-report-progress requires a `u32`: {}", err)
|
||||
fatal_error!("-Zmiri-report-progress requires a `u32`: {}", err)
|
||||
});
|
||||
miri_config.report_progress = Some(interval);
|
||||
} else if let Some(param) = arg.strip_prefix("-Zmiri-provenance-gc=") {
|
||||
let interval = param.parse::<u32>().unwrap_or_else(|err| {
|
||||
show_error!("-Zmiri-provenance-gc requires a `u32`: {}", err)
|
||||
fatal_error!("-Zmiri-provenance-gc requires a `u32`: {}", err)
|
||||
});
|
||||
miri_config.gc_interval = interval;
|
||||
} else if let Some(param) = arg.strip_prefix("-Zmiri-measureme=") {
|
||||
|
|
@ -705,7 +659,7 @@ fn main() {
|
|||
"0" => BacktraceStyle::Off,
|
||||
"1" => BacktraceStyle::Short,
|
||||
"full" => BacktraceStyle::Full,
|
||||
_ => show_error!("-Zmiri-backtrace may only be 0, 1, or full"),
|
||||
_ => fatal_error!("-Zmiri-backtrace may only be 0, 1, or full"),
|
||||
};
|
||||
} else if let Some(param) = arg.strip_prefix("-Zmiri-native-lib=") {
|
||||
let filename = param.to_string();
|
||||
|
|
@ -722,27 +676,27 @@ fn main() {
|
|||
miri_config.native_lib.push(filename.into());
|
||||
}
|
||||
} else {
|
||||
show_error!("-Zmiri-native-lib `{}` does not exist", filename);
|
||||
fatal_error!("-Zmiri-native-lib `{}` does not exist", filename);
|
||||
}
|
||||
} else if arg == "-Zmiri-native-lib-enable-tracing" {
|
||||
miri_config.native_lib_enable_tracing = true;
|
||||
} else if let Some(param) = arg.strip_prefix("-Zmiri-num-cpus=") {
|
||||
let num_cpus = param
|
||||
.parse::<u32>()
|
||||
.unwrap_or_else(|err| show_error!("-Zmiri-num-cpus requires a `u32`: {}", err));
|
||||
.unwrap_or_else(|err| fatal_error!("-Zmiri-num-cpus requires a `u32`: {}", err));
|
||||
if !(1..=miri::MAX_CPUS).contains(&usize::try_from(num_cpus).unwrap()) {
|
||||
show_error!("-Zmiri-num-cpus must be in the range 1..={}", miri::MAX_CPUS);
|
||||
fatal_error!("-Zmiri-num-cpus must be in the range 1..={}", miri::MAX_CPUS);
|
||||
}
|
||||
miri_config.num_cpus = num_cpus;
|
||||
} else if let Some(param) = arg.strip_prefix("-Zmiri-force-page-size=") {
|
||||
let page_size = param.parse::<u64>().unwrap_or_else(|err| {
|
||||
show_error!("-Zmiri-force-page-size requires a `u64`: {}", err)
|
||||
fatal_error!("-Zmiri-force-page-size requires a `u64`: {}", err)
|
||||
});
|
||||
// Convert from kilobytes to bytes.
|
||||
let page_size = if page_size.is_power_of_two() {
|
||||
page_size * 1024
|
||||
} else {
|
||||
show_error!("-Zmiri-force-page-size requires a power of 2: {page_size}");
|
||||
fatal_error!("-Zmiri-force-page-size requires a power of 2: {page_size}");
|
||||
};
|
||||
miri_config.page_size = Some(page_size);
|
||||
} else {
|
||||
|
|
@ -753,22 +707,22 @@ fn main() {
|
|||
// Tree Borrows implies strict provenance, and is not compatible with native calls.
|
||||
if matches!(miri_config.borrow_tracker, Some(BorrowTrackerMethod::TreeBorrows { .. })) {
|
||||
if miri_config.provenance_mode != ProvenanceMode::Strict {
|
||||
show_error!(
|
||||
fatal_error!(
|
||||
"Tree Borrows does not support integer-to-pointer casts, and hence requires strict provenance"
|
||||
);
|
||||
}
|
||||
if !miri_config.native_lib.is_empty() {
|
||||
show_error!("Tree Borrows is not compatible with calling native functions");
|
||||
fatal_error!("Tree Borrows is not compatible with calling native functions");
|
||||
}
|
||||
}
|
||||
|
||||
// Native calls and strict provenance are not compatible.
|
||||
if !miri_config.native_lib.is_empty() && miri_config.provenance_mode == ProvenanceMode::Strict {
|
||||
show_error!("strict provenance is not compatible with calling native functions");
|
||||
fatal_error!("strict provenance is not compatible with calling native functions");
|
||||
}
|
||||
// You can set either one seed or many.
|
||||
if many_seeds.is_some() && miri_config.seed.is_some() {
|
||||
show_error!("Only one of `-Zmiri-seed` and `-Zmiri-many-seeds can be set");
|
||||
fatal_error!("Only one of `-Zmiri-seed` and `-Zmiri-many-seeds can be set");
|
||||
}
|
||||
|
||||
// Ensure we have parallelism for many-seeds mode.
|
||||
|
|
@ -784,12 +738,12 @@ fn main() {
|
|||
assert_eq!(genmc_config.is_some(), miri_config.genmc_mode);
|
||||
if genmc_config.is_some() {
|
||||
if !miri_config.data_race_detector {
|
||||
show_error!("Cannot disable data race detection in GenMC mode (currently)");
|
||||
fatal_error!("Cannot disable data race detection in GenMC mode (currently)");
|
||||
} else if !miri_config.weak_memory_emulation {
|
||||
show_error!("Cannot disable weak memory emulation in GenMC mode");
|
||||
fatal_error!("Cannot disable weak memory emulation in GenMC mode");
|
||||
}
|
||||
} else if miri_config.weak_memory_emulation && !miri_config.data_race_detector {
|
||||
show_error!(
|
||||
fatal_error!(
|
||||
"Weak memory emulation cannot be enabled when the data race detector is disabled"
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -971,14 +971,6 @@ pub trait EvalContextExt<'tcx>: MiriInterpCxExt<'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
/// After all threads are done running, this allows data races to occur for subsequent
|
||||
/// 'administrative' machine accesses (that logically happen outside of the Abstract Machine).
|
||||
fn allow_data_races_all_threads_done(&mut self) {
|
||||
let this = self.eval_context_ref();
|
||||
assert!(this.have_all_terminated());
|
||||
this.machine.data_race.set_ongoing_action_data_race_free(true);
|
||||
}
|
||||
|
||||
/// Calls the callback with the "release" clock of the current thread.
|
||||
/// Other threads can acquire this clock in the future to establish synchronization
|
||||
/// with this program point.
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
use std::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use rustc_index::Idx;
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::thread::DynUnblockCallback;
|
||||
use super::vector_clock::VClock;
|
||||
use crate::*;
|
||||
|
||||
super::sync::declare_id!(InitOnceId);
|
||||
|
||||
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
|
||||
/// The current status of a one time initialization.
|
||||
pub enum InitOnceStatus {
|
||||
|
|
@ -25,44 +23,70 @@ pub(super) struct InitOnce {
|
|||
clock: VClock,
|
||||
}
|
||||
|
||||
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
|
||||
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
impl InitOnce {
|
||||
#[inline]
|
||||
fn init_once_status(&mut self, id: InitOnceId) -> InitOnceStatus {
|
||||
let this = self.eval_context_ref();
|
||||
this.machine.sync.init_onces[id].status
|
||||
}
|
||||
|
||||
/// Put the thread into the queue waiting for the initialization.
|
||||
#[inline]
|
||||
fn init_once_enqueue_and_block(&mut self, id: InitOnceId, callback: DynUnblockCallback<'tcx>) {
|
||||
let this = self.eval_context_mut();
|
||||
let thread = this.active_thread();
|
||||
let init_once = &mut this.machine.sync.init_onces[id];
|
||||
assert_ne!(init_once.status, InitOnceStatus::Complete, "queueing on complete init once");
|
||||
init_once.waiters.push_back(thread);
|
||||
this.block_thread(BlockReason::InitOnce(id), None, callback);
|
||||
pub fn status(&self) -> InitOnceStatus {
|
||||
self.status
|
||||
}
|
||||
|
||||
/// Begin initializing this InitOnce. Must only be called after checking that it is currently
|
||||
/// uninitialized.
|
||||
#[inline]
|
||||
fn init_once_begin(&mut self, id: InitOnceId) {
|
||||
let this = self.eval_context_mut();
|
||||
let init_once = &mut this.machine.sync.init_onces[id];
|
||||
pub fn begin(&mut self) {
|
||||
assert_eq!(
|
||||
init_once.status,
|
||||
self.status(),
|
||||
InitOnceStatus::Uninitialized,
|
||||
"beginning already begun or complete init once"
|
||||
);
|
||||
init_once.status = InitOnceStatus::Begun;
|
||||
self.status = InitOnceStatus::Begun;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct InitOnceRef(Rc<RefCell<InitOnce>>);
|
||||
|
||||
impl InitOnceRef {
|
||||
pub fn new() -> Self {
|
||||
Self(Default::default())
|
||||
}
|
||||
|
||||
pub fn status(&self) -> InitOnceStatus {
|
||||
self.0.borrow().status()
|
||||
}
|
||||
|
||||
pub fn begin(&self) {
|
||||
self.0.borrow_mut().begin();
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitProvenance for InitOnceRef {
|
||||
// InitOnce contains no provenance.
|
||||
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
|
||||
}
|
||||
|
||||
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
|
||||
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
/// Put the thread into the queue waiting for the initialization.
|
||||
#[inline]
|
||||
fn init_once_enqueue_and_block(
|
||||
&mut self,
|
||||
init_once_ref: InitOnceRef,
|
||||
callback: DynUnblockCallback<'tcx>,
|
||||
) {
|
||||
let this = self.eval_context_mut();
|
||||
let thread = this.active_thread();
|
||||
let mut init_once = init_once_ref.0.borrow_mut();
|
||||
assert_ne!(init_once.status, InitOnceStatus::Complete, "queueing on complete init once");
|
||||
|
||||
init_once.waiters.push_back(thread);
|
||||
this.block_thread(BlockReason::InitOnce, None, callback);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn init_once_complete(&mut self, id: InitOnceId) -> InterpResult<'tcx> {
|
||||
fn init_once_complete(&mut self, init_once_ref: &InitOnceRef) -> InterpResult<'tcx> {
|
||||
let this = self.eval_context_mut();
|
||||
let init_once = &mut this.machine.sync.init_onces[id];
|
||||
|
||||
let mut init_once = init_once_ref.0.borrow_mut();
|
||||
assert_eq!(
|
||||
init_once.status,
|
||||
InitOnceStatus::Begun,
|
||||
|
|
@ -79,17 +103,19 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
|
||||
// Wake up everyone.
|
||||
// need to take the queue to avoid having `this` be borrowed multiple times
|
||||
for waiter in std::mem::take(&mut init_once.waiters) {
|
||||
this.unblock_thread(waiter, BlockReason::InitOnce(id))?;
|
||||
let waiters = std::mem::take(&mut init_once.waiters);
|
||||
drop(init_once);
|
||||
for waiter in waiters {
|
||||
this.unblock_thread(waiter, BlockReason::InitOnce)?;
|
||||
}
|
||||
|
||||
interp_ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn init_once_fail(&mut self, id: InitOnceId) -> InterpResult<'tcx> {
|
||||
fn init_once_fail(&mut self, init_once_ref: &InitOnceRef) -> InterpResult<'tcx> {
|
||||
let this = self.eval_context_mut();
|
||||
let init_once = &mut this.machine.sync.init_onces[id];
|
||||
let mut init_once = init_once_ref.0.borrow_mut();
|
||||
assert_eq!(
|
||||
init_once.status,
|
||||
InitOnceStatus::Begun,
|
||||
|
|
@ -106,7 +132,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
|
||||
// Wake up one waiting thread, so they can go ahead and try to init this.
|
||||
if let Some(waiter) = init_once.waiters.pop_front() {
|
||||
this.unblock_thread(waiter, BlockReason::InitOnce(id))?;
|
||||
drop(init_once);
|
||||
this.unblock_thread(waiter, BlockReason::InitOnce)?;
|
||||
}
|
||||
|
||||
interp_ok(())
|
||||
|
|
@ -115,15 +142,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
/// Synchronize with the previous completion of an InitOnce.
|
||||
/// Must only be called after checking that it is complete.
|
||||
#[inline]
|
||||
fn init_once_observe_completed(&mut self, id: InitOnceId) {
|
||||
fn init_once_observe_completed(&mut self, init_once_ref: &InitOnceRef) {
|
||||
let this = self.eval_context_mut();
|
||||
let init_once = init_once_ref.0.borrow();
|
||||
|
||||
assert_eq!(
|
||||
this.init_once_status(id),
|
||||
init_once.status,
|
||||
InitOnceStatus::Complete,
|
||||
"observing the completion of incomplete init once"
|
||||
);
|
||||
|
||||
this.acquire_clock(&this.machine.sync.init_onces[id].clock);
|
||||
this.acquire_clock(&init_once.clock);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,18 +9,9 @@ mod vector_clock;
|
|||
pub mod weak_memory;
|
||||
|
||||
// Import either the real genmc adapter or a dummy module.
|
||||
cfg_select! {
|
||||
feature = "genmc" => {
|
||||
mod genmc;
|
||||
pub use self::genmc::{GenmcCtx, GenmcConfig};
|
||||
}
|
||||
_ => {
|
||||
#[path = "genmc/dummy.rs"]
|
||||
mod genmc_dummy;
|
||||
use self::genmc_dummy as genmc;
|
||||
pub use self::genmc::{GenmcCtx, GenmcConfig};
|
||||
}
|
||||
}
|
||||
#[cfg_attr(not(feature = "genmc"), path = "genmc/dummy.rs")]
|
||||
mod genmc;
|
||||
|
||||
pub use self::data_race_handler::{AllocDataRaceHandler, GlobalDataRaceHandler};
|
||||
pub use self::genmc::{GenmcConfig, GenmcCtx};
|
||||
pub use self::vector_clock::VClock;
|
||||
|
|
|
|||
|
|
@ -8,45 +8,10 @@ use std::time::Duration;
|
|||
|
||||
use rustc_abi::Size;
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_index::{Idx, IndexVec};
|
||||
|
||||
use super::init_once::InitOnce;
|
||||
use super::vector_clock::VClock;
|
||||
use crate::*;
|
||||
|
||||
/// We cannot use the `newtype_index!` macro because we have to use 0 as a
|
||||
/// sentinel value meaning that the identifier is not assigned. This is because
|
||||
/// the pthreads static initializers initialize memory with zeros (see the
|
||||
/// `src/shims/sync.rs` file).
|
||||
macro_rules! declare_id {
|
||||
($name: ident) => {
|
||||
/// 0 is used to indicate that the id was not yet assigned and,
|
||||
/// therefore, is not a valid identifier.
|
||||
#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
|
||||
pub struct $name(std::num::NonZero<u32>);
|
||||
|
||||
impl $crate::VisitProvenance for $name {
|
||||
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
|
||||
}
|
||||
|
||||
impl Idx for $name {
|
||||
fn new(idx: usize) -> Self {
|
||||
// We use 0 as a sentinel value (see the comment above) and,
|
||||
// therefore, need to shift by one when converting from an index
|
||||
// into a vector.
|
||||
let shifted_idx = u32::try_from(idx).unwrap().strict_add(1);
|
||||
$name(std::num::NonZero::new(shifted_idx).unwrap())
|
||||
}
|
||||
fn index(self) -> usize {
|
||||
// See the comment in `Self::new`.
|
||||
// (This cannot underflow because `self.0` is `NonZero<u32>`.)
|
||||
usize::try_from(self.0.get() - 1).unwrap()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(super) use declare_id;
|
||||
|
||||
/// The mutex state.
|
||||
#[derive(Default, Debug)]
|
||||
struct Mutex {
|
||||
|
|
@ -64,8 +29,8 @@ struct Mutex {
|
|||
pub struct MutexRef(Rc<RefCell<Mutex>>);
|
||||
|
||||
impl MutexRef {
|
||||
fn new() -> Self {
|
||||
MutexRef(Rc::new(RefCell::new(Mutex::default())))
|
||||
pub fn new() -> Self {
|
||||
Self(Default::default())
|
||||
}
|
||||
|
||||
/// Get the id of the thread that currently owns this lock, or `None` if it is not locked.
|
||||
|
|
@ -75,9 +40,8 @@ impl MutexRef {
|
|||
}
|
||||
|
||||
impl VisitProvenance for MutexRef {
|
||||
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
|
||||
// Mutex contains no provenance.
|
||||
}
|
||||
// Mutex contains no provenance.
|
||||
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
|
||||
}
|
||||
|
||||
/// The read-write lock state.
|
||||
|
|
@ -138,8 +102,8 @@ impl RwLock {
|
|||
pub struct RwLockRef(Rc<RefCell<RwLock>>);
|
||||
|
||||
impl RwLockRef {
|
||||
fn new() -> Self {
|
||||
RwLockRef(Rc::new(RefCell::new(RwLock::default())))
|
||||
pub fn new() -> Self {
|
||||
Self(Default::default())
|
||||
}
|
||||
|
||||
pub fn is_locked(&self) -> bool {
|
||||
|
|
@ -152,13 +116,10 @@ impl RwLockRef {
|
|||
}
|
||||
|
||||
impl VisitProvenance for RwLockRef {
|
||||
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
|
||||
// RwLockRef contains no provenance.
|
||||
}
|
||||
// RwLock contains no provenance.
|
||||
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
|
||||
}
|
||||
|
||||
declare_id!(CondvarId);
|
||||
|
||||
/// The conditional variable state.
|
||||
#[derive(Default, Debug)]
|
||||
struct Condvar {
|
||||
|
|
@ -171,6 +132,24 @@ struct Condvar {
|
|||
clock: VClock,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct CondvarRef(Rc<RefCell<Condvar>>);
|
||||
|
||||
impl CondvarRef {
|
||||
pub fn new() -> Self {
|
||||
Self(Default::default())
|
||||
}
|
||||
|
||||
pub fn is_awaited(&self) -> bool {
|
||||
!self.0.borrow().waiters.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitProvenance for CondvarRef {
|
||||
// Condvar contains no provenance.
|
||||
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
|
||||
}
|
||||
|
||||
/// The futex state.
|
||||
#[derive(Default, Debug)]
|
||||
struct Futex {
|
||||
|
|
@ -183,19 +162,22 @@ struct Futex {
|
|||
clock: VClock,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct FutexRef(Rc<RefCell<Futex>>);
|
||||
|
||||
impl FutexRef {
|
||||
pub fn new() -> Self {
|
||||
Self(Default::default())
|
||||
}
|
||||
|
||||
pub fn waiters(&self) -> usize {
|
||||
self.0.borrow().waiters.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitProvenance for FutexRef {
|
||||
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
|
||||
// No provenance in `Futex`.
|
||||
}
|
||||
// Futex contains no provenance.
|
||||
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
|
||||
}
|
||||
|
||||
/// A thread waiting on a futex.
|
||||
|
|
@ -207,13 +189,6 @@ struct FutexWaiter {
|
|||
bitset: u32,
|
||||
}
|
||||
|
||||
/// The state of all synchronization objects.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct SynchronizationObjects {
|
||||
condvars: IndexVec<CondvarId, Condvar>,
|
||||
pub(super) init_onces: IndexVec<InitOnceId, InitOnce>,
|
||||
}
|
||||
|
||||
// Private extension trait for local helper methods
|
||||
impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {}
|
||||
pub(super) trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
|
|
@ -237,23 +212,6 @@ pub(super) trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
impl SynchronizationObjects {
|
||||
pub fn mutex_create(&mut self) -> MutexRef {
|
||||
MutexRef::new()
|
||||
}
|
||||
pub fn rwlock_create(&mut self) -> RwLockRef {
|
||||
RwLockRef::new()
|
||||
}
|
||||
|
||||
pub fn condvar_create(&mut self) -> CondvarId {
|
||||
self.condvars.push(Default::default())
|
||||
}
|
||||
|
||||
pub fn init_once_create(&mut self) -> InitOnceId {
|
||||
self.init_onces.push(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> AllocExtra<'tcx> {
|
||||
fn get_sync<T: 'static>(&self, offset: Size) -> Option<&T> {
|
||||
self.sync.get(&offset).and_then(|s| s.downcast_ref::<T>())
|
||||
|
|
@ -663,19 +621,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
);
|
||||
}
|
||||
|
||||
/// Is the conditional variable awaited?
|
||||
#[inline]
|
||||
fn condvar_is_awaited(&mut self, id: CondvarId) -> bool {
|
||||
let this = self.eval_context_mut();
|
||||
!this.machine.sync.condvars[id].waiters.is_empty()
|
||||
}
|
||||
|
||||
/// Release the mutex and let the current thread wait on the given condition variable.
|
||||
/// Once it is signaled, the mutex will be acquired and `retval_succ` will be written to `dest`.
|
||||
/// If the timeout happens first, `retval_timeout` will be written to `dest`.
|
||||
fn condvar_wait(
|
||||
&mut self,
|
||||
condvar: CondvarId,
|
||||
condvar_ref: CondvarRef,
|
||||
mutex_ref: MutexRef,
|
||||
timeout: Option<(TimeoutClock, TimeoutAnchor, Duration)>,
|
||||
retval_succ: Scalar,
|
||||
|
|
@ -695,14 +646,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
);
|
||||
}
|
||||
let thread = this.active_thread();
|
||||
let waiters = &mut this.machine.sync.condvars[condvar].waiters;
|
||||
waiters.push_back(thread);
|
||||
|
||||
condvar_ref.0.borrow_mut().waiters.push_back(thread);
|
||||
this.block_thread(
|
||||
BlockReason::Condvar(condvar),
|
||||
BlockReason::Condvar,
|
||||
timeout,
|
||||
callback!(
|
||||
@capture<'tcx> {
|
||||
condvar: CondvarId,
|
||||
condvar_ref: CondvarRef,
|
||||
mutex_ref: MutexRef,
|
||||
retval_succ: Scalar,
|
||||
retval_timeout: Scalar,
|
||||
|
|
@ -714,7 +665,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
// The condvar was signaled. Make sure we get the clock for that.
|
||||
if let Some(data_race) = this.machine.data_race.as_vclocks_ref() {
|
||||
data_race.acquire_clock(
|
||||
&this.machine.sync.condvars[condvar].clock,
|
||||
&condvar_ref.0.borrow().clock,
|
||||
&this.machine.threads,
|
||||
);
|
||||
}
|
||||
|
|
@ -725,7 +676,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
UnblockKind::TimedOut => {
|
||||
// We have to remove the waiter from the queue again.
|
||||
let thread = this.active_thread();
|
||||
let waiters = &mut this.machine.sync.condvars[condvar].waiters;
|
||||
let waiters = &mut condvar_ref.0.borrow_mut().waiters;
|
||||
waiters.retain(|waiter| *waiter != thread);
|
||||
// Now get back the lock.
|
||||
this.condvar_reacquire_mutex(mutex_ref, retval_timeout, dest)
|
||||
|
|
@ -739,9 +690,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
|
||||
/// Wake up some thread (if there is any) sleeping on the conditional
|
||||
/// variable. Returns `true` iff any thread was woken up.
|
||||
fn condvar_signal(&mut self, id: CondvarId) -> InterpResult<'tcx, bool> {
|
||||
fn condvar_signal(&mut self, condvar_ref: &CondvarRef) -> InterpResult<'tcx, bool> {
|
||||
let this = self.eval_context_mut();
|
||||
let condvar = &mut this.machine.sync.condvars[id];
|
||||
let mut condvar = condvar_ref.0.borrow_mut();
|
||||
|
||||
// Each condvar signal happens-before the end of the condvar wake
|
||||
if let Some(data_race) = this.machine.data_race.as_vclocks_ref() {
|
||||
|
|
@ -750,7 +701,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
let Some(waiter) = condvar.waiters.pop_front() else {
|
||||
return interp_ok(false);
|
||||
};
|
||||
this.unblock_thread(waiter, BlockReason::Condvar(id))?;
|
||||
drop(condvar);
|
||||
this.unblock_thread(waiter, BlockReason::Condvar)?;
|
||||
interp_ok(true)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -97,13 +97,13 @@ pub enum BlockReason {
|
|||
/// Blocked on a mutex.
|
||||
Mutex,
|
||||
/// Blocked on a condition variable.
|
||||
Condvar(CondvarId),
|
||||
Condvar,
|
||||
/// Blocked on a reader-writer lock.
|
||||
RwLock,
|
||||
/// Blocked on a Futex variable.
|
||||
Futex,
|
||||
/// Blocked on an InitOnce.
|
||||
InitOnce(InitOnceId),
|
||||
InitOnce,
|
||||
/// Blocked on epoll.
|
||||
Epoll,
|
||||
/// Blocked on eventfd.
|
||||
|
|
|
|||
|
|
@ -283,16 +283,6 @@ impl<'tcx> MainThreadState<'tcx> {
|
|||
// to be like a global `static`, so that all memory reached by it is considered to "not leak".
|
||||
this.terminate_active_thread(TlsAllocAction::Leak)?;
|
||||
|
||||
// Machine cleanup. Only do this if all threads have terminated; threads that are still running
|
||||
// might cause Stacked Borrows errors (https://github.com/rust-lang/miri/issues/2396).
|
||||
if this.have_all_terminated() {
|
||||
// Even if all threads have terminated, we have to beware of data races since some threads
|
||||
// might not have joined the main thread (https://github.com/rust-lang/miri/issues/2020,
|
||||
// https://github.com/rust-lang/miri/issues/2508).
|
||||
this.allow_data_races_all_threads_done();
|
||||
EnvVars::cleanup(this).expect("error during env var cleanup");
|
||||
}
|
||||
|
||||
// Stop interpreter loop.
|
||||
throw_machine_stop!(TerminationInfo::Exit { code: exit_code, leak_check: true });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1333,7 +1333,6 @@ where
|
|||
|
||||
/// Check that the number of varargs is at least the minimum what we expect.
|
||||
/// Fixed args should not be included.
|
||||
/// Use `check_vararg_fixed_arg_count` to extract the varargs slice from full function arguments.
|
||||
pub fn check_min_vararg_count<'a, 'tcx, const N: usize>(
|
||||
name: &'a str,
|
||||
args: &'a [OpTy<'tcx>],
|
||||
|
|
|
|||
|
|
@ -125,10 +125,8 @@ pub use crate::concurrency::cpu_affinity::MAX_CPUS;
|
|||
pub use crate::concurrency::data_race::{
|
||||
AtomicFenceOrd, AtomicReadOrd, AtomicRwOrd, AtomicWriteOrd, EvalContextExt as _,
|
||||
};
|
||||
pub use crate::concurrency::init_once::{EvalContextExt as _, InitOnceId};
|
||||
pub use crate::concurrency::sync::{
|
||||
CondvarId, EvalContextExt as _, MutexRef, RwLockRef, SynchronizationObjects,
|
||||
};
|
||||
pub use crate::concurrency::init_once::{EvalContextExt as _, InitOnceRef};
|
||||
pub use crate::concurrency::sync::{CondvarRef, EvalContextExt as _, MutexRef, RwLockRef};
|
||||
pub use crate::concurrency::thread::{
|
||||
BlockReason, DynUnblockCallback, EvalContextExt as _, StackEmptyCallback, ThreadId,
|
||||
ThreadManager, TimeoutAnchor, TimeoutClock, UnblockKind,
|
||||
|
|
|
|||
|
|
@ -130,11 +130,11 @@ pub enum MiriMemoryKind {
|
|||
WinHeap,
|
||||
/// Windows "local" memory (to be freed with `LocalFree`)
|
||||
WinLocal,
|
||||
/// Memory for args, errno, and other parts of the machine-managed environment.
|
||||
/// Memory for args, errno, env vars, and other parts of the machine-managed environment.
|
||||
/// This memory may leak.
|
||||
Machine,
|
||||
/// Memory allocated by the runtime (e.g. env vars). Separate from `Machine`
|
||||
/// because we clean it up and leak-check it.
|
||||
/// Memory allocated by the runtime, e.g. for readdir. Separate from `Machine` because we clean
|
||||
/// it up (or expect the user to invoke operations that clean it up) and leak-check it.
|
||||
Runtime,
|
||||
/// Globals copied from `tcx`.
|
||||
/// This memory may leak.
|
||||
|
|
@ -499,9 +499,6 @@ pub struct MiriMachine<'tcx> {
|
|||
/// in `sched_getaffinity`
|
||||
pub(crate) thread_cpu_affinity: FxHashMap<ThreadId, CpuAffinityMask>,
|
||||
|
||||
/// The state of the primitive synchronization objects.
|
||||
pub(crate) sync: SynchronizationObjects,
|
||||
|
||||
/// Precomputed `TyLayout`s for primitive data types that are commonly used inside Miri.
|
||||
pub(crate) layouts: PrimitiveLayouts<'tcx>,
|
||||
|
||||
|
|
@ -713,7 +710,6 @@ impl<'tcx> MiriMachine<'tcx> {
|
|||
layouts,
|
||||
threads,
|
||||
thread_cpu_affinity,
|
||||
sync: SynchronizationObjects::default(),
|
||||
static_roots: Vec::new(),
|
||||
profiler,
|
||||
string_cache: Default::default(),
|
||||
|
|
@ -903,7 +899,6 @@ impl VisitProvenance for MiriMachine<'_> {
|
|||
let MiriMachine {
|
||||
threads,
|
||||
thread_cpu_affinity: _,
|
||||
sync: _,
|
||||
tls,
|
||||
env_vars,
|
||||
main_fn_ret_place,
|
||||
|
|
@ -1019,6 +1014,8 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
|
|||
|
||||
const PANIC_ON_ALLOC_FAIL: bool = false;
|
||||
|
||||
const TRACING_ENABLED: bool = cfg!(feature = "tracing");
|
||||
|
||||
#[inline(always)]
|
||||
fn enforce_alignment(ecx: &MiriInterpCx<'tcx>) -> bool {
|
||||
ecx.machine.check_alignment != AlignmentCheck::None
|
||||
|
|
|
|||
|
|
@ -59,15 +59,6 @@ impl<'tcx> EnvVars<'tcx> {
|
|||
interp_ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn cleanup(ecx: &mut InterpCx<'tcx, MiriMachine<'tcx>>) -> InterpResult<'tcx> {
|
||||
let this = ecx.eval_context_mut();
|
||||
match this.machine.env_vars {
|
||||
EnvVars::Unix(_) => UnixEnvVars::cleanup(this),
|
||||
EnvVars::Windows(_) => interp_ok(()), // no cleanup needed
|
||||
EnvVars::Uninit => interp_ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn unix(&self) -> &UnixEnvVars<'tcx> {
|
||||
match self {
|
||||
EnvVars::Unix(env) => env,
|
||||
|
|
@ -110,8 +101,30 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get the process identifier.
|
||||
fn get_pid(&self) -> u32 {
|
||||
let this = self.eval_context_ref();
|
||||
if this.machine.communicate() { std::process::id() } else { 1000 }
|
||||
}
|
||||
|
||||
/// Get an "OS" thread ID for the current thread.
|
||||
fn get_current_tid(&self) -> u32 {
|
||||
let this = self.eval_context_ref();
|
||||
self.get_tid(this.machine.threads.active_thread())
|
||||
}
|
||||
|
||||
/// Get an "OS" thread ID for any thread.
|
||||
fn get_tid(&self, thread: ThreadId) -> u32 {
|
||||
let this = self.eval_context_ref();
|
||||
let index = thread.to_u32();
|
||||
let target_os = &this.tcx.sess.target.os;
|
||||
if target_os == "linux" || target_os == "netbsd" {
|
||||
// On Linux, the main thread has PID == TID so we uphold this. NetBSD also appears
|
||||
// to exhibit the same behavior, though I can't find a citation.
|
||||
this.get_pid().strict_add(index)
|
||||
} else {
|
||||
// Other platforms do not display any relationship between PID and TID.
|
||||
index
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ impl<'tcx> MiriMachine<'tcx> {
|
|||
ecx,
|
||||
&["__cxa_thread_atexit_impl", "__clock_gettime64"],
|
||||
)?;
|
||||
Self::weak_symbol_extern_statics(ecx, &["getrandom", "statx"])?;
|
||||
Self::weak_symbol_extern_statics(ecx, &["getrandom", "gettid", "statx"])?;
|
||||
}
|
||||
"freebsd" => {
|
||||
Self::null_ptr_extern_statics(ecx, &["__cxa_thread_atexit_impl"])?;
|
||||
|
|
|
|||
|
|
@ -615,7 +615,9 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
// This is a no-op shim that only exists to prevent making the allocator shims instantly stable.
|
||||
let [] = this.check_shim(abi, CanonAbi::Rust, link_name, args)?;
|
||||
}
|
||||
name if name == this.mangle_internal_symbol("__rust_alloc_error_handler_should_panic_v2") => {
|
||||
name if name
|
||||
== this.mangle_internal_symbol("__rust_alloc_error_handler_should_panic_v2") =>
|
||||
{
|
||||
// Gets the value of the `oom` option.
|
||||
let [] = this.check_shim(abi, CanonAbi::Rust, link_name, args)?;
|
||||
let val = this.tcx.sess.opts.unstable_opts.oom.should_panic();
|
||||
|
|
|
|||
|
|
@ -330,18 +330,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
interp_ok(Scalar::from_i32(0)) // KERN_SUCCESS
|
||||
}
|
||||
|
||||
fn nanosleep(
|
||||
&mut self,
|
||||
req_op: &OpTy<'tcx>,
|
||||
_rem: &OpTy<'tcx>, // Signal handlers are not supported, so rem will never be written to.
|
||||
) -> InterpResult<'tcx, Scalar> {
|
||||
fn nanosleep(&mut self, duration: &OpTy<'tcx>, rem: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
this.assert_target_os_is_unix("nanosleep");
|
||||
|
||||
let req = this.deref_pointer_as(req_op, this.libc_ty_layout("timespec"))?;
|
||||
let duration = this.deref_pointer_as(duration, this.libc_ty_layout("timespec"))?;
|
||||
let _rem = this.read_pointer(rem)?; // Signal handlers are not supported, so rem will never be written to.
|
||||
|
||||
let duration = match this.read_timespec(&req)? {
|
||||
let duration = match this.read_timespec(&duration)? {
|
||||
Some(duration) => duration,
|
||||
None => {
|
||||
return this.set_last_error_and_return_i32(LibcError("EINVAL"));
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::env;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::io::ErrorKind;
|
||||
use std::{env, mem};
|
||||
|
||||
use rustc_abi::{FieldIdx, Size};
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
|
|
@ -50,20 +50,6 @@ impl<'tcx> UnixEnvVars<'tcx> {
|
|||
interp_ok(UnixEnvVars { map: env_vars_machine, environ })
|
||||
}
|
||||
|
||||
pub(crate) fn cleanup(ecx: &mut InterpCx<'tcx, MiriMachine<'tcx>>) -> InterpResult<'tcx> {
|
||||
// Deallocate individual env vars.
|
||||
let env_vars = mem::take(&mut ecx.machine.env_vars.unix_mut().map);
|
||||
for (_name, ptr) in env_vars {
|
||||
ecx.deallocate_ptr(ptr, None, MiriMemoryKind::Runtime.into())?;
|
||||
}
|
||||
// Deallocate environ var list.
|
||||
let environ = &ecx.machine.env_vars.unix().environ;
|
||||
let old_vars_ptr = ecx.read_pointer(environ)?;
|
||||
ecx.deallocate_ptr(old_vars_ptr, None, MiriMemoryKind::Runtime.into())?;
|
||||
|
||||
interp_ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn environ(&self) -> Pointer {
|
||||
self.environ.ptr()
|
||||
}
|
||||
|
|
@ -112,7 +98,7 @@ fn alloc_env_var<'tcx>(
|
|||
let mut name_osstring = name.to_os_string();
|
||||
name_osstring.push("=");
|
||||
name_osstring.push(value);
|
||||
ecx.alloc_os_str_as_c_str(name_osstring.as_os_str(), MiriMemoryKind::Runtime.into())
|
||||
ecx.alloc_os_str_as_c_str(name_osstring.as_os_str(), MiriMemoryKind::Machine.into())
|
||||
}
|
||||
|
||||
/// Allocates an `environ` block with the given list of pointers.
|
||||
|
|
@ -128,7 +114,7 @@ fn alloc_environ_block<'tcx>(
|
|||
ecx.machine.layouts.mut_raw_ptr.ty,
|
||||
u64::try_from(vars.len()).unwrap(),
|
||||
))?;
|
||||
let vars_place = ecx.allocate(vars_layout, MiriMemoryKind::Runtime.into())?;
|
||||
let vars_place = ecx.allocate(vars_layout, MiriMemoryKind::Machine.into())?;
|
||||
for (idx, var) in vars.into_iter_enumerated() {
|
||||
let place = ecx.project_field(&vars_place, idx)?;
|
||||
ecx.write_pointer(var, &place)?;
|
||||
|
|
@ -171,7 +157,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
if let Some((name, value)) = new {
|
||||
let var_ptr = alloc_env_var(this, &name, &value)?;
|
||||
if let Some(var) = this.machine.env_vars.unix_mut().map.insert(name, var_ptr) {
|
||||
this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?;
|
||||
this.deallocate_ptr(var, None, MiriMemoryKind::Machine.into())?;
|
||||
}
|
||||
this.update_environ()?;
|
||||
interp_ok(Scalar::from_i32(0)) // return zero on success
|
||||
|
|
@ -195,7 +181,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
}
|
||||
if let Some(old) = success {
|
||||
if let Some(var) = old {
|
||||
this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?;
|
||||
this.deallocate_ptr(var, None, MiriMemoryKind::Machine.into())?;
|
||||
}
|
||||
this.update_environ()?;
|
||||
interp_ok(Scalar::from_i32(0))
|
||||
|
|
@ -253,7 +239,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
// Deallocate the old environ list.
|
||||
let environ = this.machine.env_vars.unix().environ.clone();
|
||||
let old_vars_ptr = this.read_pointer(&environ)?;
|
||||
this.deallocate_ptr(old_vars_ptr, None, MiriMemoryKind::Runtime.into())?;
|
||||
this.deallocate_ptr(old_vars_ptr, None, MiriMemoryKind::Machine.into())?;
|
||||
|
||||
// Write the new list.
|
||||
let vals = this.machine.env_vars.unix().map.values().copied().collect();
|
||||
|
|
@ -274,15 +260,52 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
interp_ok(Scalar::from_u32(this.get_pid()))
|
||||
}
|
||||
|
||||
fn linux_gettid(&mut self) -> InterpResult<'tcx, Scalar> {
|
||||
/// The `gettid`-like function for Unix platforms that take no parameters and return a 32-bit
|
||||
/// integer. It is not always named "gettid".
|
||||
fn unix_gettid(&mut self, link_name: &str) -> InterpResult<'tcx, Scalar> {
|
||||
let this = self.eval_context_ref();
|
||||
this.assert_target_os("linux", "gettid");
|
||||
this.assert_target_os_is_unix(link_name);
|
||||
|
||||
let index = this.machine.threads.active_thread().to_u32();
|
||||
// For most platforms the return type is an `i32`, but some are unsigned. The TID
|
||||
// will always be positive so we don't need to differentiate.
|
||||
interp_ok(Scalar::from_u32(this.get_current_tid()))
|
||||
}
|
||||
|
||||
// Compute a TID for this thread, ensuring that the main thread has PID == TID.
|
||||
let tid = this.get_pid().strict_add(index);
|
||||
/// The Apple-specific `int pthread_threadid_np(pthread_t thread, uint64_t *thread_id)`, which
|
||||
/// allows querying the ID for arbitrary threads, identified by their pthread_t.
|
||||
///
|
||||
/// API documentation: <https://www.manpagez.com/man/3/pthread_threadid_np/>.
|
||||
fn apple_pthread_threadip_np(
|
||||
&mut self,
|
||||
thread_op: &OpTy<'tcx>,
|
||||
tid_op: &OpTy<'tcx>,
|
||||
) -> InterpResult<'tcx, Scalar> {
|
||||
let this = self.eval_context_mut();
|
||||
this.assert_target_os("macos", "pthread_threadip_np");
|
||||
|
||||
interp_ok(Scalar::from_u32(tid))
|
||||
let tid_dest = this.read_pointer(tid_op)?;
|
||||
if this.ptr_is_null(tid_dest)? {
|
||||
// If NULL is passed, an error is immediately returned
|
||||
return interp_ok(this.eval_libc("EINVAL"));
|
||||
}
|
||||
|
||||
let thread = this.read_scalar(thread_op)?.to_int(this.libc_ty_layout("pthread_t").size)?;
|
||||
let thread = if thread == 0 {
|
||||
// Null thread ID indicates that we are querying the active thread.
|
||||
this.machine.threads.active_thread()
|
||||
} else {
|
||||
// Our pthread_t is just the raw ThreadId.
|
||||
let Ok(thread) = this.thread_id_try_from(thread) else {
|
||||
return interp_ok(this.eval_libc("ESRCH"));
|
||||
};
|
||||
thread
|
||||
};
|
||||
|
||||
let tid = this.get_tid(thread);
|
||||
let tid_dest = this.deref_pointer_as(tid_op, this.machine.layouts.u64)?;
|
||||
this.write_int(tid, &tid_dest)?;
|
||||
|
||||
// Possible errors have been handled, return success.
|
||||
interp_ok(Scalar::from_u32(0))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -963,8 +963,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
this.write_null(dest)?;
|
||||
}
|
||||
"nanosleep" => {
|
||||
let [req, rem] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
|
||||
let result = this.nanosleep(req, rem)?;
|
||||
let [duration, rem] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
|
||||
let result = this.nanosleep(duration, rem)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"sched_getaffinity" => {
|
||||
|
|
|
|||
|
|
@ -56,6 +56,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
};
|
||||
this.write_scalar(res, dest)?;
|
||||
}
|
||||
"pthread_getthreadid_np" => {
|
||||
let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
|
||||
let result = this.unix_gettid(link_name.as_str())?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
|
||||
"cpuset_getaffinity" => {
|
||||
// The "same" kind of api as `sched_getaffinity` but more fine grained control for FreeBSD specifically.
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ use crate::*;
|
|||
const TASK_COMM_LEN: u64 = 16;
|
||||
|
||||
pub fn is_dyn_sym(name: &str) -> bool {
|
||||
matches!(name, "statx")
|
||||
matches!(name, "gettid" | "statx")
|
||||
}
|
||||
|
||||
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
|
||||
|
|
@ -117,7 +117,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
}
|
||||
"gettid" => {
|
||||
let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
|
||||
let result = this.linux_gettid()?;
|
||||
let result = this.unix_gettid(link_name.as_str())?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,12 +15,13 @@ pub fn futex<'tcx>(
|
|||
) -> InterpResult<'tcx> {
|
||||
let [addr, op, val] = check_min_vararg_count("`syscall(SYS_futex, ...)`", varargs)?;
|
||||
|
||||
// See <https://man7.org/linux/man-pages/man2/futex.2.html> for docs.
|
||||
// The first three arguments (after the syscall number itself) are the same to all futex operations:
|
||||
// (int *addr, int op, int val).
|
||||
// (uint32_t *addr, int op, uint32_t val).
|
||||
// We checked above that these definitely exist.
|
||||
let addr = ecx.read_pointer(addr)?;
|
||||
let op = ecx.read_scalar(op)?.to_i32()?;
|
||||
let val = ecx.read_scalar(val)?.to_i32()?;
|
||||
let val = ecx.read_scalar(val)?.to_u32()?;
|
||||
|
||||
// This is a vararg function so we have to bring our own type for this pointer.
|
||||
let addr = ecx.ptr_to_mplace(addr, ecx.machine.layouts.i32);
|
||||
|
|
@ -138,7 +139,7 @@ pub fn futex<'tcx>(
|
|||
// It's not uncommon for `addr` to be passed as another type than `*mut i32`, such as `*const AtomicI32`.
|
||||
// We do an acquire read -- it only seems reasonable that if we observe a value here, we
|
||||
// actually establish an ordering with that value.
|
||||
let futex_val = ecx.read_scalar_atomic(&addr, AtomicReadOrd::Acquire)?.to_i32()?;
|
||||
let futex_val = ecx.read_scalar_atomic(&addr, AtomicReadOrd::Acquire)?.to_u32()?;
|
||||
if val == futex_val {
|
||||
// The value still matches, so we block the thread and make it wait for FUTEX_WAKE.
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use rustc_span::Symbol;
|
|||
use rustc_target::callconv::FnAbi;
|
||||
|
||||
use crate::helpers::check_min_vararg_count;
|
||||
use crate::shims::unix::env::EvalContextExt;
|
||||
use crate::shims::unix::linux_like::eventfd::EvalContextExt as _;
|
||||
use crate::shims::unix::linux_like::sync::futex;
|
||||
use crate::*;
|
||||
|
|
@ -24,6 +25,7 @@ pub fn syscall<'tcx>(
|
|||
let sys_getrandom = ecx.eval_libc("SYS_getrandom").to_target_usize(ecx)?;
|
||||
let sys_futex = ecx.eval_libc("SYS_futex").to_target_usize(ecx)?;
|
||||
let sys_eventfd2 = ecx.eval_libc("SYS_eventfd2").to_target_usize(ecx)?;
|
||||
let sys_gettid = ecx.eval_libc("SYS_gettid").to_target_usize(ecx)?;
|
||||
|
||||
match ecx.read_target_usize(op)? {
|
||||
// `libc::syscall(NR_GETRANDOM, buf.as_mut_ptr(), buf.len(), GRND_NONBLOCK)`
|
||||
|
|
@ -53,6 +55,10 @@ pub fn syscall<'tcx>(
|
|||
let result = ecx.eventfd(initval, flags)?;
|
||||
ecx.write_int(result.to_i32()?, dest)?;
|
||||
}
|
||||
num if num == sys_gettid => {
|
||||
let result = ecx.unix_gettid("SYS_gettid")?;
|
||||
ecx.write_int(result.to_u32()?, dest)?;
|
||||
}
|
||||
num => {
|
||||
throw_unsup_format!("syscall: unsupported syscall number {num}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -222,6 +222,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
};
|
||||
this.write_scalar(res, dest)?;
|
||||
}
|
||||
"pthread_threadid_np" => {
|
||||
let [thread, tid_ptr] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
|
||||
let res = this.apple_pthread_threadip_np(thread, tid_ptr)?;
|
||||
this.write_scalar(res, dest)?;
|
||||
}
|
||||
|
||||
// Synchronization primitives
|
||||
"os_sync_wait_on_address" => {
|
||||
|
|
|
|||
|
|
@ -68,10 +68,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
// LAZY_INIT_COOKIE). This can't be hit via `std::sync::Mutex`.
|
||||
interp_ok(MacOsUnfairLock::Poisoned)
|
||||
},
|
||||
|ecx| {
|
||||
let mutex_ref = ecx.machine.sync.mutex_create();
|
||||
interp_ok(MacOsUnfairLock::Active { mutex_ref })
|
||||
},
|
||||
|_| interp_ok(MacOsUnfairLock::Active { mutex_ref: MutexRef::new() }),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -171,8 +171,7 @@ fn mutex_create<'tcx>(
|
|||
kind: MutexKind,
|
||||
) -> InterpResult<'tcx, PthreadMutex> {
|
||||
let mutex = ecx.deref_pointer_as(mutex_ptr, ecx.libc_ty_layout("pthread_mutex_t"))?;
|
||||
let id = ecx.machine.sync.mutex_create();
|
||||
let data = PthreadMutex { mutex_ref: id, kind };
|
||||
let data = PthreadMutex { mutex_ref: MutexRef::new(), kind };
|
||||
ecx.lazy_sync_init(&mutex, mutex_init_offset(ecx)?, data.clone())?;
|
||||
interp_ok(data)
|
||||
}
|
||||
|
|
@ -193,8 +192,7 @@ where
|
|||
|| throw_ub_format!("`pthread_mutex_t` can't be moved after first use"),
|
||||
|ecx| {
|
||||
let kind = mutex_kind_from_static_initializer(ecx, &mutex)?;
|
||||
let id = ecx.machine.sync.mutex_create();
|
||||
interp_ok(PthreadMutex { mutex_ref: id, kind })
|
||||
interp_ok(PthreadMutex { mutex_ref: MutexRef::new(), kind })
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -278,8 +276,7 @@ where
|
|||
)? {
|
||||
throw_unsup_format!("unsupported static initializer used for `pthread_rwlock_t`");
|
||||
}
|
||||
let rwlock_ref = ecx.machine.sync.rwlock_create();
|
||||
interp_ok(PthreadRwLock { rwlock_ref })
|
||||
interp_ok(PthreadRwLock { rwlock_ref: RwLockRef::new() })
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -372,9 +369,9 @@ enum ClockId {
|
|||
Monotonic,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
struct PthreadCondvar {
|
||||
id: CondvarId,
|
||||
condvar_ref: CondvarRef,
|
||||
clock: ClockId,
|
||||
}
|
||||
|
||||
|
|
@ -384,9 +381,8 @@ fn cond_create<'tcx>(
|
|||
clock: ClockId,
|
||||
) -> InterpResult<'tcx, PthreadCondvar> {
|
||||
let cond = ecx.deref_pointer_as(cond_ptr, ecx.libc_ty_layout("pthread_cond_t"))?;
|
||||
let id = ecx.machine.sync.condvar_create();
|
||||
let data = PthreadCondvar { id, clock };
|
||||
ecx.lazy_sync_init(&cond, cond_init_offset(ecx)?, data)?;
|
||||
let data = PthreadCondvar { condvar_ref: CondvarRef::new(), clock };
|
||||
ecx.lazy_sync_init(&cond, cond_init_offset(ecx)?, data.clone())?;
|
||||
interp_ok(data)
|
||||
}
|
||||
|
||||
|
|
@ -411,8 +407,7 @@ where
|
|||
throw_unsup_format!("unsupported static initializer used for `pthread_cond_t`");
|
||||
}
|
||||
// This used the static initializer. The clock there is always CLOCK_REALTIME.
|
||||
let id = ecx.machine.sync.condvar_create();
|
||||
interp_ok(PthreadCondvar { id, clock: ClockId::Realtime })
|
||||
interp_ok(PthreadCondvar { condvar_ref: CondvarRef::new(), clock: ClockId::Realtime })
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -817,15 +812,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
|
||||
fn pthread_cond_signal(&mut self, cond_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
|
||||
let this = self.eval_context_mut();
|
||||
let id = cond_get_data(this, cond_op)?.id;
|
||||
this.condvar_signal(id)?;
|
||||
let condvar = cond_get_data(this, cond_op)?.condvar_ref.clone();
|
||||
this.condvar_signal(&condvar)?;
|
||||
interp_ok(())
|
||||
}
|
||||
|
||||
fn pthread_cond_broadcast(&mut self, cond_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
|
||||
let this = self.eval_context_mut();
|
||||
let id = cond_get_data(this, cond_op)?.id;
|
||||
while this.condvar_signal(id)? {}
|
||||
let condvar = cond_get_data(this, cond_op)?.condvar_ref.clone();
|
||||
while this.condvar_signal(&condvar)? {}
|
||||
interp_ok(())
|
||||
}
|
||||
|
||||
|
|
@ -837,11 +832,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
) -> InterpResult<'tcx> {
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
let data = *cond_get_data(this, cond_op)?;
|
||||
let data = cond_get_data(this, cond_op)?.clone();
|
||||
let mutex_ref = mutex_get_data(this, mutex_op)?.mutex_ref.clone();
|
||||
|
||||
this.condvar_wait(
|
||||
data.id,
|
||||
data.condvar_ref,
|
||||
mutex_ref,
|
||||
None, // no timeout
|
||||
Scalar::from_i32(0),
|
||||
|
|
@ -861,7 +856,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
) -> InterpResult<'tcx> {
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
let data = *cond_get_data(this, cond_op)?;
|
||||
let data = cond_get_data(this, cond_op)?.clone();
|
||||
let mutex_ref = mutex_get_data(this, mutex_op)?.mutex_ref.clone();
|
||||
|
||||
// Extract the timeout.
|
||||
|
|
@ -884,7 +879,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
};
|
||||
|
||||
this.condvar_wait(
|
||||
data.id,
|
||||
data.condvar_ref,
|
||||
mutex_ref,
|
||||
Some((timeout_clock, TimeoutAnchor::Absolute, duration)),
|
||||
Scalar::from_i32(0),
|
||||
|
|
@ -900,8 +895,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
|
||||
// Reading the field also has the side-effect that we detect double-`destroy`
|
||||
// since we make the field uninit below.
|
||||
let id = cond_get_data(this, cond_op)?.id;
|
||||
if this.condvar_is_awaited(id) {
|
||||
let condvar = &cond_get_data(this, cond_op)?.condvar_ref;
|
||||
if condvar.is_awaited() {
|
||||
throw_ub_format!("destroying an awaited conditional variable");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -629,6 +629,23 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
this.write_scalar(name, &name_ptr)?;
|
||||
this.write_scalar(res, dest)?;
|
||||
}
|
||||
"GetThreadId" => {
|
||||
let [handle] = this.check_shim(abi, sys_conv, link_name, args)?;
|
||||
let handle = this.read_handle(handle, "GetThreadId")?;
|
||||
let thread = match handle {
|
||||
Handle::Thread(thread) => thread,
|
||||
Handle::Pseudo(PseudoHandle::CurrentThread) => this.active_thread(),
|
||||
_ => this.invalid_handle("GetThreadDescription")?,
|
||||
};
|
||||
let tid = this.get_tid(thread);
|
||||
this.write_scalar(Scalar::from_u32(tid), dest)?;
|
||||
}
|
||||
"GetCurrentThreadId" => {
|
||||
let [] = this.check_shim(abi, sys_conv, link_name, args)?;
|
||||
let thread = this.active_thread();
|
||||
let tid = this.get_tid(thread);
|
||||
this.write_scalar(Scalar::from_u32(tid), dest)?;
|
||||
}
|
||||
|
||||
// Miscellaneous
|
||||
"ExitProcess" => {
|
||||
|
|
|
|||
|
|
@ -166,6 +166,10 @@ impl Handle {
|
|||
/// Structurally invalid handles return [`HandleError::InvalidHandle`].
|
||||
/// If the handle is structurally valid but semantically invalid, e.g. a for non-existent thread
|
||||
/// ID, returns [`HandleError::ThreadNotFound`].
|
||||
///
|
||||
/// This function is deliberately private; shims should always use `read_handle`.
|
||||
/// That enforces handle validity even when Windows does not: for now, we argue invalid
|
||||
/// handles are always a bug and programmers likely want to know about them.
|
||||
fn try_from_scalar<'tcx>(
|
||||
handle: Scalar,
|
||||
cx: &MiriInterpCx<'tcx>,
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@ use std::time::Duration;
|
|||
|
||||
use rustc_abi::Size;
|
||||
|
||||
use crate::concurrency::init_once::InitOnceStatus;
|
||||
use crate::concurrency::init_once::{EvalContextExt as _, InitOnceStatus};
|
||||
use crate::concurrency::sync::FutexRef;
|
||||
use crate::*;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Clone)]
|
||||
struct WindowsInitOnce {
|
||||
id: InitOnceId,
|
||||
init_once: InitOnceRef,
|
||||
}
|
||||
|
||||
struct WindowsFutex {
|
||||
|
|
@ -37,10 +37,9 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
&init_once,
|
||||
init_offset,
|
||||
|| throw_ub_format!("`INIT_ONCE` can't be moved after first use"),
|
||||
|this| {
|
||||
|_| {
|
||||
// TODO: check that this is still all-zero.
|
||||
let id = this.machine.sync.init_once_create();
|
||||
interp_ok(WindowsInitOnce { id })
|
||||
interp_ok(WindowsInitOnce { init_once: InitOnceRef::new() })
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -48,20 +47,20 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
/// Returns `true` if we were succssful, `false` if we would block.
|
||||
fn init_once_try_begin(
|
||||
&mut self,
|
||||
id: InitOnceId,
|
||||
init_once_ref: &InitOnceRef,
|
||||
pending_place: &MPlaceTy<'tcx>,
|
||||
dest: &MPlaceTy<'tcx>,
|
||||
) -> InterpResult<'tcx, bool> {
|
||||
let this = self.eval_context_mut();
|
||||
interp_ok(match this.init_once_status(id) {
|
||||
interp_ok(match init_once_ref.status() {
|
||||
InitOnceStatus::Uninitialized => {
|
||||
this.init_once_begin(id);
|
||||
init_once_ref.begin();
|
||||
this.write_scalar(this.eval_windows("c", "TRUE"), pending_place)?;
|
||||
this.write_scalar(this.eval_windows("c", "TRUE"), dest)?;
|
||||
true
|
||||
}
|
||||
InitOnceStatus::Complete => {
|
||||
this.init_once_observe_completed(id);
|
||||
this.init_once_observe_completed(init_once_ref);
|
||||
this.write_scalar(this.eval_windows("c", "FALSE"), pending_place)?;
|
||||
this.write_scalar(this.eval_windows("c", "TRUE"), dest)?;
|
||||
true
|
||||
|
|
@ -84,7 +83,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
) -> InterpResult<'tcx> {
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
let id = this.init_once_get_data(init_once_op)?.id;
|
||||
let init_once = this.init_once_get_data(init_once_op)?.init_once.clone();
|
||||
let flags = this.read_scalar(flags_op)?.to_u32()?;
|
||||
// PBOOL is int*
|
||||
let pending_place = this.deref_pointer_as(pending_op, this.machine.layouts.i32)?;
|
||||
|
|
@ -98,7 +97,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
throw_unsup_format!("non-null `lpContext` in `InitOnceBeginInitialize`");
|
||||
}
|
||||
|
||||
if this.init_once_try_begin(id, &pending_place, dest)? {
|
||||
if this.init_once_try_begin(&init_once, &pending_place, dest)? {
|
||||
// Done!
|
||||
return interp_ok(());
|
||||
}
|
||||
|
|
@ -106,16 +105,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
// We have to block, and then try again when we are woken up.
|
||||
let dest = dest.clone();
|
||||
this.init_once_enqueue_and_block(
|
||||
id,
|
||||
init_once.clone(),
|
||||
callback!(
|
||||
@capture<'tcx> {
|
||||
id: InitOnceId,
|
||||
init_once: InitOnceRef,
|
||||
pending_place: MPlaceTy<'tcx>,
|
||||
dest: MPlaceTy<'tcx>,
|
||||
}
|
||||
|this, unblock: UnblockKind| {
|
||||
assert_eq!(unblock, UnblockKind::Ready);
|
||||
let ret = this.init_once_try_begin(id, &pending_place, &dest)?;
|
||||
let ret = this.init_once_try_begin(&init_once, &pending_place, &dest)?;
|
||||
assert!(ret, "we were woken up but init_once_try_begin still failed");
|
||||
interp_ok(())
|
||||
}
|
||||
|
|
@ -132,7 +131,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
) -> InterpResult<'tcx, Scalar> {
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
let id = this.init_once_get_data(init_once_op)?.id;
|
||||
let init_once = this.init_once_get_data(init_once_op)?.init_once.clone();
|
||||
let flags = this.read_scalar(flags_op)?.to_u32()?;
|
||||
let context = this.read_pointer(context_op)?;
|
||||
|
||||
|
|
@ -148,7 +147,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
throw_unsup_format!("non-null `lpContext` in `InitOnceBeginInitialize`");
|
||||
}
|
||||
|
||||
if this.init_once_status(id) != InitOnceStatus::Begun {
|
||||
if init_once.status() != InitOnceStatus::Begun {
|
||||
// The docs do not say anything about this case, but it seems better to not allow it.
|
||||
throw_ub_format!(
|
||||
"calling InitOnceComplete on a one time initialization that has not begun or is already completed"
|
||||
|
|
@ -156,9 +155,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
}
|
||||
|
||||
if success {
|
||||
this.init_once_complete(id)?;
|
||||
this.init_once_complete(&init_once)?;
|
||||
} else {
|
||||
this.init_once_fail(id)?;
|
||||
this.init_once_fail(&init_once)?;
|
||||
}
|
||||
|
||||
interp_ok(this.eval_windows("c", "TRUE"))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
//! Ensure we error if thread functions are called with invalid handles
|
||||
//@only-target: windows # testing Windows API
|
||||
|
||||
use windows_sys::Win32::System::Threading::GetThreadId;
|
||||
|
||||
fn main() {
|
||||
let _tid = unsafe { GetThreadId(std::ptr::dangling_mut()) };
|
||||
//~^ ERROR: invalid handle
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
error: abnormal termination: invalid handle 1 passed to GetThreadId
|
||||
--> tests/fail-dep/concurrency/windows_thread_invalid.rs:LL:CC
|
||||
|
|
||||
LL | let _tid = unsafe { GetThreadId(std::ptr::dangling_mut()) };
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ abnormal termination occurred here
|
||||
|
|
||||
= note: BACKTRACE:
|
||||
= note: inside `main` at tests/fail-dep/concurrency/windows_thread_invalid.rs:LL:CC
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
|
|
@ -1,7 +1,3 @@
|
|||
// Only works on Unix targets
|
||||
//@ignore-target: windows wasm
|
||||
//@only-on-host
|
||||
|
||||
fn main() {
|
||||
test_access_pointer();
|
||||
test_access_simple();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
// Only works on Unix targets
|
||||
//@ignore-target: windows wasm
|
||||
//@only-on-host
|
||||
//@compile-flags: -Zmiri-permissive-provenance
|
||||
|
||||
#![feature(box_as_ptr)]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
// Only works on Unix targets
|
||||
//@ignore-target: windows wasm
|
||||
//@only-on-host
|
||||
|
||||
extern "C" {
|
||||
fn add_one_int(x: i32) -> i32;
|
||||
fn add_int16(x: i16) -> i16;
|
||||
|
|
|
|||
|
|
@ -22,11 +22,11 @@ EXPORT uint32_t get_unsigned_int(void) {
|
|||
return -10;
|
||||
}
|
||||
|
||||
EXPORT short add_int16(int16_t x) {
|
||||
EXPORT int16_t add_int16(int16_t x) {
|
||||
return x + 3;
|
||||
}
|
||||
|
||||
EXPORT long add_short_to_long(int16_t x, int64_t y) {
|
||||
EXPORT int64_t add_short_to_long(int16_t x, int64_t y) {
|
||||
return x + y;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,11 @@ fn wake_nobody() {
|
|||
0,
|
||||
);
|
||||
}
|
||||
|
||||
// Wake u32::MAX waiters.
|
||||
unsafe {
|
||||
assert_eq!(libc::syscall(libc::SYS_futex, addr_of!(futex), libc::FUTEX_WAKE, u32::MAX), 0);
|
||||
}
|
||||
}
|
||||
|
||||
fn wake_dangling() {
|
||||
|
|
|
|||
183
src/tools/miri/tests/pass-dep/shims/gettid.rs
Normal file
183
src/tools/miri/tests/pass-dep/shims/gettid.rs
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
//! Test for `gettid` and similar functions for retrieving an OS thread ID.
|
||||
//@ revisions: with_isolation without_isolation
|
||||
//@ [without_isolation] compile-flags: -Zmiri-disable-isolation
|
||||
|
||||
#![feature(linkage)]
|
||||
|
||||
fn gettid() -> u64 {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(any(target_os = "android", target_os = "linux"))] {
|
||||
gettid_linux_like()
|
||||
} else if #[cfg(target_os = "nto")] {
|
||||
unsafe { libc::gettid() as u64 }
|
||||
} else if #[cfg(target_os = "openbsd")] {
|
||||
unsafe { libc::getthrid() as u64 }
|
||||
} else if #[cfg(target_os = "freebsd")] {
|
||||
unsafe { libc::pthread_getthreadid_np() as u64 }
|
||||
} else if #[cfg(target_os = "netbsd")] {
|
||||
unsafe { libc::_lwp_self() as u64 }
|
||||
} else if #[cfg(any(target_os = "solaris", target_os = "illumos"))] {
|
||||
// On Solaris and Illumos, the `pthread_t` is the OS TID.
|
||||
unsafe { libc::pthread_self() as u64 }
|
||||
} else if #[cfg(target_vendor = "apple")] {
|
||||
let mut id = 0u64;
|
||||
let status: libc::c_int = unsafe { libc::pthread_threadid_np(0, &mut id) };
|
||||
assert_eq!(status, 0);
|
||||
id
|
||||
} else if #[cfg(windows)] {
|
||||
use windows_sys::Win32::System::Threading::GetCurrentThreadId;
|
||||
unsafe { GetCurrentThreadId() as u64 }
|
||||
} else {
|
||||
compile_error!("platform has no gettid")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Test the libc function, the syscall, and the extern symbol.
|
||||
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||
fn gettid_linux_like() -> u64 {
|
||||
unsafe extern "C" {
|
||||
#[linkage = "extern_weak"]
|
||||
static gettid: Option<unsafe extern "C" fn() -> libc::pid_t>;
|
||||
}
|
||||
|
||||
let from_libc = unsafe { libc::gettid() as u64 };
|
||||
let from_sys = unsafe { libc::syscall(libc::SYS_gettid) as u64 };
|
||||
let from_static = unsafe { gettid.unwrap()() as u64 };
|
||||
|
||||
assert_eq!(from_libc, from_sys);
|
||||
assert_eq!(from_libc, from_static);
|
||||
|
||||
from_libc
|
||||
}
|
||||
|
||||
/// Specific platforms can query the tid of arbitrary threads from a `pthread_t` / `HANDLE`
|
||||
#[cfg(any(target_vendor = "apple", windows))]
|
||||
mod queried {
|
||||
use std::ffi::c_void;
|
||||
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||
use std::{ptr, thread};
|
||||
|
||||
use super::*;
|
||||
|
||||
static SPAWNED_TID: AtomicU64 = AtomicU64::new(0);
|
||||
static CAN_JOIN: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
/// Save this thread's TID, give the spawning thread a chance to query it separately before
|
||||
/// being joined.
|
||||
fn thread_body() {
|
||||
SPAWNED_TID.store(gettid(), Ordering::Relaxed);
|
||||
|
||||
// Spin until the main thread has a chance to read this thread's ID
|
||||
while !CAN_JOIN.load(Ordering::Relaxed) {
|
||||
thread::yield_now();
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawn a thread, query then return its TID.
|
||||
#[cfg(target_vendor = "apple")]
|
||||
fn spawn_update_join() -> u64 {
|
||||
extern "C" fn thread_start(_data: *mut c_void) -> *mut c_void {
|
||||
thread_body();
|
||||
ptr::null_mut()
|
||||
}
|
||||
|
||||
let mut t: libc::pthread_t = 0;
|
||||
let mut spawned_tid_from_handle = 0u64;
|
||||
|
||||
unsafe {
|
||||
let res = libc::pthread_create(&mut t, ptr::null(), thread_start, ptr::null_mut());
|
||||
assert_eq!(res, 0, "failed to spawn thread");
|
||||
|
||||
let res = libc::pthread_threadid_np(t, &mut spawned_tid_from_handle);
|
||||
assert_eq!(res, 0, "failed to query thread ID");
|
||||
CAN_JOIN.store(true, Ordering::Relaxed);
|
||||
|
||||
let res = libc::pthread_join(t, ptr::null_mut());
|
||||
assert_eq!(res, 0, "failed to join thread");
|
||||
|
||||
// Apple also has two documented return values for invalid threads and null pointers
|
||||
let res = libc::pthread_threadid_np(libc::pthread_t::MAX, &mut 0);
|
||||
assert_eq!(res, libc::ESRCH, "expected ESRCH for invalid TID");
|
||||
let res = libc::pthread_threadid_np(0, ptr::null_mut());
|
||||
assert_eq!(res, libc::EINVAL, "invalid EINVAL for a null pointer");
|
||||
}
|
||||
|
||||
spawned_tid_from_handle
|
||||
}
|
||||
|
||||
/// Spawn a thread, query then return its TID.
|
||||
#[cfg(windows)]
|
||||
fn spawn_update_join() -> u64 {
|
||||
use windows_sys::Win32::Foundation::WAIT_FAILED;
|
||||
use windows_sys::Win32::System::Threading::{
|
||||
CreateThread, GetThreadId, INFINITE, WaitForSingleObject,
|
||||
};
|
||||
|
||||
extern "system" fn thread_start(_data: *mut c_void) -> u32 {
|
||||
thread_body();
|
||||
0
|
||||
}
|
||||
|
||||
let spawned_tid_from_handle;
|
||||
let mut tid_at_spawn = 0u32;
|
||||
|
||||
unsafe {
|
||||
let handle =
|
||||
CreateThread(ptr::null(), 0, Some(thread_start), ptr::null(), 0, &mut tid_at_spawn);
|
||||
assert!(!handle.is_null(), "failed to spawn thread");
|
||||
|
||||
spawned_tid_from_handle = GetThreadId(handle);
|
||||
assert_ne!(spawned_tid_from_handle, 0, "failed to query thread ID");
|
||||
CAN_JOIN.store(true, Ordering::Relaxed);
|
||||
|
||||
let res = WaitForSingleObject(handle, INFINITE);
|
||||
assert_ne!(res, WAIT_FAILED, "failed to join thread");
|
||||
}
|
||||
|
||||
// Windows also indirectly returns the TID from `CreateThread`, ensure that matches up.
|
||||
assert_eq!(spawned_tid_from_handle, tid_at_spawn);
|
||||
|
||||
spawned_tid_from_handle.into()
|
||||
}
|
||||
|
||||
pub fn check() {
|
||||
let spawned_tid_from_handle = spawn_update_join();
|
||||
let spawned_tid_from_self = SPAWNED_TID.load(Ordering::Relaxed);
|
||||
let current_tid = gettid();
|
||||
|
||||
// Ensure that we got a different thread ID.
|
||||
assert_ne!(spawned_tid_from_handle, current_tid);
|
||||
|
||||
// Ensure that we get the same result from `gettid` and from querying a thread's handle
|
||||
assert_eq!(spawned_tid_from_handle, spawned_tid_from_self);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let tid = gettid();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
assert_ne!(gettid(), tid);
|
||||
});
|
||||
|
||||
// Test that in isolation mode a deterministic value will be returned.
|
||||
// The value is not important, we only care that whatever the value is,
|
||||
// won't change from execution to execution.
|
||||
if cfg!(with_isolation) {
|
||||
if cfg!(target_os = "linux") {
|
||||
// Linux starts the TID at the PID, which is 1000.
|
||||
assert_eq!(tid, 1000);
|
||||
} else {
|
||||
// Other platforms start counting from 0.
|
||||
assert_eq!(tid, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// On Linux and NetBSD, the first TID is the PID.
|
||||
#[cfg(any(target_os = "linux", target_os = "netbsd"))]
|
||||
assert_eq!(tid, unsafe { libc::getpid() } as u64);
|
||||
|
||||
#[cfg(any(target_vendor = "apple", windows))]
|
||||
queried::check();
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
#![no_std]
|
||||
#![no_main]
|
||||
//@compile-flags: -Zmiri-track-alloc-id=18 -Zmiri-track-alloc-accesses -Cpanic=abort
|
||||
//@normalize-stderr-test: "id 18" -> "id $$ALLOC"
|
||||
//@compile-flags: -Zmiri-track-alloc-id=20 -Zmiri-track-alloc-accesses -Cpanic=abort
|
||||
//@normalize-stderr-test: "id 20" -> "id $$ALLOC"
|
||||
//@only-target: linux # alloc IDs differ between OSes (due to extern static allocations)
|
||||
|
||||
extern "Rust" {
|
||||
|
|
|
|||
|
|
@ -1066,7 +1066,6 @@ pub fn libm() {
|
|||
assert_eq!((-1f32).powf(f32::NEG_INFINITY), 1.0);
|
||||
assert_eq!((-1f64).powf(f64::NEG_INFINITY), 1.0);
|
||||
|
||||
|
||||
assert_eq!(0f32.powi(10), 0.0);
|
||||
assert_eq!(0f64.powi(100), 0.0);
|
||||
assert_eq!(0f32.powi(9), 0.0);
|
||||
|
|
@ -1490,7 +1489,6 @@ fn test_non_determinism() {
|
|||
test_operations_f64(19., 11.);
|
||||
test_operations_f128(25., 18.);
|
||||
|
||||
|
||||
// SNaN^0 = (1 | NaN)
|
||||
ensure_nondet(|| f32::powf(SNAN_F32, 0.0).is_nan());
|
||||
ensure_nondet(|| f64::powf(SNAN_F64, 0.0).is_nan());
|
||||
|
|
|
|||
|
|
@ -29,20 +29,17 @@ fn miri_path() -> PathBuf {
|
|||
PathBuf::from(env::var("MIRI").unwrap_or_else(|_| env!("CARGO_BIN_EXE_miri").into()))
|
||||
}
|
||||
|
||||
fn get_host() -> String {
|
||||
rustc_version::VersionMeta::for_command(std::process::Command::new(miri_path()))
|
||||
.expect("failed to parse rustc version info")
|
||||
.host
|
||||
}
|
||||
|
||||
pub fn flagsplit(flags: &str) -> Vec<String> {
|
||||
// This code is taken from `RUSTFLAGS` handling in cargo.
|
||||
flags.split(' ').map(str::trim).filter(|s| !s.is_empty()).map(str::to_string).collect()
|
||||
}
|
||||
|
||||
// Build the shared object file for testing native function calls.
|
||||
fn build_native_lib() -> PathBuf {
|
||||
let cc = env::var("CC").unwrap_or_else(|_| "cc".into());
|
||||
fn build_native_lib(target: &str) -> PathBuf {
|
||||
// Loosely follow the logic of the `cc` crate for finding the compiler.
|
||||
let cc = env::var(format!("CC_{target}"))
|
||||
.or_else(|_| env::var("CC"))
|
||||
.unwrap_or_else(|_| "cc".into());
|
||||
// Target directory that we can write to.
|
||||
let so_target_dir = Path::new(env!("CARGO_TARGET_TMPDIR")).join("miri-native-lib");
|
||||
// Create the directory if it does not already exist.
|
||||
|
|
@ -201,7 +198,7 @@ fn run_tests(
|
|||
// If we're testing the native-lib functionality, then build the shared object file for testing
|
||||
// external C function calls and push the relevant compiler flag.
|
||||
if path.starts_with("tests/native-lib/") {
|
||||
let native_lib = build_native_lib();
|
||||
let native_lib = build_native_lib(target);
|
||||
let mut flag = std::ffi::OsString::from("-Zmiri-native-lib=");
|
||||
flag.push(native_lib.into_os_string());
|
||||
config.program.args.push(flag);
|
||||
|
|
@ -305,14 +302,21 @@ fn ui(
|
|||
.with_context(|| format!("ui tests in {path} for {target} failed"))
|
||||
}
|
||||
|
||||
fn get_target() -> String {
|
||||
env::var("MIRI_TEST_TARGET").ok().unwrap_or_else(get_host)
|
||||
fn get_host() -> String {
|
||||
rustc_version::VersionMeta::for_command(std::process::Command::new(miri_path()))
|
||||
.expect("failed to parse rustc version info")
|
||||
.host
|
||||
}
|
||||
|
||||
fn get_target(host: &str) -> String {
|
||||
env::var("MIRI_TEST_TARGET").ok().unwrap_or_else(|| host.to_owned())
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
ui_test::color_eyre::install()?;
|
||||
|
||||
let target = get_target();
|
||||
let host = get_host();
|
||||
let target = get_target(&host);
|
||||
let tmpdir = tempfile::Builder::new().prefix("miri-uitest-").tempdir()?;
|
||||
|
||||
let mut args = std::env::args_os();
|
||||
|
|
@ -329,7 +333,7 @@ fn main() -> Result<()> {
|
|||
ui(Mode::Panic, "tests/panic", &target, WithDependencies, tmpdir.path())?;
|
||||
ui(Mode::Fail, "tests/fail", &target, WithoutDependencies, tmpdir.path())?;
|
||||
ui(Mode::Fail, "tests/fail-dep", &target, WithDependencies, tmpdir.path())?;
|
||||
if cfg!(unix) {
|
||||
if cfg!(unix) && target == host {
|
||||
ui(Mode::Pass, "tests/native-lib/pass", &target, WithoutDependencies, tmpdir.path())?;
|
||||
ui(Mode::Fail, "tests/native-lib/fail", &target, WithoutDependencies, tmpdir.path())?;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue