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

Miri subtree update

r? `@ghost`
This commit is contained in:
Matthias Krüger 2025-07-05 22:34:41 +02:00 committed by GitHub
commit 7ad67e6b54
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
54 changed files with 1474 additions and 480 deletions

View file

@ -2394,6 +2394,7 @@ dependencies = [
"regex",
"rustc_version",
"serde",
"serde_json",
"smallvec",
"tempfile",
"tikv-jemalloc-sys",

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -627,6 +627,7 @@ dependencies = [
"regex",
"rustc_version",
"serde",
"serde_json",
"smallvec",
"tempfile",
"tikv-jemalloc-sys",

View file

@ -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"

View file

@ -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

View file

@ -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,

View file

@ -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",

View file

@ -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 "$@"

View file

@ -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()?)

View file

@ -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

View file

@ -1 +1 @@
d41e12f1f4e4884c356f319b881921aa37040de5
733b47ea4b1b86216f14ef56e49440c33933f230

View file

@ -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"
]

View file

@ -0,0 +1,2 @@
pub mod setup;
mod tracing_chrome;

View 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());
}
}

View 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>,
}

View file

@ -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"
);
};

View file

@ -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.

View file

@ -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);
}
}

View file

@ -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;

View file

@ -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)
}

View file

@ -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.

View file

@ -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 });
}

View file

@ -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>],

View file

@ -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,

View file

@ -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

View file

@ -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
}
}
}

View file

@ -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"])?;

View file

@ -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();

View file

@ -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"));

View file

@ -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))
}
}

View file

@ -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" => {

View file

@ -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.

View file

@ -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)?;
}

View file

@ -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.

View file

@ -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}");
}

View file

@ -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" => {

View file

@ -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() }),
)
}
}

View file

@ -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");
}

View file

@ -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" => {

View file

@ -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>,

View file

@ -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"))

View file

@ -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
}

View file

@ -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

View file

@ -1,7 +1,3 @@
// Only works on Unix targets
//@ignore-target: windows wasm
//@only-on-host
fn main() {
test_access_pointer();
test_access_simple();

View file

@ -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)]

View file

@ -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;

View file

@ -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;
}

View file

@ -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() {

View 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();
}

View file

@ -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" {

View file

@ -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());

View file

@ -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())?;
}