about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2023-07-31 07:54:28 +0000
committerbors <bors@rust-lang.org>2023-07-31 07:54:28 +0000
commite1071af908f70aaa32903a92a167cfb0cf1e9d84 (patch)
treeb0ee18b31edc78f9827caebe5430d5ef4b179bf5
parenta95a4329c5099d72155ab387bdbef3dc8d02197b (diff)
parentefbd2a508c3acd1dcf722808f118341e48bea9cc (diff)
downloadrust-e1071af908f70aaa32903a92a167cfb0cf1e9d84.tar.gz
rust-e1071af908f70aaa32903a92a167cfb0cf1e9d84.zip
Auto merge of #2909 - osiewicz:rewrite-miri-script-in-rust-2883, r=RalfJung
Rewrite miri script in rust

This is a sketch of a rewrite of miri script in Rust. It does not include changes made in https://github.com/rust-lang/miri/pull/2908 yet. Environment variables are not properly propagated yet, which is something I plan to address.

This PR is mostly a heads-up about the ongoing effort and it's state.
It's definitely not the cleanest code I've seen in my life, but my first goal was feature/interface parity. I will iterate on it a bit before marking it as ready.

I wonder though how this should be integrated/tested. Are you aware of anyone using `./miri` in their scripts?
I guess we should keep existing `miri` script in place and let it run miri-script package directly?

CI should probably `cargo check` this package as well.

Fixes #2883
-rw-r--r--src/tools/miri/.github/workflows/ci.yml2
-rw-r--r--src/tools/miri/CONTRIBUTING.md2
-rw-r--r--src/tools/miri/cargo-miri/src/phases.rs3
-rw-r--r--src/tools/miri/cargo-miri/src/util.rs5
-rwxr-xr-xsrc/tools/miri/miri361
-rw-r--r--src/tools/miri/miri-script/Cargo.lock160
-rw-r--r--src/tools/miri/miri-script/Cargo.toml22
-rwxr-xr-xsrc/tools/miri/miri-script/miri4
-rw-r--r--src/tools/miri/miri-script/src/commands.rs455
-rw-r--r--src/tools/miri/miri-script/src/main.rs210
-rw-r--r--src/tools/miri/miri-script/src/util.rs153
11 files changed, 1016 insertions, 361 deletions
diff --git a/src/tools/miri/.github/workflows/ci.yml b/src/tools/miri/.github/workflows/ci.yml
index 042bb9bbd2c..8ced9fa86be 100644
--- a/src/tools/miri/.github/workflows/ci.yml
+++ b/src/tools/miri/.github/workflows/ci.yml
@@ -130,7 +130,7 @@ jobs:
       - name: clippy
         run: ./miri clippy -- -D warnings
       - name: rustdoc
-        run: RUSTDOCFLAGS="-Dwarnings" cargo doc --document-private-items
+        run: RUSTDOCFLAGS="-Dwarnings" ./miri cargo doc --document-private-items
 
   # These jobs doesn't actually test anything, but they're only used to tell
   # bors the build completed, as there is no practical way to detect when a
diff --git a/src/tools/miri/CONTRIBUTING.md b/src/tools/miri/CONTRIBUTING.md
index bcdb623b090..b67e7103fd0 100644
--- a/src/tools/miri/CONTRIBUTING.md
+++ b/src/tools/miri/CONTRIBUTING.md
@@ -107,7 +107,7 @@ evaluation error was originally raised.
 ### UI testing
 
 We use ui-testing in Miri, meaning we generate `.stderr` and `.stdout` files for the output
-produced by Miri. You can use `./miri bless` to automatically (re)generate these files when
+produced by Miri. You can use `./miri test --bless` to automatically (re)generate these files when
 you add new tests or change how Miri presents certain output.
 
 Note that when you also use `MIRIFLAGS` to change optimizations and similar, the ui output
diff --git a/src/tools/miri/cargo-miri/src/phases.rs b/src/tools/miri/cargo-miri/src/phases.rs
index 465e4a1b2d2..d74e0c5157d 100644
--- a/src/tools/miri/cargo-miri/src/phases.rs
+++ b/src/tools/miri/cargo-miri/src/phases.rs
@@ -538,8 +538,7 @@ pub fn phase_runner(mut binary_args: impl Iterator<Item = String>, phase: Runner
     }
     // Respect `MIRIFLAGS`.
     if let Ok(a) = env::var("MIRIFLAGS") {
-        // This code is taken from `RUSTFLAGS` handling in cargo.
-        let args = a.split(' ').map(str::trim).filter(|s| !s.is_empty()).map(str::to_string);
+        let args = flagsplit(&a);
         cmd.args(args);
     }
 
diff --git a/src/tools/miri/cargo-miri/src/util.rs b/src/tools/miri/cargo-miri/src/util.rs
index 4c19ed97fb8..0e3b04c0d88 100644
--- a/src/tools/miri/cargo-miri/src/util.rs
+++ b/src/tools/miri/cargo-miri/src/util.rs
@@ -114,6 +114,11 @@ pub fn cargo() -> Command {
     Command::new(env::var_os("CARGO").unwrap_or_else(|| OsString::from("cargo")))
 }
 
+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()
+}
+
 /// Execute the `Command`, where possible by replacing the current process with a new process
 /// described by the `Command`. Then exit this process with the exit code of the new process.
 pub fn exec(mut cmd: Command) -> ! {
diff --git a/src/tools/miri/miri b/src/tools/miri/miri
index bccf6d835ff..c816a4bb06b 100755
--- a/src/tools/miri/miri
+++ b/src/tools/miri/miri
@@ -1,359 +1,6 @@
 #!/bin/bash
 set -e
-USAGE=$(cat <<"EOF"
-  COMMANDS
-
-./miri install <flags>:
-Installs the miri driver and cargo-miri. <flags> are passed to `cargo
-install`. Sets up the rpath such that the installed binary should work in any
-working directory. Note that the binaries are placed in the `miri` toolchain
-sysroot, to prevent conflicts with other toolchains.
-
-./miri build <flags>:
-Just build miri. <flags> are passed to `cargo build`.
-
-./miri check <flags>:
-Just check miri. <flags> are passed to `cargo check`.
-
-./miri test <flags>:
-Build miri, set up a sysroot and then run the test suite. <flags> are passed
-to the final `cargo test` invocation.
-
-./miri run <flags>:
-Build miri, set up a sysroot and then run the driver with the given <flags>.
-(Also respects MIRIFLAGS environment variable.)
-
-./miri fmt <flags>:
-Format all sources and tests. <flags> are passed to `rustfmt`.
-
-./miri clippy <flags>:
-Runs clippy on all sources. <flags> are passed to `cargo clippy`.
-
-./miri cargo <flags>:
-Runs just `cargo <flags>` with the Miri-specific environment variables.
-Mainly meant to be invoked by rust-analyzer.
-
-./miri many-seeds <command>:
-Runs <command> over and over again with different seeds for Miri. The MIRIFLAGS
-variable is set to its original value appended with ` -Zmiri-seed=$SEED` for
-many different seeds. The MIRI_SEEDS variable controls how many seeds are being
-tried; MIRI_SEED_START controls the first seed to try.
-
-./miri bench <benches>:
-Runs the benchmarks from bench-cargo-miri in hyperfine. hyperfine needs to be installed.
-<benches> can explicitly list the benchmarks to run; by default, all of them are run.
-
-./miri toolchain <flags>:
-Update and activate the rustup toolchain 'miri' to the commit given in the
-`rust-version` file.
-`rustup-toolchain-install-master` must be installed for this to work. Any extra
-flags are passed to `rustup-toolchain-install-master`.
-
-./miri rustc-pull <commit>:
-Pull and merge Miri changes from the rustc repo. Defaults to fetching the latest
-rustc commit. The fetched commit is stored in the `rust-version` file, so the
-next `./miri toolchain` will install the rustc that just got pulled.
-
-./miri rustc-push <github user> <branch>:
-Push Miri changes back to the rustc repo. This will pull a copy of the rustc
-history into the Miri repo, unless you set the RUSTC_GIT env var to an existing
-clone of the rustc repo.
-
-  ENVIRONMENT VARIABLES
-
-MIRI_SYSROOT:
-If already set, the "sysroot setup" step is skipped.
-
-CARGO_EXTRA_FLAGS:
-Pass extra flags to all cargo invocations. (Ignored by `./miri cargo`.)
-EOF
-)
-
-## We need to know which command to run and some global constants.
-COMMAND="$1"
-if [ -z "$COMMAND" ]; then
-    echo "$USAGE"
-    exit 1
-fi
-shift
-# macOS does not have a useful readlink/realpath so we have to use Python instead...
-MIRIDIR=$(python3 -c 'import pathlib, sys; print(pathlib.Path(sys.argv[1]).resolve().parent.as_posix())' "$0")
-# Used for rustc syncs.
-JOSH_FILTER=":rev(75dd959a3a40eb5b4574f8d2e23aa6efbeb33573:prefix=src/tools/miri):/src/tools/miri"
-# Needed for `./miri bench`.
-TOOLCHAIN=$(cd "$MIRIDIR"; rustup show active-toolchain | head -n 1 | cut -d ' ' -f 1)
-
-## Early commands, that don't do auto-things and don't want the environment-altering things happening below.
-case "$COMMAND" in
-toolchain)
-    cd "$MIRIDIR"
-    NEW_COMMIT=$(cat rust-version)
-    # Make sure rustup-toolchain-install-master is installed.
-    if ! which rustup-toolchain-install-master >/dev/null; then
-        echo "Please install rustup-toolchain-install-master by running 'cargo install rustup-toolchain-install-master'"
-        exit 1
-    fi
-    # Check if we already are at that commit.
-    CUR_COMMIT=$(rustc +miri --version -v 2>/dev/null | grep "^commit-hash: " | cut -d " " -f 2)
-    if [[ "$CUR_COMMIT" == "$NEW_COMMIT" ]]; then
-        echo "miri toolchain is already at commit $CUR_COMMIT."
-        if [[ "$TOOLCHAIN" != "miri" ]]; then
-            rustup override set miri
-        fi
-        exit 0
-    fi
-    # Install and setup new toolchain.
-    rustup toolchain uninstall miri
-    rustup-toolchain-install-master -n miri -c cargo -c rust-src -c rustc-dev -c llvm-tools -c rustfmt -c clippy "$@" -- "$NEW_COMMIT"
-    rustup override set miri
-    # Cleanup.
-    cargo clean
-    # Call 'cargo metadata' on the sources in case that changes the lockfile
-    # (which fails under some setups when it is done from inside vscode).
-    cargo metadata --format-version 1 --manifest-path "$(rustc --print sysroot)/lib/rustlib/rustc-src/rust/compiler/rustc/Cargo.toml" >/dev/null
-    # Done!
-    exit 0
-    ;;
-rustc-pull)
-    cd "$MIRIDIR"
-    FETCH_COMMIT="$1"
-    if [ -z "$FETCH_COMMIT" ]; then
-        FETCH_COMMIT=$(git ls-remote https://github.com/rust-lang/rust/ HEAD | cut -f 1)
-    fi
-    # Update rust-version file. As a separate commit, since making it part of
-    # the merge has confused the heck out of josh in the past.
-    echo "$FETCH_COMMIT" > rust-version
-    git commit rust-version -m "Preparing for merge from rustc" || (echo "FAILED to commit rust-version file, something went wrong"; exit 1)
-    # Fetch given rustc commit and note down which one that was
-    git fetch http://localhost:8000/rust-lang/rust.git@$FETCH_COMMIT$JOSH_FILTER.git || (echo "FAILED to fetch new commits, something went wrong"; exit 1)
-    git merge FETCH_HEAD --no-ff -m "Merge from rustc" || (echo "FAILED to merge new commits ($(git rev-parse FETCH_HEAD)), something went wrong"; exit 1)
-    exit 0
-    ;;
-rustc-push)
-    USER="$1"
-    BRANCH="$2"
-    if [ -z "$USER" ] || [ -z "$BRANCH" ]; then
-        echo "Usage: $0 rustc-push <github user> <branch>"
-        exit 1
-    fi
-    if [ -n "$RUSTC_GIT" ]; then
-        # Use an existing fork for the branch updates.
-        cd "$RUSTC_GIT"
-    else
-        # Do this in the local Miri repo.
-        echo "This will pull a copy of the rust-lang/rust history into this Miri checkout, growing it by about 1GB."
-        read -r -p "To avoid that, abort now and set the RUSTC_GIT environment variable to an existing rustc checkout. Proceed? [y/N] "
-        if [[ ! $REPLY =~ ^[Yy]$ ]]; then
-            exit 1
-        fi
-        cd "$MIRIDIR"
-    fi
-    # Prepare the branch. Pushing works much better if we use as base exactly
-    # the commit that we pulled from last time, so we use the `rust-version`
-    # file as a good approximation of that.
-    BASE=$(cat "$MIRIDIR/rust-version")
-    echo "Preparing $USER/rust (base: $BASE)..."
-    if git fetch "https://github.com/$USER/rust" "$BRANCH" &>/dev/null; then
-        echo "The branch '$BRANCH' seems to already exist in 'https://github.com/$USER/rust'. Please delete it and try again."
-        exit 1
-    fi
-    git fetch https://github.com/rust-lang/rust $BASE
-    git push https://github.com/$USER/rust $BASE:refs/heads/$BRANCH -f
-    echo
-    # Do the actual push.
-    cd "$MIRIDIR"
-    echo "Pushing Miri changes..."
-    git push http://localhost:8000/$USER/rust.git$JOSH_FILTER.git HEAD:$BRANCH
-    # Do a round-trip check to make sure the push worked as expected.
-    echo
-    git fetch http://localhost:8000/$USER/rust.git@$JOSH_FILTER.git $BRANCH &>/dev/null
-    if [[ $(git rev-parse HEAD) != $(git rev-parse FETCH_HEAD) ]]; then
-        echo "ERROR: Josh created a non-roundtrip push! Do NOT merge this into rustc!"
-        exit 1
-    else
-        echo "Confirmed that the push round-trips back to Miri properly. Please create a rustc PR:"
-        echo "    https://github.com/$USER/rust/pull/new/$BRANCH"
-        exit 0
-    fi
-    ;;
-many-seeds)
-    MIRI_SEED_START=${MIRI_SEED_START:-0} # default to 0
-    MIRI_SEEDS=${MIRI_SEEDS:-256} # default to 256
-    for SEED in $(seq $MIRI_SEED_START $(( $MIRI_SEED_START + $MIRI_SEEDS - 1 )) ); do
-        echo "Trying seed: $SEED"
-        MIRIFLAGS="$MIRIFLAGS -Zlayout-seed=$SEED -Zmiri-seed=$SEED" $@ || { echo "Failing seed: $SEED"; break; }
-    done
-    exit 0
-    ;;
-bench)
-    # The hyperfine to use
-    HYPERFINE=${HYPERFINE:-hyperfine -w 1 -m 5 --shell=none}
-    # Make sure we have an up-to-date Miri installed
-    "$0" install
-    # Run the requested benchmarks
-    if [ -z "${1+exists}" ]; then
-        BENCHES=( $(ls "$MIRIDIR/bench-cargo-miri" ) )
-    else
-        BENCHES=("$@")
-    fi
-    for BENCH in "${BENCHES[@]}"; do
-        $HYPERFINE "cargo +$TOOLCHAIN miri run --manifest-path $MIRIDIR/bench-cargo-miri/$BENCH/Cargo.toml"
-    done
-    exit 0
-    ;;
-esac
-
-## Run the auto-things.
-if [ -z "$MIRI_AUTO_OPS" ]; then
-    export MIRI_AUTO_OPS=42
-
-    # Run this first, so that the toolchain doesn't change after
-    # other code has run.
-    if [ -f "$MIRIDIR/.auto-everything" ] || [ -f "$MIRIDIR/.auto-toolchain" ] ; then
-        $0 toolchain
-        # Let's make sure to actually use that toolchain, too.
-        TOOLCHAIN=miri
-    fi
-
-    if [ -f "$MIRIDIR/.auto-everything" ] || [ -f "$MIRIDIR/.auto-fmt" ] ; then
-        $0 fmt
-    fi
-
-    if [ -f "$MIRIDIR/.auto-everything" ] || [ -f "$MIRIDIR/.auto-clippy" ] ; then
-        $0 clippy -- -D warnings
-    fi
-fi
-
-## Prepare the environment
-# Determine some toolchain properties
-TARGET=$(rustc +$TOOLCHAIN --version --verbose | grep "^host:" | cut -d ' ' -f 2)
-SYSROOT=$(rustc +$TOOLCHAIN --print sysroot)
-LIBDIR=$SYSROOT/lib/rustlib/$TARGET/lib
-if ! test -d "$LIBDIR"; then
-    echo "Something went wrong determining the library dir."
-    echo "I got $LIBDIR but that does not exist."
-    echo "Please report a bug at https://github.com/rust-lang/miri/issues."
-    exit 2
-fi
-
-# Prepare flags for cargo and rustc.
-CARGO="cargo +$TOOLCHAIN"
-# Share target dir between `miri` and `cargo-miri`.
-if [ -z "$CARGO_TARGET_DIR" ]; then
-    export CARGO_TARGET_DIR="$MIRIDIR/target"
-fi
-# We configure dev builds to not be unusably slow.
-if [ -z "$CARGO_PROFILE_DEV_OPT_LEVEL" ]; then
-    export CARGO_PROFILE_DEV_OPT_LEVEL=2
-fi
-# Enable rustc-specific lints (ignored without `-Zunstable-options`).
-export RUSTFLAGS="-Zunstable-options -Wrustc::internal -Wrust_2018_idioms -Wunused_lifetimes -Wsemicolon_in_expressions_from_macros $RUSTFLAGS"
-# We set the rpath so that Miri finds the private rustc libraries it needs.
-export RUSTFLAGS="-C link-args=-Wl,-rpath,$LIBDIR $RUSTFLAGS"
-
-## Helper functions
-
-# Build a sysroot and set MIRI_SYSROOT to use it. Arguments are passed to `cargo miri setup`.
-build_sysroot() {
-    if ! MIRI_SYSROOT="$($CARGO run $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml -- miri setup --print-sysroot "$@")"; then
-        # Run it again so the user can see the error.
-        $CARGO run $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml -- miri setup "$@"
-        echo "'cargo miri setup' failed"
-        exit 1
-    fi
-    export MIRI_SYSROOT
-}
-
-# Prepare and set MIRI_SYSROOT. Respects `MIRI_TEST_TARGET` and takes into account
-# locally built vs. distributed rustc.
-find_sysroot() {
-    if [ -n "$MIRI_SYSROOT" ]; then
-        # Sysroot already set, use that.
-        return 0
-    fi
-    # We need to build a sysroot.
-    if [ -n "$MIRI_TEST_TARGET" ]; then
-        build_sysroot --target "$MIRI_TEST_TARGET"
-    else
-        build_sysroot
-    fi
-}
-
-## Main
-
-# Run command.
-case "$COMMAND" in
-install)
-    # Install binaries to the miri toolchain's sysroot so they do not interact with other toolchains.
-    $CARGO install $CARGO_EXTRA_FLAGS --path "$MIRIDIR" --force --root "$SYSROOT" "$@"
-    $CARGO install $CARGO_EXTRA_FLAGS --path "$MIRIDIR"/cargo-miri --force --root "$SYSROOT" "$@"
-    ;;
-check)
-    # Check, and let caller control flags.
-    $CARGO check $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml --all-targets "$@"
-    $CARGO check $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml "$@"
-    ;;
-build)
-    # Build, and let caller control flags.
-    $CARGO build $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml "$@"
-    $CARGO build $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml "$@"
-    ;;
-test|bless)
-    # First build and get a sysroot.
-    $CARGO build $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml
-    find_sysroot
-    if [ "$COMMAND" = "bless" ]; then
-        export RUSTC_BLESS="Gesundheit"
-    fi
-    # Then test, and let caller control flags.
-    # Only in root project as `cargo-miri` has no tests.
-    $CARGO test $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml "$@"
-    ;;
-run|run-dep)
-    # Scan for "--target" to overwrite the "MIRI_TEST_TARGET" env var so
-    # that we set the MIRI_SYSROOT up the right way.
-    FOUND_TARGET_OPT=0
-    for ARG in "$@"; do
-        if [ "$LAST_ARG" = "--target" ]; then
-            # Found it!
-            export MIRI_TEST_TARGET="$ARG"
-            FOUND_TARGET_OPT=1
-            break
-        fi
-        LAST_ARG="$ARG"
-    done
-    if [ "$FOUND_TARGET_OPT" = "0" ] && [ -n "$MIRI_TEST_TARGET" ]; then
-        # Make sure Miri actually uses this target.
-        MIRIFLAGS="$MIRIFLAGS --target $MIRI_TEST_TARGET"
-    fi
-
-    CARGO="$CARGO --quiet"
-    # First build and get a sysroot.
-    $CARGO build $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml
-    find_sysroot
-    # Then run the actual command.
-    
-    if [ "$COMMAND" = "run-dep" ]; then
-        exec $CARGO test --test compiletest $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml -- --miri-run-dep-mode $MIRIFLAGS "$@"
-    else
-        exec $CARGO run $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml -- $MIRIFLAGS "$@"
-    fi
-    ;;
-fmt)
-    find "$MIRIDIR" -not \( -name target -prune \) -name '*.rs' \
-        | xargs rustfmt +$TOOLCHAIN --edition=2021 --config-path "$MIRIDIR/rustfmt.toml" "$@"
-    ;;
-clippy)
-    $CARGO clippy $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml --all-targets "$@"
-    $CARGO clippy $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml "$@"
-    ;;
-cargo)
-    # We carefully kept the working dir intact, so this will run cargo *on the workspace in the
-    # current working dir*, not on the main Miri workspace. That is exactly what RA needs.
-    $CARGO "$@"
-    ;;
-*)
-    echo "Unknown command: $COMMAND"
-    exit 1
-    ;;
-esac
+# 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.
+cargo build -q --manifest-path "$(dirname "$0")"/miri-script/Cargo.toml
+"$(dirname "$0")"/miri-script/target/debug/miri-script "$@"
diff --git a/src/tools/miri/miri-script/Cargo.lock b/src/tools/miri/miri-script/Cargo.lock
new file mode 100644
index 00000000000..cf6062d7d7f
--- /dev/null
+++ b/src/tools/miri/miri-script/Cargo.lock
@@ -0,0 +1,160 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "anyhow"
+version = "1.0.71"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
+
+[[package]]
+name = "dunce"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
+
+[[package]]
+name = "either"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
+
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.144"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1"
+
+[[package]]
+name = "miri-script"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "dunce",
+ "itertools",
+ "path_macro",
+ "rustc_version",
+ "shell-words",
+ "walkdir",
+ "which",
+ "xshell",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
+
+[[package]]
+name = "path_macro"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6e819bbd49d5939f682638fa54826bf1650abddcd65d000923de8ad63cc7d15"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
+
+[[package]]
+name = "shell-words"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
+
+[[package]]
+name = "walkdir"
+version = "2.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "which"
+version = "4.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269"
+dependencies = [
+ "either",
+ "libc",
+ "once_cell",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "xshell"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "962c039b3a7b16cf4e9a4248397c6585c07547412e7d6a6e035389a802dcfe90"
+dependencies = [
+ "xshell-macros",
+]
+
+[[package]]
+name = "xshell-macros"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1dbabb1cbd15a1d6d12d9ed6b35cc6777d4af87ab3ba155ea37215f20beab80c"
diff --git a/src/tools/miri/miri-script/Cargo.toml b/src/tools/miri/miri-script/Cargo.toml
new file mode 100644
index 00000000000..c0414a2fe37
--- /dev/null
+++ b/src/tools/miri/miri-script/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+authors = ["Miri Team"]
+description = "Helpers for miri maintenance"
+license = "MIT OR Apache-2.0"
+name = "miri-script"
+repository = "https://github.com/rust-lang/miri"
+version = "0.1.0"
+default-run = "miri-script"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+which = "4.4"
+walkdir = "2.3"
+itertools = "0.10"
+path_macro = "1.0"
+shell-words = "1.1"
+anyhow = "1.0"
+xshell = "0.2"
+rustc_version = "0.4"
+dunce = "1.0.4"
diff --git a/src/tools/miri/miri-script/miri b/src/tools/miri/miri-script/miri
new file mode 100755
index 00000000000..cf3ad06788a
--- /dev/null
+++ b/src/tools/miri/miri-script/miri
@@ -0,0 +1,4 @@
+#!/bin/sh
+# RA invokes `./miri cargo ...` for each workspace, so we need to forward that to the main `miri`
+# script. See <https://github.com/rust-analyzer/rust-analyzer/issues/10793>.
+exec "$(dirname "$0")"/../miri "$@"
diff --git a/src/tools/miri/miri-script/src/commands.rs b/src/tools/miri/miri-script/src/commands.rs
new file mode 100644
index 00000000000..66c2a4b0fd6
--- /dev/null
+++ b/src/tools/miri/miri-script/src/commands.rs
@@ -0,0 +1,455 @@
+use std::env;
+use std::ffi::OsString;
+use std::io::Write;
+use std::ops::Not;
+
+use anyhow::{anyhow, bail, Context, Result};
+use path_macro::path;
+use walkdir::WalkDir;
+use xshell::cmd;
+
+use crate::util::*;
+use crate::Command;
+
+/// Used for rustc syncs.
+const JOSH_FILTER: &str =
+    ":rev(75dd959a3a40eb5b4574f8d2e23aa6efbeb33573:prefix=src/tools/miri):/src/tools/miri";
+
+impl MiriEnv {
+    fn build_miri_sysroot(&mut self) -> Result<()> {
+        if self.sh.var("MIRI_SYSROOT").is_ok() {
+            // Sysroot already set, use that.
+            return Ok(());
+        }
+        let manifest_path = path!(self.miri_dir / "cargo-miri" / "Cargo.toml");
+        let Self { toolchain, cargo_extra_flags, .. } = &self;
+        let target = &match self.sh.var("MIRI_TEST_TARGET") {
+            Ok(target) => vec!["--target".into(), target],
+            Err(_) => vec![],
+        };
+        let output = cmd!(self.sh,
+            "cargo +{toolchain} --quiet run {cargo_extra_flags...} --manifest-path {manifest_path} --
+             miri setup --print-sysroot {target...}"
+        ).read();
+        let Ok(output) = output else {
+            // Run it again (without `--print-sysroot` or `--quiet`) so the user can see the error.
+            cmd!(
+                self.sh,
+                "cargo +{toolchain} run {cargo_extra_flags...} --manifest-path {manifest_path} --
+                miri setup {target...}"
+            )
+            .run()
+            .with_context(|| "`cargo miri setup` failed")?;
+            panic!("`cargo miri setup` didn't fail again the 2nd time?");
+        };
+        self.sh.set_var("MIRI_SYSROOT", output);
+        Ok(())
+    }
+}
+
+impl Command {
+    fn auto_actions() -> Result<()> {
+        let miri_dir = miri_dir()?;
+        let auto_everything = path!(miri_dir / ".auto_everything").exists();
+        let auto_toolchain = auto_everything || path!(miri_dir / ".auto-toolchain").exists();
+        let auto_fmt = auto_everything || path!(miri_dir / ".auto-fmt").exists();
+        let auto_clippy = auto_everything || path!(miri_dir / ".auto-clippy").exists();
+
+        // `toolchain` goes first as it could affect the others
+        if auto_toolchain {
+            Self::toolchain(vec![])?;
+        }
+        if auto_fmt {
+            Self::fmt(vec![])?;
+        }
+        if auto_clippy {
+            Self::clippy(vec![])?;
+        }
+
+        Ok(())
+    }
+
+    pub fn exec(self) -> Result<()> {
+        match self {
+            Command::Install { flags } => Self::install(flags),
+            Command::Build { flags } => Self::build(flags),
+            Command::Check { flags } => Self::check(flags),
+            Command::Test { bless, flags } => Self::test(bless, flags),
+            Command::Run { dep, flags } => Self::run(dep, flags),
+            Command::Fmt { flags } => Self::fmt(flags),
+            Command::Clippy { flags } => Self::clippy(flags),
+            Command::Cargo { flags } => Self::cargo(flags),
+            Command::ManySeeds { command } => Self::many_seeds(command),
+            Command::Bench { benches } => Self::bench(benches),
+            Command::Toolchain { flags } => Self::toolchain(flags),
+            Command::RustcPull { commit } => Self::rustc_pull(commit.clone()),
+            Command::RustcPush { github_user, branch } => Self::rustc_push(github_user, branch),
+        }
+    }
+
+    fn toolchain(flags: Vec<OsString>) -> Result<()> {
+        // Make sure rustup-toolchain-install-master is installed.
+        which::which("rustup-toolchain-install-master")
+            .context("Please install rustup-toolchain-install-master by running 'cargo install rustup-toolchain-install-master'")?;
+        let sh = shell()?;
+        sh.change_dir(miri_dir()?);
+        let new_commit = Some(sh.read_file("rust-version")?.trim().to_owned());
+        let current_commit = {
+            let rustc_info = cmd!(sh, "rustc +miri --version -v").read();
+            if rustc_info.is_err() {
+                None
+            } else {
+                let metadata = rustc_version::version_meta_for(&rustc_info.unwrap())?;
+                Some(
+                    metadata
+                        .commit_hash
+                        .ok_or_else(|| anyhow!("rustc metadata did not contain commit hash"))?,
+                )
+            }
+        };
+        // Check if we already are at that commit.
+        if current_commit == new_commit {
+            if active_toolchain()? != "miri" {
+                cmd!(sh, "rustup override set miri").run()?;
+            }
+            return Ok(());
+        }
+        // Install and setup new toolchain.
+        cmd!(sh, "rustup toolchain uninstall miri").run()?;
+
+        cmd!(sh, "rustup-toolchain-install-master -n miri -c cargo -c rust-src -c rustc-dev -c llvm-tools -c rustfmt -c clippy {flags...} -- {new_commit...}").run()?;
+        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(())
+    }
+
+    fn rustc_pull(commit: Option<String>) -> Result<()> {
+        let sh = shell()?;
+        sh.change_dir(miri_dir()?);
+        let commit = commit.map(Result::Ok).unwrap_or_else(|| {
+            let rust_repo_head =
+                cmd!(sh, "git ls-remote https://github.com/rust-lang/rust/ HEAD").read()?;
+            rust_repo_head
+                .split_whitespace()
+                .next()
+                .map(|front| front.trim().to_owned())
+                .ok_or_else(|| anyhow!("Could not obtain Rust repo HEAD from remote."))
+        })?;
+        // Make sure the repo is clean.
+        if cmd!(sh, "git status --untracked-files=no --porcelain").read()?.is_empty().not() {
+            bail!("working directory must be clean before running `./miri rustc-pull`");
+        }
+
+        // Update rust-version file. As a separate commit, since making it part of
+        // the merge has confused the heck out of josh in the past.
+        // We pass `--no-verify` to avoid running git hooks like `./miri fmt` that could in turn
+        // trigger auto-actions.
+        sh.write_file("rust-version", &commit)?;
+        const PREPARING_COMMIT_MESSAGE: &str = "Preparing for merge from rustc";
+        cmd!(sh, "git commit rust-version --no-verify -m {PREPARING_COMMIT_MESSAGE}")
+            .run()
+            .context("FAILED to commit rust-version file, something went wrong")?;
+
+        // Fetch given rustc commit.
+        cmd!(sh, "git fetch http://localhost:8000/rust-lang/rust.git@{commit}{JOSH_FILTER}.git")
+            .run()
+            .map_err(|e| {
+                // Try to un-do the previous `git commit`, to leave the repo in the state we found it it.
+                cmd!(sh, "git reset --hard HEAD^")
+                    .run()
+                    .expect("FAILED to clean up again after failed `git fetch`, sorry for that");
+                e
+            })
+            .context("FAILED to fetch new commits, something went wrong (committing the rust-version file has been undone)")?;
+
+        // Merge the fetched commit.
+        const MERGE_COMMIT_MESSAGE: &str = "Merge from rustc";
+        cmd!(sh, "git merge FETCH_HEAD --no-verify --no-ff -m {MERGE_COMMIT_MESSAGE}")
+            .run()
+            .context("FAILED to merge new commits, something went wrong")?;
+        Ok(())
+    }
+
+    fn rustc_push(github_user: String, branch: String) -> Result<()> {
+        let sh = shell()?;
+        sh.change_dir(miri_dir()?);
+        let base = sh.read_file("rust-version")?.trim().to_owned();
+        // Make sure the repo is clean.
+        if cmd!(sh, "git status --untracked-files=no --porcelain").read()?.is_empty().not() {
+            bail!("working directory must be clean before running `./miri rustc-push`");
+        }
+
+        // Find a repo we can do our preparation in.
+        if let Ok(rustc_git) = env::var("RUSTC_GIT") {
+            // If rustc_git is `Some`, we'll use an existing fork for the branch updates.
+            sh.change_dir(rustc_git);
+        } else {
+            // Otherwise, do this in the local Miri repo.
+            println!(
+                "This will pull a copy of the rust-lang/rust history into this Miri checkout, growing it by about 1GB."
+            );
+            print!(
+                "To avoid that, abort now and set the `--rustc-git` flag to an existing rustc checkout. Proceed? [y/N] "
+            );
+            std::io::stdout().flush()?;
+            let mut answer = String::new();
+            std::io::stdin().read_line(&mut answer)?;
+            if answer.trim().to_lowercase() != "y" {
+                std::process::exit(1);
+            }
+        };
+        // Prepare the branch. Pushing works much better if we use as base exactly
+        // the commit that we pulled from last time, so we use the `rust-version`
+        // file as a good approximation of that.
+        println!("Preparing {github_user}/rust (base: {base})...");
+        if cmd!(sh, "git fetch https://github.com/{github_user}/rust {branch}")
+            .ignore_stderr()
+            .read()
+            .is_ok()
+        {
+            println!(
+                "The branch '{branch}' seems to already exist in 'https://github.com/{github_user}/rust'. Please delete it and try again."
+            );
+            std::process::exit(1);
+        }
+        cmd!(sh, "git fetch https://github.com/rust-lang/rust {base}").run()?;
+        cmd!(sh, "git push https://github.com/{github_user}/rust {base}:refs/heads/{branch}")
+            .run()?;
+        println!();
+
+        // Do the actual push.
+        sh.change_dir(miri_dir()?);
+        println!("Pushing miri changes...");
+        cmd!(
+            sh,
+            "git push http://localhost:8000/{github_user}/rust.git{JOSH_FILTER}.git HEAD:{branch}"
+        )
+        .run()?;
+        println!();
+
+        // Do a round-trip check to make sure the push worked as expected.
+        cmd!(
+            sh,
+            "git fetch http://localhost:8000/{github_user}/rust.git{JOSH_FILTER}.git {branch}"
+        )
+        .ignore_stderr()
+        .read()?;
+        let head = cmd!(sh, "git rev-parse HEAD").read()?;
+        let fetch_head = cmd!(sh, "git rev-parse FETCH_HEAD").read()?;
+        if head != fetch_head {
+            bail!("Josh created a non-roundtrip push! Do NOT merge this into rustc!");
+        }
+        println!(
+            "Confirmed that the push round-trips back to Miri properly. Please create a rustc PR:"
+        );
+        println!("    https://github.com/{github_user}/rust/pull/new/{branch}");
+        Ok(())
+    }
+
+    fn many_seeds(command: Vec<OsString>) -> Result<()> {
+        let seed_start: u64 = env::var("MIRI_SEED_START")
+            .unwrap_or_else(|_| "0".into())
+            .parse()
+            .context("failed to parse MIRI_SEED_START")?;
+        let seed_count: u64 = env::var("MIRI_SEEDS")
+            .unwrap_or_else(|_| "256".into())
+            .parse()
+            .context("failed to parse MIRI_SEEDS")?;
+        let seed_end = seed_start + seed_count;
+        let Some((command_name, trailing_args)) = command.split_first() else {
+            bail!("expected many-seeds command to be non-empty");
+        };
+        let sh = shell()?;
+        for seed in seed_start..seed_end {
+            println!("Trying seed: {seed}");
+            let mut miriflags = env::var_os("MIRIFLAGS").unwrap_or_default();
+            miriflags.push(format!(" -Zlayout-seed={seed} -Zmiri-seed={seed}"));
+            let status = cmd!(sh, "{command_name} {trailing_args...}")
+                .env("MIRIFLAGS", miriflags)
+                .quiet()
+                .run();
+            if status.is_err() {
+                println!("Failing seed: {seed}");
+                break;
+            }
+        }
+        Ok(())
+    }
+
+    fn bench(benches: Vec<OsString>) -> Result<()> {
+        // The hyperfine to use
+        let hyperfine = env::var("HYPERFINE");
+        let hyperfine = hyperfine.as_deref().unwrap_or("hyperfine -w 1 -m 5 --shell=none");
+        let hyperfine = shell_words::split(hyperfine)?;
+        let Some((program_name, args)) = hyperfine.split_first() else {
+            bail!("expected HYPERFINE environment variable to be non-empty");
+        };
+        // Make sure we have an up-to-date Miri installed and selected the right toolchain.
+        Self::install(vec![])?;
+
+        let sh = shell()?;
+        sh.change_dir(miri_dir()?);
+        let benches_dir = "bench-cargo-miri";
+        let benches = if benches.is_empty() {
+            sh.read_dir(benches_dir)?
+                .into_iter()
+                .filter(|path| path.is_dir())
+                .map(Into::into)
+                .collect()
+        } else {
+            benches.to_owned()
+        };
+        // Run the requested benchmarks
+        for bench in benches {
+            let current_bench = path!(benches_dir / bench / "Cargo.toml");
+            // We don't attempt to escape `current_bench`, but we wrap it in quotes.
+            // That seems to make Windows CI happy.
+            cmd!(
+                sh,
+                "{program_name} {args...} 'cargo miri run --manifest-path \"'{current_bench}'\"'"
+            )
+            .run()?;
+        }
+        Ok(())
+    }
+
+    fn install(flags: Vec<OsString>) -> Result<()> {
+        Self::auto_actions()?;
+        let e = MiriEnv::new()?;
+        e.install_to_sysroot(e.miri_dir.clone(), &flags)?;
+        e.install_to_sysroot(path!(e.miri_dir / "cargo-miri"), &flags)?;
+        Ok(())
+    }
+
+    fn build(flags: Vec<OsString>) -> Result<()> {
+        Self::auto_actions()?;
+        let e = MiriEnv::new()?;
+        e.build(path!(e.miri_dir / "Cargo.toml"), &flags, /* quiet */ false)?;
+        e.build(path!(e.miri_dir / "cargo-miri" / "Cargo.toml"), &flags, /* quiet */ false)?;
+        Ok(())
+    }
+
+    fn check(flags: Vec<OsString>) -> Result<()> {
+        Self::auto_actions()?;
+        let e = MiriEnv::new()?;
+        e.check(path!(e.miri_dir / "Cargo.toml"), &flags)?;
+        e.check(path!(e.miri_dir / "cargo-miri" / "Cargo.toml"), &flags)?;
+        Ok(())
+    }
+
+    fn clippy(flags: Vec<OsString>) -> Result<()> {
+        Self::auto_actions()?;
+        let e = MiriEnv::new()?;
+        e.clippy(path!(e.miri_dir / "Cargo.toml"), &flags)?;
+        e.clippy(path!(e.miri_dir / "cargo-miri" / "Cargo.toml"), &flags)?;
+        e.clippy(path!(e.miri_dir / "miri-script" / "Cargo.toml"), &flags)?;
+        Ok(())
+    }
+
+    fn cargo(flags: Vec<OsString>) -> Result<()> {
+        Self::auto_actions()?;
+        let e = MiriEnv::new()?;
+        let toolchain = &e.toolchain;
+        // We carefully kept the working dir intact, so this will run cargo *on the workspace in the
+        // current working dir*, not on the main Miri workspace. That is exactly what RA needs.
+        cmd!(e.sh, "cargo +{toolchain} {flags...}").run()?;
+        Ok(())
+    }
+
+    fn test(bless: bool, flags: Vec<OsString>) -> Result<()> {
+        Self::auto_actions()?;
+        let mut e = MiriEnv::new()?;
+        // First build, and get a sysroot.
+        e.build(path!(e.miri_dir / "Cargo.toml"), &[], /* quiet */ true)?;
+        e.build_miri_sysroot()?;
+
+        // Then test, and let caller control flags.
+        // Only in root project as `cargo-miri` has no tests.
+        if bless {
+            e.sh.set_var("RUSTC_BLESS", "Gesundheit");
+        }
+        e.test(path!(e.miri_dir / "Cargo.toml"), &flags)?;
+        Ok(())
+    }
+
+    fn run(dep: bool, flags: Vec<OsString>) -> Result<()> {
+        Self::auto_actions()?;
+        let mut e = MiriEnv::new()?;
+        // Scan for "--target" to overwrite the "MIRI_TEST_TARGET" env var so
+        // that we set the MIRI_SYSROOT up the right way.
+        use itertools::Itertools;
+        let target = flags.iter().tuple_windows().find(|(first, _)| first == &"--target");
+        if let Some((_, target)) = target {
+            // Found it!
+            e.sh.set_var("MIRI_TEST_TARGET", target);
+        } else if let Ok(target) = std::env::var("MIRI_TEST_TARGET") {
+            // Make sure miri actually uses this target.
+            let miriflags = e.sh.var("MIRIFLAGS").unwrap_or_default();
+            e.sh.set_var("MIRIFLAGS", format!("{miriflags} --target {target}"));
+        }
+        // First build, and get a sysroot.
+        let miri_manifest = path!(e.miri_dir / "Cargo.toml");
+        e.build(&miri_manifest, &[], /* quiet */ true)?;
+        e.build_miri_sysroot()?;
+
+        // Then run the actual command.
+        let miri_flags = e.sh.var("MIRIFLAGS").unwrap_or_default();
+        let miri_flags = flagsplit(&miri_flags);
+        let toolchain = &e.toolchain;
+        let extra_flags = &e.cargo_extra_flags;
+        if dep {
+            cmd!(
+                e.sh,
+                "cargo +{toolchain} --quiet test --test compiletest {extra_flags...} --manifest-path {miri_manifest} -- --miri-run-dep-mode {miri_flags...} {flags...}"
+            ).quiet().run()?;
+        } else {
+            cmd!(
+                e.sh,
+                "cargo +{toolchain} --quiet run {extra_flags...} --manifest-path {miri_manifest} -- {miri_flags...} {flags...}"
+            ).quiet().run()?;
+        }
+        Ok(())
+    }
+
+    fn fmt(flags: Vec<OsString>) -> Result<()> {
+        Self::auto_actions()?;
+        let e = MiriEnv::new()?;
+        let toolchain = &e.toolchain;
+        let config_path = path!(e.miri_dir / "rustfmt.toml");
+
+        let mut cmd = cmd!(
+            e.sh,
+            "rustfmt +{toolchain} --edition=2021 --config-path {config_path} {flags...}"
+        );
+        eprintln!("$ {cmd} ...");
+
+        // Add all the filenames to the command.
+        // FIXME: `rustfmt` will follow the `mod` statements in these files, so we get a bunch of
+        // duplicate diffs.
+        for item in WalkDir::new(&e.miri_dir).into_iter().filter_entry(|entry| {
+            let name = entry.file_name().to_string_lossy();
+            let ty = entry.file_type();
+            if ty.is_file() {
+                name.ends_with(".rs")
+            } else {
+                // dir or symlink. skip `target` and `.git`.
+                &name != "target" && &name != ".git"
+            }
+        }) {
+            let item = item?;
+            if item.file_type().is_file() {
+                cmd = cmd.arg(item.into_path());
+            }
+        }
+
+        // We want our own error message, repeating the command is too much.
+        cmd.quiet().run().map_err(|_| anyhow!("`rustfmt` failed"))?;
+        Ok(())
+    }
+}
diff --git a/src/tools/miri/miri-script/src/main.rs b/src/tools/miri/miri-script/src/main.rs
new file mode 100644
index 00000000000..849a9168028
--- /dev/null
+++ b/src/tools/miri/miri-script/src/main.rs
@@ -0,0 +1,210 @@
+mod commands;
+mod util;
+
+use std::env;
+use std::ffi::OsString;
+
+use anyhow::{anyhow, bail, Result};
+
+#[derive(Clone, Debug)]
+pub enum Command {
+    /// Installs the miri driver and cargo-miri.
+    /// Sets up the rpath such that the installed binary should work in any
+    /// working directory. Note that the binaries are placed in the `miri` toolchain
+    /// sysroot, to prevent conflicts with other toolchains.
+    Install {
+        /// Flags that are passed through to `cargo install`.
+        flags: Vec<OsString>,
+    },
+    /// Just build miri.
+    Build {
+        /// Flags that are passed through to `cargo build`.
+        flags: Vec<OsString>,
+    },
+    /// Just check miri.
+    Check {
+        /// Flags that are passed through to `cargo check`.
+        flags: Vec<OsString>,
+    },
+    /// Build miri, set up a sysroot and then run the test suite.
+    Test {
+        bless: bool,
+        /// Flags that are passed through to `cargo test`.
+        flags: Vec<OsString>,
+    },
+    /// Build miri, set up a sysroot and then run the driver with the given <flags>.
+    /// (Also respects MIRIFLAGS environment variable.)
+    Run {
+        dep: bool,
+        /// Flags that are passed through to `miri`.
+        flags: Vec<OsString>,
+    },
+    /// Format all sources and tests.
+    Fmt {
+        /// Flags that are passed through to `rustfmt`.
+        flags: Vec<OsString>,
+    },
+    /// Runs clippy on all sources.
+    Clippy {
+        /// Flags that are passed through to `cargo clippy`.
+        flags: Vec<OsString>,
+    },
+    /// Runs just `cargo <flags>` with the Miri-specific environment variables.
+    /// Mainly meant to be invoked by rust-analyzer.
+    Cargo { flags: Vec<OsString> },
+    /// Runs <command> over and over again with different seeds for Miri. The MIRIFLAGS
+    /// variable is set to its original value appended with ` -Zmiri-seed=$SEED` for
+    /// many different seeds.
+    ManySeeds { command: Vec<OsString> },
+    /// Runs the benchmarks from bench-cargo-miri in hyperfine. hyperfine needs to be installed.
+    Bench {
+        /// List of benchmarks to run. By default all benchmarks are run.
+        benches: Vec<OsString>,
+    },
+    /// Update and activate the rustup toolchain 'miri' to the commit given in the
+    /// `rust-version` file.
+    /// `rustup-toolchain-install-master` must be installed for this to work. Any extra
+    /// flags are passed to `rustup-toolchain-install-master`.
+    Toolchain { flags: Vec<OsString> },
+    /// Pull and merge Miri changes from the rustc repo. Defaults to fetching the latest
+    /// rustc commit. The fetched commit is stored in the `rust-version` file, so the
+    /// next `./miri toolchain` will install the rustc that just got pulled.
+    RustcPull { commit: Option<String> },
+    /// Push Miri changes back to the rustc repo. This will pull a copy of the rustc
+    /// history into the Miri repo, unless you set the RUSTC_GIT env var to an existing
+    /// clone of the rustc repo.
+    RustcPush { github_user: String, branch: String },
+}
+
+const HELP: &str = r#"  COMMANDS
+
+./miri build <flags>:
+Just build miri. <flags> are passed to `cargo build`.
+
+./miri check <flags>:
+Just check miri. <flags> are passed to `cargo check`.
+
+./miri test [--bless] <flags>:
+Build miri, set up a sysroot and then run the test suite. <flags> are passed
+to the final `cargo test` invocation.
+
+./miri run [--dep] <flags>:
+Build miri, set up a sysroot and then run the driver with the given <flags>.
+(Also respects MIRIFLAGS environment variable.)
+
+./miri fmt <flags>:
+Format all sources and tests. <flags> are passed to `rustfmt`.
+
+./miri clippy <flags>:
+Runs clippy on all sources. <flags> are passed to `cargo clippy`.
+
+./miri cargo <flags>:
+Runs just `cargo <flags>` with the Miri-specific environment variables.
+Mainly meant to be invoked by rust-analyzer.
+
+./miri install <flags>:
+Installs the miri driver and cargo-miri. <flags> are passed to `cargo
+install`. Sets up the rpath such that the installed binary should work in any
+working directory. Note that the binaries are placed in the `miri` toolchain
+sysroot, to prevent conflicts with other toolchains.
+
+./miri many-seeds <command>:
+Runs <command> over and over again with different seeds for Miri. The MIRIFLAGS
+variable is set to its original value appended with ` -Zmiri-seed=$SEED` for
+many different seeds. The MIRI_SEEDS variable controls how many seeds are being
+tried; MIRI_SEED_START controls the first seed to try.
+
+./miri bench <benches>:
+Runs the benchmarks from bench-cargo-miri in hyperfine. hyperfine needs to be installed.
+<benches> can explicitly list the benchmarks to run; by default, all of them are run.
+
+./miri toolchain <flags>:
+Update and activate the rustup toolchain 'miri' to the commit given in the
+`rust-version` file.
+`rustup-toolchain-install-master` must be installed for this to work. Any extra
+flags are passed to `rustup-toolchain-install-master`.
+
+./miri rustc-pull <commit>:
+Pull and merge Miri changes from the rustc repo. Defaults to fetching the latest
+rustc commit. The fetched commit is stored in the `rust-version` file, so the
+next `./miri toolchain` will install the rustc that just got pulled.
+
+./miri rustc-push <github user> <branch>:
+Push Miri changes back to the rustc repo. This will pull a copy of the rustc
+history into the Miri repo, unless you set the RUSTC_GIT env var to an existing
+clone of the rustc repo.
+
+  ENVIRONMENT VARIABLES
+
+MIRI_SYSROOT:
+If already set, the "sysroot setup" step is skipped.
+
+CARGO_EXTRA_FLAGS:
+Pass extra flags to all cargo invocations. (Ignored by `./miri cargo`.)"#;
+
+fn main() -> Result<()> {
+    // We are hand-rolling our own argument parser, since `clap` can't express what we need
+    // (https://github.com/clap-rs/clap/issues/5055).
+    let mut args = env::args_os().peekable();
+    args.next().unwrap(); // skip program name
+    let command = match args.next().and_then(|s| s.into_string().ok()).as_deref() {
+        Some("build") => Command::Build { flags: args.collect() },
+        Some("check") => Command::Check { flags: args.collect() },
+        Some("test") => {
+            let bless = args.peek().is_some_and(|a| a.to_str() == Some("--bless"));
+            if bless {
+                // Consume the flag.
+                args.next().unwrap();
+            }
+            Command::Test { bless, flags: args.collect() }
+        }
+        Some("run") => {
+            let dep = args.peek().is_some_and(|a| a.to_str() == Some("--dep"));
+            if dep {
+                // Consume the flag.
+                args.next().unwrap();
+            }
+            Command::Run { dep, flags: args.collect() }
+        }
+        Some("fmt") => Command::Fmt { flags: args.collect() },
+        Some("clippy") => Command::Clippy { flags: args.collect() },
+        Some("cargo") => Command::Cargo { flags: args.collect() },
+        Some("install") => Command::Install { flags: args.collect() },
+        Some("many-seeds") => Command::ManySeeds { command: args.collect() },
+        Some("bench") => Command::Bench { benches: args.collect() },
+        Some("toolchain") => Command::Toolchain { flags: args.collect() },
+        Some("rustc-pull") => {
+            let commit = args.next().map(|a| a.to_string_lossy().into_owned());
+            if args.next().is_some() {
+                bail!("Too many arguments for `./miri rustc-pull`");
+            }
+            Command::RustcPull { commit }
+        }
+        Some("rustc-push") => {
+            let github_user = args
+                .next()
+                .ok_or_else(|| {
+                    anyhow!("Missing first argument for `./miri rustc-push GITHUB_USER BRANCH`")
+                })?
+                .to_string_lossy()
+                .into_owned();
+            let branch = args
+                .next()
+                .ok_or_else(|| {
+                    anyhow!("Missing second argument for `./miri rustc-push GITHUB_USER BRANCH`")
+                })?
+                .to_string_lossy()
+                .into_owned();
+            if args.next().is_some() {
+                bail!("Too many arguments for `./miri rustc-push GITHUB_USER BRANCH`");
+            }
+            Command::RustcPush { github_user, branch }
+        }
+        _ => {
+            eprintln!("Unknown or missing command. Usage:\n\n{HELP}");
+            std::process::exit(1);
+        }
+    };
+    command.exec()?;
+    Ok(())
+}
diff --git a/src/tools/miri/miri-script/src/util.rs b/src/tools/miri/miri-script/src/util.rs
new file mode 100644
index 00000000000..caebd88b14b
--- /dev/null
+++ b/src/tools/miri/miri-script/src/util.rs
@@ -0,0 +1,153 @@
+use std::ffi::{OsStr, OsString};
+use std::path::PathBuf;
+
+use anyhow::{Context, Result};
+use dunce::canonicalize;
+use path_macro::path;
+use xshell::{cmd, Shell};
+
+pub fn miri_dir() -> std::io::Result<PathBuf> {
+    const MIRI_SCRIPT_ROOT_DIR: &str = env!("CARGO_MANIFEST_DIR");
+    Ok(canonicalize(MIRI_SCRIPT_ROOT_DIR)?.parent().unwrap().into())
+}
+
+/// Queries the active toolchain for the Miri dir.
+pub fn active_toolchain() -> Result<String> {
+    let sh = shell()?;
+    sh.change_dir(miri_dir()?);
+    let stdout = cmd!(sh, "rustup show active-toolchain").read()?;
+    Ok(stdout.split_whitespace().next().context("Could not obtain active Rust toolchain")?.into())
+}
+
+pub fn shell() -> Result<Shell> {
+    let sh = Shell::new()?;
+    // xshell does not propagate parent's env variables by default.
+    for (k, v) in std::env::vars_os() {
+        sh.set_var(k, v);
+    }
+    Ok(sh)
+}
+
+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()
+}
+
+/// Some extra state we track for building Miri, such as the right RUSTFLAGS.
+pub struct MiriEnv {
+    /// miri_dir is the root of the miri repository checkout we are working in.
+    pub miri_dir: PathBuf,
+    /// active_toolchain is passed as `+toolchain` argument to cargo/rustc invocations.
+    pub toolchain: String,
+    /// Extra flags to pass to cargo.
+    pub cargo_extra_flags: Vec<String>,
+    /// The rustc sysroot
+    pub sysroot: PathBuf,
+    /// The shell we use.
+    pub sh: Shell,
+}
+
+impl MiriEnv {
+    pub fn new() -> Result<Self> {
+        let sh = shell()?;
+        let toolchain = active_toolchain()?;
+        let miri_dir = miri_dir()?;
+
+        let sysroot = cmd!(sh, "rustc +{toolchain} --print sysroot").read()?.into();
+        let target_output = cmd!(sh, "rustc +{toolchain} --version --verbose").read()?;
+        let rustc_meta = rustc_version::version_meta_for(&target_output)?;
+        let libdir = path!(sysroot / "lib" / "rustlib" / rustc_meta.host / "lib");
+
+        // Determine some toolchain properties
+        if !libdir.exists() {
+            println!("Something went wrong determining the library dir.");
+            println!("I got {} but that does not exist.", libdir.display());
+            println!("Please report a bug at https://github.com/rust-lang/miri/issues.");
+            std::process::exit(2);
+        }
+        // Share target dir between `miri` and `cargo-miri`.
+        let target_dir = std::env::var_os("CARGO_TARGET_DIR")
+            .unwrap_or_else(|| path!(miri_dir / "target").into());
+        sh.set_var("CARGO_TARGET_DIR", target_dir);
+
+        // We configure dev builds to not be unusably slow.
+        let devel_opt_level =
+            std::env::var_os("CARGO_PROFILE_DEV_OPT_LEVEL").unwrap_or_else(|| "2".into());
+        sh.set_var("CARGO_PROFILE_DEV_OPT_LEVEL", devel_opt_level);
+
+        // Compute rustflags.
+        let rustflags = {
+            let env = std::env::var_os("RUSTFLAGS");
+            let mut flags_with_warnings = OsString::from(
+                "-Zunstable-options -Wrustc::internal -Wrust_2018_idioms -Wunused_lifetimes -Wsemicolon_in_expressions_from_macros ",
+            );
+            if let Some(value) = env {
+                flags_with_warnings.push(value);
+            }
+            // We set the rpath so that Miri finds the private rustc libraries it needs.
+            let mut flags_with_compiler_settings = OsString::from("-C link-args=-Wl,-rpath,");
+            flags_with_compiler_settings.push(&libdir);
+            flags_with_compiler_settings.push(flags_with_warnings);
+            flags_with_compiler_settings
+        };
+        sh.set_var("RUSTFLAGS", rustflags);
+
+        // Get extra flags for cargo.
+        let cargo_extra_flags = std::env::var("CARGO_EXTRA_FLAGS").unwrap_or_default();
+        let cargo_extra_flags = flagsplit(&cargo_extra_flags);
+
+        Ok(MiriEnv { miri_dir, toolchain, sh, sysroot, cargo_extra_flags })
+    }
+
+    pub fn install_to_sysroot(
+        &self,
+        path: impl AsRef<OsStr>,
+        args: impl IntoIterator<Item = impl AsRef<OsStr>>,
+    ) -> Result<()> {
+        let MiriEnv { sysroot, toolchain, cargo_extra_flags, .. } = self;
+        // Install binaries to the miri toolchain's `sysroot` so they do not interact with other toolchains.
+        cmd!(self.sh, "cargo +{toolchain} install {cargo_extra_flags...} --path {path} --force --root {sysroot} {args...}").run()?;
+        Ok(())
+    }
+
+    pub fn build(
+        &self,
+        manifest_path: impl AsRef<OsStr>,
+        args: &[OsString],
+        quiet: bool,
+    ) -> Result<()> {
+        let MiriEnv { toolchain, cargo_extra_flags, .. } = self;
+        let quiet_flag = if quiet { Some("--quiet") } else { None };
+        let mut cmd = cmd!(
+            self.sh,
+            "cargo +{toolchain} build {cargo_extra_flags...} --manifest-path {manifest_path} {quiet_flag...} {args...}"
+        );
+        cmd.set_quiet(quiet);
+        cmd.run()?;
+        Ok(())
+    }
+
+    pub fn check(&self, manifest_path: impl AsRef<OsStr>, args: &[OsString]) -> Result<()> {
+        let MiriEnv { toolchain, cargo_extra_flags, .. } = self;
+        cmd!(self.sh, "cargo +{toolchain} check {cargo_extra_flags...} --manifest-path {manifest_path} --all-targets {args...}")
+            .run()?;
+        Ok(())
+    }
+
+    pub fn clippy(&self, manifest_path: impl AsRef<OsStr>, args: &[OsString]) -> Result<()> {
+        let MiriEnv { toolchain, cargo_extra_flags, .. } = self;
+        cmd!(self.sh, "cargo +{toolchain} clippy {cargo_extra_flags...} --manifest-path {manifest_path} --all-targets {args...}")
+            .run()?;
+        Ok(())
+    }
+
+    pub fn test(&self, manifest_path: impl AsRef<OsStr>, args: &[OsString]) -> Result<()> {
+        let MiriEnv { toolchain, cargo_extra_flags, .. } = self;
+        cmd!(
+            self.sh,
+            "cargo +{toolchain} test {cargo_extra_flags...} --manifest-path {manifest_path} {args...}"
+        )
+        .run()?;
+        Ok(())
+    }
+}