Skip to content

gen-verilog: a GF16 value is sixteen bits, not thirty-two - #3444

Merged
gHashTag merged 1 commit into
masterfrom
backend/parity-table
Sep 8, 2026
Merged

gen-verilog: a GF16 value is sixteen bits, not thirty-two#3444
gHashTag merged 1 commit into
masterfrom
backend/parity-table

Conversation

@gHashTag

@gHashTag gHashTag commented Sep 8, 2026

Copy link
Copy Markdown
Owner

Closes #3442 · Refs #3443

u16          -> input [15:0] x;
GF16         -> input [31:0] x;
NoSuchTypeXY -> input [31:0] x;

The third line is the finding. 32 was not a decision about GF16 — it was the
unknown-type default, and this project's flagship type was indistinguishable
from one the backend has never heard of.

The same file already said 16

HwType::GF16 => 16,
assert_eq!(HwType::GF16.hw_width(), 16);
assert_eq!(HwType::GF16.verilog_range(), "[15:0]");

Both assertions pass today. A function parameter reaches a different reader,
type_to_width, which had no arm and fell to _ => 32. Two readers of one
type, one of them green — and 2598 tests passed while the width was wrong.

The neighbouring arm carries the same story in a comment: f64 "fell through
to the 32-bit default and silently narrowed to half its width. Named explicitly
so the width is a decision rather than a fallthrough."

Three spellings, three sizes of one bug

form was now
GF16 [31:0] [15:0]
gf16::GF16 [31:0] [15:0] — one type had two widths depending on how a spec spelled it
[4]GF16 [31:0] [63:0]eight bits per element, a quarter of the data

Measured — 583 generated Verilog files, old binary against new

change files sites
bare GF16 narrowed 32 → 16 3 119
qualified gf16::GF16 narrowed 8 278
SearchResult stops being UNSUPPORTED_ICARUS, packs to 48 bits 1 1

Every added line contains [15:0] and every removed line [31:0], with one
matching pair of exceptions that is the same fact: a tuple return of three GF16
went [95:0][47:0].

Widening is_primitive_scalar_type was measured before being taken — the
comment on its neighbour records that widening a predicate here was once traced
to the corpus' largest defect class. Blast radius: one file, six lines, and the
change is the struct above.

The load-bearing test is the negative one

A "fix" that widened the default to 16 passes every positive assertion in the
file. Only an_unknown_type_still_takes_the_thirty_two_bit_default kills it.

mutant dead tests
GF16 width arm removed 3
qualifier stripping removed 1
GF16 not a primitive scalar 1
the default widened to 16 1

Full suite: 2598 passed, 0 failed — the same 2598 that passed before.

How it was found

tools/backend_parity_table.py, added here. It prints what each of the four
backends emits for one declaration form, side by side, because five previous
repairs shared this shape
(.len, the out-parameter, *T, the array size in
C, the Copy derive) and every one was found by accident.

It is a reader, not a gate — a row whose columns disagree is a question. Its
first run refuted one of its own candidates: []const u8 lowering to Rust's
&'static str looks like a type change and is a deliberate, load-bearing
choice, since the HashMap key lowering asks that mapping for its key type.

Two further rows are filed as latent, population zero (#3443): a nested
array parameter emits [3]u8* x into a C header, and tri has no C spelling
at all.

Seals

The 11 files whose Verilog moved leave 21 stale seals, resealed in this same
commit
rather than the next one. That cycle is #3440 — I merged past a red
Spec Guards twice before noticing it.

Closes #3442
Refs #3443

    u16          -> input [15:0] x;
    GF16         -> input [31:0] x;
    NoSuchTypeXY -> input [31:0] x;

The third line is the finding. 32 was not a decision about GF16; it was the
unknown-type default, and this project's flagship type was indistinguishable
from one the backend has never heard of.

The same file already knew: `HwType::GF16.hw_width()` returns 16 and
`HwType::GF16.verilog_range()` returns "[15:0]", both with passing tests. A
function parameter reaches a different reader, `type_to_width`, which had no
arm and fell to `_ => 32`. Two readers of one type, one of them green. The
neighbouring arm carries the same story in a comment -- f64 "fell through to
the 32-bit default and silently narrowed to half its width" -- so this is that
defect again, in the other direction.

Three spellings, three sizes of one bug:

    GF16          32 -> 16
    gf16::GF16    32 -> 16   one type, two widths, depending on how a spec spelled it
    [4]GF16       32 -> 64   EIGHT bits per element, a quarter of the data

Measured over 583 generated Verilog files, old binary against new: 119 sites
in 3 files for the bare form, 278 in 8 files for the qualified one, and one
struct (`SearchResult`) that stops being "UNSUPPORTED_ICARUS" and packs to 48
bits. Every added line contains [15:0] and every removed one [31:0], with a
single matching pair of exceptions that is the same fact: a tuple return of
three GF16 went [95:0] -> [47:0].

The widening of `is_primitive_scalar_type` was measured before being taken
rather than reasoned about -- the comment on its neighbour records that
widening a predicate here was once traced to the corpus' largest defect class.
Blast radius: one file, six lines, and the change is the struct above.

The load-bearing test is the negative one. A "fix" that widened the default to
16 passes every positive assertion in the file; only
`an_unknown_type_still_takes_the_thirty_two_bit_default` kills that mutant.
Four mutants, four dead. Full suite 2598 passed, 0 failed -- the same 2598
that passed while the width was wrong.

Found by tools/backend_parity_table.py, added here: it prints what each of the
four backends emits for one declaration form, side by side, because five
previous repairs shared this shape and every one was found by accident. It is
a reader, not a gate -- a row whose columns disagree is a question. Its first
run also refuted one of its own candidates: `[]const u8` lowering to Rust's
`&'static str` looks like a type change and is a deliberate, load-bearing
choice, since the HashMap key lowering asks that mapping for its key type.

Two further rows are filed as latent with a corpus population of ZERO in
#3443: a nested array parameter emits `[3]u8* x` into a C header, and `tri`
has no C spelling at all.

The 11 files whose Verilog moved leave 21 stale seals, and they are resealed in
this same commit rather than in the next one. That cycle -- every backend
change invalidating seals with nothing in the merge path refreshing them -- is
#3440, and I merged past a red `Spec Guards` twice before noticing it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@gHashTag
gHashTag enabled auto-merge (squash) September 8, 2026 01:04
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-09-08 01:04:21 UTC

Summary

Status Count
Total Open PRs 16
PRs with Failing Checks 13
PRs with All Checks Green 3
READY 2
FAILING 13
PENDING 0
NO CHECKS YET 0

These columns do not partition: 2 + 13 + 0 + 0 = 15, and there are 16 open PRs. A PR is being counted twice or not at all.

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=fed7c3957419 != 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 8, 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 merged commit e43eea3 into master Sep 8, 2026
28 of 32 checks passed
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: a GF16 parameter is 32 bits, and the same file already says 16

1 participant