Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/now/2026-09-08-my-fixture-spoke-only-one-dialect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# NOW -- My fixture spoke only one dialect (2026-09-08)

## My fixture spoke only one dialect (Refs #3450)

- The capacity gate went red on master within minutes of merging, and it was mine. The self-check asserted clang's exact wording, `use of undeclared identifier`; on the ubuntu runner `cc` is **gcc**, which says `'undefined_0' undeclared`. Same defect, other dialect, and my assertion read it as a broken compiler. The fix asserts the **identifier**, `undefined_0`, which every C compiler must name whatever its phrasing.
- The graceful degradation worked exactly as designed in the same run -- `zig`, `iverilog` and `yosys` were absent and were NAMED as skipped rather than failing -- so the failure was one wrong line, not the shape.
- **The vendors' uncapping flags are not interchangeable, and the failure mode is the worse one.** gcc rejects `-ferror-limit`; clang **accepts `-fmax-errors=0` and ignores it** -- 20 of 50 planted errors, and not one word about the flag. A flag accepted and ignored is worse than one refused. So the tool picks by vendor and the self-check verifies the EFFECT: the uncapped invocation must report all 50. A control that substitutes the ignored flag reddens it.
- Four controls all still hold: a planted truncation exits 1, a one-compiler PATH passes while naming the skips, an empty PATH exits 2, and now a flag that is accepted-and-ignored fails the self-check.
53 changes: 43 additions & 10 deletions tools/diagnostic_capacity.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,33 @@

# name -> (fixture builder, argv builder, diagnostic regex, expected count,
# note). `expected` is what was MEASURED, not what the manual claims.
# `None` means "report it, do not ratchet it": the default cap is a property of
# the vendor, not of this repository -- clang stops at 20, gcc does not stop at
# all -- so a runner switching compilers must not turn the gate red.
EXPECT = {
"clang (default)": 20,
"clang (-ferror-limit=0)": PLANTED,
"cc (default)": None,
"cc (uncapped)": PLANTED,
"rustc": PLANTED,
"zig": PLANTED,
"iverilog": PLANTED,
"yosys": 1,
}


def c_uncap_flag() -> str:
"""The flag that ACTUALLY removes the per-file cap, per vendor.

They are not interchangeable and the failure is silent in the worst
direction: gcc rejects `-ferror-limit`, while clang ACCEPTS
`-fmax-errors=0` and ignores it -- 20 errors of a planted 50, with no
diagnostic about the flag. A flag that is accepted and ignored is worse
than one that is refused, so the self-check below verifies the effect
rather than the spelling.
"""
v = run(["cc", "--version"]) or ""
return "-ferror-limit=0" if "clang" in v.lower() else "-fmax-errors=0"


def fixtures(d: str) -> dict:
c = os.path.join(d, "c50.c")
with open(c, "w") as fh:
Expand Down Expand Up @@ -106,9 +123,9 @@ def counts(d: str, f: dict) -> dict:
got = {}

t = run(["cc", "-std=c11", "-fsyntax-only", f["c"]])
got["clang (default)"] = None if t is None else len(re.findall(r"error:", t))
t = run(["cc", "-std=c11", "-ferror-limit=0", "-fsyntax-only", f["c"]])
got["clang (-ferror-limit=0)"] = None if t is None else len(re.findall(r"error:", t))
got["cc (default)"] = None if t is None else len(re.findall(r"error:", t))
t = run(["cc", "-std=c11", c_uncap_flag(), "-fsyntax-only", f["c"]])
got["cc (uncapped)"] = None if t is None else len(re.findall(r"error:", t))

t = run(["rustc", "--edition", "2021", "--crate-type", "lib", "--crate-name", "m",
"-A", "warnings", "--emit=metadata", "-o", os.path.join(out, "m.rmeta"), f["rs"]])
Expand Down Expand Up @@ -140,13 +157,17 @@ def self_check(d: str, f: dict) -> int:
ok = True
present = 0
checks = [
("clang", ["cc", "-std=c11", "-ferror-limit=0", "-fsyntax-only", f["c"]], r"use of undeclared identifier"),
# NOT the vendor's wording. This asserted clang's phrasing
# ("use of undeclared identifier") and went red on a gcc runner, where
# the same defect reads "'undefined_0' undeclared". The identifier is
# the part every C compiler must name.
("cc", ["cc", "-std=c11", c_uncap_flag(), "-fsyntax-only", f["c"]], r"undefined_0"),
("rustc", ["rustc", "--edition", "2021", "--crate-type", "lib", "--crate-name", "m",
"-A", "warnings", "--emit=metadata", "-o", os.path.join(d, "m.rmeta"), f["rs"]],
r"cannot find value"),
("zig", ["zig", "build-obj", f["zig"], "-femit-bin=" + os.path.join(d, "z.o")], r"use of undeclared identifier"),
("iverilog", ["iverilog", "-o", os.path.join(d, "v.vvp"), f["v"]], r"Unable to bind"),
("yosys", ["yosys", "-q", "-p", f"read_verilog {f['y']}; hierarchy -check"], r"is not part of the design"),
r"undefined_0"),
("zig", ["zig", "build-obj", f["zig"], "-femit-bin=" + os.path.join(d, "z.o")], r"undefined_0"),
("iverilog", ["iverilog", "-o", os.path.join(d, "v.vvp"), f["v"]], r"undefined_0"),
("yosys", ["yosys", "-q", "-p", f"read_verilog {f['y']}; hierarchy -check"], r"nosuchmod_0"),
]
for name, argv, want in checks:
t = run(argv)
Expand All @@ -160,6 +181,14 @@ def self_check(d: str, f: dict) -> int:
hit = re.search(want, t) is not None
print(f" {name:10} expects /{want}/ -> {'PASS' if hit else 'FAIL'}")
ok &= hit
# The flag must have an EFFECT, not merely be accepted. clang takes
# `-fmax-errors=0` and ignores it; nothing in its output says so.
t = run(["cc", "-std=c11", c_uncap_flag(), "-fsyntax-only", f["c"]])
if t is not None:
n = len(re.findall(r"error:", t))
good = n >= PLANTED
print(f" {'cc uncap':10} {c_uncap_flag()} reports {n} of {PLANTED} -> {'PASS' if good else 'FAIL'}")
ok &= good
if present == 0:
# A check that could not run has not passed.
print(" no instrument on PATH. Exit 2 = COULD NOT RUN.", file=sys.stderr)
Expand All @@ -185,6 +214,10 @@ def main() -> int:
absent.append(name)
continue
checked += 1
if want is None:
cap = "TRUNCATES at %d" % n if n < PLANTED else "no default cap"
print(f" {name:24} {n:4} {cap} (reported, not ratcheted)")
continue
verdict = "complete" if n >= PLANTED else ("ABORTS on the first" if n <= 1 else "TRUNCATES")
flag = "" if n == want else " <-- CHANGED, recorded " + str(want)
print(f" {name:24} {n:4} {verdict}{flag}")
Expand Down
Loading