diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 6acf84dd649..8cd7fbfdc50 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -118,7 +118,7 @@ jobs: - run: | brew install qemu cmake # 1.98.1 and not `stable`: 1.99.0 compiles a host test into an endless loop - # here (issues/rustc-1-99-0-makes-an-endless-loop-of-a-port-test-on-apple-silicon.md). + # here (issues/the-nightlys-macos-job-pins-rustc-1-98-1-for-a-hang-its-test-no-longer-shows.md). - run: | curl --proto '=https' --tlsv1.2 -sSf -o "$RUNNER_TEMP/rustup-init.sh" https://sh.rustup.rs sh "$RUNNER_TEMP/rustup-init.sh" -y --profile minimal --default-toolchain 1.98.1 diff --git a/issues/a-counters-read-under-host-load-can-go-silent-for-15-s.md b/issues/a-counters-read-under-host-load-can-go-silent-for-15-s.md index 6de1f2db100..2940cb30a33 100644 --- a/issues/a-counters-read-under-host-load-can-go-silent-for-15-s.md +++ b/issues/a-counters-read-under-host-load-can-go-silent-for-15-s.md @@ -136,11 +136,16 @@ the step each stopped in is not read from it: 77 (38 s, with ceilings paid at 8.00x and 31 s between its image and its first guest line) and 75 to 67 (7 s). -Not known of any of them: whether the fork compiler's fault reaches -`test_rs_counters_read` or the kernel under it. -`issues/the-forks-llvm-deletes-a-loops-exit-on-a-no-wrap-flag-scalar-evolution-gives-the-wrong-value.md` -has LLVM's ScalarEvolution deleting a loop's exit on both architectures, and -records every function but the ones it names as not measured. +The fork compiler's ScalarEvolution fault (llvm/llvm-project#175729, which +`src/miscompile.rs` now refuses a sysroot for) is not their cause. +`tests/virtsmpcase`'s image was built and `virt_el1_smp` run by the compiler +before the fix and by the fixed one, each with the sysroot it built, and every +function of the image's 124 crates compared in object code as the image build +makes it. The two differ in six functions of `rustc_demangle`'s `v0` printer, +a loop peeled or not, and in the sysroot's std in `fs::DirBuilder::_create`, +which tests a count where the other tests sixteen times it; a counters read +calls neither, and no function of `counters_read`, `test-runner`, +`supervisor`, `logkeeper`, `toybox`, `kernelprobe` or the loader differs. The slow ones, with registers: a probe that captured `info registers -a` over QMP whenever the read had not ended 3 s after `unmap_touch` (`debug-slow.patch` diff --git a/issues/the-forks-llvm-deletes-a-loops-exit-on-a-no-wrap-flag-scalar-evolution-gives-the-wrong-value.md b/issues/the-forks-llvm-deletes-a-loops-exit-on-a-no-wrap-flag-scalar-evolution-gives-the-wrong-value.md deleted file mode 100644 index 3bab5c8305c..00000000000 --- a/issues/the-forks-llvm-deletes-a-loops-exit-on-a-no-wrap-flag-scalar-evolution-gives-the-wrong-value.md +++ /dev/null @@ -1,505 +0,0 @@ ---- -status: assigned -kind: defect -opened: 2026-10-09 ---- - -# The fork's LLVM deletes a loop's exit on a no-wrap flag ScalarEvolution gives the wrong value - -The compiler that builds every ToyOS kernel, loader and program -(`rust/` at `6d6ad8c71906`, `rustc 1.99.0-dev`, LLVM 22.1.8 from -`src/llvm-project` at `ceaf0fbb8440`) turns a correct loop of safe Rust into an -endless one. Measured: an inclusive range that ends at `u16::MAX`, for -`aarch64-unknown-toyos`, `aarch64-unknown-none-softfloat` and -`aarch64-unknown-uefi`; and one that ends at `u128::MAX`, for -`aarch64-unknown-toyos` and `x86_64-unknown-toyos`. The fault is in LLVM's -ScalarEvolution and is the same on every target: it moves a no-wrap flag from -an increment whose wrapped result nothing uses onto a value that is used, and -`indvars` deletes the loop's exit on it. Upstream LLVM has the fault and has -merged no fix. Its open report llvm/llvm-project#175729 has the same symptom -on another loop: that ToyOS's loop is that report's fault is this file's -reading, and that the report is the known fault is a maintainer's -"probably" ("Upstream" below, "Not established"). - -## The fault, with no front end - -`m1.ll`. `@f` returns `true` after four iterations: - -```llvm -define i1 @f() { -entry: - br label %header -header: - %next = phi i16 [ -3, %entry ], [ %nextnext, %latch ] - %iter = phi i16 [ -4, %entry ], [ %next, %latch ] - %done = icmp eq i16 %iter, -1 - br i1 %done, label %exit, label %latch -latch: - %nextnext = add nuw i16 %next, 1 - br label %header -exit: - ret i1 true -} -``` - -The `add` wraps when `%next` is -1. Its result is then poison, and nothing -uses it: the loop leaves on `%iter == -1` in the next header. `opt --passes=indvars -S m1.ll`, with the `opt` of LLVM 22.1.8 that -`nightly-2026-07-22`'s `llvm-tools` ships, writes nothing to stderr and -leaves nothing of `%done`: the header is - -```llvm -header: ; preds = %latch, %entry - br i1 false, label %exit, label %latch -``` - -and the function never returns. The same with no `target datalayout`, with -AArch64's and with x86-64's. No `opt` built from the fork's LLVM was run; the -fork's source has the lines step 3 below reads, unchanged. - -A fixed `opt` leaves `%done` as it is, `icmp eq i16 %iter, -1` with the -header's branch on it, or folds it to something under which `@f` still returns -`true`. It never leaves `br i1 false`. - -Controls, each through the same `opt -passes=indvars`. The exit is folded to -`false` in: `m1` with a second exit in the latch that calls an opaque function -(`m2`); that at `i8`, `i32` and `i64`; and that with a start that is not -constant but carries `range(i16 10, 100)`. It is not folded in: `m2` without -the `nuw`; `m2` with the `add` in the header, before the rotation; and `m2` -with a start of unknown range. `m2` and the three that do not fold gave the -same under each of the three data layouts. Of eleven passes tried on `m2` -(`indvars`, `loop-unroll`, `loop-reduce`, `loop-vectorize`, `loop-idiom`, -`loop-deletion`, `loop-predication`, `loop-flatten`, `irce`, -`constraint-elimination`, `nary-reassociate`) only `indvars` leaves `br i1 -false`. - -## The cause - -Four steps. The first two are sound, the third is the fault, the fourth is -where it shows. From `-print-changed` traces of the `u16` reproducer below, -its unoptimised IR for `aarch64-apple-darwin` through that `opt -O2` and the -fork's compiler's own for `aarch64-unknown-toyos`, which agree; LLVM source -lines are the fork's at `ceaf0fbb8440`. - -1. **CorrelatedValuePropagation, on `port`, marks the range's increment - `nuw`.** Measured: the `add i16 %iter, 1` before that pass's dump is `add - nuw i16` after it. Read from the source: the add's only use is the header - phi along the back edge, that edge implies `iter != 0xFFFF`, and - `LazyValueInfo.cpp:1821` (`getValueAtUse`) reasons from exactly that. Sound: - where `iter` is `0xFFFF` the add is poison and nothing uses it. -2. **LoopRotate, on `probe` after `port` is inlined with the constant start - `0xFFFC`, makes that add a header phi's increment.** Measured: after it the - loop is `m1`'s, `%next` from -3, `%iter` from -4, and `add nuw i16 %next, 1` - in the latch. Sound: the add wraps in the iteration where `%iter` is - `0xFFFE`, and the loop leaves before the poison is used. -3. **ScalarEvolution gives `%next` the recurrence `{-3,+,1}`: the - fault.** Measured: its own printed analysis of `m2` and of the real loop - copied by hand says `{-3,+,1}` with - the unsigned range `[-3,0)`, and for `m2` beside it `exit count for header: - i16 3`, the iteration at which that recurrence is 0. Read from the source: - `ScalarEvolution.cpp:5776-5783` (`createSimpleAffineAddRec`; the general - path at 5879-5909 does the same) copies the increment's flags onto the - phi's recurrence without a condition, where only the post-increment - expression is guarded by `isAddRecNeverPoison` (5798). The flag is right of - `%next` alone, which is poison exactly where the recurrence wraps; a - ScalarEvolution expression is uniqued without its flags, so every value - with that expression takes it. -4. **`indvars` asks whether `%iter == -1` can hold and is told no.** - Measured: `-C llvm-args=-opt-bisect-limit` under `nightly-2026-07-22`, the - reproducer as an executable that prints `caller()`: limit 690 prints - `true`, 691 prints `false`, and pass 691 is `indvars` on the loop in - `probe`, whose whole effect on that function is - - ``` - - %or.cond.not.i.not = icmp eq i16 %iter, -1 - - br i1 %or.cond.not.i.not, label %exit, label %backedge - + br i1 false, label %exit, label %backedge - - %2 = add nuw i16 %0, 1 - + %2 = add nuw nsw i16 %0, 1 - ``` - - Later passes fold what is left into the endless loop. Read from the source - and not measured, the path inside: `SimplifyIndVar.cpp:275` - (`eliminateIVComparison`) calls `evaluatePredicateAt`; - `ScalarEvolution.cpp:11490` takes `getMinusSCEV(iter, -1)`, which is - `{-4,+,1} + 1`, the node `{-3,+,1}` of step 3; its range excludes - zero, so the compare is "known" false. `%iter + 1` is a defined value, 0 in - the last iteration. Upstream's report traces the same calls on its own - loop. - -**What put the loop in the tree's test in that shape is `core`, not LLVM.** -`a_port_answers_as_its_declaration_says` is compiled right by -`nightly-2026-07-09` and wrong by `nightly-2026-07-10`. -`14cae681329a...af3d95584dbd` is 106 commits, none under `src/llvm-project`, -and both ends report LLVM 22.1.8. Among them is rust-lang/rust #155114 (commit -`b3c94df68bf4`, merged as `71c64160bd0f`), which rewrote `RangeInclusive`'s -`next` to step with `Step::forward_overflowing` and keep the overflow bit in -`exhausted`. With `-C no-prepopulate-passes` the reproducer's loop differs -between 1.98.1 and `nightly-2026-07-22` only in block numbering and in that -callee. The fork contains both commits (`git merge-base --is-ancestor` exits 0 -for each against `6d6ad8c71906`). That `next` is a correct program. - -## The reproducers - -`minns.rs`, the `u16` form. `caller()` returns `true`: - -```rust -#![no_std] -#[derive(Clone, Copy)] -pub enum Mediated { Kept, ReadOnly } - -#[derive(Clone, Copy)] -pub enum Standing { Free, Declared(Mediated) } - -fn port(standing: impl Fn(u16) -> Standing, port: u16, width: u16, write: bool) -> Result { - for port in port..=port + (width - 1) { - match standing(port) { - Standing::Free => {} - Standing::Declared(Mediated::ReadOnly) if !write => {} - Standing::Declared(Mediated::ReadOnly) => return Err(9), - Standing::Declared(Mediated::Kept) => return Err(8), - } - } - Ok(port) -} - -fn standing(port: u16) -> Standing { - match port { - 0x3F8..=0x3FF | 0x20..=0x21 | 0xA0..=0xA1 | 0x70..=0x71 | 0xCF8 | 0xCFC..=0xCFF | 0xCF9 => Standing::Declared(Mediated::Kept), - 0xB2 => Standing::Declared(Mediated::ReadOnly), - _ => Standing::Free, - } -} - -fn probe() -> bool { - port(standing, 0xFFFC, 4, false).is_ok() & port(standing, 0xB2, 1, true).is_err() -} -pub fn caller() -> bool { probe() } -``` - -`c_u128.rs`, the `u128` form. `caller()` returns `true`: - -```rust -#![no_std] -#[derive(Clone, Copy)] -pub enum Mediated { Kept, ReadOnly } -#[derive(Clone, Copy)] -pub enum Standing { Free, Declared(Mediated) } -fn port(standing: impl Fn(u128) -> Standing, port: u128, width: u128, write: bool) -> Result { - for port in port..=port + (width - 1) { - match standing(port) { - Standing::Free => {} - Standing::Declared(Mediated::ReadOnly) if !write => {} - Standing::Declared(Mediated::ReadOnly) => return Err(9), - Standing::Declared(Mediated::Kept) => return Err(8), - } - } - Ok(port) -} -fn standing(port: u128) -> Standing { - match port { - 0x38..=0x3F | 0x20..=0x21 | 0xA0..=0xA1 | 0x70..=0x71 | 0xC8 | 0xCC..=0xCF | 0xC9 => Standing::Declared(Mediated::Kept), - 0xB2 => Standing::Declared(Mediated::ReadOnly), - _ => Standing::Free, - } -} -fn probe() -> bool { - port(standing, ::MAX - 3, 4, false).is_ok() & port(standing, 0xB2, 1, true).is_err() -} -pub fn caller() -> bool { probe() } -``` - -`rustc --edition 2021 --crate-type lib --emit llvm-ir,asm --target -C -opt-level=2 `, each exit 0, with the fork's `rustc` run from the store's -`sysroots/8618c089fa736cb0`, by the report of the agent that ran them: no log -records the path. That is the key the worktree's `target/.deps-stamp` gave -`aarch64-unknown-toyos` and `x86_64-unknown-toyos` at `e52e6275b`, and every -row below was made from it. The stamp gives `aarch64-unknown-none-softfloat`, -`aarch64-unknown-uefi`, `x86_64-unknown-none` and `x86_64-unknown-uefi` another -key, `dc7c468f0c07446e`, which no row here was made with: whoever repeats a -row for one of those four from the key the kernel and loader take has a -different library set than the row had. The `core` crate hash in the fork's -compiler's pass traces for the two ToyOS targets is the one in the two ToyOS -`libcore` files under `8618c089fa736cb0`. - -What `caller` became, `minns.rs`: - -| target | `caller` | -|---|---| -| `aarch64-unknown-toyos` | no `ret` in its IR; frame setup, then `.LBB0_1: b .LBB0_1` | -| `aarch64-unknown-none-softfloat` | no `ret`; `.LBB0_1: b .LBB0_1` | -| `aarch64-unknown-uefi` | no `ret`; `.LBB0_1: b .LBB0_1` | -| `aarch64-apple-darwin` | no `ret`; `LBB0_1: b LBB0_1` | -| `x86_64-unknown-toyos` | `ret i1 true`; `movb $1, %al`, `retq` | -| `x86_64-unknown-none` | `ret i1 true`; `movb $1, %al`, `retq` | -| `x86_64-unknown-uefi` | `ret i1 true`; `movb $1, %al`, `retq` | - -And by width: `c_u128.rs` with `u128` replaced by each type, `--emit llvm-ir`, -`caller` read in the IR. The `u16` row is that file's `u16` form, which -differs from `minns.rs` in `standing`'s arms and in writing the start as -`::MAX - 3`: - -| type | `aarch64-unknown-toyos` | `x86_64-unknown-toyos` | -|---|---|---| -| `u8` | `ret i1 true` | `ret i1 true` | -| `u16` | **no `ret`: a block that branches to itself** | `ret i1 true` | -| `u32` | `ret i1 true` | `ret i1 true` | -| `u64` | `ret i1 true` | `ret i1 true` | -| `u128` | **no `ret`: a block that branches to itself** | **no `ret`: a block that branches to itself** | -| `i16` | 41 lines with one `ret`, not `ret i1 true`; whether it is right was not checked | the same | - -The `i8` form does not compile (its literals are out of range). - -Both sources are fragile. Without the second call in `probe`, without the -private `probe` between `caller` and the calls, or with three of the `Kept` -arms gone, the same compiler compiles `minns.rs` right. - -The same fault in the tree's own code: `toyos-userbound/tests/firmware.rs`'s -`a_port_answers_as_its_declaration_says`, whose `(0xFFFC, Width::DWord)` case -runs `firmware::port`'s range to `0xFFFF`. On an Apple-silicon development -machine, `CARGO_INCREMENTAL=0 cargo test --locked -p toyos-userbound --test -firmware --no-run` with `RUSTUP_TOOLCHAIN` naming that sysroot directory, which -holds the fork's `aarch64-apple-darwin` libraries, exits 0, and the binary run -whole prints 20 tests `ok` and never ends (ended by PID at 120 s). Its test -function is one instruction, a branch to itself. - -## Which compilers - -The same test binary, built and run the same way under upstream's compilers on -the same machine, one rustup toolchain installed and removed per row. `r4.rs` -and `d1.rs` are two earlier single-file forms of the reproducer, compiled to -assembly beside each row. - -| toolchain | rustc | LLVM | the test binary | `caller` in `r4.rs`, `d1.rs` | -|---|---|---|---|---| -| nightly | 1.96.0-nightly (d9563937f 2026-03-03) | 22.1.0 | exit 0, 21 passed | not compiled | -| nightly-2026-05-13 | 1.97.0-nightly (8b03437a8 2026-05-12) | 22.1.4 | exit 0 | returns | -| nightly-2026-06-17 | 1.98.0-nightly (9e2abe0c6 2026-06-16) | 22.1.7 | exit 0 | returns | -| nightly-2026-07-04 | 1.98.0-nightly (c397dae80 2026-07-02) | 22.1.8 | exit 0 | returns | -| nightly-2026-07-08 | 1.99.0-nightly (f10db292a 2026-07-07) | 22.1.8 | exit 0 | returns | -| nightly-2026-07-09 | 1.99.0-nightly (14cae6813 2026-07-08) | 22.1.8 | exit 0 | returns | -| nightly-2026-07-10 | 1.99.0-nightly (af3d95584 2026-07-09) | 22.1.8 | hung, ended at 120 s | a branch to itself in both | -| nightly-2026-07-13 | 1.99.0-nightly (77cf889bc 2026-07-12) | 22.1.8 | hung | a branch to itself in both | -| nightly-2026-07-22 | 1.99.0-nightly (0e29c21d9 2026-07-21) | 22.1.8 | hung | a branch to itself in both | -| nightly-2026-09-25 | 1.100.0-nightly (f7575a9da 2026-09-24) | 23.1.1 | hung; the test's function is `b .` | not compiled; `minns.rs`: no `ret` | -| nightly-2026-10-08 | 1.101.0-nightly (1d81eb4ad 2026-10-07) | 23.1.3 | hung; the test's function is `b .` | not compiled | - -Stable 1.99.0 (LLVM 23.1.1) hangs it and stable 1.98.1 (LLVM 22.1.8) does not: -`issues/rustc-1-99-0-makes-an-endless-loop-of-a-port-test-on-apple-silicon.md` -has those rows. Five nightlies from `nightly-2026-07-10` on were run, the five -in the table, and each hangs it; no other was. `nightly-2026-10-08` was the -newest there was. - -**A compiler that passes the test escapes by the shape its `core` gives the -loop, and has the fault.** The `u16` reproducer with its range replaced by -this iterator, so that no compiler's `core` decides the loop's shape: - -```rust -pub struct Overflowing { start: u16, end: u16, exhausted: bool } -impl Iterator for Overflowing { - type Item = u16; - #[inline] - fn next(&mut self) -> Option { - if self.exhausted || !(self.start <= self.end) { - return None; - } - let (n, o) = self.start.overflowing_add(1); - self.exhausted = o; - Some(core::mem::replace(&mut self.start, n)) - } -} -``` - -| compiler | LLVM | `caller` at `-C opt-level=2` | -|---|---|---| -| 1.88.0 | 20.1.5 | right, by shape: as an executable for `aarch64-apple-darwin` it prints `true`. In the loop that runs to `u16::MAX` the `overflowing_add` is a call of `llvm.uadd.with.overflow.i16` in every dump of its trace, so there is no `add` for step 1 to mark. It says nothing of LLVM 20.1.5's ScalarEvolution | -| 1.91.0 | 21.1.2 | wrong, `aarch64-apple-darwin`; in its trace the increment is an `add`, and `add nuw` after CorrelatedValuePropagation on `port` | -| nightly, 1.96.0-nightly (d9563937f 2026-03-03) | 22.1.0 | wrong, `aarch64-apple-darwin` | -| 1.95.0 | 22.1.2 | wrong, `aarch64-apple-darwin` | -| 1.98.0 | 22.1.8 | wrong, `aarch64-apple-darwin` | -| 1.98.1 | 22.1.8 | no `ret` for `aarch64-apple-darwin` and `aarch64-unknown-none-softfloat`; the executable hangs at `opt-level` 2 and 3 and prints `true` at 0 and 1; right for `x86_64-unknown-none` | -| the fork's | 22.1.8 | no `ret` for `aarch64-unknown-toyos` and `aarch64-unknown-none-softfloat`; right for `x86_64-unknown-toyos` and `x86_64-unknown-none` | - -So 1.88.0 and 1.98.1 each pass something by a shape, the first this iterator -and the second the tree's test. Which LLVM change between 20.1.5 and 21.1.2 -made the intrinsic an `add` that early is not determined, and is not where the -fault is. - -## Upstream - -Read from GitHub's API without credentials on 9 October 2026: - -- **llvm/llvm-project#175729**, "[SCEV] Long-standing miscompile due to - absence of per-use flags in SCEV expressions": open, opened 13 January - 2026, labelled `miscompilation`. Its loop is another, with a `nuw` the loop - vectoriser left; its symptom is this one, `opt -passes=indvars` folding the - exit to `false`, and its reporter's trace runs through - `eliminateIVComparison`, `getMinusSCEV` and `isKnownNonZero`. -- **llvm/llvm-project pull request #118959**, "[SCEV] Don't blindly transfer - nowrap flags to pre-inc addrec": open, a draft, unmerged, opened 6 December - 2024, 22 months before that reading, last updated 26 March 2026. Its body - says "Test updates incomplete". It changes `ScalarEvolution.cpp` (+73 −37) - and `ScalarEvolution.h` (+10 −4) and 22 test files. It is in no LLVM - release. -- rust-lang/rust: no report of this was found by six searches. - -**Not established:** - -- **that #175729 is the known fault.** A maintainer's sentence there is "The - nuw is fine as the value is never used. I've only glanced at it, but this is - probably the known issue where we incorrectly unconditionally transfer - nowrap flags for preinc addrecs from IR to SCEV", and on 17 February 2026: - "I believe" that pull request "is the fix for this issue. However, it has - some problematic impact"; -- **that ToyOS's loop is #175729's.** Nobody upstream has seen it: that is - this file's reading, from the same symptom and the same source path; -- **that #118959 fixes either.** Nobody has built an LLVM with it, here or by - anything upstream's thread says. By reading, it drops the flag in `m2` and - in the real loop; -- **when.** Waiting for upstream has no date. - -The fork's `src/llvm-project` is one shallow commit, so its history answers -nothing; its source has no `canPreservePreIncAddRecNoWrapFlags`, the name -#118959 adds. - -No report or pull request is sent for now (root `CLAUDE.md`, "Dependencies"). - -## How far it reaches - -**Exposed: the kernel, the loader and userland, on both architectures.** -Nothing wrong has been found in them, and nothing was looked for. - -**An inclusive range to its type's maximum is one instance of a class.** The -class is a loop in which an increment's wrapped result is dead, so that step 1 -may mark it; a later rotation makes that increment a header phi's; the phi's -start has a known range; and a client of ScalarEvolution reasons about another -value with the same expression. The hand-written iterator above is in it -without `core`'s range, and `m1` without a range at all. A loop of the class -that never reaches the wrap loses an exit it never takes. - -**Why AArch64 showed it for `u16` and x86-64 did not**, read from one pair of -traces of `minns.rs` under the fork's compiler. For `x86_64-unknown-toyos`, -`indvars` changes `port`'s loop before the run of CorrelatedValuePropagation -that marks the add for AArch64: it rewrites the exit test onto the -incremented value, the add has a second use, and no dump in the trace has -`nuw` on the `i16` increment. For `aarch64-unknown-toyos` the trace has no -`indvars` change on `port`; read from the source, `IndVarSimplify.cpp:964` -refuses that rewrite for a counter whose width is not `DL.isLegalInteger`, and -16 is not in AArch64's `n32:64` where x86-64's layout is `n8:16:32:64`. So the -width decides whether the shape is reached, and nothing else: `u128`, legal on -neither, is wrong on both, by steps 1, 2 and 4 in the x86-64 trace of it. `u8` -on AArch64, not legal either, came out right in this one source, and `m2` at -`i8`, `i32` and `i64` folds: no width is safe. - -**x86-64: 0 of 40 other sources miscompiled, and one counterexample.** 39 -source variants made while reducing, judged by whether a function is left with -no `ret`, `unreachable` or `resume`, which sees the endless loop and nothing -else: 18 are miscompiled for `aarch64-unknown-none-softfloat` and the same 18 -for `aarch64-unknown-toyos`, none for `x86_64-unknown-none` or -`x86_64-unknown-toyos`; the hand-written iterator's file is the fortieth. None -of the forty names an integer wider than 64 bits, so by the paragraph above -each had a legal counter on x86-64; that is read for `minns.rs` and inferred -for the other thirty-nine. The counterexample is the `u128` row. Stable 1.99.0 -compiles the tree's test right for `x86_64-apple-darwin`. - -**The outcomes seen** are the endless loop and, with the passes after `indvars` -withheld, a wrong value: `false` for `true`. So a wrong answer without a hang -is possible, and no test in the tree would name it. - -**The x86-64 kernel's own instance of the loop has its exit.** The kernel -calls `firmware::port` once, from `kernel/src/arch/x86_64/acpi_mode.rs`'s -`port`, with a port and width the `acpi` claim's holder chooses. From `CI=true -cargo run -- --build-only` (exit 0) at `e0a61d070`, built from nothing with and -without incremental state and disassembled: a function of its own of 0x11a -bytes in the first, inlined into `acpi_mode::port` (0x13a bytes) in the second. -In both the loop counts the width's bytes down in a 16-bit register and leaves -at zero, every refusal leaves it, and no branch back is unconditional: - -``` -eef55: inc r12d -eef58: dec r13w -eef5c: je 0xeef93 <+0xb3> ; every port asked: the access is made -eef5e: mov esi, 0x1 -eef63: mov edi, r12d -eef66: call - ... ; a refusal jumps out of the loop, a pass back to eef55: -eef79: je 0xeef55 <+0x75> -eef8d: je 0xeef55 <+0x75> -``` - -The AArch64 kernel has no instance of that call: it is under `arch/x86_64`, by -the source and not by a disassembly. - -**Not measured:** - -- **any other function of the kernel, the loader or userland, on either - architecture.** This is the larger gap, and no reading closes it: the class - is not `..=` loops, nor narrow integers, nor one architecture. Today's - compiler counts nothing that would: `-C llvm-args=-stats` prints nothing - from the fork's `rustc`, `-debug-only=indvars` is refused as an unknown - argument, and read from the source `IndVarSimplify.cpp` emits no - optimisation remark, while the counter the fold increments, `NumElimCmp` - (`SimplifyIndVar.cpp:46`), counts every legitimate elimination with it. The - measurement owed is in the exit; -- the `u128` form for `x86_64-unknown-none`, `x86_64-unknown-uefi`, - `aarch64-unknown-none-softfloat` and `aarch64-unknown-uefi`, the targets of - the kernel and the loader; -- code generation's loop strength reduction, which reads ScalarEvolution too, - past `opt -passes=loop-reduce` on `m2`; -- `aarch64-unknown-none`, `aarch64-unknown-linux-gnu` and - `x86_64-unknown-linux-gnu` under an affected compiler. - -## What does not end it - -- **Moving the fork to a later upstream**: the newest nightly there was hangs - the test, and upstream's LLVM has merged no fix. -- **Waiting for upstream**: it has no date. The proposed fix has been open and - unmerged for 22 months. -- **Taking #155114 out of the fork's `core`**: `library/core` carries no delta - (`.claude/agents/implementer.md`, "A fork"), and the fault would stay for - any other code of the class, as the hand-written iterator shows. -- **Rewriting `firmware::port`**: the loop is right, and it is one loop. -- **A change to `indvars`** that makes the reproducers return: `indvars` asks - a question and is answered wrongly, and every other client of - ScalarEvolution would go on being answered so. -- **`nightly.yml`'s pin of `portability-macos` to 1.98.1**: no job installs the - fork's compiler by a version; it is the tree's own. - -## Owner and exit - -Held by the toolchain, `rust/` and its `src/llvm-project` -(`ToyOSOrg/llvm-project`, branch `toyos-rustc-22.1-2026-05-19`). A fix in the -fork's LLVM is being built on `wt/toyos-scevfix`. - -Whoever moves the fork to a later upstream runs the measurements below under -the moved compiler before the move lands, and corrects this file by what they -show. - -**Exit**: ScalarEvolution in the fork's LLVM no longer gives a value a no-wrap -flag that holds only where another value is poison, by a change to -ScalarEvolution and to no client of it, measured under the LLVM and the -compiler the tree builds with by all of: - -1. `m1.ll` above through that LLVM's `opt -passes=indvars -S`: the output has - no `br i1 false`, and `@f` returns; -2. `caller` is `ret i1 true` by the command above: for `aarch64-unknown-toyos` - and for `x86_64-unknown-toyos`, in `minns.rs` and in `c_u128.rs`, from the - key `target/.deps-stamp` gives the ToyOS targets; and for - `aarch64-unknown-none-softfloat` and `aarch64-unknown-uefi`, in `minns.rs`, - from the key it gives those two, the one the kernel and the loader take, - which is not the key the table's rows for them were made from - (`8618c089fa736cb0`, the ToyOS targets'); -3. the test binary above, built with that compiler on Apple silicon without - incremental state, exits 0; -4. the tree's own functions read: the fix behind an LLVM option, the tree - built twice with the one compiler, the option given and withheld through - `-C llvm-args`, and the two builds compared function by function. The - functions that differ are a superset of those the fault changed; one that - loses an exit branch in the build without the fix is a hit, and each hit is - named in the pull request that closes this file. The same option is the - fix's negative control. - -Items 2 and 3 rest on shapes `rustc` may stop making, so a compiler that -merely stops making them meets those two and not the exit; item 1 has no front -end in it, and item 4 is the only one that reads the code ToyOS ships. The -check that holds the fix in this tree arrives with the fix, green. diff --git a/issues/the-host-job-runs-the-toolchain-the-runner-ships.md b/issues/the-host-job-runs-the-toolchain-the-runner-ships.md index 3ecf97592e4..0b3587c818b 100644 --- a/issues/the-host-job-runs-the-toolchain-the-runner-ships.md +++ b/issues/the-host-job-runs-the-toolchain-the-runner-ships.md @@ -74,3 +74,16 @@ the tree is ready to adopt a new compiler's lints — or keep tracking whatever ships and accept that a runner-image roll reds every open pull request until someone lands the fix, the way today's did. Both are legitimate engineering positions; this entry does not choose between them. + +## Whichever it is, its LLVM drops a loop's exit + +Every host binary — `toyos-build`, the harness, every host test, every app's +host build — is compiled by an upstream rustc whose LLVM has the +ScalarEvolution fault the fork's no longer has (llvm/llvm-project#175729, open +upstream; `src/miscompile.rs` holds its reproducers for the fork's compilers +and for no host compiler). Measured for stable 1.99.0 on Apple silicon, whose +`caller` of `src/miscompile/last_exit.rs` over `u16` and over `u128` is a +branch to itself, and for 1.98.1 given the loop's shape by hand +(`issues/the-nightlys-macos-job-pins-rustc-1-98-1-for-a-hang-its-test-no-longer-shows.md`). +No version to pin is free of it, so a pin answers the lints above and not +this; it ends when a stable rustc compiles those reproducers right. diff --git a/issues/rustc-1-99-0-makes-an-endless-loop-of-a-port-test-on-apple-silicon.md b/issues/the-nightlys-macos-job-pins-rustc-1-98-1-for-a-hang-its-test-no-longer-shows.md similarity index 57% rename from issues/rustc-1-99-0-makes-an-endless-loop-of-a-port-test-on-apple-silicon.md rename to issues/the-nightlys-macos-job-pins-rustc-1-98-1-for-a-hang-its-test-no-longer-shows.md index b209d33d0fb..28d37b7e2dc 100644 --- a/issues/rustc-1-99-0-makes-an-endless-loop-of-a-port-test-on-apple-silicon.md +++ b/issues/the-nightlys-macos-job-pins-rustc-1-98-1-for-a-hang-its-test-no-longer-shows.md @@ -4,17 +4,21 @@ kind: tooling opened: 2026-10-08 --- -# rustc 1.99.0 makes an endless loop of `a_port_answers_as_its_declaration_says` on Apple silicon +# The nightly's macOS job pins rustc 1.98.1 for a hang its test no longer shows, and the host's rustc still has the fault `toyos-userbound/tests/firmware.rs`'s `a_port_answers_as_its_declaration_says` never ended in `nightly.yml`'s `portability-macos`, the one job that runs the host suite on macOS, from the first nightly that had the test (#749): the compiler that job installed, `rustc 1.99.0 (b940084d7 2026-09-28)`, LLVM -23.1.1, compiles the test's function to one instruction, a branch to itself. -The job is pinned to 1.98.1 for it. The fault is LLVM's ScalarEvolution's, -on every target, and is not this compiler's alone: -`issues/the-forks-llvm-deletes-a-loops-exit-on-a-no-wrap-flag-scalar-evolution-gives-the-wrong-value.md` -has its cause, its reproducers and the compilers that have it. +23.1.1, compiled the test's function to one instruction, a branch to itself. +The job is pinned to 1.98.1 for it, and since #780 the test passes under +1.99.0 too (below). The fault is LLVM's ScalarEvolution's, +on every target, and is not this compiler's alone: it copies an increment's +no-wrap flag onto its phi's recurrence, where it holds only if the wrapped +increment is observed, and `indvars` folds the loop's last exit to `false`. +The fork's LLVM no longer does, and `src/miscompile.rs` holds a reproducer +every sysroot's compiler must compile right; the host's `rustc` is upstream's +and still does. ## What the runner showed @@ -88,24 +92,50 @@ inlined with `standing` over the four ports `0xFFFC..=0xFFFF`. The source ends: the crate forbids `unsafe`, and an inclusive range that ends at `u16::MAX` is what `RangeInclusive` exists to get right. +## Since #780 the test no longer shows it + +The table above was made before #780 (`5f2657703`), which changed +`toyos-userbound` and `toyos-abi`. Its second row again, on an Apple-silicon +development machine, with stable `1.99.0 (b940084d7 2026-09-28)` installed for +the measurement and removed after it: + +| tree | the binary | +|---|---| +| `main` before #780 (`b3322379c`'s `toyos-userbound`, `toyos-abi` and `toyos-bootmap`) | hung: 20 tests `ok`, then libtest's line for this one, ended by PID after 120 s | +| `main` at `425f3eb9e`, whose three crates are as #780 left them (#790's worktree at its merge of it) | exit 0: `24 passed`, this test `ok` | + +So the test stopped showing the fault before #778, which pinned the job for +it, had landed: nothing reran the row at the tree that landed. **The +compiler has the fault as it had**: `src/miscompile/last_exit.rs`, this +test's loop cut out, compiled by 1.99.0 for `aarch64-apple-darwin` at +`opt-level=2` over `u16` and over `u128`, has `caller` as a branch to +itself. What moved is the code around the loop, which no longer gives it the +shape LLVM miscompiles; which of #780's changes did that was not looked for. + ## What holds it, and what the pin is worth `portability-macos` installs `1.98.1` where it installed `stable` (`nightly.yml`). That is a pin on a compiler nothing else in the tree pins. -**1.98.1 has the faulty LLVM too.** It compiles this test right because its -`core` does not yet give the range's loop the shape LLVM miscompiles; given -that shape written by hand it makes the same endless loop. The pin keeps one -test of one job green. It is no statement that 1.98.1 compiles the rest of the -host suite right, and nothing measured says either way. - -**No stable is known to be coming that the job can go back to.** Five -nightlies were run, of 10, 13 and 22 July, 25 September and 8 October 2026, -the last the newest there was, and each hangs the table's second row; none -between them was run. Upstream's LLVM has merged no fix. That its open report -llvm/llvm-project#175729 is of this fault is the reading of -`issues/the-forks-llvm-deletes-a-loops-exit-on-a-no-wrap-flag-scalar-evolution-gives-the-wrong-value.md`, -which lists it under "Not established". +**1.98.1 has the faulty LLVM too.** It compiled this test right, at the tree +1.99.0 hung it at, because its `core` does not yet give the range's loop the +shape LLVM miscompiles; given that shape written by hand it makes the same +endless loop. **The pin holds nothing today**: since #780 the one test it was +taken for passes under 1.99.0 as well. It was never a statement that either +compiler compiles the rest of the host suite right, and nothing measured says +either way. + +**No stable is known to be coming whose LLVM has the fault fixed.** Five +nightlies were run before #780, of 10, 13 and 22 July, 25 September and +8 October 2026, the last the newest there was, and each hung the table's +second row; none between them was run. Upstream's LLVM has merged no fix. +That its open report llvm/llvm-project#175729 is of this fault is a reading, +not upstream's word: the same fold on the same code path, and at `main` +before #780 the fork's compiler hangs the second row as 1.99.0 does (ended by +PID after 120 s) and with that report's proposed fix +(llvm/llvm-project#118959) exits 0, `21 passed`. At `main` since #780 both of +the fork's compilers exit 0, so the row there tells them apart no more than +it tells 1.99.0 from 1.98.1. `guest.yml`, and so `guest / suite` and the nightly's `tcg / suite`, and `nightly.yml`'s `portability-linux` install `stable` and log its version: @@ -114,9 +144,10 @@ the `tcg / suite` of run 37778826093 logged `rustc 1.99.0 (b940084d7 `x86_64-apple-darwin` (the table's last row); `x86_64-unknown-linux-gnu` was not built. The two `host` jobs, `ci.yml`'s and `nightly.yml`'s, install nothing and take the rustc of their runner's image. A developer's machine -takes whatever its `stable` is: one on Apple silicon with 1.99.0 or later who -runs `cargo run -- --ci host` with `CI` set, or without incremental -compilation, gets the step ended after 15 silent minutes with this test named. +takes whatever its `stable` is: before #780, one on Apple silicon with 1.99.0 +or later who ran `cargo run -- --ci host` with `CI` set, or without +incremental compilation, got the step ended after 15 silent minutes with this +test named. Whether the host's toolchain is pinned once for every job is `issues/the-host-job-runs-the-toolchain-the-runner-ships.md`'s to decide, and this is a second measured case for it. @@ -125,15 +156,12 @@ and this is a second measured case for it. loop is right, and a compiler that drops a loop's exit here is not made safe by rewriting the one loop where it was seen. -Assigned: the orchestrator, who holds the pin and its exit. Before #778 -lands he dispatches `nightly.yml` on its branch and reads -`portability-macos`: success, with `test -a_port_answers_as_its_declaration_says ... ok` in `the workspace's host -members`. He reads the same in the first nightly on a `main` that has #778. -At each stable release he reruns the table's second row under it. +Assigned: the orchestrator, who holds the pin and its exit, and decides what +becomes of a pin whose test no longer needs it. **Exit**: `portability-macos` installs `stable` again and is green in a -nightly on `main`, which takes a stable rustc under which the table's second -row exits 0 on Apple silicon. None exists; one arrives when upstream's LLVM -has the fault fixed, or when `core` gives the range's loop another shape -again, which is how 1.98.1 passes today and fixes nothing. +nightly on `main`. The condition this issue first gave it, a stable rustc +under which the table's second row exits 0 on Apple silicon, is met by 1.99.0 +since #780, by the test's shape and not by a compiler without the fault: no +stable has one, and the host suite is compiled by one that has it whichever +the job installs. diff --git a/issues/the-sysroot-build-discards-the-result-of-removing-libcs-scratch-target.md b/issues/the-sysroot-build-discards-the-result-of-removing-libcs-scratch-target.md new file mode 100644 index 00000000000..8af19554a18 --- /dev/null +++ b/issues/the-sysroot-build-discards-the-result-of-removing-libcs-scratch-target.md @@ -0,0 +1,17 @@ +--- +status: open +kind: tooling +opened: 2026-10-09 +--- + +# The sysroot build discards the result of removing libc's scratch target directory + +`src/sysroot.rs`'s `build` ends libc's two builds with `let _ = +fs::remove_dir_all(&libc_target);`: a removal that fails leaves +`.libc-target` beside the published sysroot in the store, and nothing +says so. The line above it, the miscompile check's scratch, fails through +`keystore::remove`; this one was left as it was because #790's fence named +that line alone. Owner: the build system. + +**Exit**: the line is `keystore::remove(&libc_target);`, and a build that +makes a sysroot is green with it. diff --git a/rust b/rust index 6d6ad8c7190..b9cc8392f0e 160000 --- a/rust +++ b/rust @@ -1 +1 @@ -Subproject commit 6d6ad8c71906c4e7ddb67e34720f297704d6c282 +Subproject commit b9cc8392f0eb21330160352e0abd85602b419b23 diff --git a/src/lib.rs b/src/lib.rs index 45229856279..7785ce21e49 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,6 +34,7 @@ pub mod metal; pub mod metaldevices; pub mod metalimage; pub mod metaltimings; +pub mod miscompile; pub mod n2; pub mod release; pub mod sdkversion; diff --git a/src/miscompile.rs b/src/miscompile.rs new file mode 100644 index 00000000000..0d3b44d2c22 --- /dev/null +++ b/src/miscompile.rs @@ -0,0 +1,125 @@ +//! What a sysroot's compiler has been seen to miscompile, compiled by every +//! sysroot before it is published and refused where the answer is wrong. +//! +//! **A loop keeps the exit its counter's maximum takes.** An LLVM whose +//! ScalarEvolution gives a header phi's recurrence the wrap flags of its +//! increment without proving them for it (llvm/llvm-project#175729) lets +//! `indvars` fold such a loop's last exit to `false`: safe Rust becomes an +//! endless loop. Held twice, by the two compilers a toolchain carries of one +//! LLVM: [`LAST_EXIT_IR`] is the loop itself, which no front end can give +//! another shape, and [`LAST_EXIT`] the Rust that was seen to make it. + +use std::fs; +use std::path::Path; +use std::process::Command; + +use crate::arch::Arch; +use crate::clang::CSysroot; +use crate::toolchain::GUEST_TARGETS; + +/// The loop as LLVM IR, whose `caller` returns `true`. +const LAST_EXIT_IR: &str = include_str!("miscompile/last_exit.ll"); + +/// An inclusive range walked to its integer type's maximum, whose `caller` +/// returns `true`. +const LAST_EXIT: &str = include_str!("miscompile/last_exit.rs"); + +/// Refuse the toolchain at `toolchain` unless its clang compiles +/// [`LAST_EXIT_IR`] to `true` for each architecture, and its rustc +/// [`LAST_EXIT`] for every guest target, at the optimisation level guests are +/// built with. Sources and IR are written in `scratch`. +pub(crate) fn refuse(toolchain: &Path, scratch: &Path) { + eprintln!("Checking that the compilers of {} keep a loop's last exit", toolchain.display()); + fs::create_dir_all(scratch).unwrap_or_else(|e| panic!("create {}: {e}", scratch.display())); + let loop_ir = scratch.join("last_exit.ll"); + fs::write(&loop_ir, LAST_EXIT_IR).unwrap_or_else(|e| panic!("write {}: {e}", loop_ir.display())); + for arch in Arch::ALL { + let c = CSysroot::of(toolchain, arch); + let ir = scratch.join(format!("last_exit-clang-{}.ll", c.target)); + let mut clang = Command::new(&c.clang); + clang.arg(format!("--target={}", c.target)).args(["-O2", "-S", "-emit-llvm", "-o"]).arg(&ir).arg(&loop_ir); + keeps_the_exit(&mut clang, &ir, c.target); + } + let source = scratch.join("last_exit.rs"); + fs::write(&source, LAST_EXIT).unwrap_or_else(|e| panic!("write {}: {e}", source.display())); + for target in GUEST_TARGETS.map(|target| target.triple()) { + let ir = scratch.join(format!("last_exit-rustc-{target}.ll")); + let mut rustc = Command::new(toolchain.join("bin/rustc")); + rustc + .args(["--edition", "2021", "--crate-type", "lib", "--target", target]) + .args(["-C", "opt-level=2", "-C", "codegen-units=1", "--emit", "llvm-ir", "-o"]) + .arg(&ir) + .arg(&source); + keeps_the_exit(&mut rustc, &ir, target); + } +} + +/// Run `compiler`, which writes `ir` for `target`, and refuse it unless +/// `caller` there is one block that returns `true`. +fn keeps_the_exit(compiler: &mut Command, ir: &Path, target: &str) { + let output = compiler.output().unwrap_or_else(|e| panic!("run {compiler:?}: {e}")); + assert!(output.status.success(), "{compiler:?} failed:\n{}", String::from_utf8_lossy(&output.stderr)); + let text = fs::read_to_string(ir).unwrap_or_else(|e| panic!("read {}: {e}", ir.display())); + assert!( + returns_true(&text, "caller"), + "{compiler:?} miscompiles a loop that ends at its counter's maximum for {target}: `caller` returns \ + `true` and its IR does not (llvm/llvm-project#175729):\n{}", + body(&text, "caller").unwrap_or("there is no `caller`"), + ); +} + +/// The definition of `function` in the LLVM IR `ir`, from its `define` to its +/// closing brace. +fn body<'a>(ir: &'a str, function: &str) -> Option<&'a str> { + let named = format!("@{function}("); + let mut at = 0; + let start = ir.split_inclusive('\n').find_map(|line| { + let here = at; + at += line.len(); + (line.starts_with("define ") && line.contains(&named)).then_some(here) + })?; + let end = ir[start..].find("\n}")?; + Some(&ir[start..start + end + 2]) +} + +/// Whether `function` in `ir` is one block that returns `true`. +fn returns_true(ir: &str, function: &str) -> bool { + let Some(body) = body(ir, function) else { return false }; + let instructions: Vec<&str> = + body.lines().skip(1).map(str::trim).filter(|line| !line.is_empty() && !line.ends_with(':') && *line != "}").collect(); + instructions == ["ret i1 true"] +} + +#[cfg(test)] +mod tests { + use super::*; + + /// **The negative control is the defect itself**: `caller` verbatim as a + /// compiler with the fault emitted it for `aarch64-unknown-toyos`, and as + /// one without it does. + #[test] + fn an_endless_caller_is_not_one_that_returns_true() { + let endless = "\ +; Function Attrs: nofree norecurse noreturn nosync nounwind memory(none) +define noundef zeroext i1 @caller() unnamed_addr #0 personality ptr @rust_eh_personality { +start: + br label %bb4.i.backedge.i + +bb4.i.backedge.i: ; preds = %bb4.i.backedge.i, %start + br label %bb4.i.backedge.i +} + +attributes #0 = { nofree norecurse noreturn nosync nounwind memory(none) } +"; + assert!(!returns_true(endless, "caller")); + let right = "\ +define noundef zeroext i1 @caller() unnamed_addr #0 { +start: + ret i1 true +} +"; + assert!(returns_true(right, "caller")); + assert!(!returns_true(&right.replace("ret i1 true", "ret i1 false"), "caller")); + assert!(!returns_true(&right.replace("@caller(", "@other("), "caller"), "a module with no `caller` answers nothing"); + } +} diff --git a/src/miscompile/last_exit.ll b/src/miscompile/last_exit.ll new file mode 100644 index 00000000000..c72cf103d7d --- /dev/null +++ b/src/miscompile/last_exit.ll @@ -0,0 +1,20 @@ +; `caller` returns `true`: `%iter` is -1 on the loop's thousandth pass, the one +; after `%nextnext` wrapped, which nothing reads. A loop of few enough passes +; to be evaluated is, before `indvars` meets it. +define i1 @caller() { +entry: + br label %header + +header: + %next = phi i16 [ -999, %entry ], [ %nextnext, %latch ] + %iter = phi i16 [ -1000, %entry ], [ %next, %latch ] + %done = icmp eq i16 %iter, -1 + br i1 %done, label %exit, label %latch + +latch: + %nextnext = add nuw i16 %next, 1 + br label %header + +exit: + ret i1 true +} diff --git a/src/miscompile/last_exit.rs b/src/miscompile/last_exit.rs new file mode 100644 index 00000000000..a3a2ff8ec85 --- /dev/null +++ b/src/miscompile/last_exit.rs @@ -0,0 +1,48 @@ +//! `caller` returns `true`: the first range's last port is `Port::MAX`, where +//! its loop ends. +#![no_std] + +type Port = u128; + +#[derive(Clone, Copy)] +pub enum Mediated { + Kept, + ReadOnly, +} + +#[derive(Clone, Copy)] +pub enum Standing { + Free, + Declared(Mediated), +} + +fn port(standing: impl Fn(Port) -> Standing, port: Port, width: Port, write: bool) -> Result { + for port in port..=port + (width - 1) { + match standing(port) { + Standing::Free => {} + Standing::Declared(Mediated::ReadOnly) if !write => {} + Standing::Declared(Mediated::ReadOnly) => return Err(9), + Standing::Declared(Mediated::Kept) => return Err(8), + } + } + Ok(port) +} + +fn standing(port: Port) -> Standing { + match port { + 0x38..=0x3F | 0x20..=0x21 | 0xA0..=0xA1 | 0x70..=0x71 | 0xC8 | 0xCC..=0xCF | 0xC9 => { + Standing::Declared(Mediated::Kept) + } + 0xB2 => Standing::Declared(Mediated::ReadOnly), + _ => Standing::Free, + } +} + +fn probe() -> bool { + port(standing, Port::MAX - 3, 4, false).is_ok() & port(standing, 0xB2, 1, true).is_err() +} + +#[no_mangle] +pub fn caller() -> bool { + probe() +} diff --git a/src/sysroot.rs b/src/sysroot.rs index dc2f6a3946e..b3abf7989b6 100644 --- a/src/sysroot.rs +++ b/src/sysroot.rs @@ -92,12 +92,13 @@ const SOURCES: &str = "SOURCES"; /// What changes how a key's sources become its libraries and its sysroot and /// is none of them, nor std's configuration ([`std_config`]), which the /// freestanding key reads whole. Moving it moves every key. -const RECIPE: &str = "bootstrap stage-0 local rebuild, libraries from the stamp, libtoyos_c merged, \ +const RECIPE: &str = "bootstrap stage-0 local rebuild, libraries from the stamp, refused where its \ + compiler miscompiles what `src/miscompile.rs` holds, libtoyos_c merged, \ a C sysroot of libc's staticlib, the empty libraries beside it, headers and \ CMake's description of ToyOS per target, refused unless a C program naming \ each library links against it, and its C++ runtime built under n2 from the \ runtimes' sources of the compiler's LLVM, the freestanding libraries cloned \ - from their key's; 13"; + from their key's; 15"; /// Whose sources a guest target's libraries compile. #[derive(Clone, Copy, PartialEq, Eq)] @@ -764,6 +765,9 @@ fn build(root: &Path, store: &Path, compiler: &Compiler, fork: &Path, keys: &Key } } } + let miscompiles = dir.with_extension("miscompile"); + crate::miscompile::refuse(partial, &miscompiles); + keystore::remove(&miscompiles); let libc_target = dir.with_extension("libc-target"); for arch in Arch::ALL { crate::libc::build(root, partial, &libc_target, arch);