Skip to content

test(parity): gate complete-corpus strict capability and engine output - #681

Merged
nnunley merged 5 commits into
nooga:mainfrom
nnunley:backend-parity-gate
Aug 13, 2026
Merged

test(parity): gate complete-corpus strict capability and engine output#681
nnunley merged 5 commits into
nooga:mainfrom
nnunley:backend-parity-gate

Conversation

@nnunley

@nnunley nnunley commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Backend miscompiles can pass human review when a differential run is optional, and a successful gogen_ir run does not by itself show that strict IR lowering succeeded. This PR turns engine-output parity and strict-capability coverage into a complete-corpus, measured, committed contract.

What changed

  • make engine-parity-gate discovers every test/gold-aot/*.lg fixture instead of maintaining hand-curated tiers.
  • Each fixture is measured under four runs: bytecode control, gogen_ir control, bytecode strict, and gogen_ir strict.
  • Classification is derived from those results:
    • full strict parity: both strict runs succeed and match their controls and each other;
    • partial strict capability: at least one strict run reaches the owned IR fallback seam;
    • strict output divergence: a successful strict run differs from its control or from the other engine;
    • fatal: setup, control, or unrelated strict execution fails.
  • Partial and divergence are independent facts, so a fixture can appear in both.
  • Strict rejection is attributed by the harness, not by matching process output: the strict wrapper binds its own fallback log and exits with a dedicated code only when that log records a real IR fallback. A runtime error that merely contains ir-compile-strict: stays fatal.
  • Control and strict wrappers are symmetric — same prerequisites, same sequential top-level evaluation — so a difference is semantic, never setup noise.

Reporting: what is and isn't working

The gate now prints a coverage report and writes it to LETGO_PARITY_REPORT when set. Non-full fixtures are measured a second time with strict off, so the report names the exact defn and the raw lowering reason behind every gap, plus a bucket census of distinct reasons and their sites. Full-parity fixtures need no extra run: strict success already proves zero fallback.

Committed case ledger

test/parity-ledger.txt records one line per case, so a commit diff shows exactly what changed:

  • the gate fails on ADDED, REMOVED, or STATUS-CHANGED cases;
  • a removed case must be explained by a TOMBSTONE entry, and an UNEXPLAINED tombstone fails;
  • a tombstoned fixture that reappears is ADDED, so the tombstone must be removed deliberately;
  • LETGO_PARITY_LEDGER_CLEANUP=1 clears tombstones inherited from an earlier revision.

Two shrink-only baselines still encode direction (partial capability, unattributed divergence). The ledger, both baselines, and any rederive are written as one staged, validated, rollback-safe set.

Measured result

  • 15 full strict parity
  • 1 partial strict capability — pipeline_dump_ir.lg, defn type-display: ir/lower: junk-below mismatch for block 15 (0 vs 2); unsupported shape
  • 0 strict output divergences
  • 0 fatal results
  • 16 total cases, 0 tombstones

That single gap is one instance of the largest structural class on the bytecode lowering path, so the ratchet has a real target rather than an aspiration.

Scope

This proves strict capability plus normalized full-output agreement across the bytecode and gogen_ir engines. It does not claim that fixture source executed through generated Go or a native entry frame. Native-entry proof using lg-compile --entry-frame and the scoped Go AST oracle is the immediate follow-up.

Validation

  • Complete-corpus rederive is byte-identical to the committed ledger and baselines
  • Tombstone lifecycle exercised end to end: removal fails, explanation passes, reappearance fails, prior-revision cleanup drops, restore returns byte-identical
  • Ambient GOFLAGS=-short falsifier
  • Classification, provenance, overlap, corpus-completeness, ledger-parser, and three-file rollback regressions
  • make gogen-diff
  • Full Go suite: 1,724 tests across 72 packages
  • Repository pre-push hooks
  • Independent engineering re-review approved this revision

ap
Related: #660, #678

This branch still contains the identical hook-environment scrub from #680; it resolves as overlap whichever lands first. The earlier #676 overlap is no longer part of this PR diff.

@mparrett mparrett left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for putting this together. The strict-mode audit and backend-parity direction are valuable + the careful evidence and the prerequisite hook fixes included here are appreciated. Ran both new targets locally: the happy-path gate passes all 10 Tier-1 fixtures, and the audit reproduces the reported 10/6/0 classification.

I found three issues to tighten up before merging, mainly so the gate provides the enforcement and strict-AOT guarantees it promises. I’ve left the details inline.

Comment thread Makefile Outdated
Comment thread test/e2e/parity_gate_phase1_test.go Outdated
Comment thread test/e2e/parity_gate_phase1_test.go Outdated
Implements the three-tier classification model for AOT-testable fixtures:
- Tier 1: both backends pass *ir-compile-strict* and agree (counts toward parity)
- Tier 2: bytecode fails strict (would trampoline; excluded from coverage)
- Tier 3: divergent (goes to per-backend allowlist as known miscompile)

Audit Results (test/gold-aot/ fixtures):
- 10 Tier-1 fixtures: arith, closure, destructure, quot, seq, setbang,
  setvar_load_stability, setvar_load_straightline, tryfinally, var
- 6 Tier-2 fixtures: binding, closure_capture, ir_pipeline, math_float,
  pipeline_dump_ir, typed_cross_fn (excluded from parity coverage)
- 0 Tier-3 fixtures (no current divergences)

Implementation:
- TestStrictModeAudit: classifies all 16 fixtures under strict mode
- TestParityGatePhase1: runs Tier-1 fixtures under both backends with
  per-backend allowlists (test/parity-xfail-{bytecode,gogen}.txt)
- Shrink-only ratchet: NEW divergence fails regression test; allowlisted
  fixture that now agrees fails as stale (improvement must be explicit)
- Make targets: parity-gate-phase1 (run gate), strict-audit (reclassify)

Verification: Green drill executed (stale allowlist detection proven);
gate passes with all 10 Tier-1 fixtures agreeing on both backends.
The pre-push hook environment (GIT_DIR, GIT_WORK_TREE, GIT_INDEX_FILE,
GIT_PREFIX) leaks into git subprocess calls in the compat-suite overlay
helpers (syncJankSubmodule, applyPatchOverlay, revertPatchOverlay).
When these vars are set, git honors them over the -C repo-discovery path,
causing submodule gitlink resolution to fail in worktree contexts. The
lg-char-unicode-scalar overlay silently no-ops and the gate reds on
TestJankSuiteCoversLetGoUnicodeScalar and TestClojureTestSuite/char tests
during push, but only in worktrees.

Filter GIT_DIR, GIT_WORK_TREE, GIT_INDEX_FILE, and GIT_PREFIX from the
environment for these git calls, restoring path-based -C discovery.
No behavior change outside hook context (Fixes issue nooga#679).
@nnunley
nnunley force-pushed the backend-parity-gate branch 2 times, most recently from 2be976d to 32abf99 Compare August 11, 2026 23:06
@nnunley nnunley changed the title test(parity): Phase-1 backend parity gate with strict-mode audit test(parity): gate complete-corpus strict capability and engine output Aug 12, 2026
@nnunley
nnunley requested review from mparrett and nooga August 12, 2026 00:06

@mparrett mparrett left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the substantial follow-up here. I rechecked the current implementation and reran make gogen-diff: both mandatory legs pass, strict failures are now classified independently from allowlisted capability gaps, and the documented rederive path is connected and reproduces the committed ledger and baselines byte-for-byte. All three requested changes are addressed.

Approving with one non-blocking whitespace nit inline.

Comment thread test/parity-divergence.txt Outdated
# The target state is empty. Rederive only from a complete valid census.
# Partial and divergence are independent facts; a fixture may appear in both.
# LETGO_PARITY_REDERIVE=1 make engine-parity-gate

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking nit: git diff --check reports this as a new blank line at EOF. The rederive path reproduces it, so the durable cleanup would be in baselineBytes (avoid the second trailing newline when the generated baseline has no entries), rather than editing this generated file alone.

@nnunley
nnunley force-pushed the backend-parity-gate branch from 32abf99 to 4a374bc Compare August 12, 2026 23:43
@nnunley
nnunley merged commit 2fe5175 into nooga:main Aug 13, 2026
14 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.

2 participants