Skip to content

fix(verilog): break and continue were no-ops; lower them to guard flags - #3014

Merged
gHashTag merged 4 commits into
masterfrom
w43-break-continue
Sep 3, 2026
Merged

fix(verilog): break and continue were no-ops; lower them to guard flags#3014
gHashTag merged 4 commits into
masterfrom
w43-break-continue

Conversation

@gHashTag

@gHashTag gHashTag commented Sep 3, 2026

Copy link
Copy Markdown
Owner

break was disable fork;, and the corpus has no fork

The Verilog emitter wrote disable fork; for break and /* continue */; for continue.

disable fork kills processes spawned by a fork in the current scope, and the token fork occurs nowhere in the generated corpus except inside that very line. All of them were no-ops: the loop ran to completion, and later iterations overwrote whatever the jump was meant to preserve.

Both lowerings parse. iverilog -g2012 accepts them, yosys accepts them, the seal hashes are stable over them. Every instrument this repository owns asked whether the output parses, so this was invisible for as long as the emitter has existed.

Population

Over every .t27 in the tree, not just specs/:

sites files
break -> disable fork; 16 7
continue -> /* continue */; 1 1
total 17 8

Two of them are in compiler/cli/gen.t27, outside specs/. A census scoped to specs/ reports 15 in 7 -- which is the number I published, twice.

Measured

  • a probe of 8 declared tests, whose expected values are confirmed by the Zig backend (8/8) -- an oracle independent of the Verilog emitter. Under icarus-simulate: 1 of 8 before, 8 of 8 after. The one that passed before is the jump-free control
  • 581 of 650 specs generate Verilog, before and after
  • 7 of 581 generated files differ
  • yosys read_verilog -sv -DSIMULATION + hierarchy on those 7: +2 (specs/ar/asp_solver, specs/compiler/lexer go FAIL -> PASS), 0 lost. The other 574 are byte-identical, so their verdicts cannot move
  • iverilog: unchanged on all 7 (all seven already fail for other reasons), so the corpus acceptance columns do not move
  • the "no guard flag in this scope" refusal fires 0 times in the corpus
  • mutation 6/6. The sixth binds break to the OUTERMOST enclosing loop; it survived five tests and a sixth that counted declarations, because the count is still one flag per loop. Only asserting that the flag a break sets is the flag its loop declared kills it

The lowering

One flag per loop that needs one, allocated only when the body actually contains the jump -- which is why the delta is 7 files and not 581.

  • reg __t27_brk_N persists and joins the loop condition
  • reg __t27_cnt_N is cleared at the top of each iteration and gates only the iteration tail
  • the scan for a jump stops at a nested loop, so an inner break binds to the inner loop
  • disable was the other candidate and is not this: it cannot express continue without a block per iteration, and disable <named block> is a parse error under yosys in all three modes this repo uses

Scope

return inside a loop is the other half and is not here. __t27_ret already exists and is set correctly; what is missing is that no loop tests it. That repair moves loops containing no jump at all, so it gets its own measurement -- #2989.

Instrument

tri jumps census names every break/continue site in the generated Verilog and says what it lowered to.

  jumps lowered             17 site(s) in 8 file(s)     <- after
    ... sharing             14 guard flag(s)
  jumps left as a NO-OP      0 site(s) in 0 file(s)
  flags declared, never set  0
  jumps lowered              0 site(s) in 0 file(s)     <- before
  jumps left as a NO-OP     17 site(s) in 8 file(s)
      compiler/cli/gen.t27   2 x break
      specs/ar/asp_solver.t27   5 x break
      ...

It also asserts the pairing -- every flag a loop declares must be written somewhere -- because a count of declarations cannot see a jump bound to the wrong loop.

Skill

ci-gates 441-443: a marker is only yours if the base count is zero (I read 535 pre-existing // NOT LOWERED BY THIS BACKEND lines as mine); a two-part construct needs its halves asserted as a pair; and when only 7 files' bytes moved, the expensive ruler only needs those 7 -- provided you say that is what you did.

Closes #2988
Refs #2989

gHashTag and others added 2 commits September 3, 2026 16:47
The Verilog emitter wrote `disable fork;` for `break` and `/* continue */;`
for `continue`. `disable fork` kills processes spawned by a `fork` in the
current scope, and the token `fork` occurs nowhere in the generated corpus
except inside that very line -- so all of them were no-ops: the loop ran to
completion and later iterations overwrote whatever the jump was meant to
preserve.

Both lowerings parse. iverilog accepts them, yosys accepts them, the seal
hashes are stable over them. Every instrument in this repository asked
whether the output PARSES, so the defect was invisible for as long as the
emitter has existed.

Population, over every .t27 in the tree and not just specs/:
17 jump sites in 8 files -- 16 break, 1 continue. Two of them live in
compiler/cli/gen.t27, outside specs/, which is why a specs-scoped census
reports 15 in 7.

The lowering is a flag per loop that needs one, allocated only when the body
actually contains the jump: `reg __t27_brk_N` persists and joins the loop
CONDITION, `reg __t27_cnt_N` is cleared at the top of each iteration and
gates only the iteration tail. The scan for a jump stops at a nested loop,
so an inner break binds to the inner loop.

Measured:
* a probe of 8 declared tests, expected values confirmed by the Zig backend
  (8/8) -- an oracle independent of the Verilog emitter. Under
  icarus-simulate: 1 of 8 before, 8 of 8 after; the one that passed before
  is the jump-free control
* 581 of 650 specs generate Verilog, before and after
* 7 of 581 generated files differ
* yosys read_verilog -sv -DSIMULATION + hierarchy on those 7: +2
  (specs/ar/asp_solver, specs/compiler/lexer go FAIL -> PASS), 0 lost. The
  other 574 are byte-identical, so their verdicts cannot move
* iverilog: unchanged on all 7, so the corpus acceptance columns do not move
* mutation: 6 of 6 killed. The sixth binds break to the OUTERMOST enclosing
  loop and survived five tests plus a sixth that COUNTED declarations --
  only asserting that the flag a break sets is the flag its loop declared
  kills it

`return` inside a loop is the other half of the defect and is deliberately
not here: __t27_ret already exists and is set correctly, and what is missing
is that no loop tests it. That repair moves loops containing no jump at all,
so it gets its own measurement -- #2989.

Adds `tri jumps census`: names every break/continue site in the generated
Verilog and says what it lowered to -- a flag, a no-op, or a refusal -- and
asserts the pairing a count cannot see.

Closes #2988
Refs #2989

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
# Conflicts:
#	.claude/skills/ci-gates/SKILL.md
@gHashTag

gHashTag commented Sep 3, 2026

Copy link
Copy Markdown
Owner Author

Renumbered to ci-gates 439-441: #3011 landed 436-438 while this was open, and the merge conflicted on exactly that.

Worth recording as a footnote to 439 itself. My resolver did s.index('<<<<<<<') and found a literal <<<<<<< x at SKILL.md:8014 -- prose describing a conflict-marker gate, not a marker -- so the split ran from the wrong offset and left a real <<<<<<< HEAD line in the output. Same shape as the 535 // NOT LOWERED BY THIS BACKEND lines this section is about: the string I searched for was not mine. Resolved instead by rebuilding from the two authoritative sides (git show origin/master:... + my three sections), which is verified identical to what I wrote modulo the three numbers.

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

📓 NotebookLM Notebook linked to this PR

This notebook contains session context, decisions, and artifacts for this work.

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-09-03 09:55:32 UTC

Summary

Status Count
Total Open PRs 11
PRs with Failing Checks 10
PRs with All Checks Green 1
READY 0
FAILING 10
PENDING 0

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=2a95b1f73357 != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

…oved

`coverage` was red on the first CI round: 7 generated Verilog files change,
and this corpus twins its seals, so that is 19 `.trinity/seals/*.json`.

Re-sealing is a statement that the new output is the one we want, so the
acceptance reading first, on an idle machine, whole corpus, both binaries:

  generates Verilog        581  ->  581
  Zig AND Verilog accept   258  ->  258
  ALL FOUR accept          184  ->  184

Identical to the digit. That is expected and was predicted before the run:
`iverilog` was measured directly on each of the 7 changed files and its
verdict does not move on any of them, and the other 574 files are
byte-identical, so no acceptance column can move.

`tri seals drift` reads 0 after the fix; it read 19 before.

ci-gates 441 gains the part that was missing: I had the 7-file delta and
still shipped without a reseal, because I never converted it into the unit
the seal gate counts in.

Refs #2988

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-09-03 10:14:55 UTC

Summary

Status Count
Total Open PRs 12
PRs with Failing Checks 10
PRs with All Checks Green 2
READY 0
FAILING 10
PENDING 0

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=2a95b1f73357 != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

📓 NotebookLM Notebook linked to this PR

This notebook contains session context, decisions, and artifacts for this work.

# Conflicts:
#	.claude/skills/ci-gates/SKILL.md
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-09-03 10:30:23 UTC

Summary

Status Count
Total Open PRs 11
PRs with Failing Checks 10
PRs with All Checks Green 1
READY 0
FAILING 10
PENDING 0

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=2a95b1f73357 != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

📓 NotebookLM Notebook linked to this PR

This notebook contains session context, decisions, and artifacts for this work.

@gHashTag

gHashTag commented Sep 3, 2026

Copy link
Copy Markdown
Owner Author

Renumbered again: ci-gates 441-443. #3015 landed 439-440 while this was open, after #3011 had landed 436-438 — two collisions on one PR, both on the append-only section numbering, both discovered by the merge and not by anything cheaper.

Resolved the same way as the first: rebuilt from the two authoritative sides (git show origin/master:… + my three sections) and verified byte-identical to what I wrote modulo the three numbers, rather than patching the merge output. That matters here because the first attempt at this — patching the merge output — split on a literal <<<<<<< x in the prose at SKILL.md:8014, which is a section describing a conflict-marker gate.

The convention question this raises is a decision, not a fix, and it is on the dashboard: assign section numbers at merge rather than at writing, or accept the collisions. It is cheap for an append-only skill file and it will not be cheap for tri subcommand names, where two sessions can add different things and git merges both without a word.

@gHashTag
gHashTag merged commit e6f02e6 into master Sep 3, 2026
39 checks passed
@gHashTag
gHashTag deleted the w43-break-continue branch September 3, 2026 10:40
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.

gen-verilog: break lowers to disable fork; and continue to a comment -- 17 no-ops in 8 files, and no fork exists anywhere in the corpus

1 participant