Skip to content

build: prepare read-only package backend state - #2282

Open
zhouguangyuan0718 wants to merge 2 commits into
xgo-dev:mainfrom
zhouguangyuan0718:agent/parallel-build-pr6-overlay
Open

build: prepare read-only package backend state#2282
zhouguangyuan0718 wants to merge 2 commits into
xgo-dev:mainfrom
zhouguangyuan0718:agent/parallel-build-pr6-overlay

Conversation

@zhouguangyuan0718

@zhouguangyuan0718 zhouguangyuan0718 commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Summary

  • collect linknames, exports, closure-env directives, nointerface markers, type backgrounds, package syntax identities, and locality metadata before backend lowering starts
  • create each backend Program with a fresh LLVM Context, TargetMachine, lowering/type caches, ABI state, and C ABI transformer while directly sharing the already-prepared Go metadata read-only
  • precompute caller-frame tracking for every Go SSA package before workers start
  • preload the effective patched package syntax explicitly because patch.Types has a distinct go/types identity

Design boundary

This PR is stacked on #2280. It deliberately replaces the earlier snapshot/overlay approach: there is no freeze API, syntax base/delta, worker merge, syntax validation pass, or metadata copying. One-shot compiler users retain Program-local mutation; the build driver switches to read-only syntax mode only after all package preparation is complete.

Parallel scheduling remains in the following PR; this change only establishes the minimal safe state boundary it needs.

Validation

  • go test ./ssa ./cl ./internal/build -count=1
  • go test ./ssa ./cl ./internal/build -coverprofile=... -count=1
  • targeted race tests for backend Program sharing, preloaded syntax, caller tracking, and build sessions
  • new executable-line patch coverage measured locally at about 92.3%

@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: parallel-build backend overlay scaffolding

The "freeze-then-share" design is coherent and applied consistently: each accessor checks its Program-local map first, then the read-only baseline (packageSyntaxBase / typbgBase / frozen locality state), which is the right shape for lock-free concurrent reads. FreezePackageSyntaxState deep-copies into a fresh snapshot so the coordinator's continued local writes don't mutate shared state, and assertMutable() gives a fail-fast guard. Tests cover the sharing/overlay/concurrency invariants well.

Findings are inline. Summary of the notable ones:

  • Latent nil-deref in the worker session path (newSession/newProgram): the "no Python" case is handled gracefully by the coordinator's lazy closure but not by the eager template path — a worker could nil-deref pyget. Not yet live because newSession is unwired, but worth fixing before it lands.
  • Unlanded session machinery (newSession, backendSession, replayProgramState, ctx.backend) is exercised only by tests — no production caller. newProgram is wired in (coordinator). If the session path is intentional staged scaffolding, a tracking reference would help; otherwise it risks silent drift.
  • Redundant work in CallerTracking.Precompute: the inner pkg.Prog.AllPackages() fan-out re-allocates the full package set N times when pkgs already equals AllPackages(). Low severity (runs once per compilation) but easy to eliminate.
  • Doc inaccuracy on FreezeLocalityState: the "parse markers remain local to each Program" sentence is both misplaced (markers moved off localityInfos) and contradicted by the shared-baseline behavior the tests assert.
  • Minor: copy shadows the builtin in two spots.

Additional context (no change requested):

  • Concurrency safety of the lock-free shared reads currently relies on an undocumented happens-before: Precompute/freeze fully completes before any worker spawns, and frozen is a plain (non-atomic) bool with no test spawning writers across the freeze transition. Correct today; worth documenting the invariant (or making frozen atomic) so a future change fails loudly under -race rather than silently.
  • The frozen-coordinator locality path is safe only because ParsePkgSyntax short-circuits on PackageSyntaxParsed; a patched package (different *types.Package pointer) declaring a package var absent from the original could reach assertMutable() and panic. Fragile invariant — an explicit assertion/comment would harden it.

Comment thread internal/build/build.go Outdated
prog.SetRuntime(t.runtimePackage)
}
if t.pythonPackage != nil {
prog.SetPython(t.pythonPackage)

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.

Latent nil-deref in the worker session path. The coordinator installs Python via a nil-safe lazy closure (SetPython(func() *types.Package { ... return nil })), so python() degrades gracefully when Python isn't in the build. Here a worker only calls SetPython when t.pythonPackage != nil, leaving both p.py == nil and p.pyget == nil. If any worker code path then reaches Program.python() it calls p.pyget() on a nil func value and panics (ssa/python.go).

This isn't live yet because newSession has no production caller, but it should be fixed before the parallel path lands: install the same nil-returning closure fallback (or set unconditionally). The SetRuntime/runtime() pair has the same asymmetry but is lower risk since runtimePackage is effectively always set (altPkgs[0].Types).

Comment thread internal/build/build.go Outdated
return prog
}

func (t backendProgramTemplate) newSession() (backendSession, error) {

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.

newSession, backendSession, replayProgramState, and the ctx.backend field (written at ctx construction, never read in production) are exercised only by backend_program_test.go — no production code spawns a session. newProgram is wired in as the coordinator program. This is a sizeable chunk of code whose behavior is guaranteed only by unit tests, with no production caller to keep it honest as the surrounding build logic evolves.

If this is intentional staged scaffolding for a follow-up parallel-build PR, a tracking issue/comment reference would make that clear. Otherwise consider gating it behind a clearly-labeled experimental path or deferring it.

Relatedly, replayProgramState calls ParsePkgSyntax (which runs DeclareLocality on the session's own fresh locality map) before UseLocalityState swaps in the frozen shared state, so those writes are discarded. Harmless today only because the shared frozen state already contains those declarations; consider installing the shared locality state first, or documenting why the pre-replay writes are intentionally thrown away.

Comment thread internal/build/build.go Outdated
func newBackendProgramTemplate(target *llssa.Target, conf *Config, funcInfoMetadata, funcInfoSites bool) backendProgramTemplate {
var targetCopy *llssa.Target
if target != nil {
copy := *target

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.

copy shadows the copy builtin here (and again at the newProgram target-copy below). Legal but a maintenance hazard — a later edit needing copy() in this scope would misbehave. Suggest renaming to targetCopy (as already done for the outer variable in this function).

Comment thread cl/instr.go Outdated
}
all[pkg] = true
if pkg.Prog != nil {
for _, programPkg := range pkg.Prog.AllPackages() {

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.

Redundant O(N·M) work. Precompute is called as ctx.callerTracking.Precompute(ctx.progSSA.AllPackages()), so pkgs already is the full program package set. All packages share the same *ssa.Program, so pkg.Prog.AllPackages() returns the identical full set on every iteration — and AllPackages() allocates a fresh slice and re-iterates the package map each call, giving N slice allocations and N·M insertions.

Low severity (runs once per compilation, dwarfed by the analysis it feeds), but pure redundant work: iterate pkgs directly, or hoist a single AllPackages() call as a fallback only if pkgs may be a subset.

Comment thread ssa/locality.go Outdated
}

// FreezeLocalityState freezes p's locality metadata and returns it without
// copying. Package syntax parse markers remain local to each Program.

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 comment is inaccurate and misplaced. parsedPackages no longer lives on localityInfos — it moved to Program (ssa/package.go), so a note about parse markers doesn't belong on FreezeLocalityState. More importantly, the claim that parse markers "remain local to each Program" is contradicted by the code: the coordinator's markers are copied into the shared PackageSyntaxState snapshot in FreezePackageSyntaxState and read across all Programs via packageSyntaxBase in PackageSyntaxParsed (and package_syntax_test.go explicitly asserts a coordinator-marked package IS visible through the shared baseline). Only newly worker-discovered markers stay Program-local.

Suggest dropping this sentence from FreezeLocalityState, and if useful adding a corrected version to FreezePackageSyntaxState.

@codecov

codecov Bot commented Aug 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.29464% with 39 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
ssa/package_syntax.go 89.23% 7 Missing and 7 partials ⚠️
internal/build/build.go 86.31% 8 Missing and 5 partials ⚠️
cl/import.go 79.31% 3 Missing and 3 partials ⚠️
ssa/type_cvt.go 96.07% 3 Missing and 1 partial ⚠️
cl/instr.go 93.54% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

LLGo baseline benchmarks

5b7167c00d35 | workflow run | long-term charts

Program measurements

Platform Workload File size vs base Build vs base Run vs base
Linux cprintf 18456 B +0.0% 250.623 ms +0.7% (worse) 1.128 ms +4.7% (worse)
Linux fmtprintf 1861416 B +1.7% (worse) 2.523 s -1.3% (better) 3.059 ms +9.2% (worse)
Linux println 68008 B +0.0% 245.366 ms -1.5% (better) 1.356 ms -2.7% (better)
macOS cprintf 84672 B +0.0% 417.124 ms -4.1% (better) 4.210 ms +9.6% (worse)
macOS fmtprintf 1869328 B +0.0% 3.355 s -13.1% (better) 14.578 ms +12.4% (worse)
macOS println 121200 B +0.0% 423.599 ms -10.0% (better) 4.714 ms -6.2% (better)
Core language and compiler benchmarks
Platform Benchmark ns/op vs base
Linux BenchmarkLookupPCRandom 9.512 ns/op -0.0% (better)
Linux BenchmarkMergeCompilerFlags 112.200 ns/op +0.3% (worse)
Linux BenchmarkMergeLinkerFlags 73.190 ns/op -0.2% (better)
Linux BenchmarkChannelBuffered 29.460 ns/op +4.7% (worse)
Linux BenchmarkChannelHandoff 19706 ns/op -2.8% (better)
Linux BenchmarkDefer 39.200 ns/op +0.5% (worse)
Linux BenchmarkDirectCall 1.363 ns/op +0.0%
Linux BenchmarkGlobalRead 1.363 ns/op +0.0%
Linux BenchmarkGlobalWrite 2.176 ns/op -0.1% (better)
Linux BenchmarkGoroutine 25626 ns/op +1.1% (worse)
Linux BenchmarkInterfaceCall 6.705 ns/op -5.5% (better)
Linux BenchmarkRuntimeGetG 1.644 ns/op +0.2% (worse)
macOS BenchmarkLookupPCRandom 12.830 ns/op -16.7% (better)
macOS BenchmarkMergeCompilerFlags 123 ns/op -31.8% (better)
macOS BenchmarkMergeLinkerFlags 86.860 ns/op -12.9% (better)
macOS BenchmarkChannelBuffered 24.830 ns/op -2.7% (better)
macOS BenchmarkChannelHandoff 8402 ns/op +0.0%
macOS BenchmarkDefer 38.250 ns/op +9.7% (worse)
macOS BenchmarkDirectCall 1.127 ns/op -6.1% (better)
macOS BenchmarkGlobalRead 1.164 ns/op -10.1% (better)
macOS BenchmarkGlobalWrite 1.127 ns/op -14.6% (better)
macOS BenchmarkGoroutine 44431 ns/op -15.2% (better)
macOS BenchmarkInterfaceCall 4.989 ns/op -14.2% (better)
macOS BenchmarkRuntimeGetG 2.277 ns/op -0.4% (better)

Compared with 6670dae3884d measured in the same runner job.

@zhouguangyuan0718
zhouguangyuan0718 force-pushed the agent/parallel-build-pr6-overlay branch from 8f51e28 to a70be63 Compare August 4, 2026 07:33
@zhouguangyuan0718 zhouguangyuan0718 changed the title build: share syntax state with worker overlays build: share package syntax with worker deltas Aug 4, 2026
@zhouguangyuan0718
zhouguangyuan0718 force-pushed the agent/parallel-build-pr6-overlay branch from a70be63 to 4db979f Compare August 4, 2026 13:43
@zhouguangyuan0718
zhouguangyuan0718 force-pushed the agent/parallel-build-pr6-overlay branch from 40ba68d to 9cc28fa Compare August 5, 2026 13:30
@zhouguangyuan0718 zhouguangyuan0718 changed the title build: share package syntax with worker deltas build: prepare read-only package backend state Aug 5, 2026
@zhouguangyuan0718
zhouguangyuan0718 force-pushed the agent/parallel-build-pr6-overlay branch from 9cc28fa to 5b7167c Compare August 5, 2026 13:44
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