Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion crates/mirth-lab/src/rustc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ pub struct Compile<'a> {
/// Name the output `<out_dir>/prog` with `-o` (the default); off when the extra options say
/// where outputs go (`--out-dir`).
pub name_output: bool,
/// Extra environment variables (`MIRTH_OUT` for a coverage-instrumented compiler, say).
pub env: Vec<(String, String)>,
}

impl<'a> Compile<'a> {
Expand All @@ -144,9 +146,15 @@ impl<'a> Compile<'a> {
timeout: Duration::from_secs(300),
bootstrap: true,
name_output: true,
env: Vec::new(),
}
}

pub fn env(mut self, key: &str, value: impl Into<String>) -> Self {
self.env.push((key.to_owned(), value.into()));
self
}

pub fn extra<I: IntoIterator<Item = S>, S: Into<String>>(mut self, extra: I) -> Self {
self.extra.extend(extra.into_iter().map(Into::into));
self
Expand Down Expand Up @@ -195,7 +203,8 @@ impl<'a> Compile<'a> {
.args(self.flags)
.args(&self.extra)
.current_dir(self.out_dir)
.env("RUST_BACKTRACE", "0");
.env("RUST_BACKTRACE", "0")
.envs(self.env.iter().map(|(k, v)| (k, v)));
if self.bootstrap {
cmd.env("RUSTC_BOOTSTRAP", "1");
} else {
Expand Down
333 changes: 320 additions & 13 deletions crates/mirth-lab/src/tools/gate_mutate.rs

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions docs/checks.md
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,41 @@ when the source test uses the harness) and searches rust-lang/rust's issues for
panic's location and the first query on the stack, or a delayed bug's message, so one bug can
show as several signatures (finding 50 as six).

#### coverage-guided feature-gate mutation

`gate-mutate --guided` compiles each mutant with the coverage-instrumented compiler (build-blk,
`MIRTH_OUT`) and reads the sites (functions and basic blocks) it reached. A mutant reaching a
site that no coverage suite reached (a snapshot of every `cov-suites/*/union.txt`: 400,501
sites) and no earlier input of the run reached joins a corpus with energy proportional to its
new sites; three mutants in four are then drawn from the corpus by energy (new sites over one
plus the times picked): spliced with another corpus entry or a UI test, moved, gated or edited.
`--measure` counts new sites the same way with the usual draws, as the baseline. Both ran at
the same time for 150 minutes (2 jobs each, the same seed and snapshot, on a loaded machine):

| minutes | guided: mutants, new sites, corpus, new signatures | unguided: mutants, new sites, new signatures |
|---:|---|---|
| 10 | 3,600 · 29,213 · 643 · 6 | 3,700 · 38,357 · 4 |
| 30 | 7,000 · 35,992 · 956 · 11 | 6,900 · 42,466 · 4 |
| 60 | 16,000 · 45,199 · 1,403 · 12 | 14,700 · 48,588 · 7 |
| 90 | 25,000 · 49,615 · 1,662 · 16 | 19,600 · 51,000 · 9 |
| 120 | 29,200 · 51,651 · 1,752 · 17 | 24,400 · 52,670 · 10 |
| 150 | 33,100 · 53,305 · 1,833 · 18 | 29,500 · 53,699 · 13 |

- New sites: the same in the end (53,352 and 53,699), almost all blocks; 47,356 in both, 5,996
only guided, 6,343 only unguided; 59,695 together, which the coverage report counts as the
suites `guided-gate-mutate` and `unguided-gate-mutate`. Most are in rustc_trait_selection,
rustc_mir_transform, rustc_hir_analysis, rustc_middle, rustc_mir_build and the printers.
- Guidance did not reach new code faster. The corpus put its energy where one input reaches
many blocks at once: tests whose own flags print internals (`-Zunpretty`, `thir-print`, the
proc-macro quote debug output: the top five corpus sources by new sites). Weighting energy
away from printers, or capping it per source test, is the next thing to try.
- Guidance found more crashes: 766 ICE or hang mutants against 95, and 19 signatures against 14,
because ICE-prone corpus entries are mutated again (finding 50's assertion alone, 444 times).
Of the signatures new to the earlier gate-mutate run, guided found six and unguided two.
After triage: two are routes to finding 50, one to finding 53, two to open #162338
(`Field::OFFSET`, gca), and three look new (findings 56–58): one from the guided run (57) and
two from the unguided run (56, 58).

## Running the checks

The checks are subcommands of `mirth-lab` (`crates/mirth-lab`; `mirth-lab --help` lists them):
Expand All @@ -559,6 +594,7 @@ target/release/mirth-lab abi-diff --rustc $R --rust ~/mirth-work/rust --work <di
target/release/mirth-lab lint-check --rustc $R --tests $T --work <dir>
target/release/mirth-lab gate-mutate --rustc $R --rust ~/mirth-work/rust --work <dir> --count 20000 --jobs 4
target/release/mirth-lab gate-mutate --rustc $R --rust ~/mirth-work/rust --work <dir> --triage
target/release/mirth-lab gate-mutate --rustc ~/mirth-work/build-blk/host/stage1/bin/rustc --rust ~/mirth-work/rust --work <dir> --guided --coverage-from ~/mirth-work/cov-suites --sites ~/mirth-work/build-blk/mirth-sites --minutes 150 --suite ~/mirth-work/cov-suites/guided-gate-mutate
target/release/mirth-lab release-diff --corpus ~/proofhouse-repos/rust --old nightly-2026-07-18 --new nightly-2026-10-06 --work <dir>
target/release/mirth-lab debug-check --toolchain nightly-2026-10-06 --work <dir> --seeds 0..4000 --jobs 4
```
Expand Down
3 changes: 3 additions & 0 deletions docs/hunt.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ with `-Zthreads=8`. `rustc/check.sh wide` runs the ordinary checks.
| 53 | "AliasConst::type_of got InherentSelf - args should always be InherentImpl at this point" (`const_kind.rs:85`, `check_well_formed`) for `fn to_bytes() -> [u8; gca!(Self::SIZE)]` in an inherent impl, when `generic_const_exprs` is also enabled; without it the program is accepted as in #162147's regression test (`gca/wf-inherentimpl.rs`) | **looks new** (a route around closed #162147, fixed 2026-09-03), low; nightly-2026-10-06; found by `gate-mutate` (a splice whose `--cfg full` turns on `generic_const_exprs`); [repro](hunt/tests/gate-mutate/gce-inherent-self.rs) |
| 54 | `rustc --test` panics "expected statement" (`rustc_expand/src/base.rs:172`) after E0736 for a `#[test] #[unsafe(naked)] extern "C" fn` nested inside another function's body; a `#[test]` inner fn without `naked` only warns "cannot test inner items" | **looks new** (closed issues with the message: #112360, #109816 (both `--test`), #83469, #149980; none open, none with `naked`), low (error recovery), **stable**: 1.82.0 through 1.88.0, 1.90.0, 1.98.0 and nightly-2026-10-06 (each tested) (1.81.0 rejects with E0658/E0787; before 1.88 the panic comes before the gate error); found by `gate-mutate` (an item moved into a generic fn); [repro](hunt/tests/gate-mutate/naked-test-inner-fn.rs) |
| 55 | a hang under the new trait solver: a closure with a `for<'a, 'b>` binder returning a TAIT with two lifetimes, passed where a `for<'a> AsyncFn<&'a mut C, …>` bound (a trait with an `FnMut` supertrait and an associated future) is required, does not finish compiling (still running after 200 s); with `-Znext-solver=coherence` (the old solver) it reports E0046/E0308/E0277 in 0.05 s | **looks new** (no issue found), medium: the new solver is nightly's default, so the plain `rustc` hangs on nightly-2026-10-06; also hangs on nightly-2026-07-18 with `-Znext-solver=globally` (not a recent regression); found by `gate-mutate` (a module splice of two tests); [repro](hunt/tests/gate-mutate/next-solver-hang.rs) |
| 56 | a generic `#[repr(simd)]` struct with `#[derive(Clone)]` in a library, `#[repr(simd)] #[derive(Copy, Clone)] pub struct Simd<T, const N: usize>([T; N]);`, gives "broken MIR in Item(… {impl#1}::clone) (after phase change to runtime-optimized)" (`rustc_mir_transform/src/validate.rs:81`, `optimized_mir`) on plain nightly, without `-Zvalidate-mir`; the non-generic struct compiles. The same "projecting into a SIMD type" family as finding 21, which needed `-Zvalidate-mir`; closed #153636 (a const-generic `repr(simd)` field read, fixed 2026-03-18) had a different route | **looks new** (no issue with the message; nearest closed #153636), low (internal feature `repr_simd`); 1.90.0 through nightly-2026-10-06 (with `RUSTC_BOOTSTRAP`), 1.89.0 and earlier compile it; found by coverage-guided gate-mutate's unguided baseline; [repro](hunt/tests/gate-mutate/simd-derive-clone-generic.rs) |
| 57 | "const parameter `M/#1` out of range when instantiating args=[…]" (`rustc_type_ir/src/binder.rs:844`, `typeck_root`) for a const parameter default naming an earlier one, `fn pass_enum<const N: usize, const M: usize = const { N }>`, called with one argument through `gca!(None)`, with `gca_macroless_args`, `generic_const_exprs` and `gca_min_const_items` | **looks new** (no issue with the message), low (incomplete features); nightly-2026-10-06 (the gca features do not exist on nightly-2026-07-18); found by coverage-guided gate-mutate; [repro](hunt/tests/gate-mutate/gca-const-default-out-of-range.rs) |
| 58 | `-Zassumptions-on-binders` with `reborrow`: two `CoerceShared` impls whose lifetimes do not line up (`FieldMut<'a, &'a ()>` to `FieldRef<'a, &'static ()>`) panic "region constraints already solved" (`rustc_infer/src/infer/mod.rs:245`, `coherent_trait`); without the flag, E0478. 1.98.0 and nightly-2026-07-18 panic earlier on the same input, at the `obligations.rs` assertions of finding 50 | **looks new**, near open #160650 (the same message with `reborrow`, for a missing lifetime, without the flag), low (experimental flag); nightly-2026-10-06; found by coverage-guided gate-mutate's unguided baseline; [repro](hunt/tests/gate-mutate/assumptions-reborrow-coherence.rs) |

Findings 1 and 2 are single-threaded: an ordinary `cargo build`, an edit, another
`cargo build`, and the metadata differs from a clean build of the edited source. Both come
Expand Down
16 changes: 16 additions & 0 deletions docs/hunt/tests/gate-mutate/assumptions-reborrow-coherence.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#![feature(reborrow)]
use std::marker::{CoerceShared, Reborrow};
struct FieldMut<'a, T> {
value: &'a mut T,
}
struct FieldRef<'a, T> {
value: &'a T,
}
impl<'a, T> CoerceShared<FieldRef<'a, T>> for FieldMut<'a, T> {}
struct Source<'a> {
field: FieldMut<'a, &'a ()>,
}
struct Target<'a> {
field: FieldRef<'a, &'static ()>,
}
impl<'a> CoerceShared<Target<'a>> for Source<'a> {}
7 changes: 7 additions & 0 deletions docs/hunt/tests/gate-mutate/gca-const-default-out-of-range.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#![feature(gca_macroless_args)]
#![feature(generic_const_exprs)]
#![feature(gca_min_const_items)]

fn pass_enum<const N: usize, const M: usize = const { N }> {
pass_enum::<{ gca!(None) }>
}
5 changes: 5 additions & 0 deletions docs/hunt/tests/gate-mutate/simd-derive-clone-generic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#![crate_type = "lib"]
#![feature(repr_simd)]
#[repr(simd)]
#[derive(Copy, Clone)]
pub struct Simd<T, const N: usize>([T; N]);
Loading