Skip to content

chore/litgen: safely refresh continuous checks by default - #2302

Closed
cpunion wants to merge 7 commits into
xgo-dev:mainfrom
cpunion:codex/litgen-update-existing-checks-20260812
Closed

chore/litgen: safely refresh continuous checks by default#2302
cpunion wants to merge 7 commits into
xgo-dev:mainfrom
cpunion:codex/litgen-update-existing-checks-20260812

Conversation

@cpunion

@cpunion cpunion commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • refresh recognized CHECK/CHECK-LABEL + CHECK-NEXT/CHECK-EMPTY snapshots in place by default, including snapshots that still pass
  • preserve hand-written checks, validate the complete source with FileCheck, and write each file atomically; ambiguous or unrecoverable ranges fail with a -force fallback
  • retain source locations and logical anchor boundaries, including split function-label/body layouts
  • generalize whitelisted unstable or platform-specific IR details such as generated hashes/IDs, test package prefixes, nest/swiftself, setjmp/longjmp names, and pthread/jmp-buffer layouts
  • cache the detected development root so a multi-file run reports it once instead of once per compiler lookup

Benefits

Compiler changes can refresh stale continuous IR snapshots without discarding hand-written test intent or requiring manual cleanup. Regenerating passing snapshots also detects stale but overly broad checks, while deterministic platform generalization keeps reviews reproducible across Linux and macOS.

Validation

  • go test ./chore/litgen -count=1 -cover (78.3%)
  • PR Superseded by #2316: preserve nil checks for unused dereferences #2256 experience test: created a worktree at its head, restored all cl/_test*/** files to its base, and ran default litgen on the 62 changed LIT sources
    • all 62 refreshed without -force
    • a second complete run produced the identical diff hash
    • the exercise caught and added tests for adjacent independent anchors and split snapshots ending at a function boundary

cl/_testlto remains intentionally excluded from bulk refreshes because its CHECKs inspect post-LTO IR while litgen produces package IR before that transform.

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review summary

This PR adds a -update mode to litgen that regenerates only failing CHECK groups in place, plus several new IR-generalization regexes (symbol/cgo hashes, numeric globals, metadata IDs, setjmp/longjmp, pthread opaque sizes, test-case path prefixes). The change is well-structured and the new -update path has good test coverage (TestUpdateSourceChecks_*).

Findings below are non-blocking. The most actionable are the missing docs for the new flag and a couple of correctness/robustness edge cases in the generalization logic. Details are in inline comments; a few cross-cutting notes:

  • -update flag is undocumented. dev/README.md describes the litgen default (full-rewrite) behavior but never mentions the new -update flag or how it differs (preserves passing groups, regenerates only failing groups at their existing byte range, never adds new function/global checks). Please document it in the litgen section.
  • updateSourceChecks uses O(G×F) FileCheck subprocess spawns. Each matchCheckText call spawns an external FileCheck process + temp file, and findFunctionForCheckGroup runs it once per IR function per check group. For a large golden (G groups × F IR functions) this becomes thousands of process launches. Acceptable for a dev-only tool, but the quadratic factor is avoidable — the definition-line match in findFunctionForCheckGroup could be a direct symbol/string comparison against fn.symbol/fn.lines[0] instead of shelling out to FileCheck per function.

Comment thread chore/litgen/rewrite.go Outdated
line = numericGlobalRE.ReplaceAllString(line, `@{{[0-9]+}}`)
line = metadataIDRE.ReplaceAllString(line, `!{{[0-9]+}}`)
line = generalizePlatformIR(line)
line = strings.ReplaceAll(line, "[[", `{{\[\[}}`)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Order-dependent [[ escaping can corrupt already-generated FileCheck patterns.

By this point scrubIRLine has already injected patterns that each contain a single [{{[0-9]+}} (numeric globals / metadata), {{[0-9a-f]+}} (cgo hash), {{[-A-Za-z0-9_]+}} (symbol hash). The blind whole-line strings.ReplaceAll(line, "[[", ...) escapes any run of two [, so if an injected [ ends up adjacent to a literal [ from the original IR, the intended {{[...}} regex gets rewritten into {{\[\[}} and the pattern is corrupted.

Safer to escape [[ on the raw IR line before injecting any FileCheck patterns, rather than after. The existing test (TestGeneralizeIRLine_EscapesFileCheckSyntaxAndCgoHash) only covers a standalone [[ with no adjacent injected pattern, so this edge case is untested.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 2032f29. Raw [[ is escaped before generated regex substitutions, and the regression test covers adjacency between an injected character class and a literal bracket.

Comment thread chore/litgen/rewrite.go
}
var matched *irFunction
for i := range funcs {
if len(funcs[i].lines) == 0 || matchCheckText(definitionCheck, strings.Join(funcs[i].lines, "\n")) != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This runs a full FileCheck subprocess (via matchCheckText → temp file + filecheck.Matchexec.Command(...).Run()) once per IR function, for every check group — i.e. O(groups × funcs) process spawns and temp files per -update run. It also re-computes strings.Join(funcs[i].lines, "\n") for every function on every group.

Since the goal here is only to identify which function a CHECK group belongs to via its single define line, a direct comparison against fn.symbol / fn.lines[0] would remove the FileCheck spawn from this inner loop entirely. Non-blocking (dev-only tool), but worth it if goldens grow.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Addressed in 2032f29. Canonical generated definition checks now use a precomputed direct index; FileCheck iteration remains only as a compatibility fallback for intentionally loose hand-written definitions and changed signatures.

Comment thread chore/litgen/rewrite.go Outdated
return "", false, fmt.Errorf("%s: no checks generated for %q", srcPath, fn.symbol)
}
if hasDefinition {
// Keep the existing definition directive byte-for-byte. It may be

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The comment says the definition directive is kept "byte-for-byte", but firstDefinitionCheck (line 204) does strings.TrimLeft(line, " \t"), and the block is then re-emitted through formatDirectiveBlock(indent, ...) which reapplies the group's computed indent. So the directive content is preserved verbatim, but leading whitespace is re-normalized — not byte-identical if the original indentation differed. Consider rewording to something like "keep the existing definition directive's content verbatim (leading indentation is re-normalized)".

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 2032f29. The inaccurate byte-for-byte wording was removed; the current comment only promises that hand-written checks are kept verbatim and validated when they are not regenerated.

Comment thread chore/litgen/rewrite.go Outdated
}

var pthreadOpaqueSizes = []pthreadOpaqueSize{
{"MutexAttr", regexp.MustCompile(`\[(?:4|8|16) x i8\]`), `[{{(4|8|16)}} x i8]`},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The disambiguation between Mutex/MutexAttr, Cond/CondAttr, RWLock/RWLockAttr in generalizePlatformIR (line 706) relies on two independent, undocumented invariants: (1) the Attr variants must be ordered before their prefixes in this slice, and (2) the trailing " in strings.Contains(line, ".../sync."+typeName+") is load-bearing (it prevents Mutex from matching MutexAttr). A future reorder or a dropped trailing quote would silently apply the wrong size set. Worth a comment documenting both, and adding MutexAttr/RWLockAttr/CondAttr cases to TestGeneralizePlatformIR (currently only Mutex is covered).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 2032f29. Pthread types are extracted with an exact type-name regexp and looked up in a map, so correctness no longer depends on prefix ordering or a trailing quote. MutexAttr, RWLockAttr, and CondAttr cases are covered.

@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown

LLGo baseline benchmarks

afffadb63c75 | workflow run | long-term charts

Program measurements

Platform Workload File size vs base Build vs base Run vs base
Linux cprintf 18656 B +0.0% 239.230 ms +1.3% (worse) 949.421 us -0.2% (better)
Linux fmtprintf 1881632 B +0.0% 2.249 s -3.7% (better) 2.485 ms -5.6% (better)
Linux println 68480 B +0.0% 233.102 ms -4.3% (better) 1.224 ms +3.5% (worse)
macOS cprintf 84672 B +0.0% 371.591 ms -40.3% (better) 2.444 ms -49.5% (better)
macOS fmtprintf 1889248 B +0.0% 2.993 s -1.1% (better) 12.439 ms -21.2% (better)
macOS println 121216 B +0.0% 428.990 ms -16.6% (better) 4.497 ms -24.5% (better)
Core language and compiler benchmarks
Platform Benchmark ns/op vs base
Linux BenchmarkLookupPCRandom 9.803 ns/op -1.5% (better)
Linux BenchmarkMergeCompilerFlags 122.300 ns/op -0.2% (better)
Linux BenchmarkMergeLinkerFlags 87.440 ns/op +4.9% (worse)
Linux BenchmarkChannelBuffered 51.640 ns/op -3.2% (better)
Linux BenchmarkChannelHandoff 27538 ns/op +1.8% (worse)
Linux BenchmarkDefer 37.740 ns/op +0.5% (worse)
Linux BenchmarkDirectCall 1.097 ns/op +8.6% (worse)
Linux BenchmarkGlobalRead 1.230 ns/op +9.6% (worse)
Linux BenchmarkGlobalWrite 7.494 ns/op +1.1% (worse)
Linux BenchmarkGoroutine 35996 ns/op +11.5% (worse)
Linux BenchmarkInterfaceCall 5.644 ns/op +1.3% (worse)
Linux BenchmarkRuntimeGetG 1.262 ns/op -4.2% (better)
macOS BenchmarkLookupPCRandom 13.350 ns/op +14.7% (worse)
macOS BenchmarkMergeCompilerFlags 116 ns/op -2.1% (better)
macOS BenchmarkMergeLinkerFlags 76.370 ns/op -2.4% (better)
macOS BenchmarkChannelBuffered 25.090 ns/op -49.3% (better)
macOS BenchmarkChannelHandoff 5619 ns/op -33.5% (better)
macOS BenchmarkDefer 26.970 ns/op -25.7% (better)
macOS BenchmarkDirectCall 1.023 ns/op -10.0% (better)
macOS BenchmarkGlobalRead 1.048 ns/op -30.2% (better)
macOS BenchmarkGlobalWrite 1.063 ns/op -25.9% (better)
macOS BenchmarkGoroutine 25781 ns/op +2.6% (worse)
macOS BenchmarkInterfaceCall 4.779 ns/op -14.2% (better)
macOS BenchmarkRuntimeGetG 1.894 ns/op -27.0% (better)

Compared with d600859372ce measured in the same runner job.

@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@cpunion cpunion changed the title chore/litgen: update failing checks in place chore/litgen: safely refresh continuous checks by default Aug 12, 2026
@cpunion cpunion closed this Aug 12, 2026
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.

1 participant