Skip to content

SYSTEM Cited by 1 source

Go linker

The Go linker (cmd/link) joins compiled package artifacts into a single binary, performs reachability analysis from the entry point, and drops symbols no reachable code uses — dead-code elimination (DCE). The size of the resulting binary is governed as much by what the linker can prove unused as by what the compiler includes.

Method dead-code elimination

In addition to function-level DCE, the linker can drop methods — but only when it can statically prove which methods are called. Any use of reflect.MethodByName(name) with a non-constant name defeats that proof, so the linker keeps every exported method of every reachable type and every symbol those methods depend on (concepts/reflect-methodbyname-linker-pessimism).

In Datadog's 2026-02-18 post, re-enabling method DCE across the Agent binaries required patching around a dozen dependencies' reflect.MethodByName uses, plus forking stdlib text/template + html/template into pkg/template/ with the method-call code path statically disabled. 16-25 % per binary / ~100 MiB total reduction. Kubernetes subsequently adopted the same optimization and reports 16-37 %.

plugin mode

Importing the stdlib plugin package at all puts the linker into dynamically-linked mode (lib.go#L288), which:

The canonical 2026 instance: containerd/plugin/plugin_go18.go imported plugin for user-loadable plugins; in the Datadog Agent this cost 245 MiB / ~20 % of binary size / ~75 % of users. Fix: upstream build tag (containerd#11203). See concepts/go-plugin-dynamic-linking-implication.

-dumpdep flag + whydeadcode

go build -ldflags=-dumpdep prints why each linker symbol is reachable. The output is enormous; whydeadcode parses it and names the first call-chain that disabled method DCE. Only the first entry is guaranteed to be a true positive — iterate (fix, re-run) until the optimization is on.

Seen in

Last updated · 200 distilled / 1,178 read