Linker and object files
Thread map, Go 1.0 – 1.17
| Thread | 1.0 | 1.1 | 1.2 | 1.3 | 1.4 | 1.5 | 1.6 | 1.7 | 1.8 | 1.9 | 1.10 | 1.11 | 1.12 | 1.13 | 1.14 | 1.15 | 1.16 | 1.17 | Status at go1.17 (or at the end of its era slice) |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Linker and object files (Go 1.0–1.9) | |||||||||||||||||||
| Early loaders 5l/6l/8l and the shared src/cmd/ld (2008–2011) | ● | replaced by the C-to-Go converted linker in 1.5 and by cmd/link thereafter → continues as Relocation-free pclntab and funcdata (offsets instead of relocations) and Per-platform object formats and internal linking (Mach-O, PE, XCOFF, wasm, ppc64, riscv64, loong64) (1.18+) | |||||||||||||||||
| Go symbol table, pc/line table and function metadata (gosymtab, pclntab, findfunc) | ● | ● | ● | ● | ● | ● | ● | ● | ● | ● | still the Go 1.2 pclntab design (contiguous per-function tables plus functab) with a findfunctab bucket index (1.5) and offsets-based types (1.7); the pclntab itself is rewritten in 1.18 → continues as Relocation-free pclntab and funcdata (offsets instead of relocations) (1.18+) | ||||||||
| Stack splitting and the linker's nosplit overflow check | ● | ● | ● | ● | ● | ● | nosplit chain checking is still done by the linker (stkcheck in cmd/link), reworked in 1.19 → continues as Nosplit stack checking and stack constants (1.19+) | ||||||||||||
| Dead code and data elimination | ● | ● | ● | ● | ● | ● | ● | reachability-flood plus method pruning conditioned on reflect.Value.Method/Call use, as designed in 1.7 → continues as Dead code elimination and reflection-aware pruning (1.18+) | |||||||||||
| DWARF generation in the linker | ● | ● | ● | ● | ● | ● | ● | ● | ● | ● | function DWARF generated in the compiler (1.8) and assembled by the linker; types/globals still in the linker → continues as DWARF quality for debuggers (types, variables, trampolines) (1.18+) | ||||||||
| External (host) linking and cgo | ● | ● | ● | ● | ● | ● | ● | ● | ● | internal linking only for cgo-free or stdlib-cgo cases; external linking required in many cgo cases → continues as ELF/relro/hardening and external-link policy (1.18+) | |||||||||
| Build modes: -shared, c-archive, c-shared, shared, PIE, plugin | ● | ● | ● | ● | ● | ● | ● | all six build modes implemented; internal PIE on linux/amd64 exists but external is the default → continues as ELF/relro/hardening and external-link policy (1.18+) | |||||||||||
| liblink, the object file format and the move into the compilers | ● | ● | ● | ● | ● | ● | cmd/internal/obj + goobj object format with symbol references, contiguous data and per-package indexes; replaced in the 1.15–1.17 era by the new object file format → continues as Relocation-free pclntab and funcdata (offsets instead of relocations) (1.18+) | ||||||||||||
| From C to Go: cmd/link | ● | ● | ● | ● | ● | ● | ● | cmd/link written in Go, still based on the 1.5 c2go output, being re-architected in the 1.15–1.17 era → continues as Linker performance and memory (1.18+) | |||||||||||
| Linker speed and memory (Go 1.7 focus) | ● | ● | ● | ● | ● | incremental; the 1.17-era work moved to the new object format and loader → continues as Linker performance and memory (1.18+) | |||||||||||||
| Runtime type data, GC metadata and sections laid out by the linker | ● | ● | ● | ● | ● | ● | ● | ● | type descriptors with offsets (nameOff, typeOff, textOff) and GC bitmaps in dedicated sections → continues as Type, itab and typelink layout (.go.type section, relro rework) (1.18+) | ||||||||||
| Container writers: Windows PE, Mach-O and Plan 9 | ● | ● | ● | ● | ● | ● | ● | ● | ● | PE, Mach-O and ELF writers in cmd/link/internal/ld; Mach-O code-signing friendly and Windows external linking supported → continues as Per-platform object formats and internal linking (Mach-O, PE, XCOFF, wasm, ppc64, riscv64, loong64) (1.18+) | |||||||||
| Architecture and OS ports in the linker (arm, power64, arm64, mips, s390x, nacl, BSDs, Solaris) and trampolines | ● | ● | ● | ● | ● | ● | ● | ● | ● | ● | all ports in cmd/link/internal/<arch>; direct-branch trampolines for ARM and ppc64x → continues as Per-platform object formats and internal linking (Mach-O, PE, XCOFF, wasm, ppc64, riscv64, loong64) (1.18+) | ||||||||
| Build IDs, ABI hashes, flags and determinism | ● | ● | ● | ● | ● | ● | ● | ● | ● | -buildid, -X, -importcfg, -B, -linkmode, -extld all present → continues as Reproducible build IDs, buildinfo and symbol tables (1.18+) | |||||||||
| Linker and object files (Go 1.10–1.17) | |||||||||||||||||||
| New object file format and the loader (dev.link) | ● | ● | ● | ● | done; the linker reads only the indexed object format through cmd/link/internal/loader, sym.Symbol is gone, and the format gained a weak-reloc bit and 16-bit reloc types in 1.17 → continues as Relocation-free pclntab and funcdata (offsets instead of relocations) and Type, itab and typelink layout (.go.type section, relro rework) (1.18+) | ||||||||||||||
| Wavefront conversion of linker passes to the loader; sym.Symbol removal | ● | ● | done (all passes run on loader.Sym; cmd/oldlink gone from the story) → continues as Relocation-free pclntab and funcdata (offsets instead of relocations) (1.18+) | ||||||||||||||||
| Linker performance and memory engineering | ● | ● | ● | ● | ● | ● | ● | ● | mmapped, parallel, streaming pipeline with generator symbols; ongoing → continues as Linker performance and memory (1.18+) | ||||||||||
| pclntab, funcdata and pc tables | ● | ● | ● | ● | ● | ● | pclntab split into runtime.pcheader, funcnametab, pctab and generator-built tables; still relocation-based → continues as Relocation-free pclntab and funcdata (offsets instead of relocations) (1.18+) | ||||||||||||
| Dead-code elimination and method pruning | ● | ● | ● | ● | ● | ● | ● | index-based deadcode with UsedInIface/R_USEIFACE/R_USEIFACEMETHOD method pruning, weak itab edges and reflect-aware tracking → continues as Dead code elimination and reflection-aware pruning (1.18+) | |||||||||||
| DWARF generation in the linker | ● | ● | ● | ● | ● | ● | ● | ● | DWARF 4 with per-compilation-unit DIE and line-table symbols generated in parallel; line tables produced by the compiler → continues as DWARF quality for debuggers (types, variables, trampolines) (1.18+) | ||||||||||
| DWARF compression and Mach-O dsymutil combining | ● | ● | ● | ● | still the design at go1.17 (zlib ELF/Mach-O/PE compression; dsymutil combine on macOS/amd64 and arm64) → continues as Linker performance and memory (1.18+) | ||||||||||||||
| Symbol ABIs: stable ABI0 vs ABIInternal in the linker and object files | ● | ● | ● | ● | wrapper-based ABIs with name-mangled shared linkage and ABI-aware cgo exports; ABI aliases only for code without wrappers → continues as Type, itab and typelink layout (.go.type section, relro rework) (1.18+) | ||||||||||||||
| Mach-O and Darwin support | ● | ● | ● | ● | ● | ● | ● | ● | internal linking, PIE, plugins and c-shared on darwin/arm64 with code signing, __DATA_CONST, LC_BUILD_VERSION → continues as Per-platform object formats and internal linking (Mach-O, PE, XCOFF, wasm, ppc64, riscv64, loong64) (1.18+) | ||||||||||
| PE / Windows | ● | ● | ● | ● | ● | ● | ● | ● | internal/external linking, PIE, ASLR, DEP, windows/arm and windows/arm64 → continues as Per-platform object formats and internal linking (Mach-O, PE, XCOFF, wasm, ppc64, riscv64, loong64) (1.18+) | ||||||||||
| AIX / XCOFF | ● | ● | ● | done; aix/ppc64 supports internal and external linking, c-archive and PIE → continues as Per-platform object formats and internal linking (Mach-O, PE, XCOFF, wasm, ppc64, riscv64, loong64) (1.18+) | |||||||||||||||
| WebAssembly | ● | ● | ● | ● | ● | still the design at go1.17 → continues as Per-platform object formats and internal linking (Mach-O, PE, XCOFF, wasm, ppc64, riscv64, loong64) (1.18+) | |||||||||||||
| Build modes: PIE, plugin, c-shared, c-archive, shared libraries | ● | ● | ● | ● | ● | ● | ● | internal-linking PIE on linux/amd64+arm64, windows and darwin; plugins on darwin/arm64 and freebsd/amd64, disabled without cgo → continues as ELF/relro/hardening and external-link policy (1.18+) | |||||||||||
| Host object loading and external linking | ● | ● | ● | ● | ● | ● | ● | ● | host objects (ELF, Mach-O, PE, XCOFF) read with loader symbols; external linker flags probed and filtered; cgo directive encoding as JSON → continues as ELF/relro/hardening and external-link policy (1.18+) | ||||||||||
| Architecture ports and trampoline insertion | ● | ● | ● | ● | ● | ● | two-pass trampoline pass for arm/arm64/ppc64; riscv64, mips64, windows/arm64 external linking supported → continues as Per-platform object formats and internal linking (Mach-O, PE, XCOFF, wasm, ppc64, riscv64, loong64) (1.18+) | ||||||||||||
| GOEXPERIMENT plumbing and tool configuration | ● | ● | ● | experiments are a struct in internal/goexperiment parsed by objabi/buildcfg → still the design at go1.17 | |||||||||||||||
| Build identity, buildinfo and reproducibility | ● | ● | ● | ● | still the design → continues as Reproducible build IDs, buildinfo and symbol tables (1.18+) | ||||||||||||||
| Linker restructuring (1.10 code health and error reporting) | ● | ● | ● | done; later passes build on these package splits | |||||||||||||||
Thread map, Go 1.18 – 1.28
| Thread | 1.18 | 1.19 | 1.20 | 1.21 | 1.22 | 1.23 | 1.24 | 1.25 | 1.26 | 1.27 | 1.28 | Status at HEAD |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Linker, object files and debug info (Go 1.18–1.28) | ||||||||||||
| Relocation-free pclntab and funcdata (offsets instead of relocations) | ● | ● | ● | ● | ● | done; pclntab has no relocations, lives read-only in .gopclntab together with funcdata and findfunctab; moduledata in its own section | ||||||
| Type, itab and typelink layout (.go.type section, relro rework) | ● | ● | ● | ● | ● | type descriptors, itabs and GC masks have dedicated linker layout; typelinks and itablinks lists removed; alg/noalg descriptors deduplicated in 1.28 cycle | ||||||
| //go:linkname restrictions and the blocklist | ● | ● | ● | ● | -checklinkname on by default; push-only linknames required for std symbols; blocklist extended each release | |||||||
| DWARF 5 migration | ● | ● | enabled by default since the 1.25 cycle (ca19f987ca, 2024-12-19; experiment dwarf5 added bdb4676043); internal/buildcfg/exp.go keeps it off on macOS (dwarf5Supported) | |||||||||
| DWARF quality for debuggers (types, variables, trampolines) | ● | ● | ● | ● | ● | ● | ● | ● | ● | ● | ongoing, driven by Delve/gdb/lldb needs | |
| Darwin split DWARF (dSYM) | ● | default on Darwin | ||||||||||
| Nosplit stack checking and stack constants | ● | ● | done | |||||||||
| Dead code elimination and reflection-aware pruning | ● | ● | ● | ● | done | |||||||
| Linker performance and memory | ● | ● | ● | ● | ● | ● | incremental | |||||
| Reproducible build IDs, buildinfo and symbol tables | ● | ● | ● | ● | ● | GNU build ID and LC_UUID generated by default | ||||||
| FIPS 140 support in the linker | ● | done | ||||||||||
| ELF/relro/hardening and external-link policy | ● | ● | ● | ● | ● | ● | ● | |||||
| Per-platform object formats and internal linking (Mach-O, PE, XCOFF, wasm, ppc64, riscv64, loong64) | ● | ● | ● | ● | ● | ● | ● | ● | ● | ● | ● | active |
Linker and object files (Go 1.0–1.9)
Reader: Claude Sonnet. Cites 1,189 of 1,343 commits in this slice; every non-noise commit is cited, plus 13 larger commits the noise filter had set aside, which a separate gap reader described. Read the opening lines of the design-heavy messages and about 55 full messages, but no diffs. Grouped “Other” items for 1.0–1.8 are hash lists with short labels.
Release digest for this slice
- Go 1.0 — Everything from the first
6l/8l/5lthrough Go 1: ELF, Mach-O and PE writers, the sharedsrc/cmd/ld, dead-code elimination, symbol/pclntab as rodata, DWARF, NOSPLIT checking, cgo dynamic imports, and ports to OpenBSD/NetBSD/Windows/NaCl. - Go 1.1 — External (host) linking is born (
-hostobjthen-linkmode,-extld),-sharedPIC output, symbol table repacked host-endian, args/locals info, race/fieldtrack hooks, and typelinks forreflect.ArrayOf. - Go 1.2 — New in-memory symbol table and contiguous pclntab with FUNCDATA GC metadata and argument bitmaps; ARM external linking and TLS merge.
- Go 1.3 — liblink and the object-file reader/writer move instruction selection out of the linker; a Go skeleton
cmd/linkappears; Solaris and NaCl land. - Go 1.4 — Go-specific DWARF type info and fixed frame tables, linker-defined symbols moved into package runtime, Android TLS, and a quiet cycle in the linker core.
- Go 1.5 — The linker is converted from C to Go via c2go and moved to
cmd/link;-buildmodec-archive/c-shared/shared/-linksharedappear; power64, arm64 and darwin/arm ports; external linking on Windows and arm64;findfunclookup table. - Go 1.6 — PIE and many shared/c-shared architecture additions,
-msan,-libgcc/-extar, mips64, ppc64 external linking. - Go 1.7 — Linker speed/memory campaign, new object format with reference indexes and contiguous data, string merging and method pruning, typelink offsets,
cmd/newlinkremoved, deterministic builds, s390x. - Go 1.8 — Plugins and plugin versioning, rel.ro segment and internal PIE groundwork, DWARF for functions generated in the compiler, ARM/ppc64x trampolines, mips, global state removed from cmd/link.
- Go 1.9 — PIE linked externally by default, Windows external linking and DWARF fixes, Mach-O DWARF for dsymutil,
-importcfg, object magicgo19ld, objabi split and symbol-kind trimming.
Early loaders 5l/6l/8l and the shared src/cmd/ld (2008–2011)
Span: Go 1.0 → Go 1.0 · Status at go1.17: replaced by the C-to-Go converted linker in 1.5 and by cmd/link thereafter → continues as Relocation-free pclntab and funcdata (offsets instead of relocations) and Per-platform object formats and internal linking (Mach-O, PE, XCOFF, wasm, ppc64, riscv64, loong64) (1.18+)
Design. The Go linkers started in June 2008 as one C program per architecture (6l for amd64, then 8l for 386 in 7d443bb67a, 5l for ARM imported from the Inferno distribution in db3a21d7c6). Each had its own obj.c (object reader), asm.c (container writer), span.c (instruction encoding and span-dependent jumps), pass.c (stack-split insertion, branch following) and optab.c. The object format was the Plan 9 style .6 file: text-ish records with a $$ delimited export-data section (4958d41d3b changed the delimiter to \n$$) followed by instruction records. Libraries were read through libbio (a570eaabcd, bfc70d0f0a).
Over 2009–2011 the portable parts were progressively hoisted out of the three copies into src/cmd/ld: Go-specific loader code (b87e3e8b7f, “ldbody”), portable object and library bits (c2874976e4), the XputY writers (fb55941539), asmlc (30edda690f), symbol table and ELF generation (d37c572ad5, 1.1: one asmbelf in elf.c replacing three nearly identical copies, plus a single genasmsym). A new flag parser matching package flag semantics replaced the C-style clustered flags in the linkers and compilers (cbbc6a102d, 1.1); -V (b4fb00b69b), -L (48974f553d, e6bc5bf5af), -r/rpath (e6443f6255), -I interpreter (9040da9e40), -B build ID (32316bba5b), -X string-variable setting (267f56e10b parsed, e3755434b8 implemented), -w (64b497c62a), -s eliding the Go symbol table (10d0dffd76) and -H as an enum with symbolic names (0cf6f8c096) appear in this period.
Layout changed in several steps. Initially the linker laid out the whole program as one instruction list; 9c20485268 (6l) and eb6f683691 (8l) switched to function-at-a-time layout and reworked span-dependent jump resolution: forward jumps resolved at their targets via a comefrom list, jumps marked small or big with only small to big transitions, so most functions need two passes and the maximum dropped from 5 to 3. 01df088f8d split address assignment into a separate pass (code laid out before data) and e5e9211071 introduced sub-symbols, symbols laid out inside a larger one but addressable directly (first used for the Mach-O pointer array). Archive loading changed from symbol-driven to loading whole archives (49084db386), earlier having been tuned with a heuristic (a3c4faf83f) that was dropped again in favour of letting the dead-code pass discard unused objects (fc8dca9dac). Duplicate loading of the same object was stopped (69e244a104), only one object file (package main) is accepted on the command line and the rest are pulled in via the package path (3b1a0355b6, 00f4c6a1b5), and the link fails if the top-level package is not main (11e254e62c).
Timeline.
- Go 1.0 — Done:
- 2008: ELF header and
.shstrtabhandling for 6l (6a659ebf13,2c52881a85,67e141b637); symbol table and line/pc information in6.out(e9c9c9ace5), Mach-O debug support (073486c3ed); segmented stacks in 6l (1f6828bcdf,b987f7a757);.gosymtab/.gopclntabas ELF note sections and text starting at 4MB (c3ca056143); archive/__.PKGDEFhandling and a pkg search directory (12c81a0ce8); dynamically linked output on darwin (cfb9425460). - 2009: 8l generating Mach-O and ELF (
7d443bb67a); 5l ELF support (4e1896a1b5,3c7a1ef208); dead data and code actually eliminated, not just symbols (8d8225d529,9cba9c8890); elf64 scaffolding (1593b1fc23,e86dcf1603,b63d40a53e) and dynamically-linked 6l output with.interp(3d42e691b9,304b7954e8); dynamic loading / FFI pragmas feeding cgo (bd4161fcba,165a99038f,133a158bd8,732f3919ab,9e0ae94e72); FreeBSD touch-ups (60b1a17b9e),-dmeaning flipped; hash table for dynamic symbols (dc43b74eb1). - 2010: Native Client steps in 8l (
4a4ec23870,eae0906bed,7390aa0102); Windows PE output for 8l (977e19d69f,3b1a71862e,30f981d980) and Windowsdynexport(a3120f67ca);-sbinaries kept valid (000ab98df6); rodata moved into the text segment (698fb4f192,b7e9e9188a); function-at-a-time layout, pclntab/symtab as ordinary rodata (19fd5c787f, below); ELF/Mach-O object reading for cgo (4d8d6d5cda,cf42a45587,f5690004c2); weak symbols (d94bf76239); OpenBSD (ffa14e849c,26d9c804f8) and netbsd/ELF note (420fe22921,5842f7e46a) support; 6l moved to PE for windows/amd64 (3aec5516ed). - 2011: Plan 9 native-build fixes (
9baaa6f742,10b5519d3a,5ea4d73b17,4c6280b0f1); rpath dropped (afac1c2654); Mach-O linkedit rework socodesignworks (78ad19f214,4566868b41,280d85a80b); alignment of ELF data sections (bf43161c00);-X(e3755434b8);GOROOT_FINALrewrite of file names (6e3a7930eb); fix of NOSPLIT stack-overflow near segment size (9e5db8c90a). Left open: the pclntab/symtab encoding was still the Plan 9 format with big-endian values (see 1.1); the three linkers still shared only part of their code; no way to link with the host linker.
- 2008: ELF header and
Go symbol table, pc/line table and function metadata (gosymtab, pclntab, findfunc)
Span: Go 1.0 → Go 1.5 · Status at go1.17: still the Go 1.2 pclntab design (contiguous per-function tables plus functab) with a findfunctab bucket index (1.5) and offsets-based types (1.7); the pclntab itself is rewritten in 1.18 → continues as Relocation-free pclntab and funcdata (offsets instead of relocations) (1.18+)
Continues in: pclntab, funcdata and pc tables (Go 1.12 → Go 1.17)
- design doc Go 1.2 Runtime Symbol Information cited in this thread's commits
- motivating issue #6853 all: binaries too big and growing referenced by 4 of this thread's commits
Design. The binaries carried two tables the runtime and debug/gosym read: gosymtab (Plan 9 style nm symbols, used for names) and gopclntab (per-function pc-to-line and pc-to-SP-delta tables). Early on they were loaded by a special mapping to a high address (0x99000000, “pad Go symbol table out to page boundary” fe43325b30, “move mapped symbol table lower in memory” 8ec9ffc742). 19fd5c787f (2010) turned both into ordinary rodata symbols so the information is available on systems that cannot do the magic load (NaCl, ARM QEMU, Linux without highmem) and removed Windows special cases. In 2011 the ELF overlap of sections that owned the same bytes was fixed so strip was happy (cf143e9dbf, ba2e3af177).
A large share of binary size was symbol names for compiler-generated data. 7a09a88274 (2011) deleted the names of type.*, string.* and go.string.* symbols from the symbol table, aggregating them into contiguous blocks, shrinking godoc from 9.15 MB to 4.29 MB (3.40 MB without DWARF). Go 1.1 changed encoding for host-linking: 4e2aa9bff0 stored symbol values as native-endian (the table begins with a magic saying which) so a host linker can fill them via relocations, and c8dcaeb25d repacked the table with pointer-sized host-endian addresses and varint-encoded other values.
Go 1.2 is the structural change: “new in-memory symbol table format” (5d363c6357, design go12symtab) and a “contiguous pcln table” (c3de91bb15). pclntab became a single table with a header, a sorted functab of (entry pc, funcoff) pairs, a Func record per function containing offsets to pcsp/pcfile/pcln/pcdata tables, a file table and a funcdata array. GC information moved from pseudo-instructions in the instruction stream to FUNCDATA (48769bf546), args pointer bitmaps (4e0a51c210) and accurate args/locals sizes in bytes (f466617a62) were added. In 1.3 liblink’s new object reader/writer introduced a pcdata iterator and removed the linker’s “line number stack” and “path fragments” (a9f6db58ce). In 1.5, 63116de558 added a build-time lookup table (the later findfunctab) to find the function containing a pc, 13.35% faster stack copy and ~0.5% of text size, with a 16-byte minimum function size; 6dd31660b0 and 214c7a2c43 stopped putting container symbols in functab/symtab.
Timeline.
- Go 1.0 — Done: symbol and line tables added (
e9c9c9ace5); stack offset table marker per TEXT (f997bc6eb6,ddba96aed8) later replaced by the in-memory symbol table (3aa063d79c); Go type info in the symbol table (43f29e64a6,311c0b4807: archive size +70%, binary +30%), later with the gotype pointer carved out (57a9bd0ee3); symbols for type/string/go.string omitted (7a09a88274); pclntab/symtab as ordinary rodata (19fd5c787f);-selides the Go symbol table (10d0dffd76); ELF symbol table fornm/oprofile (96ea081c04,70798eaad6); symbol table letter correctness (c2cd0d09c2) and sizes preserved through data layout (f0ab14647a). Left open: encoding fixed to big-endian Plan 9 format. - Go 1.1 — Done: host-endian symbol values (
4e2aa9bff0), repacked varint format (c8dcaeb25d), sorted-symtab check in the runtime (40ed753ebd), fix of scope of static objects in the table (7ac20853fc), accurate args/locals (f466617a62), moved symtab and ELF generation to portable code (d37c572ad5). Left open: the commit message of5d363c63571.2 notes the old symtab and pclntab were not yet moved back into an unmapped part of the file. - Go 1.2 — Done: new in-memory symtab (
5d363c6357), contiguous pcln table (c3de91bb15), funcdata for GC info (48769bf546), argument pointer bitmaps (4e0a51c210), file table shrunk (d011f0aa89),-sno longer discards pclntab (0d85d5423b),-vreports pclntab/funcdata sizes (d43ee1b714). Left open: linker still emitted per-function pcln from instruction lists. - Go 1.3 — Done: line number stack removed and pcdata iterator added with liblink (
a9f6db58ce), “Plan 9 symbol table” removed (964f6d3ec4),gcargs/gclocalsdropped from the table (2541cc8197), pclntab file numbers fixed (91e3681105), pclntab generated by the Gocmd/linkskeleton (a453f28c70), PE symbol table populated with Go symbols (fce4f0484c). - Go 1.4 — Done: linker-defined symbols (
runtime.pclntab,runtime.etextetc.) moved into package runtime (2c110a11e0); PC deltas in DWARF line table fixed (507afa68c5). - Go 1.5 — Done: faster
findfuncvia a link-time lookup table (63116de558), poisonedpcln.frame(a0ac493852), missing pcdata tables recorded correctly (434e0bc0a0), filetab slice size fixed (cf125a36d51.6, 1.6). Left open: the pclntab still contained pointers requiring relocations in PIE/shared builds (visible in 1.7 offset work). - Go 1.6 — Done: duplicate symtab entries for global functions removed (
ebafc80eba); fixed filetab slice size (cf125a36d5). - Go 1.7 — Done:
typelinksstored as offsets (f028b9f9e2), padding between functions rather than inside them (5701174c52), short type symbol names (f81ae3b22c). - Go 1.8 — Done: number-of-files entry in gopclntab fixed (
bb12894d2b), stackmaps no longer padded to 4 bytes (303b69feb7), funcsym symbols hidden (7c3fc4b8e4). - Go 1.9 — Done:
runtime.epclntabforced into.textfor the PE symbol table (99e1e4f657,b5e964cc1c).
Stack splitting and the linker’s nosplit overflow check
Span: Go 1.0 → Go 1.5 · Status at go1.17: nosplit chain checking is still done by the linker (stkcheck in cmd/link), reworked in 1.19 → continues as Nosplit stack checking and stack constants (1.19+)
Design. In the earliest linkers, pass.c inserted the prologue that calls morestack for segmented stacks (“segmented stack” 1f6828bcdf, b987f7a757, then 5l support 83632c7359, 1ac2cfc720 with argsize passed). The comparison was made unsigned (c2eb89af92), large frames were supported (9e99383df5, “frame size > StackBig”), the frame size is passed to morestack when needed (1fa4173444), and -K checks stack underflow (f74ec00278, fixed e642503d6f). A NOSPLIT function skips the prologue, so a chain of nosplit calls must fit in the guaranteed red zone; d9fd11443c (2011) added the linker check (“nosplit stack overflow”, printing the chain with the assumed bytes on entry), which immediately found runtime functions (assertI2T, selectgo, newselect) on amd64 with unexpected frames and ARM functions wrongly tagged NOSPLIT (e.g. recursive printpanics).
Timeline.
- Go 1.0 — Done: the above plus the detection of NOSPLIT overflow (
d9fd11443c), a fix for near-segment-size stacks (9e5db8c90a), frame-size-aware morestack on 5l/8l (1fa4173444). Left open: nothing noted. - Go 1.1 — Done:
-Zflag to zero the stack frame on entry for 6l/8l (b9da27bed2), no-ops separating zero-stack funcs from nosplits (2e73453aca), function entry aligned on an architecture-specific boundary (16 on x86, 4 on ARM) for run-to-run consistency (8820ab5da9: e.g. Fannkuch11 -5.85%). - Go 1.2 — Done: large-frame stack split for the preempt check fixed (
031c107cad) and the stack-split code refactored (1a6576db34). - Go 1.3 — Done: the nosplit chain check was disabled by the liblink move;
a9f6db58cesays a future CL must reenable it (issue 6931). - Go 1.5 — Done: check restored for power64 (“detect, fix nosplit overflows”
ed68c7df55) and made flexible and fixed incmd/link(ca19e55f9a,767e065809); the size of the fixed part of the stack centralized (d66f6c2c861.6, 1.6); PE stack reservation unified with the runtime (da11a9dda3). Left open: nothing noted. - Go 1.9 — Done: 64-bit Windows always reserves 2MB stacks, independent of cgo (
c2c07c7989).
Dead code and data elimination
Span: Go 1.0 → Go 1.8 · Status at go1.17: reachability-flood plus method pruning conditioned on reflect.Value.Method/Call use, as designed in 1.7 → continues as Dead code elimination and reflection-aware pruning (1.18+)
Continues in: Dead-code elimination and method pruning (Go 1.10 → Go 1.17)
- motivating issue #6853 all: binaries too big and growing referenced by 4 of this thread's commits
Design. The first linkers tracked reachability with a pass that discarded unreferenced symbols and then (after 9cba9c8890, “eliminate dead code, not just the symbols”) really removed their code from the instruction lists; 8d8225d529 turned GC of unused data/code back on. 7b29dbb866 stopped emitting unreachable data symbols. fc8dca9dac replaced the heuristic of not pulling objects from archives purely for their init function with “pick up everything and let the dead code/data eliminator throw away the parts that weren’t useful”. Line numbers survive removal of dead functions (47e27758db, fda0e78b23). The Go 1.3 skeleton of the new Go linker had a worklist-based dead.go (7dcc652f10).
The substantive 1.7 change is 862b9ddda7: previously all methods of reachable types were kept to support reflect.Value.Call. The linker now looks for use of reflect.Value.Call (and Method, e2836935bb) and, if absent, prunes methods: a method is kept if it is directly called or matches the signature of a method of some used interface. Whether a method body is kept is decided by the relocation from the receiver’s *rtype to the method’s *rtype; d06b0db5bd also prunes the funcType, and 44d3f89e99/f2772a4935 removed some method type data (second method type computed at runtime). The go.plugin.tabs symbol is marked reachable only in plugins (67b2927990) and DWARF function symbols marked reachable (aad06da2b9). 6f31abd23a (1.8) added R_WEAKADDROFF, a weak variant of R_ADDROFF used for the ptrToThis field so the pointer-to type is kept only if otherwise reachable.
Timeline.
- Go 1.0 — Done: dead data and code elimination (
8d8225d529,9cba9c8890,7b29dbb866), object-granularity loading replaced by “load everything and flood” (fc8dca9dac,49084db386); struct field tracking experiment is separate (1.1). Left open: nothing noted. - Go 1.1 — Done: user-visible
GOEXPERIMENT=fieldtrack(3d40062c68), a toolchain-time experiment in which fields taggedgo:"track"are reported by the linker if reachable; line-number fix for itsUSEFIELDpseudo-op (a14e143c211.2). Left open: “may be removed at any time” per its message. - Go 1.3 — Done: dead code pass in the new Go linker skeleton (
7dcc652f10); call graph dump restored in Cld(1e2a61aee1). - Go 1.4 — Done: symbols created by
-Xno longer automatically marked reachable (f6fc14094a). - Go 1.5 — Done:
Etextpmaintained when dead code is removed (8e9c6431f0); c-archive uses the usual flooding (46b4f675bf); gcbits symbols elided individually (0acecb7164); compiler lets the linker drop string headers when not needed (e8f2eb4349). - Go 1.7 — Done: method pruning (
862b9ddda7,e2836935bb,d06b0db5bd), unusedgo.string.hdrsymbols (1.8,80a034642e1.8),-dumpdepflag for the dependency graph (d224e98d9a) and Allsym no longer contains.dupsymbols (956e9e6c83). Left open: method pruning is all-or-nothing onreflect.Value.Call. - Go 1.8 — Done: weak ptrTo relocation (
6f31abd23a), mark DWARF functions reachable (aad06da2b9), plugin tables only reachable in plugins (67b2927990).
DWARF generation in the linker
Span: Go 1.0 → Go 1.9 · Status at go1.17: function DWARF generated in the compiler (1.8) and assembled by the linker; types/globals still in the linker → continues as DWARF quality for debuggers (types, variables, trampolines) (1.18+)
Continues in: DWARF generation in the linker (Go 1.10 → Go 1.17)
Design. DWARF began in 2010 with ca9098ee94 (“Dwarf output for 6l. Part 1, general scaffolding and pc/lc sections”), then b433552ebc (8l in ELF), 728003e340 (Mach-O), later PE (f1751e7de5, 34ac4ec30c, b256358008), frame info (eb572e0174), global/local variables and type info (4228e62259), types of builtin structured types with gdb pretty-printing (9a71bb00bb), gdb support for interfaces and goroutines (7a4ce23d65) and runtime reflection type decoding in dwarf (2aa6cf3e7e, 63544c4754). A language code reserved for Go (d2d650bdc9) and -w to disable (64b497c62a).
Go 1.4 made the output closer to what debug/dwarf parses and added Go-specific type info (125d1e9269, e1821692ca), array types as count rather than upper bound with varint encoding (cddad8affe, 74c9cc8394, 79b106ec62), and fixed .debug_frame off-by-ones (7b0ee53429, 060b24006a). In 1.3, liblink’s rewrite made the linker write a single compilation unit instead of one per package (a9f6db58ce).
The big design change was in 1.7–1.8: a037c73ccf (1.7) generated DWARF via symbols instead of direct buffer writes; 795ad07b3b (1.8, a copy of an earlier CL by Ryan Brown) moved per-function DWARF (DW_TAG_subprogram, variables, location lists) into cmd/compile through the new cmd/internal/dwarf package, costing roughly 0.1s on go build cmd/go (2.25s to 2.36s) while the linker only stitches the compiler-provided symbols together. Other 1.8 steps: more efficient line table encoding (8f65379f94), types referenced in ptabs/debug_info (9f5673d930), no truncated fieldname locations (d80e8de54e). In 1.9: external linking writes DWARF relocations and sections on Windows (aada49038c, 29f061960d), lexical blocks for locals (2ad41a3090), DW_AT_producer (41d0bbdc16), DW_AT_go_embedded_field (b1868cf107), specialized runtime._type variants exported (073247f11f), and a Mach-O DWARF segment that dsymutil accepts (18e77673d8, b28f2f7399, 23dc3633ca).
Timeline.
- Go 1.0 — Done: initial DWARF for 6l/8l on ELF, Mach-O and later PE (
ca9098ee94,b433552ebc,728003e340,f1751e7de5); frame info (eb572e0174); gdb type support (9a71bb00bb,7a4ce23d65); filenames in the.debug_lineheader (d164b6081d); Mach-O DWARF before linkedit (4566868b41); relocated DWARF for external objects (below). Left open: ARM had no line info until 1.1. - Go 1.1 — Done: ARM line numbers (
0cb04168d3); noDW_AT_typeforunsafe.Pointerto match gcc (a891b916bd); relocated DWARF in host-object mode and ELF-only when external (40d356e9ab,825b1e1591,2a1ca145cf). - Go 1.2 — Done:
.debug_framerelocations in external link mode (28bbc6c27a); fieldtrack line fix (a14e143c21). - Go 1.3 — Done:
.debug_lineoff-by-one fixed (15ec569ba9), opcode base 10 (ebe1eb6537),map.bucketdata field removed (059c10b552), lldb-friendly Mach-O addresses (a4a1fadfa2), single compilation unit (a9f6db58ce). - Go 1.4 — Done: Go-specific type info (
e1821692ca), array encoding (cddad8affe,74c9cc8394,79b106ec62), frame table fixes (7b0ee53429), struct output change (125d1e9269). - Go 1.5 — Done: DWARF in external link mode on darwin (
8b83306cf2,be59731d9f), combined dwarf file location (5659964d67), correct.debug_frameon RISC (fd392ee52b), no dwarf on darwin/arm (58578de0dc). - Go 1.6 — Done: aranges padding on 32-bit (
68aa7fb636), no warnings on unnamed.debug_strsymbols (26263354a3). - Go 1.7 — Done: DWARF from symbols (
a037c73ccf), own hashmap replaced (c0f7195d62), PC table without seeking (3601a1e77d), framepointer offsets fixed (adff422779), hide go.dwarf symbols (9658b7ef83), DWARF disabled without symtab (e6181eb9e1),.defsuffix collision (6901b08482). - Go 1.8 — Done: functions’ DWARF generated in the compiler (
795ad07b3b), compact line encoding (8f65379f94), ptab types in debug_info (9f5673d930). - Go 1.9 — Done: see Design; also
.debug_gdb_scriptson windows (1d44c4e378),go.dwarflinkage unification viadwarf.Varslices (1a7582f5e9). Left open: nothing noted.
External (host) linking and cgo
Span: Go 1.1 → Go 1.9 · Status at go1.17: internal linking only for cgo-free or stdlib-cgo cases; external linking required in many cgo cases → continues as ELF/relro/hardening and external-link policy (1.18+)
Continues in: Host object loading and external linking (Go 1.10 → Go 1.17)
- design doc Go Execution Modes cited in this thread's commits
- motivating issue #4069 cmd/ld: invoke host linker for Windows cgo build referenced by 5 of this thread's commits
Design. Go 1.1 added the ability to produce a host object file and let the system linker finish the link (issue 4069). The design stages: 4e2aa9bff0 (native-endian symtab), d37c572ad5 (portable ELF writer), 0cb0f6d090 (-hostobj: write a host object file instead of an executable, so 6l -hostobj -o x.o x.6 followed by ld -e _rt0_amd64_linux x.o works), 60f783d92b (host linking on linux/amd64, go test -ldflags -hostobj misc/cgo/test passes), 30e29ee9b6 (TLS relocations in the .o so gcc picks m and g offsets), b4f3533c92 (-hostobj replaced by -linkmode), then extension to darwin (b83d4af330, df6072b41c, 3b85b724c5), 386 (7663ffcae6, cd94cabad6), FreeBSD amd64 (18f926aab3), and 3197be4807 (GO_EXTLINK_ENABLED=0 defaults to -linkmode=internal). 9182c364aa added -extld and -extldflags, e982ecacd1 -tmpdir, 696901204f always uses external for -linkmode=external. Relocation reading from external objects: ELF/Mach-O/PE object readers (4d8d6d5cda, cf42a45587, f5690004c2) existed since 2010 and were extended for GOT relocs and scattered relocations (e607380ff6, 9de61e7c8c, 65b782e951).
Later stages: 1.2 ARM external linking (45233734e2, merging runtime.g/m into a runtime.tlsgm TLS symbol and using MRC), 1.3 FreeBSD/ARM (d31d19765b), TLS relocations for ELF in external mode (6f8b120869) and importing runtime/cgo for external link (f85600859d, 1683dab725); 1.5 external linking on windows 386/amd64 (04642e92ac, e7df053977, stdcall decoration 6112e6e404), arm64 (f55b2a11f4, 7a96ecde4d), darwin/arm64 (4fd9a3fdbb) and the policy that cgo forces external link on darwin/arm (1083715b7f) and linux/arm64; 1.6 ppc64 (c155e59062); 1.7 mips64 (073d292c45, dcd613862b); 1.8 mips (a3b670e333) and trampolines for external ARM (c7e855658d); 1.9 PIE uses external by default (ab067cde34).
Timeline.
- Go 1.1 — Done: user-visible external linking mode for cgo on linux/amd64/386, darwin and FreeBSD/amd64 with
-linkmode,-extld,-extldflags,-tmpdir(commits above);-shandled for ELF executables and passed to gcc (f2c3122307,e50e4f7ec11.2 in 1.2);-rdynamiconly for ELF (47ec6e23d2);-rpassed as-rpath(8877a2dfee); relocation range checks (700a126c64,5c0d782ab8);cgo_export_static/dynamicsplit (fb59aed60b,5a529b61aa). Left open:-hostobjand 386 steps “disabled” at several checkpoints; Windows and ARM unsupported (30e29ee9b6says so). - Go 1.2 — Done: ARM external linking and
-sharedin terms of external linking (45233734e2);.tbssfor dynamic internal linking (a848475324); duplicate static symbols in COFF/Mach-O (9add729a1f); PE label symbols (c742179c49). - Go 1.3 — Done: FreeBSD/ARM (
d31d19765b), ELF TLS relocations (6f8b120869),-Qunused-argumentsfor clang (1b42d25ae3), no-rdynamicwith-static(827aab07b8),runtime/cgoforced in external mode (f85600859d), negative 32-bit address relocation aborts (661298358c). - Go 1.4 — Done: hostobj sections marked noptr (
65e2b6f847), diagnostics for Go calling C (0bb14d74f1), clang integrated assembler disabled in cgo (cf9558c8ab), ignore.Linfo_string(82a5e95ccc). - Go 1.5 — Done: the cases above plus
-Bpassed to host linker (e9fcd6ed2b),.bssfrom external objects into real.bss(1c0c611fc2), ppc64 R_CALLPOWER (6f755f2f8f,005ba4db82),-vshows external linker output (714291f2d8),-sfixed with external linking (34846aef78). - Go 1.6 — Done: concurrent object copy for external linking (
cf73357e37),-z relro(9a6a8a0586),-z nodeletefor shared libs (bd7de94d7f), use a copy of libgcc.a for internal linking and-libgcc(754f707f5f,8d2f60f8a8),-extar(1aab7b9626), symbols from.aoverride.so(cd9fc3ebfb). - Go 1.7 — Done: deterministic output: pass
-gno-record-gcc-switchesand-fdebug-prefix-map=$WORK=/tmp/go-build, empty ELF NAME symbol (5bbb98df09);-znocopyreloc(3c8d6af8e0), gold on arm64 (a3c92c9db1),-no-piefor race (3a72d626a8,cb97fd7741,2a6544b604), no PIE with sanitizers (2c1791b13b),-z relrofor PIE (45f39fb467), mips64 (073d292c45). - Go 1.8 — Done:
-no-piefor exe (53aec79ce0), delay mingwex/mingw32 (dfbbe06a20), mips (a3b670e333), ARM external trampolines (c7e855658d), plugins force external (413afcafbf), hide ppc64le trampolines when external (ec77b8e09c), STT_COMMON (45f759500e). - Go 1.9 — Done: PIE external by default (
ab067cde34), always show external linker output (4d7a5edb7e), PE adjustments for external linker (e31144f128,a7e2556255,6db4d92e4c,e94b9d4143),-snot passed to host linker on macOS (371c83b594),-no-piefallback to-nopie(9e859d5e9c), ppc64le trampolines with external linking (738739f565).
Build modes: -shared, c-archive, c-shared, shared, PIE, plugin
Span: Go 1.1 → Go 1.9 · Status at go1.17: all six build modes implemented; internal PIE on linux/amd64 exists but external is the default → continues as ELF/relro/hardening and external-link policy (1.18+)
Continues in: Build modes: PIE, plugin, c-shared, c-archive, shared libraries (Go 1.10 → Go 1.17)
- design doc Go Execution Modes listed in Go: A Documentary
- discussion Proposal: Shared libraries in Go, take 2 (+linux/amd64 implementation) golang-nuts · cited in this thread's commits
- discussion Re: [PATCH] RFC: go shared libraries support (Linux/x64) golang-nuts · cited in this thread's commits
Design. All position-independent modes rest on generating PIC code and dynamic relocations. 3bdeaf2a64 (1.1, Elias Naur) added -shared to 5l/6l: PIC output with RIP-relative addressing and dynamic relocations. 45233734e2 (1.2) implemented -shared in terms of external linking. In 1.5 the linker gained -buildmode: 2a5f88d850 c-shared as an alternative to -shared; 8da54a4eec finds _rt0_<GOARCH>_<GOOS>_lib and adds it to .init_array so the runtime initializes at dlopen; f616af23e0 calls runtime.addmoduledata from .init_array so shared objects register their moduledata (arm64 4e3a1e409a, arm 1b4d28f8cf, ppc64le 1ccefcd1b8, 386 09d7de8d61 in 1.6); 449969a4ac and ced7ffe95b c-archive on linux and darwin, using ar (-extar in 1.6, c9778ec302 table of contents in output); b2f95a167a c-archive forces external link; 00bc19e996 -buildmode=shared and 75c0566b55 -linkshared on amd64, with 7556948ebf/de6d5b0801 writing the list of packages in the shared library to an ELF note, bcc1870fcf the libraries linked against and 65518032b9 an ABI hash note used by 77fc03f4cd to abort on a shared library ABI mismatch at start.
In 1.6 the modes spread: PIE on linux/amd64 (b7e4de4b1e), ppc64le (2ffdbd7ce4), android/arm (26205cb3c0); shared on 386 (3ae4dc8a75), arm64 (51586aadfb), ppc64le (9a476028c0); c-shared on 386 (cbc55971e2), arm64 (ab7e82ef3b), android/386 (bda885511d), darwin/386 (9efc46f1cb); .data.rel.ro for read-only relocated data (2c2cbb69c8, 5cbca8d84b); local.moduledata naming (7f9e443d9c); lazy PLT resolution disabled (69a99ccc62) and functions resolved locally (c34fb3cfc6).
1.7 delivered deterministic builds and PIE fixes, c-archive on windows and darwin text relocations (ed8f0e5c33, aea4222f67), itablinks as a slice in moduledata (79688ca58f), types kept together when dynamic linking (2d5688952d). In 1.8: eced6754c2 plugin support on linux (collect exported plugin symbols from the compiler into moduledata, treat any binary that imports plugin as dynamically linked), 73e7a569b4 darwin/amd64 plugins, 03da2690c9 plugin versioning (hash of every imported package, local and exported versions; the first exported symbol per package becomes canonical), 9da7058466/eb4e17b73b full plugin path in symbols and visibility analysis, d0e408334b no-export plugins, 5923df1af9 compiler table of main symbol types, 8eb9fdaa01 type symbols for ptabs; internal PIE: rel.ro segment (276803d611) with PT_GNU_RELRO (d9d1399eea), dynamic relocs (81ee93679d, 5940a007c1), TLS IE-to-LE (44ee2b00db), ET_DYN (04b4dbe1f0), enabling (1a42d8fbd6) but then d185cc3b64 disabled internal PIE “for now” and ab067cde34 (1.9) made external the default for PIE. d92a3606f5 (1.8) builds c-archive position independent on ELF; 361d2738d5 removed the -shared flag in favour of buildmode.
Timeline.
- Go 1.1 — Done: user-visible
-sharedPIC output for 5l/6l (3bdeaf2a64). Left open: 8l only had dummy support. - Go 1.2 — Done:
-sharedthrough external linking on ARM (45233734e2). - Go 1.5 — Done: user-visible
-buildmode=c-shared,c-archive,sharedand-linksharedas above (2a5f88d850,449969a4ac,ced7ffe95b,00bc19e996,75c0566b55,cc6554f750for darwin/amd64 c-shared,8da54a4eec,f616af23e0,7820d27049global vars of types from other modules,9f5d0bff41R_PCREL to function in another library). Left open: arm/arm64/ppc64le/386 not yet supported. - Go 1.6 — Done: user-visible PIE (
b7e4de4b1e,2ffdbd7ce4,26205cb3c0) and the architecture expansion listed above;-msanoption (7b4b96f248); sections.data.rel.ro(2c2cbb69c8). - Go 1.7 — Done: c-archive on windows/darwin text relocation fix (
ed8f0e5c33,aea4222f67), mips64 external (073d292c45),-linkobj(feb6131b1a, compiler flag splitting export data from link data), PIE/sanitizer interplay (2c1791b13b). - Go 1.8 — Done: user-visible
-buildmode=pluginon linux and darwin/amd64, plugin versioning, internal PIE groundwork then disabled (commits above);cmd/distand plugin forcing external linking (413afcafbf); darwin dynlink support (e9fddf8f86,ab5a2173f9). - Go 1.9 — Done: user-visible PIE external by default (
ab067cde34), plugins on s390x (a6b480bc07). Left open: internal PIE remains disabled.
liblink, the object file format and the move into the compilers
Span: Go 1.3 → Go 1.9 · Status at go1.17: cmd/internal/obj + goobj object format with symbol references, contiguous data and per-package indexes; replaced in the 1.15–1.17 era by the new object file format → continues as Relocation-free pclntab and funcdata (offsets instead of relocations) (1.18+)
Continues in: New object file format and the loader (dev.link) (Go 1.14 → Go 1.16)
- design doc Go 1.3 Linker Overhaul cited in this thread's commits
Design. 7d507dc6e6 (Dec 2013) created liblink, “a new library based on linker code” with include/link.h defining a Link structure that holds all linker-relevant state replacing global variables, with the architecture-independent parts of the old linker moved in. The next step, a9f6db58ce, moved instruction selection and layout into the compilers and assemblers: “new object file reader/writer (liblink/objfile.c)”, removal of the old writing routines, a pcdata iterator, removal of line-number stack and path fragments, dwarf writing a single compilation unit; the linker’s nosplit check was temporarily disabled. Linker input became already-assembled machine code with relocations (8d39e55c65 removed arch-specific constants from the file format, cc08d9232c added a leaf bit, 468cf82780 fixed a hash lookup keyed on name+version that compared only the name). ca85d572d6 (1.4) used pc-relative addressing for all amd64 memory references so code is position-independent.
In 1.5 the C code was converted to Go with c2go (see next thread) and cmd/internal/obj became the Go incarnation of liblink. In 1.7 the format was reworked for size and speed: strings no longer NUL-terminated or aligned (735e5a483c, 08c2cd8469), string data deduplicated for long strings and merged into one symbol (afd835434c, a0232ea0dd, 0c84c4f19c, then replaced by compiler-side dedup 21af2d39c2), edde955d7f arbitrarily sized string data, symbol references as an index table (3648d2d4cd: pkg/linux_amd64 shrank 61MB to 43MB; LinkCmdGo -15.22%, LinkJuju -10.16%), all symbol data in one contiguous section (36d5650a1c: -3.3%/-4.3%), lengths recorded (7f067c87d8), static data written directly into LSyms instead of ADATA (7c18f8cdc3), __.(GO)?SYMDEF entries dropped (67dbde0d71), object version bump (6b019e216b), -linkobj two-part objects (feb6131b1a), common architecture representation (c6e11fe037). The 1.9 bumps the magic to go19ld (7c84dc79fd) after another format change, moves LSym type fixing to the compiler (18fb670e5e), adc80c0665 refactors Linksym, and 1e3570ac86/be2ee2a4b4 create cmd/internal/objabi with linker-only symkinds moved to the linker. 3e7d4f11c0 (1.5) moved debug/goobj to cmd/internal/goobj; a99f812cba (1.8) taught cmd/objdump to read .o files.
Timeline.
- Go 1.3 — Done: liblink (
7d507dc6e6), move of instruction selection and layout out of the linker (a9f6db58ce), leaf bit (cc08d9232c), arch-constant removal (8d39e55c65), fix for hash collision (468cf82780), ARM TLS fix (b377c9c6a9),-packflag in gc (b022334029), 2G archives (cdc556556c). Left open: per-symbol name+version references repeated through the file. - Go 1.4 — Done:
%^formatting and C_* constants moved (d69d0fe92b), pc-relative amd64 addressing (ca85d572d6). - Go 1.5 — Done: goobj into cmd/internal (
3e7d4f11c0), stricter object decoding (1082e2390e), export data may be$$or$$B(28ef4c38c81.6, 1.6), 0-bytes allowed in export data (4e777c8eff1.6, 1.6). - Go 1.7 — Done: the size/speed changes above; Biobuf code extracted to
cmd/internal/bio(4b7e36cdfe,8f2edf1199). - Go 1.8 — Done: objdump of
.o(a99f812cba), weak relocation (6f31abd23a),SymKindtype (3877f820a6). - Go 1.9 — Done: magic bump (
7c84dc79fd), objabi extraction (1e3570ac86), symbol kinds trimmed (be2ee2a4b4,d2a9545178,4aca8b00ff,b2363ee9f6) and attribute bits (a69222d949,f72534ab6a),Setuintxx/WriteIntunified (89a355c930),dxxxtodxxxLSymrefactors (4d600b8e5f,bea8ffdbb1,c2b4fb5a3b,976a5ce1a8,92607fdd30).
From C to Go: cmd/link
Span: Go 1.3 → Go 1.8 · Status at go1.17: cmd/link written in Go, still based on the 1.5 c2go output, being re-architected in the 1.15–1.17 era → continues as Linker performance and memory (1.18+)
- design doc Go 1.3 Linker Overhaul cited in this thread's commits
Design. Two parallel Go linkers existed in 1.3–1.5. 146897b031 (Jan 2014) was a “skeleton of linker written in Go” (cmd/link, with prog.go, layout.go, load.go, dead.go), followed by Mach-O formatter (8449863d31), dead code removal (7dcc652f10) and pclntab generation (a453f28c70). That effort was an independent rewrite (cmd/link) that never completed; 2a141dedc4 (1.5) renamed it to cmd/newlink, and fb880b8a03 deleted it in 1.7 together with removing weak symbol support (9f4a359fc2).
The linker that shipped was the C one converted mechanically: 5f1efe738b made cmd/ld “a real library”, 1f9dbb60ef (“convert from C to Go using rsc.io/c2go’s run.ld script”: 25 files, 17,636 insertions in cmd/internal/ld) and dca5f2e9b3 (“replace C code with Go code”: 56 files, 4,256 insertions vs 19,460 deletions) moved cmd/new5l etc. into place on 2015-02-28. Subsequent 1.5 commits tidied the generated Go (717cb74907, 665c3da33a, 8c3cc58d0c, d7b10060b6, 2692f48330, c776592a4f, 91938fd1ca putting type decoding in decodesym.go), consolidated adddynlib/adddynsym (004706099d, 4cfff271c2) and then cmd/internal/ld was moved to cmd/link/internal/ld with per-arch directories (cf932cd897, the “retire architecture letter” commit, and flags: 12795c02f3 -X name=value, aefa6cd1f9 dead flags, a2c50ece2b -h, ac1f48e2f6 -buildid, 9ae3c560b9 for non-ELF).
The 1.7 and 1.8 releases were an internal Go cleanup: Allsym as a slice (4b92cd4ec1), LSym attributes packed (a3c258a567), Autom linked list to slice (5be961a352), textp slice (7d56215bcb), Minalign in ld.Arch (90cb1fdaf2), the removal of the global Ctxt (4338e5a891, 2f783c3458), Bso (64f5023e6f), Cursym (1d3fae461c), Linklookup (25d094034b, 324f6ab48c), Symbols split from Link (d284d4ff92, d41a7f77c5, b6324ef5ff), standard flag package (0a15d95091, fa8a28d559), LSym renamed Symbol (8e90b9026b), a SymKind type (3877f820a6), Headtype type (428c349552), ld.Errorf (5a597e2d29), ctxt.Logf (d61c07ffd8), and debug/pe replaced the hand-written ldpe reader (d08c3d1329, 237d7e34bc). Many of these are the groundwork for the 1.9+ cleanups.
Timeline.
- Go 1.3 — Done: Go-skeleton linker (
146897b031,8449863d31,7dcc652f10,a453f28c70), liblink (7d507dc6e6). Left open: unfinished; replaced by conversion. - Go 1.4 — Done:
eae9fee3bffixes zig-zag decoding in the Go skeleton; nothing else notable. Left open: nothing noted. - Go 1.5 — Done: C-to-Go conversion and rename (
5f1efe738b,1f9dbb60ef,dca5f2e9b3), move to cmd/link (cf932cd897), new5l skeleton moved tocmd/newlink(2a141dedc4),cmd/internal/ldcleanups. Left open: the code is “c2go-flavoured” with globals. - Go 1.6 — Done: dead code cleanup (
e92d0d82e0),stringsCompareremoval (550b7ccf77), “link go.o first” (6bf794a36d),-Wremoved (a0c7f57903),-libgccand-extarflags. - Go 1.7 — Done: delete
cmd/newlink(fb880b8a03); cleanups and concurrency: alignment concurrently (5a0881a1d1), data symbol processing with slices (ed41054b6d). - Go 1.8 — Done: globals removal and flag conversions as above. Left open: nothing noted.
- Go 1.9 — Done: quadratic library loading fixed (
51711d1429),-importcfg(4f2269edbd),Segment.Sectionslinked list to slice (743fe0697d),markextraeliminated (d343478295), goto removed in ldelf (d728be70f4).
Linker speed and memory (Go 1.7 focus)
Span: Go 1.7 → Go 1.9 · Status at go1.17: incremental; the 1.17-era work moved to the new object format and loader → continues as Linker performance and memory (1.18+)
Paths not taken: Linker speed experiments of 2016: mmap input, section-oriented objects (superseded)
Continues in: Linker performance and memory engineering (Go 1.10 → Go 1.17)
Design. In the 1.7 cycle (driven by Shahar Kohanim, Michael Hudson-Doyle, David Crawshaw, Brad Fitzpatrick and others) the Go linker was profiled with LinkCmdGo and LinkJuju benchmarks. Techniques: batch allocation of Lsym (8096881927), preallocated Lsym map (e0b04fa508), allocate small []byte reads together (36f25a7e7b), track offset instead of seeking (6d0b551931), batched byte writes (d3b00a8cc4), integer parsing (3504945081: -1.5%), encbuf writes (d6d33f678d), bufio.Reader based readSymName (73edd7b208, -1.1% allocs), replacing the custom DWARF hashmap (c0f7195d62), string map for single-version symbols (eb57a1dd75), patching up symbols once per object (93098de0cc), deleting unreachable collision checks (70ef564e79, 49be1ebab4), reusing duplicate symbol objects (b6fe2c2c20), stdlib sort in dodata (90a59d448e), symbol generation optimizations (4f12cc0813), concurrent section alignment (5a0881a1d1) and typelink generation moved into the linker (4140da7b57, d8d445280a, up to -6.9% time / -4.9% RSS). Earlier, 1.6 had cf73357e37 concurrent object copy for external linking, and 1.0 had 1bc84b7e18 (“25% faster”), 8a6da2f6a0 (n^2 to n log n in data initialization) and 9c7535432a/505fd760a2 (6l/5l speedups). 1.5’s 1082e2390e rejected data size over 2 GB (6f80e5edf4).
Timeline.
- Go 1.0 — Done:
1bc84b7e18,8a6da2f6a0,9c7535432a,505fd760a2. - Go 1.1 — Done:
f47346c5fc(8l, 5l: 2% faster), reduced size of Prog and Sym in 5l (9b568ef2b8,e00c9f0dbb). - Go 1.6 — Done:
cf73357e37. - Go 1.7 — Done: the micro-optimizations above. Left open: nothing noted.
- Go 1.9 — Done: quadratic library loading fixed (
51711d1429);cmd/internal/srcfilename cache (57e038615d, compile-side); DWARFencbufremoval (f978301144, enabling a concurrent backend, withPutFuncbuffer reuse and 7-bit fast path).
Runtime type data, GC metadata and sections laid out by the linker
Span: Go 1.0 → Go 1.9 · Status at go1.17: type descriptors with offsets (nameOff, typeOff, textOff) and GC bitmaps in dedicated sections → continues as Type, itab and typelink layout (.go.type section, relro rework) (1.18+)
- motivating issue #6853 all: binaries too big and growing referenced by 3 of this thread's commits
Design. From 2009 the linker generated type strings (0532f89b1f “invent gotypestrings if needed”, 47caf6424c gotypesigs on demand) until the compiler emitted them (7c534e1bd2 makes ld-supplied typestrings obsolete). In 1.0 4e3f8e915f tagged pointer-free data and bss and allocated them in separate noptr sections (5bcad92f07 NOPTRBSS and #pragma textflag) so the GC need not scan them; 1.1 added .gcdata/.gcbss (032e5bfb30, fixed by 251baea1af in 1.2), hostobj sections marked noptr in 1.4 (65e2b6f847). 1120982590 (1.1) introduced a linker-generated typelinks section listing array/chan/map/slice types to support reflect.ArrayOf etc., while removing the interface{} header from rtype: in godoc about 10,000 types lose two words each and about 500 types regain one. 44fd7573aa supports reflect.PtrTo and f76f120324 aligns large objects to 64 bits.
Go 1.5’s 512f75e8df replaced GC programs with a simpler encoding (data layout for large arrays) and the linker’s c949cff6a7 reads the gcdata without relying on local symbols; 0acecb7164 elides gcbits symbols; e8f2eb4349 lets the linker drop string headers. In 1.7: offsets replace pointers in read-only data (f028b9f9e2, the first of a series to cut dynamic relocations in PIE/c-shared: cmd/go -86KB (0.7%) on linux/amd64 PIE), name data not aligned (bb52ceafea), go.string dedup, typelinks sorted in the linker (4140da7b57). In 1.8: typelink generation via a flag set by the compiler (d8d445280a), a ptrToThis weak relocation (6f31abd23a), and a rel.ro segment (276803d611).
Timeline.
- Go 1.0 — Done: noptr data/bss (
4e3f8e915f,5bcad92f07,433e47dc03), type symbol machinery (0532f89b1f,47caf6424c), reflectPtrTo(44fd7573aa). - Go 1.1 — Done:
.gcdata/.gcbss(032e5bfb30), typelinks forArrayOf/ChanOf/MapOf/SliceOf(1120982590), 64-bit alignment of large data (f76f120324), floating point constants in.rodata(3dbbb6eb4c). - Go 1.2 — Done:
gcdata/gcbssfix (251baea1af), textflag constants in a shared header (5a54696d78), GC metadata via funcdata (48769bf546). - Go 1.3 — Done: GC bitmaps in read-only memory (
a26c01ad44). - Go 1.5 — Done:
512f75e8df,c949cff6a7,0acecb7164,e8f2eb4349, module data initialized directly (3a84e3305b). - Go 1.7 — Done: offsets (
f028b9f9e2),go.stringhandling, itablinks slice (79688ca58f). - Go 1.8 — Done:
d8d445280a,6f31abd23a,5923df1af9. - Go 1.9 — Done:
runtime._typevariants exported to debug_info (073247f11f).
Container writers: Windows PE, Mach-O and Plan 9
Span: Go 1.0 → Go 1.9 · Status at go1.17: PE, Mach-O and ELF writers in cmd/link/internal/ld; Mach-O code-signing friendly and Windows external linking supported → continues as Per-platform object formats and internal linking (Mach-O, PE, XCOFF, wasm, ppc64, riscv64, loong64) (1.18+)
Continues in: Mach-O and Darwin support (Go 1.10 → Go 1.17) · PE / Windows (Go 1.10 → Go 1.17)
Design. Windows support began with 977e19d69f (8l PE output, 2010), then an import table for kernel32.dll (3b1a71862e), a DOS stub (30f981d980), //dynimport and dynexport (70deac67cf, a3120f67ca), DWARF in PE (f1751e7de5, 34ac4ec30c, b256358008, 2d99974ec5), resources (2ad58d48fb), -Hwindowsgui (75fb2f6c82), windows/amd64 (3aec5516ed, cgo 9f636598ba, 1aa2d88739), reading PE objects (ldpe: fa249cae38 redo pe writing, 4e5e12e305, 7ce1a4bdc0, 3199a6ca8d, 9e2ffc315f for dllimport). The PE header link version was bumped to allow code signing (c9bf048337), stack size raised for cgo (428062da4e), thread count raised (074354c2ea), timestamp zeroed in 1.5 (98376204e8), and gcc 4.9 long section names via the string table are read (d0ee959ab7, d704bb0dc9). In 1.7 the PE symbol table writer was rewritten (bb48b864b1): the old one walked all symbols twice, allocated a global array of 32-byte COFFSym structs and walked again; the new one writes each record in a single walk. In 1.8 ldpe.go was rewritten on top of debug/pe (d08c3d1329) and in 1.9: all PE section names in the symbol table (b660a4b04d), section reordering (d0a978f5b5), shNames (e9abf1a716), peemitreloc simplification (b32170abdf), IMAGE_SYM_CLASS_STATIC for locals (d3f5e36917), file parameters computed after linkmode is known (0ad247c6f0), DWARF sections and relocations on external windows linking (29f061960d, aada49038c), .bss not read in ldpe (507f4d5f48).
Mach-O: 073486c3ed (debug support), 2157025ef0 factoring out macho code, 133a158bd8 (8c, 8l dynamic loading, cgo on darwin 386 and amd64), 0f6926474f (LC_RPATH), exported dynamic symbols (882f9d6d7a, 3a43ff1a77, ac1c09458c, fa563ae82e sorting), code signing (78ad19f214 string table to end of __LINKEDIT, 280d85a80b), c5444a8937 zero-fill sections, b83d4af330 Mach-O sections matching internal sections, df6072b41c full symbol table, 1.2 a2c30fe648 sizes to Mach-O symbols, 1.3 a4a1fadfa2 lldb addresses for DWARF sections, 1.5 47746f10fe .init_array, 1.6 4a0eee2faa LC_VERSION_MIN_MACOSX, 1.8 9d1efba28d (text at address 0x1000000 on darwin/amd64) and 7ee7936523 R_GOTPCREL. 1.9 reworked the DWARF segment for dsymutil and -s/-w handling (18e77673d8, b28f2f7399, 23dc3633ca, 371c83b594).
Timeline.
- Go 1.0 — Done: PE, Mach-O code signing, dynamic Mach-O and Windows cgo as listed (
977e19d69f,3aec5516ed,78ad19f214,c9bf048337,133a158bd8). Left open: Plan 9 output was maintained by community patches (c14c4e55e0,8221eb9103,c7008f47ec). - Go 1.1 — Done: PE section symbol values for
ld -r(1c4b77a7c8), PE ignored sections (9ca4a25f4b,782a57811f), Darwin scattered relocs (e607380ff6),__image_base__(5490814c30), zero-fill Mach-O (c5444a8937). - Go 1.2 — Done: COFF duplicates and labels (
9add729a1f,c742179c49),_image_base__fix (60513bf2f4). - Go 1.3 — Done: PE symbol table filled and corrected (
fce4f0484c,80e7f97206,cf78f96244), header string kept for Windows (d35b22d128). - Go 1.5 — Done: stack reservation unified (
da11a9dda3), PE timestamp zero (98376204e8),.exehandling in external Windows link (ad84f84cb4),-scrash on windows (ab08f79af3). - Go 1.6 — Done: PE
.text32-byte aligned under external link (821e124c24). - Go 1.7 — Done: PE symbol table rewrite (
bb48b864b1). - Go 1.8 — Done:
debug/pereader (d08c3d1329,237d7e34bc,02c1d8a158). - Go 1.9 — Done: the PE and Mach-O items above, including
c7a7c5a9b4/e94b9d4143(windows/386 cgo underscore prefix) and4e3a1e409a,361af94d5d(windowsgui as a linker variable rather than a header type).
Architecture and OS ports in the linker (arm, power64, arm64, mips, s390x, nacl, BSDs, Solaris) and trampolines
Span: Go 1.0 → Go 1.9 · Status at go1.17: all ports in cmd/link/internal/<arch>; direct-branch trampolines for ARM and ppc64x → continues as Per-platform object formats and internal linking (Mach-O, PE, XCOFF, wasm, ppc64, riscv64, loong64) (1.18+)
Continues in: Architecture ports and trampoline insertion (Go 1.10 → Go 1.17)
Design. Each port added a linker directory with asm.c/obj.c/l.h (later asm.go, obj.go) holding archreloc, adddynrel, elfreloc1, machoreloc1 and the Arch description. ARM: 4e1896a1b5 ELF in 5l, softfloat and VFP (9c6df3ca13, ae60526848), GOARM embedded as runtime.goarm (88ba4de152, 77e42e2108), aborting when VFPv3 is missing (55ca5ab0be, GOARM default resets to 6), CLZ/PLD/BSWAP style instructions (d186d07eda, 2031252fb8, ed480128a6), dynamic linking (452a9e452b, dac4c3eee9 auto detection of the dynamic linker), FreeBSD/NetBSD ELF (d901808869, 2ebfaa36b5), 64k alignment on linux/arm (f64b7d301d), BLX for BL (Rx) (35e1deaebf), and in 1.8 trampolines for too-far calls (7c431cb7f9: 24-bit offset only reaches +/-32M; direct jump relocs resolved during address assignment and packages laid down in dependency order).
Power: 1a1d450712 (dev.power64 9l), db923390a0 internal linking, 15c67e21da ELF ABI version, e32fe2049d local entry offsets, c155e59062 external linking on ppc64 (1.6), 1ccefcd1b8 addmoduledata on ppc64le, split of large text sections (b4efd09d18, 1.8), trampolines for internal (d26b0661c2, 1.8) and external linking (738739f565, 1.9: the earlier approach of splitting text so the GNU linker adds stubs failed when a plt_branch was created), function alignment 16 (09b71d563a), st_other (5331e7e9df), PLT stubs at start of Textp (15b37655bc).
ARM64: 3d1ce27ba5 (7l, 1.5), external linking linux/arm64 and darwin/arm64 (f55b2a11f4, 4fd9a3fdbb, 7a96ecde4d), two-instruction loads (3a9bc571b0), absolute addressing removed in .text (6e3a2a3f9f), arm64 GOT workaround (4bae454d68), gold on arm64 (a3c92c9db1). MIPS: mips64 (053c75411f, 1.6), external (073d292c45, 1.7), mips32 (76d8e60451, 585a0b03b2, 5b147122d6, a3b670e333, 1.8). s390x (163ff700b8, 29a6149e67, 78715cebcf, 1.7). NaCl: 4a4ec23870 .. eae0906bed, 7390aa0102 (8l, 2010), d9c6ae6ae8 (1.3 final merge, 32-byte “halt sled”, a9b2651ade fix in 1.4). Solaris: 6ebf59b953, 4687b54147 (1.3), e481aac0e1 (PLT instead of GOT, 1.5), non-exec stack (f373bf1eb9, 1.8), e79fef8e55. Others: OpenBSD (ffa14e849c, 31758b2c1a signatures, e6ca125f14 no PT_TLS, 9417c022c6 PT_TLS in 1.9), NetBSD (420fe22921, 37e195cf72 note size), DragonFly (71dc91db0f, fd0af9b56c, d0206101c8), Android TLS emulation (12b990ba7d), amd64p32 (e09ac3cf2a).
Timeline.
- Go 1.0 — Done: ARM, 386, amd64 ports with the ISA additions listed (e.g.
9c6df3ca13,fc41e621e8,895631770a); OS ports: OpenBSD, NetBSD, FreeBSD, Windows, NaCl. - Go 1.1 — Done: PLD/CLZ/ABS instructions (
2031252fb8,d186d07eda,dee5adcf74), PAX flags (3f34248a77,af92b29fe6), ELF ARM flags (1cf27acfed), OpenBSD (31758b2c1a), AES-NI (e039c405c8,297bb12809), AUNDEF (f2bd3a977d),-Zzeroing (b9da27bed2). - Go 1.2 — Done: DragonFly (
71dc91db0f,fd0af9b56c),UNDEFfix for 5l (ed6dce6f9d), MOVBS/MOVHS (3670337097,5636b60b70), PCLMULQDQ (6bea504b94). - Go 1.3 — Done: Solaris amd64 (
6ebf59b953,4687b54147), NaCl (d9c6ae6ae8), amd64p32 (e09ac3cf2a), ARM integer divide profiler-friendly (b0db472ea2,2c98a3bc2e). - Go 1.4 — Done: Android TLS emulation (
12b990ba7d), NaCl fix (a9b2651ade), big-endianaddstrdata(f29bd6c4a4), large link base addresses (9abf0b6e9f). - Go 1.5 — Done: power64, arm64, darwin/arm, darwin/arm64 ports and the ELF ABI details above (
1a1d450712,3d1ce27ba5,db923390a0,1083715b7f),R_CALLPOWER(6f755f2f8f), 2-byte relocations (fcdffb3f33), relocation variants (ac5a1ac318). - Go 1.6 — Done: mips64 (
053c75411f), ppc64 external and shared (c155e59062,1ccefcd1b8,9a476028c0), arm64addmoduledata(4e3a1e409a1.9), ARMaddmoduledata(1b4d28f8cf), IE TLS on 386 (3bf61fb2e5), R_PPC_REL32 (f1b919574c), new 386/amd64 relocs (914db9f060), ARM cgo ELF headers (3e6334e2e0). - Go 1.7 — Done: s390x (
163ff700b8,29a6149e67), mips64 external,Minalign(90cb1fdaf2), sys.Arch common representation (c6e11fe037,d481ffc1af,22ef687da8), gdb backtrace on link register architectures (55154cf0b2), dynamic symbol size for 386 (0b6e5e3d73). - Go 1.8 — Done: mips/mipsle (
585a0b03b2,76d8e60451,5b147122d6,a3b670e333), ARM trampolines (7c431cb7f9,c7e855658d), ppc64x trampolines (d26b0661c2), text splitting (b4efd09d18,6623988eb7), NetBSD EABI (8419c85eaa). - Go 1.9 — Done: ppc64le external trampolines (
738739f565), alignment 16 (09b71d563a), mips64 CPIC (39c07ce9e5), plugin on s390x (a6b480bc07), PT_TLS on OpenBSD (9417c022c6), canonicalized package of dupok text symbols (a1cedf0842).
Build IDs, ABI hashes, flags and determinism
Span: Go 1.0 → Go 1.9 · Status at go1.17: -buildid, -X, -importcfg, -B, -linkmode, -extld all present → continues as Reproducible build IDs, buildinfo and symbol tables (1.18+)
Continues in: Build identity, buildinfo and reproducibility (Go 1.10 → Go 1.17)
Design. 32316bba5b (1.1) added -B to set a build ID (an ELF note, with NetBSD/OpenBSD identification notes 5842f7e46a, 31758b2c1a, and e69b9ddd42). In 1.5 ac1f48e2f6 added -buildid to write the Go build ID to ELF output, 9ae3c560b9 for non-ELF binaries, and the ELF note identifier was made “Go” (7e27625e25). -X: parsed in 2011, implemented as -X name=value (12795c02f3), checked for non-string variables (69f0d4c6be), f6fc14094a stopped marking symbols as reachable, full path escaping fixed in 1.8 (1773cdd081). Determinism: 5bbb98df09 (1.7), 83746fd55a (1.6, “$GOROOT” in object paths substituted at link time with GOROOT_FINAL) and 6e3a7930eb (1.0, GOROOT_FINAL rewrite). 533ed967c6 restricts the ABI hash to version info and export data (1.9), 4f2269edbd adds -importcfg. 3d40062c68 (1.1) is the fieldtrack experiment mentioned under dead code.
Timeline.
- Go 1.0 — Done:
GOROOT_FINALrewrite (6e3a7930eb),-X(e3755434b8),-usafe check (6519a6ba5b,829896168c). - Go 1.1 — Done: user-visible
-Bbuild ID flag (32316bba5b), consistent binary for cgo (section symbol names use pkg path,4d7c81bc67),fieldtrack(3d40062c68), race linker support (cc8cfefd8a: emits dependency onruntime/racewith-b). - Go 1.2 — Done:
-sdocumented and passed to gcc (71051953e0,e50e4f7ec1). - Go 1.4 — Done: flags documented (
6012ac9b79,c3dbf56a14). - Go 1.5 — Done: user-visible
-X name=value(12795c02f3),-buildid(ac1f48e2f6,9ae3c560b9),-memprofilerate(00d4a6b35d), ABI hash note (65518032b9). - Go 1.6 — Done: user-visible
-msan,-libgcc,-extarflags;$GOROOTsubstitution (83746fd55a);-Wremoved. - Go 1.7 — Done: user-visible deterministic cgo builds (
5bbb98df09),-dumpdep(d224e98d9a),-H elfremoved (71ab3c1ccf). - Go 1.8 — Done:
-sharedflag removed (361d2738d5), time stamp in-vhostlink output (404ae84aa2),-Xpath help (4284edb999,f78cd569f5). - Go 1.9 — Done: user-visible
-importcfg(4f2269edbd); ABI hash limited (533ed967c6).
Changes in this slice that are not part of a thread are listed on the release pages: 1.0 · 1.1 · 1.2 · 1.3 · 1.4 · 1.5 · 1.6 · 1.7 · 1.8 · 1.9
Linker and object files (Go 1.10–1.17)
Reader: Claude Sonnet. Cites 1,101 of 1,267 commits in this slice; every non-noise commit is cited, plus 11 larger commits the noise filter had set aside, which a separate gap reader described. Read the design-heavy messages and subject lines. About 300 small commits (PE refactor steps, loader API additions) are cited in grouped items.
Release digest for this slice
- Go 1.10 — Code-health release for
cmd/link: globals moved intoLink, packages split out (object reader,sym, loadelf/loadmacho/loadpe), the PE writer rewritten aroundpeFile/peSection, DWARF given one compilation unit per package, content-based build IDs, plus PIE/plugin/c-shared enablement across arches. - Go 1.11 — DWARF compression (ELF, then Windows and Darwin) and separation of VA from file layout, is_stmt line tables, dsymutil workarounds, DWARF on darwin/arm*, WebAssembly port,
GOROOTdefault at link time, JSON-encoded cgo directives. - Go 1.12 — AIX/XCOFF port, stable ABI0 vs ABIInternal separation and ABI aliases in the object file, linker memory slimming (cold
Symbolfields), deadcode-before-mangling. - Go 1.13 — mmapped output and object data,
Relocshrunk, pctab deduplication in pclntab,-strictdups, runtime version info symbol, AIX external linking and PIE, riscv64 groundwork,GOWASM. - Go 1.14 — Compiler-generated DWARF line tables with multi-CU packages, open-coded defer funcdata, libFuzzer counters, and the first shipped pieces of the new object file format/loader (default via
-newobj) plus initial riscv64 and PIE work. - Go 1.15 — The dev.link conversion: old object format deleted, all passes moved to
loader.Sym, new DWARF on loader, parallel relocation/asmb/dwarfcompress, internal PIE and Windows PIE. - Go 1.16 —
sym.Symboldeleted; pclntab split into pcheader/funcnametab/pctab with generator symbols and hashed symbols; streaming external relocations; UsedInIface/R_USEIFACE/R_USEIFACEMETHOD method pruning; darwin/arm64 internal linking and code signing. - Go 1.17 — Register ABI plumbing (ABI-mangled shared/PE/Mach-O names, cgo export ABIs, argument-printing metadata, DWARF location expressions), weak itab references, two-pass trampolines, windows/arm64, centralised GOEXPERIMENT.
New object file format and the loader (dev.link)
Span: Go 1.14 → Go 1.16 · Status at go1.17: done; the linker reads only the indexed object format through cmd/link/internal/loader, sym.Symbol is gone, and the format gained a weak-reloc bit and 16-bit reloc types in 1.17 → continues as Relocation-free pclntab and funcdata (offsets instead of relocations) and Type, itab and typelink layout (.go.type section, relro rework) (1.18+)
Paths not taken: Linker speed experiments of 2016: mmap input, section-oriented objects (superseded)
Earlier history: liblink, the object file format and the move into the compilers (Go 1.3 → Go 1.9)
- design doc (PUBLIC) Building a better Go linker listed in Go: A Documentary
Design. Through Go 1.13 the compiler wrote an object file whose symbols were identified by name strings and which the linker (objfile.Read) expanded eagerly into one heap-allocated sym.Symbol (with []sym.Reloc, auxiliary fields, DWARF autos, etc.) per symbol. The dev.link branch replaced this with a new object file (“goobj2” in cmd/internal/goobj2, renamed back to cmd/internal/goobj once the old one was deleted) in which every symbol is addressed by an index: a defined symbol is (package index, local index), referenced non-package symbols and builtins use pseudo-package indices (PkgIdxNone, PkgIdxBuiltin, later PkgIdxHashed), and the file is laid out in blocks (header, string table, per-symbol definition array, reloc array, aux array, data) so the linker can mmap it and read only what a pass needs. The reader, NewReaderFromBytes, works directly on the mapped bytes.
The linker side is the loader.Loader. It assigns each symbol a global loader.Sym index in object order (package DAG order), keeps a local-to-global array per object, and tracks overwrite (a dupok or contentless definition replaced by a real one) and ABI aliases. The first design gave every appearance its own global index with a redirect map; 8e2b5d3e71 made the index unique per symbol (N-to-one local-to-global mapping, a global-to-primary-local array, no overwrite map) and 2f22143cd5 stopped reserving index space for dups so there are no holes. Package-defined symbols were added first so they need no name lookup (b0275bfbf4). Symbol metadata (type, value, size, alignment, section, attributes, ELF type, dynimport, plt/got, Gotype, compilation unit, outer/sub relationship) lives in loader side arrays and bitmaps instead of struct fields; 24814e2147 enforced a single level of “outer” symbol. Symbols created by the linker itself are “external” loader.Syms (07914eda40, b720014743 SymbolBuilder, later MakeSymbolUpdater) that carry a payload (extSymPayload) until the end of loading.
Costs of the old design were the motivation: memory (a full sym.Symbol for every symbol even if dead), startup time, and GC pressure. The new design lets dead-code elimination run on indices before any contents are loaded, and every later pass materialize only live symbols. Further format changes tuned locality and self-description: StringRef was changed to {len, offset} so a string header is two adjacent uint32s (ff29f97437), referenced symbol names were written to the file so nm/objdump are self-contained (73dd74a9fe), content-addressable “hashed” symbols were added (see the pclntab thread) (27342e5bd9, 4f217d5aaa), and in 1.17 the relocation type became uint16 with a separate weak bit (f901ea701d, 414fa8c35e).
Timeline.
- Go 1.14 — Done: the branch work lands on master piecemeal. Compiler finishes all data generation before writing the object file (
dc8453964a); linker/goobj can parse the new magic (d79380026c); the linker loads it behind-newobj(6051fd0ad9), initially still creatingsym.Symbols right after loading. Deadcode is rewritten on indices and symbol contents are loaded after it (8a9be4921a,2495095275,48151b3f0f,cab29ebd84), the object file is mmapped (0d7404c852), and DWARF compilation-unit logic is ported to the format (65a649c565). The format records InlTree (6ba3ae9ca5), uses aux symbols to tie DWARF symbols to their text symbol (27111e5fec), splits Autolib from Pkglist (928623ca95), has flag accessors and object-level flags for shared/gotype symbols (4a9b30cfc9,15634a0230,8011051361), implements symbol overwrite logic (dab05a0484), rewrites"".to the package prefix (0108b54a3d), and moves the loader to its own package (c9470b0483). Host-object loaders are ported (Mach-Occ21e4d130, ELF0e76e2f4e5, XCOFFe961b26c27, PE496e4273e4), basic shared-library support appears (2bbf2e0233). Name lookup moves to a string map, static symbols are kept out of it, and builtins get preassigned indices (df01b7968b,c480d32fad,fb06609817).5caac2f73emakes the new format the default. Left open: that commit disables internal-linking cgo and shared libraries (“does not work yet in newobj mode”), and the tests known broken; the old format and thesym.Symbolconversion still exist. - Go 1.15 — Done:
62dfb27827deletes old-object-format support from the linker (+39/-1049 lines);3e6ff18247detects format mismatch withcmd/oldlink(the old linker kept around as fallback, invoked via5ffa696ade). Thesym.Symbolwavefront is pushed through the passes (next thread); the loader gains external symbols, attribute set,SymbolBuilder,ExtReloc, per-symbol alignment arrays (2f83d68926), content updates of obj-based symbols (d5854bf8e6), multi-level overwrite (db48d45838). Accessors are redone to avoid slice copies:Relocsview (c951514da9), aux accessors (8a074fa275,b33b6e857d),Symaccessors (6dab0942a4), then old-style accessors and types removed (cc3039f818,e7c16412b7); the package loses its “2” suffix (a1c29156ae).027055240fchecks an index fingerprint for consistency,0f99ab4e86moves NoSplit into a symbol flag,c33b7c7592adds an index to indexed symbol names. Left open:sym.Symbolstill exists for the late passes and conversion (PropagateLoaderChangesToSymbols,LoadFull);cmd/oldlinkstill shipped. - Go 1.16 — Done:
b97212d1f1deletessym.Symbolandsym.Reloc,4e974292d4sym.Attribute,de1f07d56dsym.Symbols,b8c5d6871fthe SCONST type;80b287fd28removesloader.Reloc;4a92371291/27e3778793drop the “2” suffixes. Content-addressable symbols arrive (27342e5bd9),4f217d5aaa(short hash for short symbols),0e3114871efixes the collision check,f204ca3051drops hash maps after loading; the package hash becomes the compiler fingerprint so the linker never reads export data (7bc3b6e5e4) and “safe mode” is removed (4cc043d883); symbol attribute setting is made atomic (721716ca1c1.15,847b9be3f6); builtins that are referenced but undefined are reported (8e5778ed70);83ed734df0pre-resolves package references to avoid a map lookup per cross-package ref;ce36e7f79echanges the local object/index representation;b22af9b407records only the first reach inReachparent. Left open: nothing noted for the loader itself. - Go 1.17 — Done:
f901ea701dstores relocation type as uint16,414fa8c35euses a separate bit to mark weak relocations,e8700f1ce6uses a weak reference in itabs;89ca1ce9a8/cef3a442eaabstract and use the full objabi header string;3651eff74edeletesCompilationUnit.Pkg. Left open: nothing noted.
Wavefront conversion of linker passes to the loader; sym.Symbol removal
Span: Go 1.15 → Go 1.16 · Status at go1.17: done (all passes run on loader.Sym; cmd/oldlink gone from the story) → continues as Relocation-free pclntab and funcdata (offsets instead of relocations) (1.18+)
- design doc (PUBLIC) Building a better Go linker listed in Go: A Documentary
Design. The linker is a sequence of phases (load, deadcode, symtab/dodata, address, reloc, asmb, DWARF, …). Rather than convert everything at once, the dev.link team moved a “wavefront”: everything before it works on loader.Sym, everything after still on *sym.Symbol, with LoadFull / PropagateLoaderChangesToSymbols shims (3fa98a0436, 7055f01e12) copying state across. Each CL pushed the wavefront one pass later (“move the wavefront past addexport()”, 84111acd35; “past address”, dc83274c95), and removed *Link dependencies from architecture hooks by introducing Target and ArchSyms (e9056a6a73, b9642cbb8d, 4ab7ab54b0, cb4c84bd9a, bf5dc7af0f) so that relocation can be made thread-safe. The dodata (data layout and section assignment) was split up and rewritten on the loader (a6a8974a5a, 817bd10cae, 941de9760b) and enabled per architecture, from darwin/amd64 (b2fde1098a) via plan9/wasm (f9ed846a46), ELF arm/arm64 (d5c9327628), MIPS/RISCV64 (aa74fce005), s390x (5ea431fb63), ppc64 (8bd7c01417) and darwin/arm64 (512a0219ef), until the old dodata could be deleted (98e3fdab3e). Later the relocation pass (76c6cce116, external linking 095d2a4532), asmb2 (1adae7fe76, then per-OS in 1.16) and the architecture archreloc functions were converted, one architecture per commit.
Timeline.
- Go 1.15 — Done: ELF host objects first (
fa284af720, default3fa9500dd1), DWARF type generation (25f1b093fe, gated by-newdw), thensym.Symbolsremoved from the loader (664accc7be), and the conversion of linksetup (5279166402), stack-bound check (4df1db99c4), doelf/dope/domacho/doxcoff (a3e965ce8a,e5f7a8334a,4d03b6543f,b1a19f3cc7), textbuildid (c5f6920212), callgraph (a9fb2e5155), gentext for s390x/ppc64 (587faf67d7,95ea64ba96), text address assignment (850c602ed6), pcln phase (db48c404cf), typelinks (a35b322709), buildinfo (9669c3f361), findfunctab (2f9decbe95), symtab (47cac82e36), dwarfcompress (3e8975172f), asmb (1adae7fe76), plus the second DWARF phase (0c41a37c6c) and deletion of legacy DWARF generation (cdc1b19513,e440f05251). Fields are peeled offsym.Symbolone by one: Gotype/File (43a85a39bf), Unit (3f408fc7fd), Sub (9a3bf7d542), ELF fields (0722a49b37),FuncInfo(68305f3fec),Reachparent(245a2f5780). Memory is returned afterLoadFull(f886ff7b41, -19.7% Loadlib GC for cmd/compile, -30% with external linking),7aa6e0faf2drops fields of external symbols afterwards.-aflag removed (3b32a44699). Left open: 1.15 ships with the two worlds coexisting (theLoadFullbridge andsym.Symbolfor relocation/asmb). - Go 1.16 — Done: new reloc pass on each architecture (
0586fbb9d3s390x,8f697c7ac8mips64,9408417d44mips,9f80d91b13riscv64,afbf9d47ddarm,dc048e24abarm64,fee06a6bdappc64) and old reloc pass deleted (85298173f6); asmb2 converted per OS (75ca90e309darwin,fdb9249d82windows,25e9417b98ELF/amd64,744641ef37wasm,88a12a99b4plan9) then moved into generic functions (c551318046,7260ad6f52,13c1401b8e,ebdb15f7f8,c7ade964ba,c5a9b3d63a);2867a85541deletes the undef pass; then the final deletion ofsym.Symbol(b97212d1f1). Symbols’ section info becomes a 16-bit index in a lazily created pointerless array (9570fc8f711.15, -14% Loadlib alloc). Left open: nothing noted.
Linker performance and memory engineering
Span: Go 1.10 → Go 1.17 · Status at go1.17: mmapped, parallel, streaming pipeline with generator symbols; ongoing → continues as Linker performance and memory (1.18+)
Earlier history: Linker speed and memory (Go 1.7 focus) (Go 1.7 → Go 1.9)
- design doc (PUBLIC) Building a better Go linker listed in Go: A Documentary
Design. Three families of work. (1) Avoid memory: shrink hot structs (Reloc moved cold fields to a lazily initialised side struct, 8ef45cf60b: kube-apiserver inuse_space 1240MB to 1123MB, max RSS under GOGC=5 2.05G to 1.83G; Symbol fields moved to cold sections in 1.12: 09df9b06a1, 2975914a1a, f78cc1324a, dd9e81f678; Reachparent as side table 32bc0976f9; Dynimp strings split f00bfb385b), read-only mmap backing for symbol-name strings and object data (9ac471a87d, dbe32284ff), then the loader’s lazy loading. (2) Avoid I/O and copies: the output file is mmapped (b2f94d3e88, Windows af880809d8 with MapViewOfFile), relocation is applied while writing symbols (37682f7a79) instead of in a pre-pass (earlier f957a7e357 “apply relocations later”, 15a31bd9c8, 248444d5eb apply DWARF relocations during compression), OutData removed (ee8541e5b8: Asmb alloc 3.78MB to 0.14MB), fallocate the output and drop msync (8ab37b1baf: Munmap 16.0ms to 0.8ms), a heap-side OutBuf flushed at 10MB (7466cad9c4), buffered file IO removed (b2def42d9e, 93c9a3bd38). (3) Parallelism: per-phase CPU/mem profiles (416561c9e2, 105053fa19, 6def07cf1e), parallel relocsym (491615e3f7: Reloc 13.9ms to 9.0ms; made possible by Target e9056a6a73), parallel dwarfcompress (a3873ab49f: 181ms to 62ms; TotalTime -14%), parallel asmb with a parallel OutBuf (c46632a2e0, -78% Asmb on amd64, then all platforms 6506cf1981), parallel second-stage DWARF (d1a186d29c), parallel ELF relocation writing (36939aef77), and parallel generator functions (e674b7703e, 06839e3c9a).
Streaming external relocations were a memory trade: instead of converting Go relocations into an in-memory external format during relocsym and writing them in asmb2, the writer emits ELF/Mach-O records straight from the Go relocations into the mmapped output (987ce93824: Asmb2 alloc 66.8MB to 8.2MB; 59a702aa6a; 88382a9f97 amd64 where Asmb2 became 23% slower but Asmb -84% alloc; then 386, arm64, MIPS, s390x, arm, windows and ppc64 with 936a2d6966, bf1816c7b7, ed6b8af509, 71e2133304, e4a3e57f47, 9d22325681, and the old path deleted 4b3cfcee58). External relocations were first stored in Reloc2 format (6e3bde5f30) then as an index into the original reloc (cdfff4d25a). Generator symbols (f033e23414) are real symbols with size but no data; a function fills the bytes during Asmb, so very large tables never occupy heap (used for pclntab pieces, see that thread).
Timeline.
- Go 1.10 — Done: mostly code health:
SymKindshrunk to uint8 with attribute bits (1341104ae2,1b8987f1a7,253e605af4,302f0d1646), globals removed (5d95de2072,5fe9bbcf63,2e8545531e,6738c494ad,d05f82a11a,38cfeb9cb5), symbolAdd*as methods (0346664421), object reader andsymdata types split into packages (9f9bb97420,475d92ba4d,a910fe2c83), ldmacho/loadelf/loadpe moved to own packages (f7ad3a04f9,ecfa7375e4,d06815ba3f),Cwriteinstead of unrolled loops (f84a1db19f). Left open: nothing noted. - Go 1.11 — Done:
bd83774593/8998c55ec0separate virtual-address layout from file layout (needed for compression); DWARF compression switched to BestSpeed because it was ~30% of linker time (12ed0ddec1: cmd/go link overhead of DWARF 75% to 37%); fieldtrack pass skipped when not enabled (8bb391312c, ~1% on hello-net/http), duplicate error suppression (2c01b7d632,289ab30457). Left open: DWARF still the dominant cost. - Go 1.12 — Done: fewer allocs in
Archrelocby returning the value instead of an out pointer (328adf9d62: 1.8%), avoidingReloccopies in range loops (bc276c585b, 48-byte struct), cold-field splitting (above),3d72ca9908direct max-pc in findfunctab. Left open: nothing noted. - Go 1.13 — Done: mmap of output (
b2f94d3e88) and object data (dbe32284ff),Relocshrink (8ef45cf60b), fix of a seek-syscall regression introduced by the mmap’d reader (0793c81009), pctab dedup in pclntab (below),-strictdupschecking (d923309a17, refinedcacab64555),9ac471a87dread-only strings. Left open: relocations still applied in a separate pass over heap data. - Go 1.14 — Done:
c3e8a20a65removesCputime; loader tuning on the dev.link side that shipped with 1.14: PkgNone resolver cache (2e2ef666b4, LinkCompiler -15%), bulk allocation (5aea597905), aux-sym slice interface (851b1f40b4), 1-element object cache intoLocal(e90e6e75f8), min-heap work queue for locality (36dbdbe9bd: -7.8%/-9%), ABI-alias postprocessing (17722c21e71.15: Hyperkube relink -2.4%). Left open: heap algorithm via container/heap allocates. - Go 1.15 — Done: parallel reloc/dwarfcompress/asmb as above;
Dwarfcompress,Loadlibbenchmarks recorded;b2def42d9eetc. remove buffered IO;ca290169abbatch-allocates external relocs (kubelet relink -3.1%);f875f8fe76alignment tweaks (-1.3%); deadcode reuses relocs without slices (cf5c718cba: -6.4%, alloc -7.9%);38437ce118/ab1253b59a/c4ccd08a25read only necessary fields;4098ab3ff41.16 fewer Preload allocations. Left open: external-link memory still large (addressed by streaming relocations in 1.16). - Go 1.16 — Done: custom deadcode heap (
7179e426e2: Deadcode 59.8ms to 42.2ms, allocs -95%) and slice reuse (78b96db04e), streaming relocs, generator symbols,586d0755e2(name expansion only when needed, Loadlib -6.4%),d08bab99b7(line table generation with less heap),188e995ae9(reduced alignment for symtab/elfstr),f204ca3051,64fdc8b47a,5402d40d5b“fix memory growth on dev.link”,ae3680b30bno reflection. Left open: nothing noted. - Go 1.17 — Done: two-pass trampoline insertion makes small programs skip trampoline work (
18852e8372);9f7079c44esorts pclntab relocations,199575a119counts relocations better,48531da9e7builds dynexp list directly,e03383a2e2checks mmap errors. Left open: nothing noted.
pclntab, funcdata and pc tables
Span: Go 1.12 → Go 1.17 · Status at go1.17: pclntab split into runtime.pcheader, funcnametab, pctab and generator-built tables; still relocation-based → continues as Relocation-free pclntab and funcdata (offsets instead of relocations) (1.18+)
Earlier history: Go symbol table, pc/line table and function metadata (gosymtab, pclntab, findfunc) (Go 1.0 → Go 1.5)
Design. Up to Go 1.15 the linker built a single runtime.pclntab symbol in pcln.go: header, function table (ftab, pc to func offset), each _func record, function names, file table and the pc-value tables (pcsp, pcfile, pcln, pcdata) concatenated into one big heap symbol, with strings and file names deduplicated but renumbered per link. Memory and time were dominated by this symbol. Jeremy Faller’s 2020 plan (documented in ba9c639470 as an “improved format” doc) broke the table into independent pieces that no longer need rewriting: header, function names, file names, pc tables, and per-function _func data, each emitted late in the link from generator symbols and addressed by offsets rather than copied.
Mechanisms used: (a) deduplicate pc tables - first by a cache keyed by content inside the linker (d79aea6b96, 33% hit rate during make.bash, binaries about 1% smaller), then by making pcdata content-addressable symbols in the object file (5387cdcb24); (b) hashed/content-addressable symbols and PkgIdxHashed (27342e5bd9) to dedupe across packages; (c) moving per-function derived facts into the compiler/assembler so the linker does not need to load names (89cf569a45 FuncID, e4e1c6a7af compilation-unit index in Func); (d) per-package file tables used directly as file indices to avoid renumbering (9bdaf99966, 9ae8f71c94); (e) generator symbols filling bytes during Asmb (f033e23414).
Timeline.
- Go 1.12 — Done:
3d72ca9908simplifies findfunctab max-pc;af134b17daproper tracebacks with mid-stack inlining (runtime reads inline tree funcdata that the linker lays out);9dac0a8132sets the traceback address to a deferreturn call on signals,eafe9a186chides compiler-generated init functions from tracebacks via a funcID wrapper class. Left open: nothing noted. - Go 1.13 — Done: off-by-one fixed in
ftabaddstring(7f9e0220cd, few KB saved),d79aea6b96pctab dedup,134ef176f0uses encoding/binary for varints,3a62f4ee40/6d64dd731bcleanups,d6b06f0254(see build-info thread). Left open: names, files, tables still in one symbol. - Go 1.14 — Done:
dad616375f/be64a19d99addFUNCDATA_OpenCodedDeferInfofor open-coded defers and the linker’s deferreturn detector (fixed later by9eb403159d1.13,2b70ffe9301.15);8f816259eeexportsPCIterto the compiler and the line table moves into the compiler (376fc48338,ee3dded36d,8506b7d42f; see DWARF thread). Left open: nothing noted. - Go 1.15 — Done: pcln linker phase converted to loader APIs (
db48c404cf),FuncInforead from the object file through accessors (e00da38b81,30b0c819d1,2820bcede0), inltree symbols made anonymous (ad6fcf6993), pclntab built from function address rather than symbol lookup (c364079a53), findfunctab converted (2f9decbe95). Left open: all still written into one symbol. - Go 1.16 — Done:
ba9c639470carves outruntime.pcheader;1fdf5ba50cfindfunctab as generator symbol (allocation 172kB to 0);3067a8dc02pclntabState replaces globals;6ac9914383runtime.funcnametab(the FuncID still needed the name, later fixed by89cf569a45);9bdaf99966per-package filenames;5387cdcb24content-addressable pcdata;26407b2212runtime.pctabfor all deduplicated pc tables (alloc/op 26.4MB to 13.8MB on cmd/compile);c863e14a6cmoves the rest to generator symbols (alloc 13.9MB to 6.4MB, Pclntab time -24% but Asmb +50%);7dbd40babf,a9a1217112handle runtime.text/etext uniformly;575ea5a87asets runtime.text to the address of the first function;b85033d153AIX fix;f0cf4d4d7ffixes last-instruction file/line in the line table.db924fd99ealso removes the compile-time itablink symbols: the linker now generates thego.itablinkslice. Left open: pclntab still relocation-based for function entries andmoduledatapointers (continues in 1.18). - Go 1.17 — Done:
9f7079c44esorts pclntab relocations;537cde0b4badds argument-layout metadata funcdata so tracebacks print register-ABI arguments in syntax order (binary size +1.5%);bdb8044665keeps arginfo symbols out of the symbol table. Left open: hot-spill liveness for arg printing not implemented in that CL (“just assumes all the slots are live”).
Dead-code elimination and method pruning
Span: Go 1.10 → Go 1.17 · Status at go1.17: index-based deadcode with UsedInIface/R_USEIFACE/R_USEIFACEMETHOD method pruning, weak itab edges and reflect-aware tracking → continues as Dead code elimination and reflection-aware pruning (1.18+)
Earlier history: Dead code and data elimination (Go 1.0 → Go 1.8)
Design. deadcode.go does a flood from roots (_rt0_*, runtime.main, init tasks, exported cgo symbols) over relocations. Methods get special treatment: a method of a reachable type is kept only if it can be called indirectly. The original rule matched type methods against all methods of every reachable interface type (by name+signature string), plus R_METHODOFF-based tracking for reflect.Type.Method/MethodByName (which disable pruning entirely when reachable).
The dev.link rewrite ran on loader indices (8a9be4921a, method tracking 7d6f79e617, reflect.Type.Method d6535415fb, relocation hooks 376ef734a7, field tracking 219922e95b, -dumpdep restored c6621d9241) with a work queue ordered by a min-heap because global indices follow package DAG order (36dbdbe9bd; hand-written heap 7179e426e2). In 1.16 Cherry Zhang tightened the semantics in several steps: compare the method name and type descriptor rather than strings (e4d6bfdbdf); compile marks type descriptors converted to interfaces and the linker only considers methods of such UsedInIface types, propagating the mark to “child” types reachable by reflection (95848fc5c6: cmd/go -5.1% bytes) and through method descriptors (789d77a87e); the mark is applied only when the converting code is reachable via a new marker relocation R_USEIFACE (af18bce87c: cmd/go 13.6MB to 13.47MB); used interface methods are marked by R_USEIFACEMETHOD, so only called interface methods keep matching concrete methods alive (39dde09126); itabs reference methods weakly so unused ones can be pruned (e8700f1ce6, 1.17); unreachable methods get offset -1 (0ab72ed020) so a bug fails fast.
Timeline.
- Go 1.10 — Done: nothing substantive in this slice. Left open: nothing noted.
- Go 1.12 — Done: type-name mangling moved after deadcode (
618bfb28dc,9c833831b2) so fewer symbols get renamed,ffbf479ae1removes statictmp variables from the symbol table. Left open: nothing noted. - Go 1.13 — Done:
2cdebb5174removes a stale reference;951143cf14raises the call limit in the stack-bound check. Left open: nothing noted. - Go 1.14 — Done: the new index-based deadcode (commits above), special-casing for runtime.gcbits removed (
fcfbe25dca), dupok symbols resolved to other packages added to Textp (5fec788239),5a210b5858keeps DWARF constant DIEs alive. Left open:8a9be4921aitself says “method tracking logic hasn’t been implemented” (done by7d6f79e617, whose message still lists reflect.Type.Method as a TODO, done byd6535415fb). - Go 1.15 — Done:
b0da26a668stops checkingreflect.Value.Call,af9ab6b2e8checksreflect.Value.MethodByNameexplicitly;ab1253b59a,38437ce118read only needed fields;44d22869a8does not mark a symbol’s Gotype reachable;d7553d9af9marks special symbols reachable;19a932ceb8does not mark shared-library symbols reachable unconditionally. Left open: all methods of a reachable type matched against every interface method. - Go 1.16 — Done: UsedInIface / R_USEIFACE / R_USEIFACEMETHOD (see Design),
3a34395363always marks runtime.buildVersion/modinfo,7e54aa2c25does not mark GoType reachable under -linkshared,98f8454a73no longer decodes shared-library type symbols in deadcode. Left open: itab method edges still strong (untile8700f1ce61.17). - Go 1.17 — Done: weak itab references (
e8700f1ce6),92c189f211/b4833f7c06resolve and always markruntime.unreachableMethod,a1222b7535adds a debug print,a655208c9ehandles types converted to interface under dynlink,18510ae88fand runtime disable memory profiling whenruntime.MemProfileRatedata are unreachable,a22e317220/aa5540cd82/49402bee36change map type andmap.zerohandling for linking. Left open: nothing noted.
DWARF generation in the linker
Span: Go 1.10 → Go 1.17 · Status at go1.17: DWARF 4 with per-compilation-unit DIE and line-table symbols generated in parallel; line tables produced by the compiler → continues as DWARF quality for debuggers (types, variables, trampolines) (1.18+)
Earlier history: DWARF generation in the linker (Go 1.0 → Go 1.9)
Design. The linker builds .debug_info, .debug_line, .debug_ranges, .debug_frame, .debug_loc and (until 1.16) pubnames/pubtypes from go.info.* DIE fragments emitted by the compiler plus type DIEs it synthesizes from runtime type descriptors (dwarf.go). Key transitions: one compilation unit per package with its own line table, instead of one giant CU (d4dda76b5f; motivation: sequential line-table scanning, CU PC ranges 97920373fa, per-package producer flags 2c1d2e06af, and moving more generation into the compiler); .debug_aranges eliminated (77c27c3102); DW_AT_go_runtime_type to link a DWARF type to its runtime type for decoding interface contents (393f84a125); constants exported (9daee93121, 7830a19a4f); function-type size (f4abbc0e61); sudoge70fae8a64); location lists and compact encodings (84188296e9 then reverted 4410900a3c, 31ddd8a39d, cd702b171c, 38bd725bf1); args vs results distinguished (3ba818c894); DW_AT_decl_line (4a1be1e1da); is_stmt in line tables (619679a397, dead03b794: +2614 bytes of .debug_line on go1.test, 3636d53c61, 8dba66dfeb); regabi location expressions (162d4f9c92, 1.17).
The big structural change is moving line-table generation from linker to compiler (Jeremy Faller, 1.14): the compiler records a debug-lines symbol per function (376fc48338, f7f85bdc2c, ee3dded36d: a state machine reset at the end of every function so blocks can be concatenated after dead code), a compilation unit can no longer be assumed 1:1 with a package since assembly files only know their own file names (78a3734714), and the linker switches to simply pasting these fragments (8506b7d42f: +52/-200 lines; 7defbffcda removes the old isStmt symbol). In 1.16 the linker emits include directories in the line prologue (fb184a383e: kubelet .zdebug_line ~30% smaller) and changes line generation to cut heap (d08bab99b7), DWARF pub tables are dropped (bc1dbe58cf). Under dev.link, DWARF was converted to loader APIs in two phases (types 25f1b093fe; funcs/CUs 0c41a37c6c), SDWARFINFO split into six subtypes (96ec09da48), DWARF subsymbols demoted to aux (8634642f9a), file renumbering removed (0dff5b0b9e), and second-stage generation parallelised (d1a186d29c).
Timeline.
- Go 1.10 — Done: CU per package (
d4dda76b5f), producer flags (2c1d2e06af), file reference relocs (b52b77cb95), no compilation dir (2f170520aa,4a3d408d18), range lists without base selectors (018642d6cf),a0402b6bf8avoids relocs that confuse dsymutil, constants (9daee93121,7830a19a4f),c58b98b2d6hasmain bit in moduledata, DW_AT_data_member_location as constant (37dbfc51b0), go.info syms not created for non-Go functions (6d7db25e5c). Left open:24e4a128c9disables DWARF for plugins and programs using plugin.Open because type mangling is incompatible with the go.info generator. - Go 1.11 — Done: is_stmt (
dead03b794); location-list/range workarounds for dsymutil (230b0bad1f, revertedf8973fcafb; re-landed aseca41af012removing base-address selectors only when the external linker on macOS will run);393f84a125runtime-type attribute; DWARF on darwin/arm and arm64 (5f5168e004);402dd10a99.debug_line back to DWARF version 2 (dsymutil/LLDB); prologue-end plumbing (7bac2a95f6,836fe697c4). Left open: is_stmt only in internal-linker tables. - Go 1.12 — Done:
a0f5d5f883refs point at the typedef entry, global variable DIEs moved to their compile unit (1c8943bd59),ec4ae29f52abstracts DWARF metadata symbol lookup,75a0b9dbf3DWARF64 for AIX. Left open: nothing noted. - Go 1.13 — Done:
ed2fce2d87dwarf64 in .debug_frame,e29f74efb9exports package name in debug_info,4d8a37a6d9fixes pubnames/pubtypes,b56d1bad29call_line as data4 on iOS,26954bde44/3f1422c799__DWARF segment protection on macOS,d41a0a0690removes a large slice in scopePCs. Left open: nothing noted. - Go 1.14 — Done: linker DWARF types for autos from
R_USETYPE(e72f002ed0,70a1efbb5cstops reading Autom records), multi-CU per package and compiler-generated line tables (above),65a649c565,32190b0a49expanded import path in function DIE,ab4a71fca7R_ADDRCUOFFrelative to CU start,8506b7d42f,9bba63bbbe/c112289ee4bogus-line for infinite loops. Left open: DWARF generation still onsym.Symbol. - Go 1.15 — Done: both DWARF phases converted to loader (
25f1b093fe,0c41a37c6c,61a0b0d624,da9f383ca1,cdc1b19513),8634642f9a,0dff5b0b9e,ef44bd8413/b0b185c5faPatchDWARFName hook then removal,58f017bceafixes end_sequence. Left open: heap use of line tables (addressed byd08bab99b71.16). - Go 1.16 — Done: include dirs (
fb184a383e),d08bab99b7,bc1dbe58cf,27144d166bCU-centric methods,d1a186d29cparallel,ef9e8fbfbcremove dwarf2.go,96ec09da48,8ca2eae206,1e6ad65b43DWARF combining on macOS ARM64,7e25bdba5e. Left open: nothing noted. - Go 1.17 — Done:
c819907754relocates global-variable DWARF generation,537cde0b4b/162d4f9c92/b83610699aregabi support (producer records regabi status),fd4631e24f/169155d61e/41afd3af42parameter DIE ordering and abbrevs,fce2a94d84/e78e04ce39panics in inlined/obfuscated code, dwarfgen package split in the compiler (01fd2d05c8),6d98301114correct PE DWARF alignment. Left open: nothing noted.
DWARF compression and Mach-O dsymutil combining
Span: Go 1.11 → Go 1.16 · Status at go1.17: still the design at go1.17 (zlib ELF/Mach-O/PE compression; dsymutil combine on macOS/amd64 and arm64) → continues as Linker performance and memory (1.18+)
- tracking issue #11799 cmd/link: compress debug info referenced by 5 of this thread's commits
Design. Compressing DWARF requires relocations to be applied first and breaks the constant offset between file position and virtual address; the linker therefore separates VA layout from file layout (bd83774593), then compresses DWARF sections at the end of the file and recomputes their addresses from the compressed size (594eae5ad0: hello 1.60MB to 1.05MB, -34%; StdCmd +5.4% time). External-link mode compresses debug sections too (b397f633ca), then Windows (83515df3f3) and Darwin (726a2d04ea) in 1.11; BestSpeed (12ed0ddec1). In 1.13 DWARF relocations are applied during compression (248444d5eb), in 1.15 compression is parallel (a3873ab49f) and converted to the loader (3e8975172f), 0bbc19760b frees compressed symbols.
On Mach-O, dsymutil cannot handle base-address selectors in location lists/ranges, so the linker rewrites them to CU-relative entries only when the external link will go through dsymutil (230b0bad1f, eca41af012). Binaries are post-processed by machoCombineDwarf (0fe1986a72 extracts load-command parsing and disables combining for non-macOS Apple platforms), STAB symbols stripped after dsymutil for reproducibility (e8f918535e, using -Wl,-S when DWARF is off), xcode strip used (7e25bdba5e).
Timeline.
- Go 1.11 — Done:
594eae5ad0,b397f633ca,83515df3f3,726a2d04ea,12ed0ddec1. Left open: compression was sequential (30% of link time before BestSpeed). - Go 1.13 — Done:
248444d5eb,0fe1986a72,807761f334revises a change because LLDB is picky. Left open: nothing noted. - Go 1.15 — Done:
a3873ab49f(Dwarfcompress -66%),3e8975172f. Left open: nothing noted. - Go 1.16 — Done:
e8f918535e,1e6ad65b43,7e25bdba5e. Left open: nothing noted.
Symbol ABIs: stable ABI0 vs ABIInternal in the linker and object files
Span: Go 1.12 → Go 1.17 · Status at go1.17: wrapper-based ABIs with name-mangled shared linkage and ABI-aware cgo exports; ABI aliases only for code without wrappers → continues as Type, itab and typelink layout (.go.type section, relro rework) (1.18+)
- proposal #27539 proposal: runtime: make the ABI undefined as a step toward changing it referenced by 4 of this thread's commits
- proposal #40724 cmd/compile: switch to a register-based calling convention for Go functions referenced by 3 of this thread's commits
- design doc Proposal: Register-based Go calling convention Austin Clements, 2020-08-10 · linked from #40724
- design doc Proposal: Create an undefined internal calling convention Austin Clements, 2019-01-14 · listed in Go: A Documentary
- proposal #18597 proposal: cmd/compile: define register-based calling convention linked from #27539
Design. The compiler/linker split of calling conventions (685aca45dc) prepared for the register ABI: the assembler produces symabis, the compiler generates ABI wrappers for body-less Go declarations and for Go functions referenced from assembly, and the linker represents the two ABIs as symbol “versions” (c5718b6b26 repurposes the reference version field as an ABI number, -1 for static, and adds a list of ABI alias symbols). While the ABIs were identical, wrappers were “ABI alias” symbols resolved by the linker (63815923fc resolves aliases for external symbols; 1794ee6829 nice error message on ABI mismatch). In 1.17, with GOEXPERIMENT=regabi the aliases stop (59703d53e2), shared linkage mangles ABIInternal names with .abiinternal/.abi0 suffixes so the ABI of a shared-library symbol is known (a21de9ec73, d7473fd907), PE and Mach-O external linking also mangle names with ABI (fd5e0bd385, dcf85b30ba), and cgo export directives carry definition ABIs so host objects bind to the right Go entry point (69262d4871; before that, host references were assumed ABI0 and crashed through function values).
Timeline.
- Go 1.12 — Done:
c5718b6b26,685aca45dc,1794ee6829,c0281afd87(obj writer stops de-duplicating symbols). Left open: the ABIs are identical; wrappers are aliases “until the ABIs diverge”. - Go 1.14 — Done:
63815923fcABI aliases for external symbols,37b194a4221.13 puts shlib “.type” functions in internal ABI,a2b1dc863ffactors out direct-call identification. Left open: nothing noted. - Go 1.16 — Done: ABI alias handled in loader (
2a00c137b11.15 folds the zero symbol check),2b8b06ebbfadds theregabiGOEXPERIMENT. Left open: nothing noted. - Go 1.17 — Done: the commits above plus
d28aae26b0recognising internal/abi as a runtime package for trampoline ordering,bfc7418e6dmarking Darwin syscall wrappers ABIInternal,1f7ddf57d2usingabi.FuncPCABI0for libc wrappers. Left open: “for functions without ABI wrapper generated, only one ABI exists but we don’t know what it is, so we still use alias” (a21de9ec73).
Mach-O and Darwin support
Span: Go 1.10 → Go 1.17 · Status at go1.17: internal linking, PIE, plugins and c-shared on darwin/arm64 with code signing, __DATA_CONST, LC_BUILD_VERSION → continues as Per-platform object formats and internal linking (Mach-O, PE, XCOFF, wasm, ppc64, riscv64, loong64) (1.18+)
Earlier history: Container writers: Windows PE, Mach-O and Plan 9 (Go 1.0 → Go 1.9)
- tracking issue #38485 runtime: tracking bug for ARM-based macOS and GOOS/GOARCH values referenced by 9 of this thread's commits
Design. macho.go writes segments __TEXT, __DATA, __DWARF and a LINKEDIT with symbol and dynamic-link info; external linking hands the Go object (go.o) to the system ld; internal linking needs dynamic imports and stubs (macho PLT, MACHOPLT).
Timeline.
- Go 1.10 — Done:
b7c600d6baenablesbuildmode=pieon darwin/amd64, plugin on darwin/amd64 re-enabled (1134411a83),1f8433c66ano duplicate dylib,90ffc40e4fcorrect file flag,89a7adf8e4/4a5b99c3a8pagezero size for iOS,e285f39248ignore macho symbols in invalid sections,f797e485e0fix runtime.text twice with plugins,3e1e66fa05don’t pass -gdwarf-2 to external linker. Left open: nothing noted. - Go 1.11 — Done:
4af08e7a13sets S_ATTR_PURE_INSTRUCTIONS on text,5f5168e004DWARF on darwin/arm/arm64,1a92cdbfc1handles all load commands,3363e98371removes arm64 R_ADDR workaround. Left open: nothing noted. - Go 1.12 — Done:
66bb8ddb95silences a bogus Xcode.tbdwarning,cfac65bf2fomits deprecated linker arg for iOS. Left open: nothing noted. - Go 1.13 — Done: bitcode builds for iOS/tvOS/watchOS (
ba965640a4,1bd974eeb71.14,f4e32aeed11.14),277609f844copies platform version commands to go.o,a74e01204econsolidates platform setup,4cd6c3bac7bumps macOS/SDK version to 10.9,637f34fee0/c90f6dd496allow duplicate weak symbols on Mach-O,3cb92fcba7c-archive compatibility. Left open: nothing noted. - Go 1.14 — Done:
2959ba1f55/06e5529ece/1aa64b55f1prefix syms with “_” on darwin links,d856e05d64PLATFORM_IOS,d3660e8ef8PIE on darwin/arm by moving RODATA out of TEXT,cc21e4d130/396a9b98baloader port. Left open: nothing noted. - Go 1.15 — Done:
b4245ea544mmapped macho host objects,d9c51620b9/d8ab10525edelete darwin/386 and darwin/arm host loading,1f325af4ddSCONST on darwin. Left open: nothing noted. - Go 1.16 — Done:
ffd95aadcd__DATA_CONST,1aa43a53beexport only necessary symbols dynamically,7d6b304f12/234de9e1c2/f8e554021bplugin/PIE/c-shared on macOS/ARM64,e4ec30965b/627959eb04/b85c2dd56cinternal linking and cgo on darwin/arm64 made default,3a76627df4-race,6f84993e90/0aba8f24cb/cf7aa585accode signing,4bc5f6f45fDYLD_INFO placement,f46a5b1e45PIE internal on darwin/amd64,b110733327/5abda2618blarge addends,612b119447,c321430bdc,59a702aa6aMach-O relocations in mmap,7ee2622443auto-linking dylibs for cgo_import_dynamic (Security/CoreFoundation only linked with x509). Left open: “this signature will likely be invalidated when we stamp the buildid” (kept to size the load command). - Go 1.17 — Done:
d5b2d809b0LC_BUILD_VERSION,b6ff3c69d5more load commands,dcf85b30baABI name mangling,f39997b2besplits large text sections on Darwin/ARM64 for external linking. Left open: nothing noted.
PE / Windows
Span: Go 1.10 → Go 1.17 · Status at go1.17: internal/external linking, PIE, ASLR, DEP, windows/arm and windows/arm64 → continues as Per-platform object formats and internal linking (Mach-O, PE, XCOFF, wasm, ppc64, riscv64, loong64) (1.18+)
Earlier history: Container writers: Windows PE, Mach-O and Plan 9 (Go 1.0 → Go 1.9)
- tracking issue #36439 all: port to Windows/arm64 referenced by 3 of this thread's commits
Design. In 1.10 pe.go was refactored from procedural code with global section tables into peFile, peSection, peStringTable types (20832e6d4b … 3bd79713b7, about 30 small commits: addDWARFSection, checkOffset/checkSegment/pad, emitRelocations, writeSymbols, writeOptionalHeader, etc.). Subsequent work is feature-driven.
Timeline.
- Go 1.10 — Done: the peFile/peSection refactor (representative:
20832e6d4b,2c3d13b210,7188e00287,04d410d6cd,ce54a0c10a,d77d4f509c),410b73728f/f58c48f834isolate Windows-specific code,bb0bfd002ac-shared on Windows,923299a6b8stack commit size back to 4KB,77ea9f9f311MB stacks for 32-bit. Left open: nothing noted. - Go 1.11 — Done:
d67db88146splits .text and .rdata,83515df3f3DWARF compression on Windows. Left open: nothing noted. - Go 1.12 — Done:
aa311fecdawindows/arm,8256bcdae0moves .rel into .text,41fd4c88adno_GLOBAL_OFFSET_TABLE_on windows/386. Left open: nothing noted. - Go 1.13 — Done:
e883d000f4stops generating NT 4 compat headers (subsystem version 6.1),1a423bec001.14 (header copy-paste fix). Left open: nothing noted. - Go 1.14 — Done:
31bfab4ac6DEP,8fc35238a7terminal-services aware,c96d794f66consistent PE versions,e4c3925925libmsvcrt.a during internal link,af880809d8/274f4cef93output via MapViewOfFile / FlushViewOfFile. Left open: nothing noted. - Go 1.15 — Done:
95f3821390-buildmode=pieon windows,4ec5e7c50fmmapped OutBuf on Windows,da07c59f24debug/pe constants. Left open: nothing noted. - Go 1.16 — Done:
56dac60074ASLR for c-shared, resource-section fixes for LLVM windres (663cd862ed,8981092d71,c9fb4eb0a2,033390d9ad,48dfa2b2dc),cb84d831c9windows/arm all-PIE. Left open: nothing noted. - Go 1.17 — Done: windows/arm64 (
985d087782,f41460145e,c0e1301b8c,8e368708c5external linking,e5a6c5243cforce external for cgo),a51daac840/91cfbf39e4/811167e2c9COFF section fixes,a0248a99a8,c432917061links libgcc after mingw,fd5e0bd385. Left open: windows/arm64 cgo requires external linking (e5a6c5243c).
AIX / XCOFF
Span: Go 1.12 → Go 1.14 · Status at go1.17: done; aix/ppc64 supports internal and external linking, c-archive and PIE → continues as Per-platform object formats and internal linking (Mach-O, PE, XCOFF, wasm, ppc64, riscv64, loong64) (1.18+)
- tracking issue #25893 all: port to AIX/ppc64 referenced by 3 of this thread's commits
Design. AIX uses XCOFF, big-TOC addressing and a host linker with an export file. The linker gained an internal/xcoff reader (b171b28138, later moved to internal/xcoff, e256afff51), an XCOFF writer (269e531917, 1315 new lines), DWARF64 (75a0b9dbf3), and TOC-relative data access (cebf9d47cf: addi instead of load for .data/.bss symbols).
Timeline.
- Go 1.12 — Done: initial port (
a3a69afff8,269e531917), XCOFF fixes (63ac89bfb1,3f9efe7500,b41cdc4a59,4295ed9bef,9a3c1a1bc8),cebf9d47cf. Left open: only internal linking (external linking disabled). - Go 1.13 — Done: external linking and c-archive (
80f10965ee,f0749c74fd,359fb4a9afexport file,3cf89e509b,697f4183e6,cf952e9e62PIE,cb8054a6b6/0cd74d1f11trampolines/C trampolines,2deda8792a,d3631955d4,0ff9df6b53,a891f2e2ae),0c9e0c257b.go.buildinfo. Left open: DWARF disabled with old ld. - Go 1.14 — Done:
cfa421c9a6relocation-based TOC lookup in new loader,e961b26c27. Left open: nothing noted.
WebAssembly
Span: Go 1.11 → Go 1.14 · Status at go1.17: still the design at go1.17 → continues as Per-platform object formats and internal linking (Mach-O, PE, XCOFF, wasm, ppc64, riscv64, loong64) (1.18+)
- design doc WebAssembly architecture for Go cited in this thread's commits
Design. The wasm port has no ELF-style layout; cmd/link/internal/wasm writes the type, import, function, table, memory, global, export, element, code and data sections plus custom sections directly, with the Go heap/data placed in wasm linear memory.
Timeline.
- Go 1.11 — Done: wasm architecture added to the linker (
f41dc711d8); name-section fix (4eb1c84752), build ID in modules (3b0b3a0223), Go version custom section (8589f46fe0). Left open: nothing noted. - Go 1.12 — Done:
48e22da1d2fixes the deferreturn location on wasm. Left open: nothing noted. - Go 1.13 — Done:
99e223289boptimises the data section,fb9b818bbdcaps data segments at 100000,b434bbf197adds theGOWASMenvironment variable (comma-separated experimental wasm features, none defined yet) user-visible,460f9c6068optimises memory allocation. Left open: no experimental features exist yet. - Go 1.14 — Done:
57662b1575producer section replaces the go.version section,30521d5126valid binaries with large data sections,03bb3e9ad1fast path inwriteUleb128(97% of 858277 calls need under 7 bits),1c8e6077f6/2686e74948runtime async-event goroutine kept in deadlock traces and made short-lived. Left open: nothing noted. - Go 1.15 — Done: wasm moves onto loader APIs (
2a33f5368a,dd34841de7,f9ed846a46); see the wavefront thread. Left open: nothing noted.
Build modes: PIE, plugin, c-shared, c-archive, shared libraries
Span: Go 1.10 → Go 1.17 · Status at go1.17: internal-linking PIE on linux/amd64+arm64, windows and darwin; plugins on darwin/arm64 and freebsd/amd64, disabled without cgo → continues as ELF/relro/hardening and external-link policy (1.18+)
Earlier history: Build modes: -shared, c-archive, c-shared, shared, PIE, plugin (Go 1.1 → Go 1.9)
Design. Each build mode changes the relocation strategy. In PIE and c-shared, absolute R_ADDR relocations in data have to be dynamic relocations; internal-link PIE requires the linker to emit them itself (and in 1.15 to emit cheap R_X86_64_RELATIVE instead of symbol-lookup relocations, because a simple HTTP server needed over 1s of dynamic linking, 675a2e7078). Plugins and shared libraries need type-symbol name mangling (24e4a128c9, 6355d6c7e2, 273b657b4e for -X main.*), no coalescing of type descriptors when dynamically linking Go (d6a27e8edc) and a linker-visible plugin.Open marker (b1a20253fe caches the check). Which modes work on which OS/arch is mostly down to each architecture’s adddynrel.
Timeline.
- Go 1.10 — Done: PIE on darwin/amd64 (
b7c600d6ba), plugin on ppc64le (e9cbabb334), c-shared on ppc64le (3e7abf82e3), c-shared on windows (bb0bfd002a), c-shared/c-archive on s390x (c2efb2fde5), plugin on darwin/amd64 re-enabled (1134411a83,88a1e85c70,f797e485e0),273b657b4e.c99cfd93a8forbids internal linking for cgo on ppc64 (multiple TOC sections). Left open: the ppc64 internal linker cannot handle multiple TOC sections; plugins and DWARF were incompatible (24e4a128c9). - Go 1.11 — Done:
2d26a65f8fno-no-piewith-linkshared,ceda47d08aplugin on linux/arm64. Left open: nothing noted. - Go 1.13 — Done: PIE with internal link on linux/arm64 including GOT/PLT and many
R_AARCH64_*relocations (d2765de863), PIE and c-archive on aix (cf952e9e62,697f4183e6), c-archive Mach-O compatibility (3cb92fcba7),e4665da9bcreproducible toolchain builds under PIE. Left open: nothing noted. - Go 1.14 — Done: plugin on freebsd/amd64 (
88ca80b322),795e8c2317DWARF with plugins on Darwin, PIE on darwin/arm (d3660e8ef8),6dc250f456typerel size with c-archive. Left open: nothing noted. - Go 1.15 — Done:
b2696fde40PIE defaults to internal linking again, relative relocs (675a2e7078), C-object absolute relocations as dynamic relocs plus static application (baea3cd7c9,d532d5f0fa),c70f069902re-enables internal PIE after an accidental rebase disabling, windows PIE (95f3821390, which adds.relocand DYNAMIC_BASE; HIGH_ENTROPY_VA on amd64) user-visible,6aeaf4a0f4/6290a54365plugin fixes under dev.link. Left open: nothing noted. - Go 1.16 — Done: c-shared ASLR on Windows (
56dac60074), darwin/arm64 plugin/PIE/c-shared (7d6b304f12,234de9e1c2,f8e554021b), internal PIE darwin/amd64 (f46a5b1e45),cb84d831c9windows/arm all PIE. Left open: nothing noted. - Go 1.17 — Done:
983dea90c1disables plugin support when cgo is disabled,723f199eddcorrect flags in.dynamicfor PIE. Left open: nothing noted.
Host object loading and external linking
Span: Go 1.10 → Go 1.17 · Status at go1.17: host objects (ELF, Mach-O, PE, XCOFF) read with loader symbols; external linker flags probed and filtered; cgo directive encoding as JSON → continues as ELF/relro/hardening and external-link policy (1.18+)
Earlier history: External (host) linking and cgo (Go 1.1 → Go 1.9)
Design. For cgo the linker either links C objects itself (“internal” mode, using ldelf/ldmacho/ldpe/ldxcoff readers) or writes go.o and invokes the system linker (“external”). A 1.10 change stopped go build from pre-linking all cgo objects with ld -r (8e5ac83d43, fixing a libgcc multiple-definition problem), which required the Mach-O loader to apply unsigned relocations relative to symbol values. Host-object readers were moved into packages loadelf, loadmacho, loadpe (and loadxcoff), and eventually converted to the loader (ELF fa284af720, 3fa9500dd1; Mach-O 4ad94a5db9; XCOFF 8896a6b8aa; PE 83ba044be6), with read-only mmapped data for ELF (9d673bf1db) and Mach-O (b4245ea544). Flag probing (linkerFlagSupported) was made robust: pass provided ldflags, not all args (6417e91962, 023dbb188d), consider alternative linkers (bcda68447b), pass arch flags (a318d56c1e), use -Qunused-arguments only if supported (565e70fcef).
Timeline.
- Go 1.10 — Done:
8e5ac83d43,1053ae5cf5(goobj parses native objects in the archive),589ea93678(nm handles cgo archive),4eca0898ec/0c4d035ca8refactors,ef6978b218R_X86_64_PC64 relocations,2763672ecbnative relocation type names in errors,cd5c10f040ELF note alignment,ba42b3ffd3sniff runtime-gdb.py path,6424c40c22no default entry symbol in some situations,8396015e80setsruntime.GOROOTdefault at link time user-visible (so a moved toolchain reports its relocated root),28639df158apply -X after symbol load. Left open: nothing noted. - Go 1.11 — Done:
8f6ae33796cgo directives encoded as JSON (+82/-163 lines; JSON escapes newlines so the object-file$$delimiter stays safe),f861f66d1dtreat cgo exported symbols as C symbols,ad4e6370fedrop-rdynamicwith-staticin cgo LDFLAGS,506d6a32ceerror for ext linking on ppc64,17fbb83693response files for long argument lists on Windows,b2d3d6e6761.10/d1be0fd9101.10 loader fixes. Left open: nothing noted. - Go 1.12 — Done:
484fc06849doesn’t link sections not named .o,281ce28c50in-package syso linking,35435b27c4closes input files,1135071b47initial cgo support for ppc64. Left open: nothing noted. - Go 1.13 — Done:
9f9c5fa8c7-awith external linker,3235f7c072duplicate ELF section names tolerated,83f205fa88R_AARCH64_LDST128_ABS_LO12_NC,e27402aee0configurable default dynamic linker/loader user-visible,a4f9c2a29eopenbsd/arm64,4590abe072. Left open: nothing noted. - Go 1.14 — Done:
5e514b76e2loads symbols from .syso in external link mode,59a6847039pass-through of undefined call targets,bf7e55b618directly exec archive when external tmpdir,c78ac39ae9gold only on arm/arm64 Linux/Android,d3c2b1f176,f29e53d664/e4c01f088bfixes forld -rELF host objects,ffa5c85c92MIPS64 ELF relocation record layout. Left open: nothing noted. - Go 1.15 — Done:
e6d7326fb6internal linking default for android/arm64,bcda68447balternative linkers,e5a6a94aeblld on Android,9b9556f660definitions from debug/elf,e95b858530NetBSD ABI 7.0,fd18f3ba50ASLR off for netbsd+race. Left open: nothing noted. - Go 1.16 — Done:
9138a2a67fno duplicate DT_NEEDED,38367d098ededup shared libraries on openbsd,6398b4725c/75789880a6C static symbols in the symbol table,a52a5d8a43PLT sections executable on ELF,e61d17d3b9,740851bacaavoid-Twith lld,a36ba090fdalwaysR_X86_64_PLT32for SDYNIMPORT calls,f4e7a6b905. Left open: nothing noted. - Go 1.17 — Done:
7252e1e5b6converts-Ito-Wl,--dynamic-linkerfor external linking,24ea547a21not with-static,3bf645a633force external for DragonFly cgo,24e9707cbfsupport-fltoin CFLAGS,bd0fc0b9c3preserve ELF phdr flags from external objects,e4253cd023/975b097307reloc-variant plumbing,6208b10d1e/007e247af1setCgoAttr refactor. Left open: nothing noted.
Architecture ports and trampoline insertion
Span: Go 1.10 → Go 1.17 · Status at go1.17: two-pass trampoline pass for arm/arm64/ppc64; riscv64, mips64, windows/arm64 external linking supported → continues as Per-platform object formats and internal linking (Mach-O, PE, XCOFF, wasm, ppc64, riscv64, loong64) (1.18+)
Earlier history: Architecture and OS ports in the linker (arm, power64, arm64, mips, s390x, nacl, BSDs, Solaris) and trampolines (Go 1.0 → Go 1.9)
- tracking issue #27532 all: port to RISC-V referenced by 2 of this thread's commits
Design. On RISC targets a direct call has limited range, so trampoline passes insert veneers. The 1.17 redesign by Cherry Zhang replaced a single dependency-ordered pass (which fails with linkname cycles and with PLT stubs placed late) by a two-pass approach: pass one assigns addresses assuming the program is small; only if some call is out of range does it start over inserting trampolines for far targets and targets with unknown addresses (18852e8372). ARM64 trampolines (f12dfeac89), PLT-call trampolines on ARM64 and ARM (657f58d845, 12eaefead4), R12 as ARM scratch (11052d77a3), ppc64 split text sections forced to 64-byte alignment (6edd573218), and 098504c73f (1.16) conservative trampolines for inter-dependent packages.
Timeline.
- Go 1.10 — Done: GOMIPS (
12abacb555), s390x modes (c2efb2fde5). Left open: nothing noted. - Go 1.13 — Done:
a4f9c2a29eopenbsd/arm64,a3863fc18afix constant. Left open: nothing noted. - Go 1.14 — Done: riscv64 initial support (
bf5f64107a,1046a9cb1barchreloc), freebsd/arm64 (e0a5fae676,152dddee7euse ld.bfd),2e4f490b31open-coded defers on riscv64. Left open: nothing noted. - Go 1.15 — Done: ARM64 function alignment 16 (
2568d323f6), large function alignment (d92a5a80b5,71d477469c/1dbcbcfca4:PCALIGNandALIGN2048for the gVisor interrupt table), trampoline fixes on PPC64/AIX (33213039e5,025bca8746,66ee97590c),490bc6097aARM no longer requires math,ec177e4c83frame pointer on iOS arm64. Left open: nothing noted. - Go 1.16 — Done:
3a63d04d2eriscv64 TLS,15eaa870e1external linking linux/riscv64,0b798c46cdloadelf riscv64,bb48f9925copenbsd/mips64,e508c1c67bMIPS64 ELF relocations,d41b9066dadebug option encouraging ppc64 text splits,098504c73f1.17,04b8a9fea5GO386=softfloat user-visible,a2a2237ae0ARM DYNIMPORT jump,623319a847arm64 SDYNIMPORT internal linking. Left open:098504c73f1.17 may generate unnecessary trampolines when packages turn out adjacent. - Go 1.17 — Done: two-pass trampolines and per-arch support (above),
f68878f0fcopenbsd/mips64 cgo,31df4e3fcdmips64x relocs,ff5cf4ced3mips32.gnu.attributes/.MIPS.abiflags,3e5bba0a4432-bit TLS_LE offsets on PPC64 (for thread sanitizer),0a655598e1ppc64le glink resolver,82a1e0f9d3,9ed736ac2a. Left open: nothing noted.
GOEXPERIMENT plumbing and tool configuration
Span: Go 1.16 → Go 1.17 · Status at go1.17: experiments are a struct in internal/goexperiment parsed by objabi/buildcfg → still the design at go1.17
- proposal #42681 build: move GOEXPERIMENT knob from make.bash to cmd/go referenced by 1 of this thread's commits
Design. Until 1.16 experiments (fieldtrack, framepointer, staticlockranking…) were scattered GOEXPERIMENT string lookups in cmd/internal/objabi and the runtime. The 1.17 work centralises them: parsing in one place (06ca809410), build-time default plus runtime override handled in cmd/go (7fc638d6f1; bootstrap built without any GOEXPERIMENT, 6461d74bf2), boolean flags (af4388aee1), consolidation into internal/goexperiment.Flags (6304b401e4), representation as a diff from the baseline (a8e55538af, cf2396c70e), GOEXPERIMENT=none meaning no flags (756e2b1529), objabi.Experiment holding all flags (0c93b16d01), simpler runtime side (095ba22597, aeaa4519b5). 2b8b06ebbf (1.16) adds the regabi experiment as the toggle for the register ABI, and c847932804 (1.17) uses regabidefer to replace reflectcall of defers with direct calls.
Timeline.
- Go 1.13 — Done:
4814e7d591includes full GOEXPERIMENT flags in the tool ID for the cache key (release versions previously ignored them),a5248acd911.12 enables GODEBUGCPU in non-experimental builds. Left open: nothing noted. - Go 1.16 — Done:
2b8b06ebbf. Left open: work “expected to extend over a few releases”. - Go 1.17 — Done: the commits above. Left open: nothing noted.
Build identity, buildinfo and reproducibility
Span: Go 1.10 → Go 1.17 · Status at go1.17: still the design → continues as Reproducible build IDs, buildinfo and symbol tables (1.18+)
Earlier history: Build IDs, ABI hashes, flags and determinism (Go 1.0 → Go 1.9)
Design. The go command computes a content-based build ID (7dea509703, 1.10) that includes compiler and linker flags; the linker records it in an ELF note / Mach-O UUID or text-segment magic. d6b06f0254 (1.13) adds a writable-data symbol starting with a magic number and pointers to runtime.buildVersion and runtime.modinfo, so go version <binary> can find them without disassembly (runtime.buildVersion/modinfo always marked reachable by 3a34395363, 1.16). Paths: -trimpath accepts a semicolon-separated list with =>replacement (4891a3b66c), the compilation directory is not stored in DWARF (2f170520aa, 4a3d408d18), STABs stripped on darwin for reproducibility (e8f918535e), e4665da9bc reproducible PIE toolchain builds.
Timeline.
- Go 1.10 — Done:
7dea509703,840f2c167fstandard-Voutput,0be2d52eba-importcfg. Left open: nothing noted. - Go 1.13 — Done:
d6b06f0254,4891a3b66c,0c9e0c257b.go.buildinfoin XCOFF symtab. Left open: nothing noted. - Go 1.16 — Done:
3a34395363,e8f918535e,79fb187be4cgo-trimpath. Left open: nothing noted. - Go 1.17 — Done:
a78879ac67movessys.DefaultGoroottoruntime.defaultGOROOT,b05903a9f6fixes the linker’s setter. Left open: nothing noted.
Linker restructuring (1.10 code health and error reporting)
Span: Go 1.10 → Go 1.12 · Status at go1.17: done; later passes build on these package splits
Design. Go 1.10 turned a C-translated linker with many package-level variables into a library-shaped one: ld.Thearch, SysArch, Headtype, Iself, FlagLinkshared, coutbuf and build/link-mode globals moved onto *Link (5d95de2072, 2e8545531e, 6738c494ad, d05f82a11a, 38cfeb9cb5, 5fe9bbcf63; da02dcda02 unexports Thearch in 1.11), the object reader got its own package without ctxt (c80338accb, 9f9bb97420), and symbol types moved to cmd/link/internal/sym.
Timeline.
- Go 1.10 — Done: the commits above, SymKind attribute bit extraction (
1341104ae2,1b8987f1a7,302f0d1646),0c4d035ca8*relocrefactor,e29efbcbcbfixes unintentional symbol creation,d02647242dchecks the magic header. Left open: nothing noted. - Go 1.11 — Done:
2c01b7d632and289ab30457report undefined relocation targets once (and a better message whenmain.mainis missing),6f7fa68b91regenerate SymKind String. Left open: nothing noted. - Go 1.12 — Done:
66065c3115clearer unresolved-symbol error,14560da7e4file-local symbols start at version 10,9555769aa5. Left open: nothing noted.
Changes in this slice that are not part of a thread are listed on the release pages: 1.10 · 1.11 · 1.12 · 1.13 · 1.14 · 1.15 · 1.16 · 1.17
Linker, object files and debug info (Go 1.18–1.28)
Reader: Claude Sonnet. Cites 529 of 693 commits; 49 small platform and test fixes are uncited.
Relocation-free pclntab and funcdata (offsets instead of relocations)
Span: Go 1.18 → Go 1.26 · Status at HEAD: done; pclntab has no relocations, lives read-only in .gopclntab together with funcdata and findfunctab; moduledata in its own section
Earlier history: Early loaders 5l/6l/8l and the shared src/cmd/ld (2008–2011) (Go 1.0 → Go 1.0) · Go symbol table, pc/line table and function metadata (gosymtab, pclntab, findfunc) (Go 1.0 → Go 1.5) · liblink, the object file format and the move into the compilers (Go 1.3 → Go 1.9) · New object file format and the loader (dev.link) (Go 1.14 → Go 1.16) · Wavefront conversion of linker passes to the loader; sym.Symbol removal (Go 1.15 → Go 1.16) · pclntab, funcdata and pc tables (Go 1.12 → Go 1.17)
- tracking issue #76038 cmd/link: rearrange compiler/linker generated data for clarity referenced by 6 of this thread's commits
- background Go 1.2 Runtime Symbol Information linked from #76038
- discussion Do we document the history of .gosymtab and .gopclntab, and the differences with .symtab and .debug_line, .debug_frame? golang-nuts · linked from #76038
- discussion Presence of PCLN Table in CGo binaries golang-nuts · linked from #76038
Design. The pclntab (cmd/link/internal/ld/pcln.go) is the linker-generated function table that the runtime and debug/gosym read. Historically every function entry, funcdata pointer and stack-object pointer was a relocation, which cost linker time and, for externally linked or PIE binaries, dynamic relocations (and forced the table into relro). The thread converted each such pointer into a 32-bit offset from a well-known base symbol (runtime.text, go.func.*, go.funcrel.*) so the data becomes position independent and relocation-free.
On the object side the same logic removed per-function side blocks: PCDATA moved from a separate object-file block to ordinary aux symbols (SymPcdata-style aux in cmd/internal/goobj), funcdata offsets were dropped from the object file and replaced by aux symbol references, and symbol alignment moved into the object writer so the linker no longer computes it. The result is a smaller goobj format and a linker that only emits plain bytes for these tables.
By Go 1.26 the process was completed for the header and the surrounding runtime tables: the only remaining relocation (textStart in pcHeader) was dropped, the pclntab magic constants moved to internal/abi (shared by linker, runtime and debug/gosym), and funcdata symbols, runtime.findfunctab and the pclntab all share one .gopclntab section.
Timeline.
- Go 1.18 — Done:
cmd/link,runtime: remove functab relocationsuses an offset fromruntime.texte31c9ab557; stkobjs relocations replaced by offsets fromgo.func.*3100dc1a7f;_func.entrybecomes an offset (about 10% fewer relocations on darwin/arm64)d3ad216f8e; FUNCDATA relocations converted to offsets (about 40% fewer relocations on darwin/arm64, smaller external-link binaries)017ffcd10d; all FUNCDATA refs moved intogo.func.*cfd74f7727; goobj loses the PCDATA block850a4ffb63and funcdataoffe180e2c27c; pcdata accessed via aux symbols587b3c1192,583eeaae50; symbol alignment logic moved to the object writer17674e2f17;funcSizemade constant2d6d558417; textsectmap fields reshaped for the runtime058fa255bc; holes in functab experimentally marked then reverted1ceb72394e,bd41f2d3cd. Caching field size indebug/gosym.funcTabavoided a 20% PCToLine regression (message ofe31c9ab557). Left open:pcHeader.textStartstill needs a relocation. - Go 1.19 — Done: stop sorting pclntab entries (the sort was a no-op because all values were 0)
1f97f960e7. Left open: nothing noted. - Go 1.20 — Done: unused fields removed from
inlinedCallc83e1b6d5b; linker_funcnames aligned with runtime3e75859390; panic if an inlined function lacks FuncInfo8a0cf719a6. Left open: nothing noted. - Go 1.23 — Done: pcbucketsize and minfunc constants merged into
internal/abi284e553035,40fab294de. Left open: nothing noted. - Go 1.26 — Done:
.gosymtabno longer generated (empty since Go 1.2)7347b54727;textStartremoved from pcHeader (the only pclntab relocation)0e1bd8b5f1; pclntab moved out of relroad3ccd92e4; funcdata symbols placed in.gopclntabb437d5bf36;findfunctabtoo312b2034a4; pclntab magic moved tointernal/abi43cfd785e7; moduledata in a dedicated.go.modulesection9f5cd43fe6; DWARF is generated for moduledatab5f6816cea; cutab length fixae8eba071b. Left open: the pcHeader zero placeholder “can be removed at some point in coordination with Delve” (0e1bd8b5f1).
Type, itab and typelink layout (.go.type section, relro rework)
Span: Go 1.18 → Go 1.28 · Status at HEAD: type descriptors, itabs and GC masks have dedicated linker layout; typelinks and itablinks lists removed; alg/noalg descriptors deduplicated in 1.28 cycle
Earlier history: Runtime type data, GC metadata and sections laid out by the linker (Go 1.0 → Go 1.9) · New object file format and the loader (dev.link) (Go 1.14 → Go 1.16) · Symbol ABIs: stable ABI0 vs ABIInternal in the linker and object files (Go 1.12 → Go 1.17)
- tracking issue #76038 cmd/link: rearrange compiler/linker generated data for clarity referenced by 2 of this thread's commits
Design. The linker previously kept a family of per-kind relro symbol kinds (STYPERELRO, SITABLINK, typelink section, etc.) and shifted symbol kinds around to make relro data writable until relocation. The 1.27 cycle (issue #76038/#6853) rewrote this. Possible relro data now lives in its own sections that become relro when appropriate: type descriptors and their data go into a new .go.type section (runtime.etypes is now the end of that section instead of end of rodata), function descriptors into .go.func, ordinary relro rodata stays in .data.rel.ro. The typerel:* and go:funcdescrel symbols are dropped.
With types contiguous, the runtime can walk them: typelinks was removed in favour of marking the start and end of typelinked descriptors, and the runtime steps through them lazily (cmd/go shrank 15K). Itabs were treated the same (itablinks removed, itabs live in .go.type, and their size is recorded because the zero-sized runtime.etypes can float on AIX external linking). The type-section length is stored rather than the end address, saving a relocation and fixing Darwin dynamic linking. GC masks are grouped under runtime.gcmask.* in noptrbss and removed from the symbol table; earlier (1.24) data/BSS pointer masks were already built from types instead of GC programs.
Timeline.
- Go 1.18 — Done: type descriptor method arginfo placed in the correct section
52b23a50f8; gcprog symbols put with gcbitsd480b5c790;type..importpathfixe5ad363fff. Left open: nothing noted. - Go 1.24 — Done: data/bss pointer masks built from the types the linker knows rather than constituent GC programs; when linking against shared libraries it loads the extra relocations needed to break arrays/structs apart
7588cc9b00. Left open: the message says it is part of removing GC programs from the runtime. - Go 1.26 — Done: ELF section headers sorted by address and shdr/shstrtab built from actual sections (no more
.go.fuzzcntrsstring in every binary)0ff323143d,4bc3410b6c. Left open: nothing noted. - Go 1.27 — Done:
.go.typesection and relro rewrite13096a62e6; typelinks removed481ab86aaf; itabs into.go.typebcc369284d; itablinks removed6ef7fe9a48; gcmask grouped in noptrbss291ee5d7de; type descriptor length stored instead of end1172294145;type:*forced to the start of type datac08f6a5b26; AIX special cases removedee7a2119ac,9fd5a5fa7d;runtime.typeoffset reservation by size rather than GOOS20bc194972; type descriptor size calculation centralized ininternal/abi3390ec59af; itab sizes recorded9387929d09; SITABLINK and SFUNCTAB kinds removed5a928e5a37,5ec5fdc093; STYPE sort precomputes isItab/typeStr, recovering a dodata regression (714 ms to 325 ms on a large cgo binary versus 207 ms in 1.26.2)1215bd5ff6. Left open: reflect was to switch to type pointers rather than offsets (message of481ab86aaf); the dodata regression was only partly recovered (325 ms vs 207 ms). - Go 1.28 — Done: compiler and linker use one name for alg and noalg descriptors; the algorithm-bearing one wins via a weak definition and TFlag marks the noalg one, avoiding two descriptors per type
15bcf54708. Left open: nothing noted.
//go:linkname restrictions and the blocklist
Span: Go 1.23 → Go 1.26 · Status at HEAD: -checklinkname on by default; push-only linknames required for std symbols; blocklist extended each release
- tracking issue #67401 cmd/link: lock down future uses of linkname referenced by 4 of this thread's commits
Design. Linkname was an unintended hole in API integrity. Go 1.23 added an attribute set by the compiler on linknamed symbols declared in the current package (so inlining or instantiation from another package is not conflated) and the linker checks it against rules. “Push” linknames (definition side opts in) are always allowed; “pull-only” references to standard-library symbols are rejected unless the definition carries a linkname (possibly to itself). References to external non-Go (C) symbols stay allowed; assembly references are treated like linknames. First a hard-coded blocklist of symbols newly added in 1.23 was introduced, then the pull-only rule gated by -checklinkname, then the flag was defaulted to on. To avoid breaking widely used libraries, push linknames were added to std for legacy symbols found in a large code corpus, in badlinkname.go files.
Timeline.
- Go 1.23 — Done: user-visible blocklist for newly added internal functions
4721f95058; pull-only linknames disallowed (-checklinkname, off)c4772d30bf; default on003683f41d; push linknames added for legacy pulls41aab30bd2; vdso linkname fix0bc684a994; flag documented148755a27b. Left open: only standard-library definitions enforced. - Go 1.24 — Done: user-visible new std linknames blocked
fb764cdad0; runtime mapiter compatibility layer for legacy linknames8071f2a169; FIPS frozen-tree linkname fix4f48ad5c6b. Left open: nothing noted. - Go 1.25 — Done: user-visible more blocked
683810a368,47b941f445; linkname reference to a TEXT symbol allowed regardless of size70109eb326. Left open: nothing noted. - Go 1.26 — Done:
runtime.addmoduledatalinkname disallowed9f6936b8da; more names blocked34af879dde. Left open: nothing noted.
DWARF 5 migration
Span: Go 1.25 → Go 1.28 · Status at HEAD: enabled by default since the 1.25 cycle (ca19f987ca, 2024-12-19; experiment dwarf5 added bdb4676043); internal/buildcfg/exp.go keeps it off on macOS (dwarf5Supported)
- tracking issue #26379 cmd/compile: consider using DWARF 5 referenced by 11 of this thread's commits
Design. Issue #26379. The work moves compiler (cmd/internal/dwarf, ssa/debug.go) and linker (ld/dwarf.go) to DWARF 5 in stages, all gated by the dwarf5 GOEXPERIMENT. Line tables first: DWARF 5 drops the implicit zeroth directory/file entries so files and dirs become 0-based and the prologue changes 767e7680ec. Then .debug_info goes to version 5 and subprogram high/low PC use DW_FORM_addrx: the compiler emits new R_DWTXTADDR_* relocations (sized to the largest function index in the package) targeting the function, and the linker, in the parallel DWARF phase, assigns a slot in .debug_addr (new SDWARFADDR symbol kind) and later sets each CU’s DW_AT_addr_base. Range lists move to .debug_rnglists, location lists to .debug_loclists, both more compact and able to reference .debug_addr. Lexical scope hi/lo cannot use addrx+constant, so scopes and inlined subroutines always use DW_AT_ranges.
Timeline.
- Go 1.25 — Done: dead
R_DWARFFILEREFremoved38825ef347;DW_LNCT/DW_UTconstants41a30dd192;R_DWTXTADDR_*relocations31b800abe6;SDWARFADDR282a14ec61; line tables767e7680ec;.debug_addrand v5.debug_infoe7cd4979be; rnglists4c0a47a8ff; loclistsbef2bb80a9; fix of wrong inlined-subroutine pc range (alwaysDW_AT_ranges)955cf0873f,bdfa604b2e; DWARF 5 enabled by defaultca19f987ca; loclist fix for heap return vars without optimizations71c2bf5513. Left open:e7cd4979besays DWARF stays damaged until the later patches landed; not enabled on macOS. - Go 1.27 — Done: location list representation unified: structured
LocListEntry,PutLocationListDwarf4/5encode directly, singlecreateDwarfVarsloop70dc75b79b. Left open: nothing noted.
DWARF quality for debuggers (types, variables, trampolines)
Span: Go 1.18 → Go 1.28 · Status at HEAD: ongoing, driven by Delve/gdb/lldb needs
Earlier history: DWARF generation in the linker (Go 1.0 → Go 1.9) · DWARF generation in the linker (Go 1.10 → Go 1.17)
Design. Compiler dwarfgen and linker ld/dwarf.go generate type DIEs from the runtime types (DW_AT_go_kind, DW_AT_go_runtime_type), variable DIEs with location lists, inline trees and scopes. Changes here are mostly additive attributes that debuggers consume.
Timeline.
- Go 1.18 — Done: dictionary entries described in DWARF
72bb8185b5, types reachable only via dictionaries6268468e02; wrappers markedDW_AT_trampoline5b48fca1fa; register-resident output params79e03a9281; gdb script section pinneddaa55b21d1; column info dropped when line saturates1de2344af1. Left open: nothing noted. - Go 1.19 — Done: no typedef DIE for
noalg.struct{...}f79a983891; no label symbols for DWARF on Windows/ARM64cccfb8ffdb. Left open: nothing noted. - Go 1.20 — Done: DIEs for zero-sized variables
e59d873ff9; trampolines no longer break line info2340d37179. Left open: nothing noted. - Go 1.21 — Done:
DW_AT_decl_linefor functions9e45b1d53e; inlinedDW_AT_call_linehonours//linef60a2a9c94; DWARF section sizes checked separatelyc2becd70c5. Left open: nothing noted. - Go 1.22 — Done: user-visible
-sno longer disables DWARF on Mach-O; flags rationalized1d84c89bec,4d21b3ec2d,15eea6a193; fix of go-kind for synthetic ptr types37971b31df; DWARF off by default for c-shared on darwine924ea03cc;Sym.Lengthreplaced byContext.Size227ec02824. Left open: nothing noted. - Go 1.23 — Done:
DW_AT_go_runtime_typeforunsafe.Pointerb89ad46451; captured-variable DIEs carry a closure offset attribute (+0.06% size on a Delve sample)f048829d70; putvar/putAbstractVar refactored to a single abbrev table08029be9fc; range-over-func internal vars dropped82c371a307. Left open: nothing noted. - Go 1.24 — Done:
variable_parameteron functype outparamsae33b66c19; DWARF/gdb support for Swiss maps067c091564; name slice index made self-describing08770a5b94. Left open: nothing noted. - Go 1.26 — Done: types only referenced through interfaces are exported to DWARF by scanning function relocations, fixing unreadable runtime types in Delve/viewcore after 1.25
80038586ed. Left open: nothing noted. - Go 1.27 — Done: dot vars needed by debuggers preserved
d7b6fb44b5; preInliningDcls filtering improvedb77ae50ad1; heap vars in dead code9c9e880fe1; simplified closure names (always pre-inliningpkg.outer.funcN)409f784bea. Left open: nothing noted. - Go 1.28 — Done: source-less
DW_TAG_subprogramwith address-valuedDW_AT_trampolinefor each direct linker trampoline (ARM, ARM64, Loong64, PPC64, RISC-V), inspired by Delve561f6e6101, simplified inf91ce18c4d. Left open: dynamic/GOT trampolines excluded.
Darwin split DWARF (dSYM)
Span: Go 1.28 → Go 1.28 · Status at HEAD: default on Darwin
- proposal #62577 cmd/link, cmd/go: emit split DWARF on darwin referenced by 4 of this thread's commits
Design. With -splitdwarf the linker writes DWARF into <out>.dSYM/Contents/Resources/DWARF instead of combining it into the executable (external mode used to run dsymutil then merge). Because LLDB loads the symbol table from the DWARF file and, for MH_DSYM, relies solely on .debug_aranges, the linker also emits a symbol table and brings .debug_aranges back. LLDB cannot read compressed DWARF so -compressdwarf=0 is needed.
Timeline.
- Go 1.28 — Done: split DWARF behind a flag
a3b72a4a0d; enabled by default with a test once the go command supported it48f0ff2044; symbol table in the DWARF file3223d9d127;.debug_aranges844f96c5d1;-dmade no-op on Darwin95bc6b93fc. Left open: LLDB can’t read compressed DWARF.
Nosplit stack checking and stack constants
Span: Go 1.19 → Go 1.21 · Status at HEAD: done
Earlier history: Stack splitting and the linker’s nosplit overflow check (Go 1.0 → Go 1.5)
Design. The linker’s nosplit check was a top-down walk from each nosplit function, revisiting functions at different depths and reporting only one chain. The rewrite is bottom-up: it computes maximum stack height for each function’s call tree, visiting each function once. Errors trigger a slower top-down re-analysis that prints all over-limit paths as a tree.
Timeline.
- Go 1.19 — Done: new algorithm: Dostkcheck 7.9 ms to 1.8 ms (−77%), total link −1.6% on cmd/go, at +430% pass memory (40 KiB to 212 KiB)
e25f46e596; AIX output fix5834024057. Left open: nothing noted. - Go 1.21 — Done: StackLimit/StackGuard rationalized into
abi.StackNosplitBase(landed, revertedd11ff3f081, relanded)921699fe5f,0f099a4bc5; stack check forced to 1 in distb12366bbb51.20 (1.20). Left open: nothing noted.
Dead code elimination and reflection-aware pruning
Span: Go 1.18 → Go 1.23 · Status at HEAD: done
Earlier history: Dead code and data elimination (Go 1.0 → Go 1.8) · Dead-code elimination and method pruning (Go 1.10 → Go 1.17)
Design. The deadcode pass keeps method sets alive only for types converted to interfaces whose called method names appear. Two hazards were handled: generic interface calls and reflection by name. For generics, a new relocation (R_USEGENERICIFACEMETHOD, later R_USENAMEDMETHOD b4c889b561) records that a method was called on a generic interface so implementations are kept. The compiler learned to flag Type.Method/Value.Method users as ReflectMethod, letting the linker drop its special cases for Method() and MethodByName(). Large unreferenced map variables are removed by the compiler making their init functions weak, with a linker pass after deadcode that redirects weak R_CALL relocations to unreachable map.init.* helpers at a no-op (mapinitnoop).
Timeline.
- Go 1.18 — Done: generic interface call relocations
c26a32a500; dead reference link fix58490972c0. Left open: nothing noted. - Go 1.21 — Done: user-visible linker side of dead-map removal
e2ca417ee7; ReflectMethod flag in-dumpdep0bbb54a02d. Left open: nothing noted. - Go 1.22 — Done: special cases for Method/MethodByName removed
6b52bfa393, test casesbb74a53c21; rename toR_USENAMEDMETHODb4c889b561; deadcode of global map vars avoided for plugins660620dd45. Left open: nothing noted. - Go 1.23 — Done: deadcoded function symbols excluded in shared mode
98529a8e7c. Left open: nothing noted.
Linker performance and memory
Span: Go 1.18 → Go 1.28 · Status at HEAD: incremental
Paths not taken: PGO beyond inlining: block layout, edge profiles, function ordering (dormant)
Earlier history: From C to Go: cmd/link (Go 1.3 → Go 1.8) · Linker speed and memory (Go 1.7 focus) (Go 1.7 → Go 1.9) · Linker performance and memory engineering (Go 1.10 → Go 1.17) · DWARF compression and Mach-O dsymutil combining (Go 1.11 → Go 1.16)
Design. The loader (cmd/link/internal/loader) indexes symbols across mmap’d object files. Optimizations replace maps with slices/bitmaps and narrow types.
Timeline.
- Go 1.18 — Done: output file not unmapped on error exit (race with concurrent writers)
e9f0381a80. Left open: nothing noted. - Go 1.20 — Done: fewer allocations in object writing (−3–6% compiler allocs)
5497300d9c. Left open: nothing noted. - Go 1.21 — Done: symbol index
Symbecomes uint32 (−17.7% peak memory linking cmd/compile externally)d33a5136e1; outer-symbol slice and special/generator bitmaps (Symtab 12.9 to 6.4 ms)21c2fdd91c; data symbols sorted by index not name (Dodata −4.7%)ddd822e5ca; type name decode without allocation630ef2edc2; per-binary slice cleanup8e8f4be3f4Left open: nothing noted. - Go 1.23 — Done:
-randlayout=seedrandomizes function order to test benchmark sensitivity5258d4ed60; profile files closed7b3c38045b,8bf9f4bb66; telemetry7fcf38d715. Left open: nothing noted. - Go 1.25 — Done:
-eunlimited errors76c18e2ed2;-funcalign=N9302a57134; size fixups after references load88cf0c5d55. Left open: nothing noted. - Go 1.28 — Done: ELF
.strtabdeduplicated (cmd/go −0.21%, cmd/compile −0.34%)4dd8b7df66; funcdata scratch slice reuse6a1d404935. Left open: nothing noted.
Reproducible build IDs, buildinfo and symbol tables
Span: Go 1.18 → Go 1.24 · Status at HEAD: GNU build ID and LC_UUID generated by default
Earlier history: Build IDs, ABI hashes, flags and determinism (Go 1.0 → Go 1.9) · Build identity, buildinfo and reproducibility (Go 1.10 → Go 1.17)
- motivating issue #41004 cmd/link: add support for automatically generating .note.gnu.build-id referenced by 3 of this thread's commits
- motivating issue #68678 cmd/link: LC_UUID not generated by go linker, resulting in failure to access local network on macOS 15 referenced by 4 of this thread's commits
Design. The linker embeds a Go build ID; -B gobuildid derives a GNU build ID from it deterministically, and since 1.24 both ELF and Mach-O get IDs by default and -B applies to external linking.
Timeline.
- Go 1.18 — Done: version info made easier to extract
b37a5391f9. Left open: nothing noted. - Go 1.21 — Done: go.buildinfo kept with
--gc-sections54bdb8ee70; plugin build id6c8ea391cf; reproducible toolchain build8a27154bcd. Left open: nothing noted. - Go 1.22 — Done: user-visible
-B gobuildid089cc68676. Left open: nothing noted. - Go 1.23 — Done: LC_UUID rewritten for external Darwin links
3bcefa5276. Left open: nothing noted. - Go 1.24 — Done: user-visible Mach-O UUID with
-B20ed603118, by default6a4feb5644, applied to external linkingc15e589733; ELF GNU build ID by default427d1a23ef; not passed if C linker lacks support15c5580160;cmd/internal/machopackagef2d9f5ffca. Left open: nothing noted.
FIPS 140 support in the linker
Span: Go 1.24 → Go 1.24 · Status at HEAD: done
- tracking issue #69536 crypto: obtain a FIPS 140-3 validation referenced by 3 of this thread's commits
Design. For init-time self-verification, FIPS code and data get distinguished symbol kinds (cmd/internal/obj/fips.go), the linker places them in contiguous regions (ld/fips.go), records the section bounds and expected checksum for the runtime.
Timeline.
- Go 1.24 — Done: symbol kinds
43f889b9e5,4582f239c3; verification support (disabled)239dbd7dba; enabled6c841e4640; large-program fixes00709919d0; delta applied to RODATA to DATA relocationsb9955f0ad9. Left open: nothing noted.
ELF/relro/hardening and external-link policy
Span: Go 1.18 → Go 1.28
Earlier history: External (host) linking and cgo (Go 1.1 → Go 1.9) · Build modes: -shared, c-archive, c-shared, shared, PIE, plugin (Go 1.1 → Go 1.9) · Build modes: PIE, plugin, c-shared, c-archive, shared libraries (Go 1.10 → Go 1.17) · Host object loading and external linking (Go 1.10 → Go 1.17)
Design. Policy for when to link externally and how ELF is laid out.
Timeline.
- Go 1.18 — Done: note sections merged into one segment
1ea4d3b911; SHT_INIT_ARRAYcfb0cc3552; non-exec stacks on FreeBSD782aa42255;-asanoptionae4d67c89d;CCwith spaces and quotes742dcba7bb; start address 0 for external linksc92f5ee170. Left open: nothing noted. - Go 1.19 — Done: ELF compression default
d34287a4f6; DT_NEEDED for all 64-bita8f019e956; unlinkable object when compiled without-pe853464055; visibility-hidden fixes2049649e8b;-Wl,-zarguments38607c5538. Left open: nothing noted. - Go 1.20 — Done: glibc vs musl ldso detection
3315066f46;":"used for compiler symbols0f8dffd0aa; coverage counter hooks072c7d4969. Left open: nothing noted. - Go 1.21 — Done: only needed symbols exported in ELF external links
1f29f39795; external linking default for cgo std packages06b67591e7reverted20da76da91, replaced bypreferlinkexttoken for odd cgo flags035db07d7c; c-archive consolidation43115ff094. Left open: nothing noted. - Go 1.23 — Done:
-bindnow88480fadcc;.dynamicand.gotinto relro2dddc7ef88(after revert of earlier attemptbe58fd0070); relro overlap fixa548dee75c; no-BTI marking on openbsd1e433915ce. Left open: nothing noted. - Go 1.26 — Done:
.deffile for Windows DLL exports instead of__declspec(dllexport)eaf2345256; weak binding on Darwin8cf7a0b4c9. Left open: nothing noted. - Go 1.27 — Done: bfd ld 2.36+ replaces gold on linux/arm64
a8032d4c78; PIE on linux/s390x without cgo58bc91a967. Left open: nothing noted.
Per-platform object formats and internal linking (Mach-O, PE, XCOFF, wasm, ppc64, riscv64, loong64)
Span: Go 1.18 → Go 1.28 · Status at HEAD: active
Earlier history: Early loaders 5l/6l/8l and the shared src/cmd/ld (2008–2011) (Go 1.0 → Go 1.0) · Container writers: Windows PE, Mach-O and Plan 9 (Go 1.0 → Go 1.9) · Architecture and OS ports in the linker (arm, power64, arm64, mips, s390x, nacl, BSDs, Solaris) and trampolines (Go 1.0 → Go 1.9) · Mach-O and Darwin support (Go 1.10 → Go 1.17) · PE / Windows (Go 1.10 → Go 1.17) · AIX / XCOFF (Go 1.12 → Go 1.14) · WebAssembly (Go 1.11 → Go 1.14) · Architecture ports and trampoline insertion (Go 1.10 → Go 1.17)
Design. Each platform needs a host-object loader (loadelf, loadmacho, loadpe, loadxcoff), a relocation handler in cmd/link/internal/<arch>, trampolines and format writers (ld/elf.go, macho.go, pe.go, xcoff.go, wasm/asm.go).
Timeline.
- Go 1.18 — Done: ppc64le TOC rework and internal linking of PIE
9295723079,2c4f389c02,fa16efb835; riscv64 trampolines3bbc82371e; Mach-O runtime.text.N9112d296e8; AIX symbol manglingf1f626de53; wasm command-line args fix77f2750f43. Left open: nothing noted. - Go 1.19 — Done: loong64 linker
cc4957a5f6; PE COMDAT support and crt2.o1edc2769ca,a9d13a9c23; ppc64 TOC trampolinescaa46312ee, register save/restore9c9090eb1d. Left open: nothing noted. - Go 1.20 — Done: PPC64 power10 prefixed relocs
69abfab979,-fno-pltsupport7fda98a8d9; freebsd/riscv649faf6b7929. Left open: nothing noted. - Go 1.21 — Done: PE
.xdata39ca989b88and.pdata14cf82aa37(start of SEH unwinding) with SEH aux symbols76ac54b50e; power10 PCrel and NOTOCccb8db88c5,29d5272da8; loong64 psABI v2a3dd959229; wasip199de78e175; ARM trampoline fixes0b5affb193,f26bf203ac; macho label symbols for duff7dbd6de7d4; c-shared on loong641f908bd060. Left open: nothing noted. - Go 1.22 — Done: Mach-O symbol-targeted relocations always
be910bf2fd,bad9ca8a61,ac81156bce; no DYSYMTAB for external Mach-Of55e7e104b; riscv64 internal cgo linking and call relocse68c027204,bda5e6c3d0; openbsd/ppc647f92dcc88d; loong64 plugind7fcb5cf80; .pdata/.xdata merging from host objects3aa2823d8b; arm text split1e69040920; per-package text addresses landed then reverted7e1713e93c,7c446bab63. Left open: nothing noted. - Go 1.23 — Done: loong64 reloc naming unified
0584574797; openbsd/arm64 PIE68d3a9e417; riscv64 local text symbolsb5a64ba62e; PE.relocalignmentf71c00b616. Left open: nothing noted. - Go 1.24 — Done: loong64 trampolines
1d925fcc7e; wasmexport on js/wasmf38d42f2c4; Wasm memory 16 MB to 1 MB extra24bc473173; PE export cap 6553578c0ea5df7; riscv64R_GOT_PCREL_ITYPE_RELOC371ee1469c1.25. Left open: nothing noted. - Go 1.25 — Done: loong64 internal linking
f212b6499a; Wasm method/func tables hold only function index so >65536 funcs fit9c507e7942; Mach-OX86_64_RELOC_SUBTRACTORfor race syso5fb9e5dc19;.gotin__DATA_CONST61641c1145; >4 GB base for 64-bit PE176a2154aa. Left open: nothing noted. - Go 1.26 — Done: internal linking for windows/arm64
9e25c2f6de; MSVC clangc70713da82; Mach-O ARM64POINTER_TO_GOTandSUBTRACTOR6e95748335,7c8166d02d; darwin/386 and windows/arm relocations removed17a0fabc43,ca0e03560d; loong64 relocs4b0e3cc1d6. Left open: nothing noted. - Go 1.27 — Done:
-macos/-macsdkflags and 13.0 minimum23fde5c48c,6614616b75; Windows 10 minimum341b5e2c02;.pdatasortedbbed50aaa3; loong64 immediate range checksb48b6fa2fa; openbsd/mips64 removed31a8396219. Left open: nothing noted. - Go 1.28 — Done: Wasm PC high bit set so PCs never look like data pointers to GC
9d8bad3bbd; 32-bit Darwin removeda479c5b3ef;__ImageBaseon Windows3b21b1d0cc; split text sections for over-aligned functions2fb15af7f0. Left open: nothing noted.
Changes in this slice that are not part of a thread are listed on the release pages: 1.18 · 1.19 · 1.20 · 1.21 · 1.22 · 1.23 · 1.24 · 1.25 · 1.26 · 1.27 · 1.28