Skip to content

build: pipeline package SSA into LLVM backends - #2242

Open
zhouguangyuan0718 wants to merge 13 commits into
xgo-dev:mainfrom
zhouguangyuan0718:agent/package-pipeline
Open

build: pipeline package SSA into LLVM backends#2242
zhouguangyuan0718 wants to merge 13 commits into
xgo-dev:mainfrom
zhouguangyuan0718:agent/package-pipeline

Conversation

@zhouguangyuan0718

@zhouguangyuan0718 zhouguangyuan0718 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Summary

  • replace the all-SSA-then-all-LLVM boundary with one package pipeline under the existing -p concurrency budget
  • keep at least one SSA producer active while SSA work remains, then let completed packages enter isolated LLVM backends as soon as their own and effective dependency SSA state is ready
  • preserve the dependency-free LLVM scheduling introduced by build: run LLVM backends in parallel by package #2182; LLVM completion is never used as a package dependency barrier
  • compute immutable caller-tracking summaries once when each SSA package completes and create a package-local frozen tracker for each backend
  • prepare an immutable merged type view for source patches before workers start; keep the legacy patched backend exclusive because it still rewrites shared go/ssa package type pointers
  • retain deterministic error selection, runtime gating, and coordinator-only compatibility paths while each backend task publishes its archive/cache before completing

Dependency and review boundary

Depends on #2182. This remains an optional follow-up; in-memory package archiving is implemented earlier in #2175 and does not depend on this pipeline.

The intended diff is:

Please review only da5685a0..a456e8d7.

Validation

  • focused pipeline scheduler and archive race tests
  • clean -a -p=8 cgo + Plan9 build and runtime check
  • 78 in-process LLVM archives, no external ar, no LLVM package temporary .o
  • git diff --check

The earlier pipeline-only cold-cache measurements remain historical; absolute timings should be refreshed after the new #2175 archive path lands.

@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: build — pipeline package SSA into LLVM backends

Reviewed the incremental range the PR requests, 455fd3e4..6e0a230a (12 files, +1050/-19), across four dimensions (quality, performance, security/concurrency, docs). The change is well-structured and the trickiest invariants (single shared -p budget, patched-package exclusivity, immutable merged type view, frozen per-backend caller-tracking) are documented and hold up. Edge cases I checked are correct: empty input, -p=1 legacy SSA-then-backend ordering, mid-flight SSA error draining, and patched-backend exclusivity.

Concurrency/security: no data-race findings. Shared maps (ctx.patches, ctx.sfilesCache, caller-tracking summaries) are provably populated/frozen before any goroutine launches, panics are recovered on both SSA and backend paths, and no goroutine-leak or blocked-send paths were found (events is buffered to parallelism, active never exceeds it).

Findings below (all non-blocking). Inline comments carry the specifics.

Summary of findings

  • internal/build/package_build.go / scheduler (worth confirming): a non-patched coordinator backend (plan9asm package) runs executeCoordinatorPackageWithCallerTracking via task := *ctx, i.e. on the shared ctx.prog, while serialActive only serializes coordinators against each other — isolated backends can run concurrently. Only patched coordinators get full exclusiveActive exclusivity. Please confirm building a coordinator on shared ctx.prog/ctx.dedup concurrently with isolated sessions is race-free; this is a behavioral change from the legacy strictly-sequential group ordering, and plan9asm packages may not be exercised by the -race targets listed in the PR.
  • Performance (medium): O(n²) scheduler scans — allBackendsDone() and the nextBackend() isolated fallback linearly scan tasks on every scheduling event. A doneCount counter and a dependency-indexed ready-queue would remove it for large dependency graphs.
  • Minor cleanups: dead packagePipelineSSA.order field; duplicate ctx.patches[...] lookup + redundant patched local; SSA-centric stall-error message; a couple of doc-comment wording nits.

No praise-only or low-confidence items are included.

View job run

Comment thread internal/build/package_build.go Outdated

func (ctx *context) executeCoordinatorPackageWithCallerTracking(spec packageBuildSpec, tracking *cl.CallerTracking, verbose bool) error {
task := *ctx
task.callerTracking = tracking

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.

Please confirm this is race-free — potential behavioral change from the legacy ordering.

executeCoordinatorPackageWithCallerTracking does task := *ctx, so it builds on the shared ctx.prog (and shares ctx.dedup, ctx.pkgs, ...). In buildPackagePipeline, serialActive only serializes coordinators against each other; isolated backends (each with their own session.prog) can still be launched while a coordinator runs. Only patched coordinators get full exclusivity via exclusiveActive/active==0.

A non-patched coordinator arises for plan9asm packages (packageRequiresCoordinator). In the legacy buildPreflightedPackageGroup, the patched→coordinator→isolated groups ran strictly sequentially, so coordinators finished before any isolated backend started. Here they overlap. If building a plan9asm coordinator on the shared ctx.prog concurrently with isolated sessions is safe, a one-line comment stating why would prevent a future regression; plan9asm packages may not be covered by the -race targets in the PR description.

}
return nil
}
allBackendsDone := func() bool {

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.

Performance (medium): O(n^2) scheduler scans. allBackendsDone() here, and the isolated fallback loop in nextBackend (lines 199-203), each do a full linear scan of tasks. allBackendsDone() runs on every outer-loop iteration (line 232) and again at line 285; nextBackend() runs on every inner-loop iteration. With ~n SSA events + ~n backend events, aggregate cost is O(n^2) for a graph of n packages (hundreds–thousands transitively).

Suggested mitigation: keep a running doneCount (bumped where tasks are marked done) and compare against len(tasks) instead of scanning; index dependents by SSA node (you already know which dependents to reconsider when a node finishes at line 305) to feed a ready-queue rather than re-scanning all tasks. advanceSerial is already correctly amortized via the serialPos cursor, so only these two spots are affected.

node.entry.fixOrder = node.entry.fixOrder || entry.fixOrder
continue
}
node := &packagePipelineSSA{entry: entry, order: len(nodes)}

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: packagePipelineSSA.order is effectively dead in production — it is assigned here (order: len(nodes)) but only read by package_pipeline_test.go. The real ordering is the topological sort in orderPackagePipelineSSA, which never reads order. Consider dropping the field (adjusting the test) or adding a comment that it exists solely as a stable first-seen index for tests.

Comment thread internal/build/package_pipeline.go Outdated
}

patched := false
if _, ok := ctx.patches[spec.pkg.PkgPath]; ok {

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 cleanup: ctx.patches[spec.pkg.PkgPath] is looked up twice (here and at line 441 as patch, ok), and the patched local (line 449) duplicates the task.patched field set just below. You can reuse the line-441 ok/patch and read task.patched directly, removing the second lookup and the shadow variable.

if allBackendsDone() && ssaDone == len(ssaNodes) {
break
}
return nil, fmt.Errorf("package pipeline stalled with %d/%d SSA packages complete", ssaDone, len(ssaNodes))

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: this stall error is reachable only on an internal invariant break, but with ssaDone == len(ssaNodes) the remaining stall cause is backend-side (an unschedulable ready()/ssaDeps case), while the message reports only SSA completion counts. Including remaining undone backend-task counts would make a future stall easier to diagnose.

Comment thread cl/instr.go

// NewPackageCallerTrackingForPackages is NewPackageCallerTracking for a
// backend that compiles more than one SSA package, such as an alternate-package
// patch.

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.

Doc nit: NewPackageCallerTrackingForPackages is described as being NewPackageCallerTracking "for a backend that compiles more than one SSA package," but it is actually the general/primary form — NewPackageCallerTracking (line 1050) is the single-package wrapper that delegates here. Consider "generalizes NewPackageCallerTracking to a backend that may compile more than one SSA package" to avoid implying the singular form is primary.

@codecov

codecov Bot commented Jul 31, 2026

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown

LLGo baseline benchmarks

b44fe7488c35 | workflow run | long-term charts

Program measurements

Platform Workload File size vs main Build vs main Run vs main
Linux cprintf 18544 B +0.0% 296.734 ms -9.0% (better) 1.280 ms -18.9% (better)
Linux fmtprintf 2217544 B -0.0% (better) 3.474 s +6.6% (worse) 2.394 ms -2.4% (better)
Linux println 71504 B +0.0% 290.973 ms -6.7% (better) 1.574 ms -7.6% (better)
macOS cprintf 84672 B +0.0% 325.495 ms -27.2% (better) 2.348 ms -59.2% (better)
macOS fmtprintf 2361520 B +0.0% 2.735 s -9.1% (better) 19.289 ms +1.5% (worse)
macOS println 125712 B +0.0% 322.940 ms -22.6% (better) 3.278 ms -25.5% (better)
Core language and compiler benchmarks
Platform Benchmark ns/op vs main
Linux BenchmarkLookupPCRandom 13.180 ns/op -1.8% (better)
Linux BenchmarkMergeCompilerFlags 151.100 ns/op -0.3% (better)
Linux BenchmarkMergeLinkerFlags 96.560 ns/op +1.7% (worse)
Linux BenchmarkChannelBuffered 33.950 ns/op -1.8% (better)
Linux BenchmarkChannelHandoff 25091 ns/op -4.7% (better)
Linux BenchmarkDefer 42.750 ns/op -7.8% (better)
Linux BenchmarkDirectCall 1.556 ns/op -0.1% (better)
Linux BenchmarkGlobalRead 1.869 ns/op -0.2% (better)
Linux BenchmarkGlobalWrite 2.486 ns/op -0.1% (better)
Linux BenchmarkGoroutine 29832 ns/op -9.9% (better)
Linux BenchmarkInterfaceCall 8.092 ns/op -0.0% (better)
Linux BenchmarkRuntimeGetG 1.867 ns/op -0.2% (better)
macOS BenchmarkLookupPCRandom 11.990 ns/op +0.9% (worse)
macOS BenchmarkMergeCompilerFlags 131.700 ns/op +2.5% (worse)
macOS BenchmarkMergeLinkerFlags 88.870 ns/op +4.5% (worse)
macOS BenchmarkChannelBuffered 21.730 ns/op -3.0% (better)
macOS BenchmarkChannelHandoff 7587 ns/op +9.2% (worse)
macOS BenchmarkDefer 29.510 ns/op +3.0% (worse)
macOS BenchmarkDirectCall 1.018 ns/op -1.4% (better)
macOS BenchmarkGlobalRead 0.973 ns/op -5.5% (better)
macOS BenchmarkGlobalWrite 0.946 ns/op -8.5% (better)
macOS BenchmarkGoroutine 57597 ns/op +114.9% (worse)
macOS BenchmarkInterfaceCall 4.664 ns/op +3.2% (worse)
macOS BenchmarkRuntimeGetG 2.131 ns/op +2.5% (worse)

Compared only with the latest matching platform in the main series.

@zhouguangyuan0718
zhouguangyuan0718 force-pushed the agent/package-pipeline branch 3 times, most recently from efc18f2 to c81529c Compare July 31, 2026 23:32
@zhouguangyuan0718
zhouguangyuan0718 force-pushed the agent/package-pipeline branch 9 times, most recently from 65b5dde to c56f542 Compare August 2, 2026 14:37
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