SYSTEM Cited by 2 sources
Go compiler¶
The Go compiler (go build front-end, cmd/compile) compiles
each required package into its own intermediate artifact (.a)
that the Go linker later joins into a single
binary. Works at package granularity, not file granularity.
File selection rules¶
For a given package, the compiler includes every non-test file
(not ending in _test.go) whose
build constraints are satisfied.
Constraints can depend on:
- OS (
GOOS=linux,GOOS=darwin, …) - Architecture (
GOARCH=amd64,GOARCH=arm64, …) - Explicit build tags passed to
go build -tags t1,t2 - Compiler identity (
gcvsgccgo) - Go version (
go1.24) - CGO enablement (
cgo/!cgo) - Architecture features
Package selection (transitive)¶
Starts from the main package; transitively adds every import
it encounters in files that aren't excluded by build constraints.
Also includes the stdlib runtime package + its internal
dependencies (unavoidable — every Go binary needs them).
Important: a file excluded by build tag does not contribute its imports. This is one of the two principal ways dependencies are pruned in Go. The other is moving symbols into a separate package so only binaries that explicitly import that package pull in its deps (patterns/single-function-forced-package-split).
Listing included packages¶
go list -f '{{ join .Deps "\n" }}' -tags t1,t2 ./path/to/main
(with the appropriate GOOS/GOARCH env vars) prints the full
transitive package set that ends up in the binary. This is the
what — for the why, use goda; for the cost,
use go-size-analyzer.
Side effects of import¶
Per Datadog: "simply importing a package has side effects: init
functions run and global variables are initialized, which can be
enough to force the linker to keep many unnecessary symbols."
Canonical wiki instance: importing the stdlib plugin package
(concepts/go-plugin-dynamic-linking-implication) reshapes the
linker's entire dead-code policy even though no plugin code is
exercised.
Seen in¶
- sources/2026-02-18-datadog-how-we-reduced-agent-go-binaries-up-to-77-percent — Datadog's 77 % Agent-binary reduction program; extensive use of the compiler's build-tag + package-scoping mechanics.
- sources/2025-10-08-cloudflare-we-found-a-bug-in-gos-arm64-compiler — Cloudflare's 2025 arm64 codegen bug hunt; canonical wiki instance of compiler-emit as a runtime-safety concern.
Arm64 codegen and preemption safety¶
The compiler's arm64 backend
(cmd/internal/obj/arm64/obj7.go)
emits function prologues + epilogues that adjust the stack
pointer by the frame size. For frames > 1<<12 bytes, the IR
pre-go1.23.12 expressed this as a single logical ADD $n, RSP,
RSP and relied on the assembler to
split the immediate into ADD $low, RSP, RSP plus
ADD $(high<<12), RSP, RSP (the arm64
ISA's 12-bit ADD immediate forces this). Between the two
opcodes, RSP pointed into the middle of the stack frame.
Async preemption landing in that one-instruction window leaves the stack pointer partially adjusted — invalid for stack unwinding. At Cloudflare's 84 M req/s scale across 330 cities this produced ~30 fatal panics per day across <10 % of data centers. See concepts/split-instruction-race-window.
The fix — shipped in
go1.23.12,
go1.24.6,
go1.25.0
— promotes the immediate-aware decomposition from the
assembler to the compiler: for wide offsets the compiler now
builds the offset in a scratch register (MOVD + MOVK on
R27) and applies it via a single indivisible register-form
ADD R27, RSP, RSP. Preemption can land before or after, never
during. See patterns/preemption-safe-compiler-emit.