Skip to content

build: snapshot package linker state - #2179

Open
zhouguangyuan0718 wants to merge 12 commits into
xgo-dev:mainfrom
zhouguangyuan0718:agent/parallel-build-pr5
Open

build: snapshot package linker state#2179
zhouguangyuan0718 wants to merge 12 commits into
xgo-dev:mainfrom
zhouguangyuan0718:agent/parallel-build-pr5

Conversation

@zhouguangyuan0718

@zhouguangyuan0718 zhouguangyuan0718 commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Summary

  • introduce LLVM-free PackageSummary as the linker-facing package result
  • snapshot link arguments, runtime requirements, ABI state, globals, funcinfo/PCLN, stubs, and C-shared exports before package LLVM state is released
  • make final linking and metadata generation consume summaries instead of live worker packages
  • persist summary metadata in cache manifests and safely rebuild legacy cache entries
  • retain the serial live-package compatibility path for C archive/shared header generation
  • keep backend execution serial in this layer

Dependency and review boundary

Depends on #2175.

The intended diff for this PR is:

Please review only dcf4cca4..3a677a12.

Tests

  • focused PackageSummary, linker metadata, cache, funcinfo/PCLN, archive error, fingerprint cycle, link-only preflight, and SSA scheduling tests
  • inherited build: split package compilation stages #2175 archive tests
  • git diff --check

@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.

FennoAI Review — parallel build pipeline

Solid, well-structured refactor. The register/build split, the packageBuildPlan/dependency-level algorithm, and the PackageSummary decoupling (linking no longer reads live LLVM modules) are coherent, and the concurrency-sensitive paths are guarded (builtMu, sfilesMu, cacheManagerMu, llvmVersionMu). Channel/waitgroup usage in both worker pools is correct (no deadlock or goroutine leak). The -p flag parsing, atomic-on-error config application, and the flagfile whole-line-value split are all covered by tests. No blocking issues.

A few concrete points, mostly inline. Two non-inline items worth flagging:

1. -p speedup is bounded by a serial backend (performance). Only two stages run in parallel: buildSSAPkgs (ssa.Package.Build) and the cache/fingerprint preflight (preflightPackageBuilds). The dominant cost — buildPreflightedPackageexecutePackageBuildbuildPkg (frontend + LLVM RunPasses/object emission) — still runs in the strictly serial for _, spec := range normalPkgs loop in buildAllPkgs. The comments acknowledge this is intentional ("backend remains deliberately serial until it owns an isolated LLVM context"), so this is a heads-up rather than a defect: on cold builds -p mainly speeds up type-check/SSA and cache-stat work, not the backend. Consider noting this expectation wherever -p is user-documented.

2. Cache manifest is trusted without re-verifying it against the fingerprint (pre-existing, not a regression). tryLoadFromCache uses the fingerprint only to derive the on-disk filename, then trusts meta.LinkArgs and the new meta.Summary (which now includes LinkArgs flowing onto the linker command line) without recomputing/comparing the fingerprint of the stored manifest. This PR widens the trusted payload but does not change the model (the cache dir is local). Not blocking; recomputing the fingerprint on load would make entries self-authenticating and close this for both old and new fields.

Automated review; treat as suggestions. The concurrency note below (shared fingerprint state) is the item I'd most want confirmed before merge.

Comment thread internal/build/collect.go Outdated
if aDep, ok := c.pkgByID[dep.ID]; ok {
if aDep.Fingerprint == "" {
if err := c.collectFingerprint(aDep); err != nil {
if err := c.collectFingerprintWithStack(aDep, fingerprinting); err != 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.

Parallel preflight correctness here rests on a DAG-ordering invariant that is worth confirming and testing. dependencyFingerprint reaches into the shared ctx.pkgByID[dep.ID] aPackage and, when its Fingerprint == "", writes aDep.Fingerprint/aDep.Manifest on that shared object. Within a dependency level, workers run concurrently, so this is only race-free if every in-plan dependency was already fully fingerprinted in an earlier level.

That holds today because newPackageBuildPlan and collectDependencyInputs both derive edges from effectiveDependencies, and pkgByID/plan.byID come from the same allPkgs set — so an in-plan dep is always an earlier level, and out-of-plan deps use fresh local temp objects. But it's fragile: any future case where a fingerprinted dependency is reachable via pkgByID yet not represented as a plan edge (e.g. a dep filtered from the plan but still traversed here, or a plan/fingerprint classification mismatch) would let two same-level workers write the same shared aDep concurrently — a data race producing corrupt manifests.

Coverage is thin: only module_hook_test.go sets Parallel = 2, and there's no -race run over a real multi-package dependency graph. Recommend adding a go test -race case with Parallel >= 2 across a non-trivial import graph, and/or guarding the shared aDep fingerprint write rather than relying solely on level ordering.

Comment thread internal/build/build.go Outdated
PCLNModeSet bool
AllowNoBody bool // allow declarations without bodies, as go tool compile does
// Parallel is the maximum number of LLGo's internally parallel build tasks.
// Zero uses GOMAXPROCS; command-line -p, like go build -p, must be >= 1.

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 field's own contract differs from this comment. The comment describes the CLI rule ("-p ... must be >= 1"), but the Parallel field itself accepts 0 programmatically (parallelism() maps 0GOMAXPROCS, and Do rejects only negative values). For a direct API caller, 0 is valid and means "use GOMAXPROCS". Consider rewording so the field contract (0 = default/GOMAXPROCS, negative = error) is stated for the field, with the >= 1 note scoped to the -p flag.

Comment thread internal/build/build.go
}
// Snapshot every linker-facing LLVM fact before cache publication. The
// summary is the hand-off point to a future worker-local Program.
aPkg.Summary = summarizePackage(aPkg)

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.

summarizePackage currently always returns non-nil on this path, so this is fine today. As defensive robustness: if saveToCache were ever reached with pkg.Summary == nil, metadata() returns nil and the entry is written with summary: absent — which tryLoadFromCache then treats as a permanent cache miss (meta.Summary == nil guard). That is a silently non-cacheable entry rather than an error. A short assertion/comment here that Summary must be set before saveToCache would make the invariant explicit.

Comment thread internal/goflags/gobuild.go Outdated
if err != nil || parallel <= 0 {
return 0, false, fmt.Errorf("-p must be a positive integer, got %q", value)
}
present = true

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.

Minor: when -p= appears multiple times in the normalized flags, this silently uses the last occurrence. That matches go build's last-wins semantics, so it's defensible, but it's undocumented — a one-line comment noting the intentional last-wins behavior would help future readers.

@codecov

codecov Bot commented Jul 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.72477% with 23 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/build/build.go 91.17% 8 Missing and 10 partials ⚠️
internal/build/package_archive.go 88.09% 3 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

@zhouguangyuan0718
zhouguangyuan0718 force-pushed the agent/parallel-build-pr5 branch 7 times, most recently from 68b3b55 to b818839 Compare July 31, 2026 01:50
@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown

LLGo baseline benchmarks

fae6e2770566 | workflow run | long-term charts

Program measurements

Platform Workload File size vs main Build vs main Run vs main
Linux cprintf 18544 B new 247.357 ms new 1.052 ms new
Linux fmtprintf 2217544 B new 2.530 s new 2.125 ms new
Linux println 71504 B new 245.369 ms new 1.374 ms new
macOS cprintf 84672 B new 296.402 ms new 2.685 ms new
macOS fmtprintf 2361520 B new 3.151 s new 19.310 ms new
macOS println 125712 B new 322.468 ms new 3.963 ms new
Core language and compiler benchmarks
Platform Benchmark ns/op vs main
Linux BenchmarkLookupPCRandom 9.492 ns/op new
Linux BenchmarkMergeCompilerFlags 111.100 ns/op new
Linux BenchmarkMergeLinkerFlags 72.580 ns/op new
Linux BenchmarkChannelBuffered 28.140 ns/op new
Linux BenchmarkChannelHandoff 19797 ns/op new
Linux BenchmarkDefer 32.560 ns/op new
Linux BenchmarkDirectCall 1.363 ns/op new
Linux BenchmarkGlobalRead 1.364 ns/op new
Linux BenchmarkGlobalWrite 2.175 ns/op new
Linux BenchmarkGoroutine 25133 ns/op new
Linux BenchmarkInterfaceCall 6.817 ns/op new
Linux BenchmarkRuntimeGetG 1.364 ns/op new
macOS BenchmarkLookupPCRandom 10.680 ns/op new
macOS BenchmarkMergeCompilerFlags 97.800 ns/op new
macOS BenchmarkMergeLinkerFlags 64.210 ns/op new
macOS BenchmarkChannelBuffered 20.600 ns/op new
macOS BenchmarkChannelHandoff 6455 ns/op new
macOS BenchmarkDefer 24.990 ns/op new
macOS BenchmarkDirectCall 0.943 ns/op new
macOS BenchmarkGlobalRead 0.944 ns/op new
macOS BenchmarkGlobalWrite 0.942 ns/op new
macOS BenchmarkGoroutine 21107 ns/op new
macOS BenchmarkInterfaceCall 4.097 ns/op new
macOS BenchmarkRuntimeGetG 1.881 ns/op new

No main baseline exists yet; all metrics are marked new.

Warning

  • The rendered benchmark data could not be pushed.

@zhouguangyuan0718
zhouguangyuan0718 force-pushed the agent/parallel-build-pr5 branch 7 times, most recently from 01a91ba to 8f35e91 Compare August 1, 2026 23:22
@zhouguangyuan0718
zhouguangyuan0718 force-pushed the agent/parallel-build-pr5 branch from 8f35e91 to 3a677a1 Compare August 2, 2026 01:33
@zhouguangyuan0718
zhouguangyuan0718 force-pushed the agent/parallel-build-pr5 branch from 3a677a1 to fae6e27 Compare August 2, 2026 03:21
@zhouguangyuan0718
zhouguangyuan0718 force-pushed the agent/parallel-build-pr5 branch from fae6e27 to c72f759 Compare August 2, 2026 05:09
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