feat(core): t27core -- self-hosted compiler core MVP, fixpoint with gen-c - #6041
Merged
Merged
Conversation
…en-c A t27 compiler written in t27 (specs/compiler/core/t27core.t27): lexer, parser, checks and C emitter for the subset `t27c gen-c` lowers without loss. Built by gen-c and run on its own source, it writes gen-c's output byte for byte. bootstrap/tests/core_selfhost.rs gates the fixpoint, the core's own test blocks, 6 fixtures, 16 refusals with their codes, and a differential over specs/: 58 specs accepted, all byte-identical to gen-c (floor 50). Shapes gen-c lowers with loss are refused, not copied: `- -x` as `--x`, declarations after `endmodule`, top-level consts truncated after their first token, and the earlier ones recorded on the epic. Closes #5981 Refs #5980 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
This was referenced Oct 4, 2026
This was referenced Oct 4, 2026
This was referenced Oct 4, 2026
Merged
gHashTag
added a commit
that referenced
this pull request
Oct 4, 2026
… nine as switch (#5964) * fix(t27c): typecheck refuses a Rust match block the parser kept as text Since W914 the parser captures a Rust `match` block verbatim into a StmtExpr so parsing does not fail, and no backend reads that node: a match used as a fn's value generated an empty body in every backend with every gate green. check_captured_match now refuses each captured block in typecheck, naming its line and its fn or test, and points at t27's own `switch`. Parse still accepts it, so generation is unchanged. `match` as an identifier, field, member or call is not refused. Unit tests: tests_5949_captured_match (refusal names lines 4 and 10; negative control: switch, var match, match = false, Hit{match:1}, g(h.match), match(n)). Closes #5949 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(specs): rewrite nine match blocks as t27 switch; ledger git/diff 31 captured Rust match blocks in 7 specs. 9 are rewritten as switch (or if) in auth/config, ar/restraint, ar/coa_planning, ar/composition, igla/training/roadmap and igla/evaluation/multi_lang_harness, and their seals re-saved. ar/composition execute_ar_component keeps its match: its arms assign a tuple, which t27 has no statement for; that spec is already red upstream. git/diff matches Ok/Err on Command::new("git") in 21 blocks and is ledgered at typecheck (issue 5949, expires 2026-11-30). max_entries 112 -> 113 is a hand edit, recorded in _why_entries_rose: no entry could be retired without inventing a value. Closes #5949 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(now): t27c refuses a Rust match block the parser kept as text (Closes #5949) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(gen): signed and float % lowers to @Rem (Closes #5973) Zig refuses infix `%` unless both operands are unsigned ("signed integers and floats must use @Rem or @mod"), so `return a % b;` with `a: i32` made the generated file fail to compile. The W593 `@divTrunc` arm for `/` already had the right test; `%` now uses it and emits `@rem(a, b)`, the truncated remainder (rem(-7, 2) == -1, as in C, Rust and t27b). Unsigned `%` stays infix. `/` needed no change. 8 of 1184 specs change gen output and 0 change gen-c; none has a new zig error, and compiler/optimizer.t27 now passes 14/14. 12 seals resealed. Found by the t27b differential test. Part of #5905. Closes #5973 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(compiler): type integral Rust repeat counts (Fixes #5986) * feat(t27b): run invariant blocks like tests, report them apart An `invariant` block lowers exactly like a `test` (a parameterless body, the test binding rule) and runs through the same trap machinery. t27c's Zig backend emits it as `comptime { ... }`, so a broken invariant is a compile error there; t27b runs it at test time and prints INVARIANT PASS / INVARIANT FAIL, plus an `invariants N held, M broken, K not checked` line. An invariant whose body the front-end discarded (a `forall`, an unparseable clause) is NOT CHECKED, never "held"; a partially parsed one is rejected, as a partial test already was. Clause-form blocks carry no line on any node, so their header line is looked up in the source text. Corpus: 48 supported (47 pass, 1 spec failure), was 37; 0 mismatches. Differential: 0 mismatches, 2500 programs per mode. Refs #5977 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(t27b): memory primitives -- frame slots, loads/stores, rodata, bounds The IR primitives that structs, arrays, slices and strings lower onto, end to end through the interpreter, the JIT and the Mach-O object: - Ty::Ptr; ExprKind::Slot / Data / Load{addr,off} / Offset{base,idx, scale} / Bounds{idx,len,site}; Stmt::Store / Copy; Func::slots; Program::data; TrapKind::Bounds = 16 (numbered high, so the scalar lane can add kinds below it). - eval (the oracle): a byte-addressed model with per-call slots, a gap between objects and per-byte written flags. It faults on a read of unwritten bytes, an access outside a live slot or blob, a store into data, and a bool load of anything but 0/1. A fault is a lowering defect and never agrees with the JIT. - codegen: - an aggregate area below x29, up to 16 KiB; - x29-relative ldur/stur fast paths; - uimm, unscaled and register load/store forms, with signed narrow loads; - ADRP + ADD for data; - shifted add or madd for Offset; - cmp + b.hs to a Bounds trap stub; - Copy unrolled 8/4/2/1, or as a post-indexed loop past 64 bytes. - JIT: data blobs appended to the image and patched in place. - Mach-O: a __const section with PAGE21/PAGEOFF12 relocations against local l_t27b_data.N symbols. - a64: sized load/store, adr/adrp encoders and disassembly, with 32 new clang-verified encodings and an otool comparison. Tests: - The random differential generator covers every primitive: slots up to 9000 bytes, data blobs, loads and stores of every scalar type at constant, masked and bounds-checked indices, short and looped copies, and pointer parameters into the caller's frame (the hidden-pointer ABI). 0 mismatches over 2500 programs per overflow mode. - tests/macho.rs links an object with clang and runs it against the interpreter. - tests/memory.rs pins the interpreter's faults. Corpus unchanged (47 pass): nothing lowers to these yet. Refs #5977 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(compiler): preserve immutable constant names in Rust (Fixes #5994) * gate(coq): re-bless the three trios-coq files #4754 built, and run on trios-coq/** (#6006) Coq kernel has been red on master on every run since 2026-09-24 (six, 28fd522 through 558dab4), at "No new Coq file falls outside every build": NullorReversible.v, SparsityMask.v and SpeculativeExit.v joined trios-coq/_CoqProject in #4754 without a re-bless. - tools/coq_outside_build_baseline.txt: `check_coq_in_build.py --bless`, 18 -> 15. The three compile locally (Rocq 9.1.1, `coqc -Q . TriosCoq`, exit 0 each, 11 Qed each, `Admitted` only inside comments). - coq-kernel.yml: `trios-coq/**` in both path lists. The checker reads every _CoqProject, but the workflow ran only for coq/ and proofs/, so #4754 started no run and #4755 inherited the red. Negative control: putting SparsityMask.v back in the baseline exits 1 with "1 file(s) joined a build". `--self-check` 6/6, `tri census pin --gate` PASS, check_pr_branch_filters.py exit 0. Closes #6005 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(c): initialize array-repeat wrappers by value (Fixes #6013) * tri: stranded-work finder and cron tick resume card (#5787) * tri: stranded-work finder and cron tick resume card tri stranded lists remote branches with commits off the default branch, tips older than --hours, and no open same-repo PR. It skips dependabot/*, patch-equivalent branches (git cherry) and squash-merged ones (merge-tree result equals the default's tree), reports merge clean/conflict and whether the branch ever had a PR, and runs merge-tree against a throwaway object directory so the clone is never written. With no PR state nothing is called stranded: the rows are "undecided" and the exit is 2. tri tick is the resume card for a cron loop: claim and its age, the last ledger section, each worktree's branch, dirty count, ahead/behind vs its base and last commit age, and anomalies with a one-line fix each. git runs with GIT_OPTIONAL_LOCKS=0; nothing is written. Both self-tests run in loop-tools-gate, each with a negative control, and each was seen to fail on a mutated copy of its tool. Refs #5786 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * tri census: re-bless fetches 4->5 and shell 283->285 for tri stranded/tick Two pinned numbers moved, both because of this PR: - fetches: SURFACE bounded reads in scripts/tri_loop/*.py 4 -> 5. The new read is `gh pr list --limit PR_LIMIT` in scripts/tri_loop/stranded.py. It already refused to answer on a full page, but said so as "a lower bound" in lowercase, which the census (says_lower_bound, the Rust side's wording) does not read. The message now says LOWER BOUND, so the split is 5 guarded and 0 that do not, rather than 4 and 1. - shell: run steps 283 -> 285 (runner bash 262 -> 264). The rise comes from loop-tools-gate.yml, which gains the two tri_loop tests (test_stranded_work_is_found_without_writing.py and test_a_tick_resumes_from_what_it_reads.py). quiet was re-recorded byte-identical. Refs #5786 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * tri pr-state: a loop's pull requests with tri pr ready's verdict, not "open" (#5824) * tri pr-state: a loop's pull requests with tri pr ready's verdict, not "open" A cron loop reported t27#5787 as "open" for six ticks while cli-tri was red. tri pr-state reads every PR a tick-state.json names: gh pr view, then tri pr ready while it is open, quoting the verdict and its exit code. SETTLED only when every verdict is "safe to merge"; an open PR with a running check exits 1. Anomalies: head-moved, not-open, unresolved (an alias is never guessed), unreadable, no-verdict. Read-only; --answers replaces gh and tri for the offline test. tri tick's pick_id/usage take the command name so tri pr-state errors under its own; its card points at tri pr-state for "still open?". First real run, cron 8782e5f8, 9 PRs: all safe, exit 0, and one head-moved on 999#3554 -- the loop's own tick-8 push, never recorded. Census: shell run: steps 285 -> 286 and runner bash 264 -> 265, the one new loop-tools-gate step for scripts/ci/test_an_open_pr_is_not_a_green_one.py. Fetches unchanged: pr_state.py has no --limit read. Closes #5823 Refs #5786 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * tri pr-state: the printed card says safe is not proof any check ran The billing-blocked caveat lived only in the docstring, while #5824's body said the card's NOT ESTABLISHED block carried it. Now the printed footer says it, and the test asserts it on the settled (healthy) card; seen red with the word removed. No census moved. Closes #5823 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(tri tick): a released claim is not held, and the dead-tick fix names its files (#5864) * fix(tri tick): a released claim is not held, and the dead-tick fix names its files tri tick never read claim.released. A tick that had finished read as dead 45 minutes later. On tick 18 of cron 8782e5f8, its fix line was `git add -A && git commit` on PR #5839's worktree, whose only dirty files were cron_tracking/ and .claude/launch.json, both untracked on purpose. - A release that parses and is not older than `since` ends the hold. A leftover or unparseable release does not; the unparseable one is reported as claim-unparseable on claim.released. - Dirty excludes the state directory and a worktree's keep_untracked paths, counted per file (--untracked-files=all). Every other untracked file still counts. - The fix is `git add -- <each file, quoted>`, never `add -A`. - Test 20 -> 36 checks; 12 mutations, each red. Closes #5863 Refs #5786 #5823 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(tri tick): dirty work nobody holds is an anomaly, released claim or not (#5896) A released claim made every dirty worktree silent. Tick 21 of cron 8782e5f8 delegated two tasks to background agents and released; the session ended, both agents died uncommitted, and tick 22's card said ANOMALIES: 0. New anomaly orphaned-dirty: no claim held, a worktree dirty, its newest dirty file older than --stale-minutes, and no process with its cwd inside it (lsof -d cwd; "NOT RUN" without lsof). Fresh writes or a live process print a DELEGATE line instead. The detail quotes held_by; the fix names the files, never add -A, and clears held_by. Test: orphaned, no claim, and two negative controls (fresh write, a live sleep standing in the tree). 7 mutations, all red. Closes #5895 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * feat(tri pr-state): --why, and each name under the heading tri printed it under (#5868) tri pr-state could not pass --why to tri pr ready, so a failure red elsewhere under the same check name read as pre-existing in the loop's report. Its parser also put every " - name" after the VERDICT into one only_here list: CANNOT TELL's no-baseline names and --why's new-reason names read as "only here", and the NOT compared list above the VERDICT was never read. - --why is passed through; a tri without the flag (clap usage error, exit 2 = WAIT's code) is a no-verdict that says so, never WAIT - only_here / no_baseline / new_reason / not_compared are kept apart; an unknown heading, an unknown verdict, or a name under no heading is kept as `other` with its heading and printed - with --why a REASONS line counts over the rows that got a verdict and names the rest as not compared (a live run with an old tri read "REASONS: 0" over 18 no-verdicts before this) - test 27 -> 51 checks, #5452's real answer as a fixture, a fake gh and tri on PATH proving the flag reaches argv; 19 mutations, each red Closes #5867 Refs #5823 #5852 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(zig): quote declared primitive value bindings (Fixes #6040) * feat(t27b): structs, field access, pointers and struct results Lower struct declarations, struct literals, field reads and writes, `&x` / `p.*` / `*T`, struct parameters and struct results to the memory primitives of the previous change. Structs always live in memory: - A struct local or temporary lives in a frame slot. Its literal is built in place when the destination is fresh and its address is pure; otherwise it is built in a temporary and copied. - A struct parameter is the caller's memory, passed by address and never written. - A struct result is built through a hidden last pointer parameter. Such functions are listed in `Program.internal_abi`, and `t27b build` does not export them (it prints a note). - A module struct constant goes to read-only data. A scalar field of it folds to a constant (`const OX: i32 = ORIGIN.x`). - Layout follows the C rules and is computed on first use. A pointee is only named, so `next: *const Node` works. A struct that contains itself by value is rejected, and so is a generic struct. - An address-taken scalar local or parameter lives in a slot. - Compound assignment through an impure address evaluates the address once. Every rejection is precise: a missing, unknown or duplicate field, a positional initializer, an anonymous `.{}` with no result type, a write through a constant, a struct or pointer operand, a non-value `X.y`, and a struct that contains itself. IR: new `ExprKind::Seq { stmts, value }` (stores and copies, then a value), so a struct temporary is built exactly where its argument is evaluated. The interpreter and the code generator both implement it, and the random differential generator now emits it. Corpus (specs/, --jobs 6): 47 -> 70 files supported with every test passing, 0 JIT/interpreter mismatches. - One newly reached file fails one test (fpga/verification/build_verify, module count 33 vs 31). t27c gen + zig test fails the same assertion, so the reference fails here too. - Two files time out in the t27c parse phase alone at a load average near 780. Refs #5977 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ci): install Zig for native controls; shell run steps 264 to 265 (Refs #6040) * fix(ci): install corpus Icarus prerequisite; shell 268 to 269 (Refs #6040) * fix(verilog): preserve conditional return and assertion context; Fixes #6043 * fix(rust): preserve function and conditional tail values; Fixes #6062 * feat(core): t27core -- self-hosted compiler core MVP, fixpoint with gen-c (#6041) A t27 compiler written in t27 (specs/compiler/core/t27core.t27): lexer, parser, checks and C emitter for the subset `t27c gen-c` lowers without loss. Built by gen-c and run on its own source, it writes gen-c's output byte for byte. bootstrap/tests/core_selfhost.rs gates the fixpoint, the core's own test blocks, 6 fixtures, 16 refusals with their codes, and a differential over specs/: 58 specs accepted, all byte-identical to gen-c (floor 50). Shapes gen-c lowers with loss are refused, not copied: `- -x` as `--x`, declarations after `endmodule`, top-level consts truncated after their first token, and the earlier ones recorded on the epic. Closes #5981 Refs #5980 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * feat(agents): Gamma holds a /self-host skill for epic #5980 (#6054) The rules of the t27-in-t27 compiler had no home a bee loads: byte identity with gen-c, refusing the shapes gen-c lowers with loss, probing gen-c first, and the ;-alone-line trap. They now live in .claude/skills/self-host/SKILL.md. Its card is specs/skills/t27-self-host.t27, and agent C holds it through its prompt's Skills section and the alphabet's domain-lead table. Closes #6053 Part of #5980 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(t27c): refuse a colon inside a field type and @as to a slice type; fix the six silent specs (#5971) * fix(t27c): typecheck refuses a colon inside a field type and @as to a slice type Six specs parsed, typechecked and generated output their backend cannot compile (`tri misread` silent pairs). Two defects: - A struct field whose type, with `::` paths removed, still holds a `:`. Either the field has no `,` after it (missing, or inside a trailing `#` comment, which runs to the end of the line) and swallowed the fields after it, or it is a map type `[K: V]`, which t27 does not have. - `@as(T, x)` with a slice or array type: in argument position the parser reads `[]u8` as an array literal, and every backend loses the type. Both are refused in typecheck, naming the line. The parser now records a struct field's line; no backend reads it, so generated output does not move (seal currency: 0 stale generated-code hashes). Closes #5968 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(specs): put the comma before the comment, drop the map type and slice casts - ar/coa_planning: the missing `,` after `verification_status`. - tri/agent/governance_agent, tri/utils/help: `field : T # note,` is now `field : T, // note`; the `#` comment had taken the `,` with it. - git/schema: `env: [str: str]` is now `env: []EnvVar`; the one constructor, git/operations' options_construction test, builds EnvVar. - port/tools/wp18: `_ssot` writes one `// CATALOG: id=<id>` line per id, as the Python original does, instead of an `@as([]u8, ..)` cast. - tri/pipeline/builder: `@as([]T, &.{})` in a test is now a length check. Re-sealed with `t27c seal --save`, including git/status and git/diff, whose generated code inherits Options. Closes #5968 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(now): t27c refuses a colon inside a field type and a slice cast (Closes #5968) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(t27c): var tuple destructure silences never-mutated Zig locals (Closes #5886) (#5902) `var (s, d) = f();` lowered to `var s, var d = f();` with no silencer, so Zig rejected every element the function never reassigns ("local variable is never mutated") and `t27c test-report` printed BLOCKED for d_slow_blink.t27 (Refs #5682). The StmtLocal tuple-destructure branch now emits, for a `var` destructure, the same `_ = &name;` per named element that the single-name `var` path already emits, and records the name in `discarded_by_ref` so W730 drops a later `_ = name;` (a pointless discard in Zig). A bare `_` element gets no silencer; `let (s, d)` is unchanged. Test: test_var_destructure_never_reassigned_zig. Without the fix it fails by assertion ("`_ = &s;` silencer missing"); with it, it passes, and the whole t27c bin unit suite passes (1759, 0 failed, 2 ignored). FROZEN_HASH resealed to the new compiler.rs digest. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(seals): re-seal wp18 with this branch's compiler after the master merge The master merge (2ec2d00) took master's wp18 seal, but this branch keeps the #5971 spec (spec_hash c2c89bb3..., the CATALOG port of `_ssot`). Lab run of 2ec2d00: seal-currency and seal-coverage red on this one file only. Re-sealed on the Railway t27c lab: t27c built from 2ec2d00, zig 0.16.0 on PATH, `t27c seal --save` + `tri seals sync-twins` (517 twins consistent, 0 written). gen_hash_zig is 7fd0ed8c, the same output #5971 sealed; the zig test result is unchanged (expected `;` at the `sha256::Hasher` line, 86:24 in the emitted source). Refs #6092 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(gen-zig): keep `_ = call(..);` instead of deleting it (Closes #5984) (#6003) * fix(gen-zig): keep `_ = call(..);` instead of deleting it (Closes #5984) Dead-store elimination read `_` as a variable nobody reads and deleted every top-level `_ = call(args);` in a fn body, call and all. The unused-parameter pass then added `_ = p;` for a parameter that only the deleted call used, which Zig rejects as a pointless discard once the call is back. OptConfig gains keep_call_discards. Compiler::compile (gen-zig) sets it, so a discard whose right-hand side holds a call survives. It defaults to false, so the Verilog path is byte-identical (0 of 37 specs with a discarded call differ). Measured over 1297 tracked .t27 files: gen-zig output changes for 5 specs, 9 calls come back, 2 spurious `_ = param;` lines and 1 `_ = json_str;` line go. Their 9 seals are re-sealed. FROZEN_HASH re-pinned. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * seal: re-seal wp18 selftest after the gen-zig discard-call fix Spec unchanged; only gen_hash_zig moves (the emitter fix in this PR). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * seal: re-seal wp18 selftest with the merged compiler (Closes #5949) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Dmitrii Fedorov <dmitrii.f@t27.ai>
gHashTag
added a commit
that referenced
this pull request
Oct 4, 2026
…down specs (#5923) (#5947) * fix(specs): rewrite eight Markdown documents named .t27 as t27 modules Eight files under specs/ were Markdown with a .t27 name. The parser refused each at its first line, so they generated nothing and their "tests" had comment-only bodies. Each is now a module whose prose is `;` comments and whose arithmetic is checked: 54 test blocks, all passing under gen-zig. physics/hslm_benchmark (7) physics/e8_lqg_bridge (8) physics/lqg_cs_bridge (5) physics/gamma-conflict (10, module gamma_conflict) benchmarks/bench_nn (6) benchmarks/gf16_bfloat16_nmse (7) api/tri_net_api (5) conformance/e2e_scenarios (6) Where the document contradicted itself or the repository, the module records the defect rather than the claim: gamma-conflict carries six CORRECTIONS, tri_net_api states the FROZEN_HASH line format build.rs actually reads, and e2e_scenarios pins a pack scenario that overwrites its input before reading it and a total verdict its own rows do not imply. Seals re-taken with `t27c seal <spec> --save`. Refs #5923 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(t27c): refuse Rust macro calls in parse and typeless fields in typecheck Two ways the compiler read a spec wrong while every gate stayed green. parse: `format!("{}", a)` was read as `format` followed by the statement `!("{}", a)`, which gen-rust lowered to `(("{}", a) == 0);`. `assert!(c)` in a test became an assert of a negated tuple. t27 has no macros; the parser now refuses an identifier glued to `!` and followed by `(`, `[` or `{` on the same line, naming the macro. `a != b`, a prefix `!x`, strings and comments are untouched (unit tests cover both sides). typecheck: a struct field whose type is empty or an integer literal is an error (#3225). The parser recovers list-valued declarations it does not implement as fields, and gen-rust wrote them as `pub f: ,` / `pub f: 0,`. Generation does not run typecheck, so those specs still generate exactly as before; what changes is that a gate now goes red on them. Specs that used macros are fixed in their source: auth/config `panic!` -> `unreachable`, seven port specs `assert!(` -> `assert(`. The generate baseline drops the eight rewritten Markdown specs and records the two whose remaining failure is now the named `format!` refusal. FROZEN_HASH updated for compiler.rs. Refs #5923, #3225 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(tri): misread splits each pair into refused and silent; ledger the new rule `tri misread` now typechecks every spec that carries a shape and reports each spec-shape pair as REFUSED (typecheck goes red) or SILENT (every gate green). A refusing typecheck is not trusted until a clean spec has been seen to typecheck and show no shape -- a broken binary would otherwise turn the whole table "refused" and read as progress. Measured on the corpus: 41 pairs, 35 refused, 6 silent (one Vec<> type, five colon-in-type specs). Suite ledger: +25 typecheck entries for the specs the new rule refuses (issue 3225, each naming its struct and fields), -8 parse entries for the specs rewritten as modules; cap 126 -> 112. The tool card's ABOUT follows the new clap doc. Closes #5923 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ledger): swap two Lean completeness entries the Markdown rewrite moved; add the NOW entry corpus_classifier_matches_lean_completeness failed on CI. Two of the eight Markdown files this PR rewrote as t27 modules changed their Rust verdict: - physics_lqg_cs_bridge now agrees with its Lean theorem; retired. - benchmarks_gf16_bfloat16_nmse became classifiable and disagrees. The spec computes in f64, which Icarus does not lower (with the f64 lines removed the classifier answers true); the theorem was written over the Markdown document. Entered with its reason, as #2884 did. max_entries stays at 77 and max_vacuous at 44. Closes #5923 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(t27c): refuse a Rust match block the parser kept as text; rewrite nine as switch (#5964) * fix(t27c): typecheck refuses a Rust match block the parser kept as text Since W914 the parser captures a Rust `match` block verbatim into a StmtExpr so parsing does not fail, and no backend reads that node: a match used as a fn's value generated an empty body in every backend with every gate green. check_captured_match now refuses each captured block in typecheck, naming its line and its fn or test, and points at t27's own `switch`. Parse still accepts it, so generation is unchanged. `match` as an identifier, field, member or call is not refused. Unit tests: tests_5949_captured_match (refusal names lines 4 and 10; negative control: switch, var match, match = false, Hit{match:1}, g(h.match), match(n)). Closes #5949 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(specs): rewrite nine match blocks as t27 switch; ledger git/diff 31 captured Rust match blocks in 7 specs. 9 are rewritten as switch (or if) in auth/config, ar/restraint, ar/coa_planning, ar/composition, igla/training/roadmap and igla/evaluation/multi_lang_harness, and their seals re-saved. ar/composition execute_ar_component keeps its match: its arms assign a tuple, which t27 has no statement for; that spec is already red upstream. git/diff matches Ok/Err on Command::new("git") in 21 blocks and is ledgered at typecheck (issue 5949, expires 2026-11-30). max_entries 112 -> 113 is a hand edit, recorded in _why_entries_rose: no entry could be retired without inventing a value. Closes #5949 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(now): t27c refuses a Rust match block the parser kept as text (Closes #5949) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(gen): signed and float % lowers to @Rem (Closes #5973) Zig refuses infix `%` unless both operands are unsigned ("signed integers and floats must use @Rem or @mod"), so `return a % b;` with `a: i32` made the generated file fail to compile. The W593 `@divTrunc` arm for `/` already had the right test; `%` now uses it and emits `@rem(a, b)`, the truncated remainder (rem(-7, 2) == -1, as in C, Rust and t27b). Unsigned `%` stays infix. `/` needed no change. 8 of 1184 specs change gen output and 0 change gen-c; none has a new zig error, and compiler/optimizer.t27 now passes 14/14. 12 seals resealed. Found by the t27b differential test. Part of #5905. Closes #5973 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(compiler): type integral Rust repeat counts (Fixes #5986) * feat(t27b): run invariant blocks like tests, report them apart An `invariant` block lowers exactly like a `test` (a parameterless body, the test binding rule) and runs through the same trap machinery. t27c's Zig backend emits it as `comptime { ... }`, so a broken invariant is a compile error there; t27b runs it at test time and prints INVARIANT PASS / INVARIANT FAIL, plus an `invariants N held, M broken, K not checked` line. An invariant whose body the front-end discarded (a `forall`, an unparseable clause) is NOT CHECKED, never "held"; a partially parsed one is rejected, as a partial test already was. Clause-form blocks carry no line on any node, so their header line is looked up in the source text. Corpus: 48 supported (47 pass, 1 spec failure), was 37; 0 mismatches. Differential: 0 mismatches, 2500 programs per mode. Refs #5977 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(t27b): memory primitives -- frame slots, loads/stores, rodata, bounds The IR primitives that structs, arrays, slices and strings lower onto, end to end through the interpreter, the JIT and the Mach-O object: - Ty::Ptr; ExprKind::Slot / Data / Load{addr,off} / Offset{base,idx, scale} / Bounds{idx,len,site}; Stmt::Store / Copy; Func::slots; Program::data; TrapKind::Bounds = 16 (numbered high, so the scalar lane can add kinds below it). - eval (the oracle): a byte-addressed model with per-call slots, a gap between objects and per-byte written flags. It faults on a read of unwritten bytes, an access outside a live slot or blob, a store into data, and a bool load of anything but 0/1. A fault is a lowering defect and never agrees with the JIT. - codegen: - an aggregate area below x29, up to 16 KiB; - x29-relative ldur/stur fast paths; - uimm, unscaled and register load/store forms, with signed narrow loads; - ADRP + ADD for data; - shifted add or madd for Offset; - cmp + b.hs to a Bounds trap stub; - Copy unrolled 8/4/2/1, or as a post-indexed loop past 64 bytes. - JIT: data blobs appended to the image and patched in place. - Mach-O: a __const section with PAGE21/PAGEOFF12 relocations against local l_t27b_data.N symbols. - a64: sized load/store, adr/adrp encoders and disassembly, with 32 new clang-verified encodings and an otool comparison. Tests: - The random differential generator covers every primitive: slots up to 9000 bytes, data blobs, loads and stores of every scalar type at constant, masked and bounds-checked indices, short and looped copies, and pointer parameters into the caller's frame (the hidden-pointer ABI). 0 mismatches over 2500 programs per overflow mode. - tests/macho.rs links an object with clang and runs it against the interpreter. - tests/memory.rs pins the interpreter's faults. Corpus unchanged (47 pass): nothing lowers to these yet. Refs #5977 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(compiler): preserve immutable constant names in Rust (Fixes #5994) * gate(coq): re-bless the three trios-coq files #4754 built, and run on trios-coq/** (#6006) Coq kernel has been red on master on every run since 2026-09-24 (six, 28fd522 through 558dab4), at "No new Coq file falls outside every build": NullorReversible.v, SparsityMask.v and SpeculativeExit.v joined trios-coq/_CoqProject in #4754 without a re-bless. - tools/coq_outside_build_baseline.txt: `check_coq_in_build.py --bless`, 18 -> 15. The three compile locally (Rocq 9.1.1, `coqc -Q . TriosCoq`, exit 0 each, 11 Qed each, `Admitted` only inside comments). - coq-kernel.yml: `trios-coq/**` in both path lists. The checker reads every _CoqProject, but the workflow ran only for coq/ and proofs/, so #4754 started no run and #4755 inherited the red. Negative control: putting SparsityMask.v back in the baseline exits 1 with "1 file(s) joined a build". `--self-check` 6/6, `tri census pin --gate` PASS, check_pr_branch_filters.py exit 0. Closes #6005 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(c): initialize array-repeat wrappers by value (Fixes #6013) * tri: stranded-work finder and cron tick resume card (#5787) * tri: stranded-work finder and cron tick resume card tri stranded lists remote branches with commits off the default branch, tips older than --hours, and no open same-repo PR. It skips dependabot/*, patch-equivalent branches (git cherry) and squash-merged ones (merge-tree result equals the default's tree), reports merge clean/conflict and whether the branch ever had a PR, and runs merge-tree against a throwaway object directory so the clone is never written. With no PR state nothing is called stranded: the rows are "undecided" and the exit is 2. tri tick is the resume card for a cron loop: claim and its age, the last ledger section, each worktree's branch, dirty count, ahead/behind vs its base and last commit age, and anomalies with a one-line fix each. git runs with GIT_OPTIONAL_LOCKS=0; nothing is written. Both self-tests run in loop-tools-gate, each with a negative control, and each was seen to fail on a mutated copy of its tool. Refs #5786 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * tri census: re-bless fetches 4->5 and shell 283->285 for tri stranded/tick Two pinned numbers moved, both because of this PR: - fetches: SURFACE bounded reads in scripts/tri_loop/*.py 4 -> 5. The new read is `gh pr list --limit PR_LIMIT` in scripts/tri_loop/stranded.py. It already refused to answer on a full page, but said so as "a lower bound" in lowercase, which the census (says_lower_bound, the Rust side's wording) does not read. The message now says LOWER BOUND, so the split is 5 guarded and 0 that do not, rather than 4 and 1. - shell: run steps 283 -> 285 (runner bash 262 -> 264). The rise comes from loop-tools-gate.yml, which gains the two tri_loop tests (test_stranded_work_is_found_without_writing.py and test_a_tick_resumes_from_what_it_reads.py). quiet was re-recorded byte-identical. Refs #5786 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * tri pr-state: a loop's pull requests with tri pr ready's verdict, not "open" (#5824) * tri pr-state: a loop's pull requests with tri pr ready's verdict, not "open" A cron loop reported t27#5787 as "open" for six ticks while cli-tri was red. tri pr-state reads every PR a tick-state.json names: gh pr view, then tri pr ready while it is open, quoting the verdict and its exit code. SETTLED only when every verdict is "safe to merge"; an open PR with a running check exits 1. Anomalies: head-moved, not-open, unresolved (an alias is never guessed), unreadable, no-verdict. Read-only; --answers replaces gh and tri for the offline test. tri tick's pick_id/usage take the command name so tri pr-state errors under its own; its card points at tri pr-state for "still open?". First real run, cron 8782e5f8, 9 PRs: all safe, exit 0, and one head-moved on 999#3554 -- the loop's own tick-8 push, never recorded. Census: shell run: steps 285 -> 286 and runner bash 264 -> 265, the one new loop-tools-gate step for scripts/ci/test_an_open_pr_is_not_a_green_one.py. Fetches unchanged: pr_state.py has no --limit read. Closes #5823 Refs #5786 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * tri pr-state: the printed card says safe is not proof any check ran The billing-blocked caveat lived only in the docstring, while #5824's body said the card's NOT ESTABLISHED block carried it. Now the printed footer says it, and the test asserts it on the settled (healthy) card; seen red with the word removed. No census moved. Closes #5823 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(tri tick): a released claim is not held, and the dead-tick fix names its files (#5864) * fix(tri tick): a released claim is not held, and the dead-tick fix names its files tri tick never read claim.released. A tick that had finished read as dead 45 minutes later. On tick 18 of cron 8782e5f8, its fix line was `git add -A && git commit` on PR #5839's worktree, whose only dirty files were cron_tracking/ and .claude/launch.json, both untracked on purpose. - A release that parses and is not older than `since` ends the hold. A leftover or unparseable release does not; the unparseable one is reported as claim-unparseable on claim.released. - Dirty excludes the state directory and a worktree's keep_untracked paths, counted per file (--untracked-files=all). Every other untracked file still counts. - The fix is `git add -- <each file, quoted>`, never `add -A`. - Test 20 -> 36 checks; 12 mutations, each red. Closes #5863 Refs #5786 #5823 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(tri tick): dirty work nobody holds is an anomaly, released claim or not (#5896) A released claim made every dirty worktree silent. Tick 21 of cron 8782e5f8 delegated two tasks to background agents and released; the session ended, both agents died uncommitted, and tick 22's card said ANOMALIES: 0. New anomaly orphaned-dirty: no claim held, a worktree dirty, its newest dirty file older than --stale-minutes, and no process with its cwd inside it (lsof -d cwd; "NOT RUN" without lsof). Fresh writes or a live process print a DELEGATE line instead. The detail quotes held_by; the fix names the files, never add -A, and clears held_by. Test: orphaned, no claim, and two negative controls (fresh write, a live sleep standing in the tree). 7 mutations, all red. Closes #5895 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * feat(tri pr-state): --why, and each name under the heading tri printed it under (#5868) tri pr-state could not pass --why to tri pr ready, so a failure red elsewhere under the same check name read as pre-existing in the loop's report. Its parser also put every " - name" after the VERDICT into one only_here list: CANNOT TELL's no-baseline names and --why's new-reason names read as "only here", and the NOT compared list above the VERDICT was never read. - --why is passed through; a tri without the flag (clap usage error, exit 2 = WAIT's code) is a no-verdict that says so, never WAIT - only_here / no_baseline / new_reason / not_compared are kept apart; an unknown heading, an unknown verdict, or a name under no heading is kept as `other` with its heading and printed - with --why a REASONS line counts over the rows that got a verdict and names the rest as not compared (a live run with an old tri read "REASONS: 0" over 18 no-verdicts before this) - test 27 -> 51 checks, #5452's real answer as a fixture, a fake gh and tri on PATH proving the flag reaches argv; 19 mutations, each red Closes #5867 Refs #5823 #5852 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(zig): quote declared primitive value bindings (Fixes #6040) * feat(t27b): structs, field access, pointers and struct results Lower struct declarations, struct literals, field reads and writes, `&x` / `p.*` / `*T`, struct parameters and struct results to the memory primitives of the previous change. Structs always live in memory: - A struct local or temporary lives in a frame slot. Its literal is built in place when the destination is fresh and its address is pure; otherwise it is built in a temporary and copied. - A struct parameter is the caller's memory, passed by address and never written. - A struct result is built through a hidden last pointer parameter. Such functions are listed in `Program.internal_abi`, and `t27b build` does not export them (it prints a note). - A module struct constant goes to read-only data. A scalar field of it folds to a constant (`const OX: i32 = ORIGIN.x`). - Layout follows the C rules and is computed on first use. A pointee is only named, so `next: *const Node` works. A struct that contains itself by value is rejected, and so is a generic struct. - An address-taken scalar local or parameter lives in a slot. - Compound assignment through an impure address evaluates the address once. Every rejection is precise: a missing, unknown or duplicate field, a positional initializer, an anonymous `.{}` with no result type, a write through a constant, a struct or pointer operand, a non-value `X.y`, and a struct that contains itself. IR: new `ExprKind::Seq { stmts, value }` (stores and copies, then a value), so a struct temporary is built exactly where its argument is evaluated. The interpreter and the code generator both implement it, and the random differential generator now emits it. Corpus (specs/, --jobs 6): 47 -> 70 files supported with every test passing, 0 JIT/interpreter mismatches. - One newly reached file fails one test (fpga/verification/build_verify, module count 33 vs 31). t27c gen + zig test fails the same assertion, so the reference fails here too. - Two files time out in the t27c parse phase alone at a load average near 780. Refs #5977 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ci): install Zig for native controls; shell run steps 264 to 265 (Refs #6040) * fix(ci): install corpus Icarus prerequisite; shell 268 to 269 (Refs #6040) * fix(verilog): preserve conditional return and assertion context; Fixes #6043 * fix(rust): preserve function and conditional tail values; Fixes #6062 * feat(core): t27core -- self-hosted compiler core MVP, fixpoint with gen-c (#6041) A t27 compiler written in t27 (specs/compiler/core/t27core.t27): lexer, parser, checks and C emitter for the subset `t27c gen-c` lowers without loss. Built by gen-c and run on its own source, it writes gen-c's output byte for byte. bootstrap/tests/core_selfhost.rs gates the fixpoint, the core's own test blocks, 6 fixtures, 16 refusals with their codes, and a differential over specs/: 58 specs accepted, all byte-identical to gen-c (floor 50). Shapes gen-c lowers with loss are refused, not copied: `- -x` as `--x`, declarations after `endmodule`, top-level consts truncated after their first token, and the earlier ones recorded on the epic. Closes #5981 Refs #5980 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * feat(agents): Gamma holds a /self-host skill for epic #5980 (#6054) The rules of the t27-in-t27 compiler had no home a bee loads: byte identity with gen-c, refusing the shapes gen-c lowers with loss, probing gen-c first, and the ;-alone-line trap. They now live in .claude/skills/self-host/SKILL.md. Its card is specs/skills/t27-self-host.t27, and agent C holds it through its prompt's Skills section and the alphabet's domain-lead table. Closes #6053 Part of #5980 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(t27c): refuse a colon inside a field type and @as to a slice type; fix the six silent specs (#5971) * fix(t27c): typecheck refuses a colon inside a field type and @as to a slice type Six specs parsed, typechecked and generated output their backend cannot compile (`tri misread` silent pairs). Two defects: - A struct field whose type, with `::` paths removed, still holds a `:`. Either the field has no `,` after it (missing, or inside a trailing `#` comment, which runs to the end of the line) and swallowed the fields after it, or it is a map type `[K: V]`, which t27 does not have. - `@as(T, x)` with a slice or array type: in argument position the parser reads `[]u8` as an array literal, and every backend loses the type. Both are refused in typecheck, naming the line. The parser now records a struct field's line; no backend reads it, so generated output does not move (seal currency: 0 stale generated-code hashes). Closes #5968 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(specs): put the comma before the comment, drop the map type and slice casts - ar/coa_planning: the missing `,` after `verification_status`. - tri/agent/governance_agent, tri/utils/help: `field : T # note,` is now `field : T, // note`; the `#` comment had taken the `,` with it. - git/schema: `env: [str: str]` is now `env: []EnvVar`; the one constructor, git/operations' options_construction test, builds EnvVar. - port/tools/wp18: `_ssot` writes one `// CATALOG: id=<id>` line per id, as the Python original does, instead of an `@as([]u8, ..)` cast. - tri/pipeline/builder: `@as([]T, &.{})` in a test is now a length check. Re-sealed with `t27c seal --save`, including git/status and git/diff, whose generated code inherits Options. Closes #5968 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(now): t27c refuses a colon inside a field type and a slice cast (Closes #5968) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(t27c): var tuple destructure silences never-mutated Zig locals (Closes #5886) (#5902) `var (s, d) = f();` lowered to `var s, var d = f();` with no silencer, so Zig rejected every element the function never reassigns ("local variable is never mutated") and `t27c test-report` printed BLOCKED for d_slow_blink.t27 (Refs #5682). The StmtLocal tuple-destructure branch now emits, for a `var` destructure, the same `_ = &name;` per named element that the single-name `var` path already emits, and records the name in `discarded_by_ref` so W730 drops a later `_ = name;` (a pointless discard in Zig). A bare `_` element gets no silencer; `let (s, d)` is unchanged. Test: test_var_destructure_never_reassigned_zig. Without the fix it fails by assertion ("`_ = &s;` silencer missing"); with it, it passes, and the whole t27c bin unit suite passes (1759, 0 failed, 2 ignored). FROZEN_HASH resealed to the new compiler.rs digest. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(seals): re-seal wp18 with this branch's compiler after the master merge The master merge (2ec2d00) took master's wp18 seal, but this branch keeps the #5971 spec (spec_hash c2c89bb3..., the CATALOG port of `_ssot`). Lab run of 2ec2d00: seal-currency and seal-coverage red on this one file only. Re-sealed on the Railway t27c lab: t27c built from 2ec2d00, zig 0.16.0 on PATH, `t27c seal --save` + `tri seals sync-twins` (517 twins consistent, 0 written). gen_hash_zig is 7fd0ed8c, the same output #5971 sealed; the zig test result is unchanged (expected `;` at the `sha256::Hasher` line, 86:24 in the emitted source). Refs #6092 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(gen-zig): keep `_ = call(..);` instead of deleting it (Closes #5984) (#6003) * fix(gen-zig): keep `_ = call(..);` instead of deleting it (Closes #5984) Dead-store elimination read `_` as a variable nobody reads and deleted every top-level `_ = call(args);` in a fn body, call and all. The unused-parameter pass then added `_ = p;` for a parameter that only the deleted call used, which Zig rejects as a pointless discard once the call is back. OptConfig gains keep_call_discards. Compiler::compile (gen-zig) sets it, so a discard whose right-hand side holds a call survives. It defaults to false, so the Verilog path is byte-identical (0 of 37 specs with a discarded call differ). Measured over 1297 tracked .t27 files: gen-zig output changes for 5 specs, 9 calls come back, 2 spurious `_ = param;` lines and 1 `_ = json_str;` line go. Their 9 seals are re-sealed. FROZEN_HASH re-pinned. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * seal: re-seal wp18 selftest after the gen-zig discard-call fix Spec unchanged; only gen_hash_zig moves (the emitter fix in this PR). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * seal: re-seal wp18 selftest with the merged compiler (Closes #5949) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Dmitrii Fedorov <dmitrii.f@t27.ai> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Dmitrii Fedorov <dmitrii.f@t27.ai>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #5981
Refs #5980
First step of the epic: a t27 compiler written in t27, and the gate that holds it to the Rust one.
What lands
specs/compiler/core/t27core.t27-- lexer, parser, checks and C emitter in one module. It accepts the subsett27c gen-clowers without loss and writes gen-c's C for it byte for byte. It is written in that same subset, so it compiles itself. There are no string literals in the subset, so fixed output text is packed into 64-bit words, each with the text as a;comment above it.bootstrap/tests/core_selfhost.rs-- needscc, and skips with a message when there is none. It checks:testblocks pass under-DT27_TEST_MAIN;core(t27core.t27) == gen-c(t27core.t27), byte for byte;#define/ arrays / else-if chains / while / casts / every operator / tests and the runner / comments);specs/: every spec the core accepts must match gen-c. 58 are accepted today, all identical, and the floor is 50, so the gate cannot pass by refusing everything.Refuse, don't copy
Where gen-c lowers a shape with loss, the core refuses it instead of reproducing the defect. The three new ones are commented on #5980:
- -x--xendmoduleconstThe earlier ones are already on the epic:
**drops its value,assertis used without<assert.h>, a test-level assignment shadows a global, a char const vanishes.Evidence
cargo test --release -p t27c --test core_selfhost: 1 passed, in 28s.,instead of,in one text function makes the harness fail.tri mods orphan --gate,tri types ratchet,tri types classifiedandt27c checkare clean.check_ring_spec_driftexits 1, but on drift in existing ring crates; its report never names a file this PR adds.Not merged, and not for auto-merge.
🤖 Generated with Claude Code