Skip to content

production: a generated rust-std package can need more rustc stack than a default Windows thread gives (found by #160) #164

Open

Description

@afflom

Found on the first CI run of #160 (the axiom-output parser pilot, BS-01).

What happened

On the x86_64-pc-windows-msvc host leg, conformance_bs_01 failed to build the generated packages of its two roots:

thread 'optimize module run_boot_parse_audit_output_std.…-cgu.0' has overflowed its stack
error: could not compile `run_boot_parse_audit_output_std` (bin "run_boot_parse_audit_output_std")

The same for run_boot_is_closed_lean_name_std. Linux and macOS built the same bytes. The contributor reproduced it on Linux by giving rustc a 1 MiB thread (RUST_MIN_STACK): both generated library crates (boot_is_closed_lean_name_std, boot_parse_audit_output_std) are killed by SIGSEGV at 1 MiB and pass at 2 MiB. Candidates for the deepest nesting in the emitted Rust: a 20-term append chain (decimal) and nested conditionals (whiteLength).

What was done, and what is left

  • feat(bootstrap): the axiom-output parser as language-1.2 source (BS-01) #160 sets RUST_MIN_STACK=64 MiB for the cargo children of the conformance harness on every host (rust_build::cargo_in), so the evidence is the same everywhere. That is a workaround of the test, not of the output.
  • Left open here: a consumer who builds a certified package on a host whose default rustc thread stack is 1 MiB (Windows) gets the overflow. Every certified package so far builds with the default; this is the first that does not.

To decide

Whether the Rust backend should bound the nesting depth of what it emits (flatten a let-chain or an append chain into sequential statements, which the calculus already allows), or whether the package's manifest should say what stack its build needs. The answer belongs to #91 (the Rust AST and its printer) and the RustSemantics model must keep covering whatever shape is emitted. A measurement first: the maximum expression depth of every committed package under tests/production-export/ against the smallest rustc stack that builds it.

Not a blocker for #160; no milestone until the measurement is done.

Activity

  1. afflom commented on Oct 18, 2026

    @afflom
    MemberAuthor

    Measurement of expression/delimiter nesting depth and minimum rustc 1.85.0 thread stack across all committed packages under tests/production-export/ (68 packages on main + lex and audit_tokens on #171):

    1. Maximum expression / delimiter nesting depth and minimum RUST_MIN_STACK (tests/production-export/**/src/lib.rs)

    Separating the runtime preamble (rust-core lines 1–81, max delimiter depth 4; rust-std lines 1–293, max delimiter depth 11 at List::cons) from the generated root functions (Fn*, f*, run), and binary-searching RUST_MIN_STACK in 16 KiB steps for rustc --crate-type lib --edition 2021 (both dev and -C opt-level=2 on x86_64-unknown-linux-gnu):

    Package (tests/production-export/…) Generated delimiter depth File delimiter depth Lines Bytes Min RUST_MIN_STACK (dev / -O2, 16 KiB steps) Deepest generated construct
    bootstrap/packages/rust-std/audit_tokens (#171) 31 31 4,234 112,924 1,168 KiB / 1,168 KiB nested let / match blocks in f0
    bootstrap/packages/rust-std/parse_audit_output 22 22 2,396 67,759 1,168 KiB / 1,168 KiB 20-term append_bytes chain in decimal (lib.rs:588)
    bootstrap/packages/rust-std/lex (#171) 17 17 3,350 89,938 1,168 KiB / 1,168 KiB token step state transitions
    stdlib/packages/rust-std/lines 13 13 533 17,962 1,168 KiB / 1,168 KiB nested match / let blocks in f4 (lib.rs:441)
    production-coverage/packages/rust-std/sequences 13 13 348 14,263 1,168 KiB / 1,168 KiB nested let / tuple destructuring (lib.rs:333)
    stdlib/packages/rust-std/split_on 12 12 474 16,766 1,168 KiB / 1,168 KiB nested match / let blocks (lib.rs:434)
    production-coverage/packages/rust-std/text 12 12 341 14,109 1,168 KiB / 1,168 KiB nested let blocks (lib.rs:328)
    bootstrap/packages/rust-std/is_closed_lean_name 10 11 487 16,786 1,168 KiB / 1,168 KiB byte classification if/else chain (f5)
    Remaining 31 rust-std packages 2–10 11 300–947 12,866–28,755 1,168 KiB / 1,168 KiB runtime preamble (List::cons) dominates
    All 31 rust-core packages (#![no_std]) 3–9 4–9 96–322 3,618–11,482 1,168 KiB / 1,168 KiB generated let / match blocks
    Empty baseline crate (pub fn empty() {} or #![no_std]) 1 1 1 18 1,168 KiB / 1,168 KiB baseline rustc 1.85.0 'rustc' thread frame

    All 70 packages build at the exact same 1,168 KiB RUST_MIN_STACK threshold as a 1-line pub fn empty() {} crate.


    2. Why RUST_MIN_STACK=1 MiB segfaulted on Linux and why Windows failed in #160

    rustc uses two distinct kinds of worker threads with different default stack sizes when RUST_MIN_STACK is unset:

    1. The 'rustc' frontend/typecheck/MIR thread (rustc_interface::util::run_in_thread_with_globals): spawned with an explicit .stack_size(8 * 1024 * 1024) (8 MiB on Linux, macOS, and Windows) unless RUST_MIN_STACK is set in the environment, in which case RUST_MIN_STACK overrides the 8 MiB default.
      • As the table above shows, rustc 1.85.0's 'rustc' thread requires 1,168 KiB even on an empty 1-line pub fn empty() {} crate, and 1,168 KiB across every one of the 70 packages under tests/production-export/.
      • Setting RUST_MIN_STACK=1048576 (1 MiB) on Linux reproduced a SIGSEGV on boot_is_closed_lean_name_std and boot_parse_audit_output_std only because RUST_MIN_STACK=1048576 overrides rustc's 8 MiB default for the 'rustc' frontend thread down to 1,024 KiB — below the 1,168 KiB baseline that rustc 1.85.0 needs for an empty crate.
    2. The 'optimize module …' LLVM codegen threads (rustc_codegen_ssa::back::write::start_executing_work): spawned via std::thread::Builder::new() without .stack_size(…), so when RUST_MIN_STACK is unset they inherit the platform default (2 MiB on Linux/macOS, 1 MiB on Windows).
      • Measured separately via a pthread_attr_setstacksize preload probe that overrides only the unconfigured 2 MiB codegen threads while keeping 'rustc' at its normal 8 MiB, the 'optimize module' thread builds every committed library package (parse_audit_output, is_closed_lean_name, lex, audit_tokens, graphs, etc.) at 64 KiB (dev) and 128 KiB (-O2 / -O3) — 1/8th of Windows's 1 MiB default thread stack.
      • Therefore, every committed rust-std and rust-core library package under tests/production-export/ builds on Windows with default rustc settings (no RUST_MIN_STACK needed by consumers).
      • The Windows crash in feat(bootstrap): the axiom-output parser as language-1.2 source (BS-01) #160 (thread 'optimize module run_boot_parse_audit_output_std.…-cgu.0' has overflowed its stack, could not compile run_boot_parse_audit_output_std (bin)) occurred in the conformance test harness binaries run_boot_parse_audit_output_std and run_boot_is_closed_lean_name_std (after the library crates boot_* had already compiled), where rust_harness::render_calls emits all 12,487 audit calls (or 40,127 name calls) as one inline array literal for (a0, a1) in [ … ] inside fn harness_group0().
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions