Go: A Documentary

Backends and assemblers

Part I · 85 threads in 5 era slices, from Go 1.0 to Go 1.28. Each thread gives its design, then a per-release timeline of what landed (Done) and what was still open. Release labels in a timeline link to that release's page.

● = the thread had substantive commits in that cycle; hover for the stage summary, click to jump to it in the release page.

Thread map, Go 1.0 – 1.17

Thread1.01.11.21.31.41.51.61.71.81.91.101.111.121.131.141.151.161.17Status at go1.17 (or at the end of its era slice)
Backends and assemblers (Go 1.0–1.6)
Per-architecture compilers 6g/8g/5g/9g and the shared gc code generator●●●●●●●the Prog-list code generators were replaced by the SSA backend (1.7 onward, other area); in 1.6 they still existed as Go packages cmd/compile/internal/{amd64,x86,arm,arm64,ppc64,mips64} → continues as Register-based calling convention (ABIInternal) ports beyond amd64 and amd64 code generation and GOAMD64 microarchitecture levels (1.18+)
Register allocation and the peephole optimizer (reg.c, peep.c)●●●●●●●replaced by the SSA register allocator; the old optimizer was gone before go1.17 → continues as Register allocator, scheduler and block-level backend passes (1.18+)
Expression and statement code quality in 6g/8g/5g (division, bounds checks, loops, slices, interfaces)●●●●●●●superseded by SSA rules and lowering → continues as amd64 code generation and GOAMD64 microarchitecture levels (1.18+)
Stack frames, pointer maps and liveness analysis in the compiler●●●●●liveness analysis (plive) and per-call-site stack maps remain; pointer bitmaps are still 1 bit per word → continues as Stack slot merging, liveness and stack maps (1.18+)
Stack frame layout, prologues, stack split and frame pointers●●●●●●●prologue generation per architecture in cmd/internal/obj/; frame pointers kept on amd64 (default) and arm64 → continues as Prologue, frame pointer, stack split and function alignment (cmd/internal/obj) (1.18+)
Zeroing and copying: Duff's device, REP, unaligned moves and AVX2 runtime assembly●●●●●duffzero/duffcopy still used by amd64 at go1.17 → continues as Duff's device replaced by generated loops; Zero/Move lowering (1.20+)
liblink: a shared assembler/linker library●●●converted to Go as cmd/internal/obj in 1.5 (this thread); the C library itself was deleted (3317e7f19a) → continues as Prologue, frame pointer, stack split and function alignment (cmd/internal/obj) (1.18+)
Normalising Prog/Addr in cmd/internal/obj (the portable instruction representation)●●the design (portable Prog, Addr with TYPE_, From3/RegTo2, shared A numbers, Link context) is still in place → continues as Prologue, frame pointer, stack split and function alignment (cmd/internal/obj) (1.18+)
The Plan 9 C compilers (cc, 5c/6c/8c) in the Go toolchain●●●●●●removed in 1.5 (4a42fae2cd on dev.cc); runtime was rewritten in Go and assembly
Assemblers: 5a/6a/8a/9a and the Go cmd/asm●●●●●●●cmd/asm (cmd/asm/internal/{arch,asm,flags,lex} + cmd/internal/obj) is the assembler → continues as arm64 code generation and assembler (and the other per-arch assembler threads) (1.18+)
C→Go translation of the toolchain (liblink, assemblers, compilers; dev.cc)●●●complete; all tools are Go
The ppc64 / ppc64le port (dev.power64, 9g/9a/9l)●●production port with shared-library support → continues as PPC64 / Power10 backend (GOPPC64, prefixed instructions, PC-relative code) (1.18+)
The arm64 (ARMv8) port (7g/7a/7l)●●production port → continues as arm64 code generation and assembler (1.18+)
The mips64 port and the mips/mips64 register space●production port → continues as s390x, MIPS, 386 and 32-bit ARM backends (1.18+)
Native Client (nacl/386, nacl/amd64p32, nacl/arm) and amd64p32 in the toolchain●●●not covered by this range's commits after 1.5
Position-independent code, dynamic linking and TLS in the assemblers●●●●working on amd64, 386, arm, arm64, ppc64le → continues as Prologue, frame pointer, stack split and function alignment (cmd/internal/obj) (1.18+)
Backends and assemblers (Go 1.7–1.11)
SSA backend bring-up on amd64 (dev.ssa merge)●still the design at go1.17 (the same cmd/compile/internal/ssa package: Value/Block/Func, generic ops lowered by gen/.rules, passes list in compile.go) → continues as Register allocator, scheduler and block-level backend passes (1.18+)
Register allocation, flag allocation and stack slots●●●●●still the design at go1.17 (global allocator in regalloc.go, separate flagalloc, stackalloc) → continues as Register allocator, scheduler and block-level backend passes (1.18+)
Scheduling, tighten, block layout and CFG passes●●●●●still the design at go1.17 (schedule, tighten, layout, looprotate, branchelim, fuse) → continues as Register allocator, scheduler and block-level backend passes (1.18+)
Rewrite-rule machinery and compile-time performance of the SSA compiler●●●●●still the design at go1.17 (rulegen.go, | alternation, automatic commutation) → continues as Late-lower pass, memory-op combining and arch-independent rule migration (1.18+)
Multi-architecture SSA ports: ARM, 386, ARM64 (dev.ssa)●●●still the design at go1.17 (each architecture owns ssa/gen/ARCH.rules, ARCHOps.go and cmd/compile/internal/ARCH/ssa.go) → continues as s390x, MIPS, 386 and 32-bit ARM backends (1.18+) and arm64 code generation and assembler (1.18+)
Multi-architecture SSA ports: PPC64, MIPS64, MIPS32, s390x●●●●●still the design at go1.17; ppc64x, mips64x, mipsx and s390x all remain stack-ABI backends → continues as PPC64 / Power10 backend (GOPPC64, prefixed instructions, PC-relative code) (1.18+) and s390x, MIPS, 386 and 32-bit ARM backends (1.18+)
WebAssembly backend●still the design at go1.17 → continues as WebAssembly backend (1.18+)
Removal of the legacy backend and decoupling gc from the code generators●●●done; the per-architecture ssa.go files are the only code generators → continues as Prologue, frame pointer, stack split and function alignment (cmd/internal/obj) (1.18+) (frame handling)
Liveness, stack maps, safepoints and write barriers●●●●●still the design at go1.17 (liveness over SSA, asynchronous-preemption-capable register maps) → continues as Stack slot merging, liveness and stack maps (1.18+)
DWARF variable locations, statement positions and inline info from the backend●●●still the design at go1.17 (location lists generated from SSA LocalSlot tracking; is_stmt boundaries) → continues as Register allocator, scheduler and block-level backend passes (1.18+)
Intrinsics for atomics, math/bits, math and runtime helpers●●●●●still the design at go1.17 (intrinsics table in ssa.go, SSA ops with per-arch lowerings) → continues as Atomics and memory-ordering code generation (1.18+) and Multi-word arithmetic, intrinsics and carry-chain support (1.18+)
Memory-op combining, byte-order idioms and load/store folding●●●●●still the design at go1.17 (rewrite-rule based) → continues as Late-lower pass, memory-op combining and arch-independent rule migration (1.18+)
amd64 and 386 instruction selection●●●●●still the design at go1.17 → continues as amd64 code generation and GOAMD64 microarchitecture levels (1.18+)
ARM and ARM64 instruction selection and fused operations●●●●still the design at go1.17 → continues as arm64 code generation and assembler (1.18+)
Memory clearing and copying: Zero, Move and Duff's device●●●●●still the design at go1.17 (Duff's device for large Zero/Move, inline stores for small) → continues as Duff's device replaced by generated loops; Zero/Move lowering (1.20+)
cmd/internal/obj: Prog/LSym refactoring, concurrent backend and object-writing●●●●●still the design at go1.17 (assemblers take a newprog allocator, per-function FuncInfo) → continues as Prologue, frame pointer, stack split and function alignment (cmd/internal/obj) (1.18+)
x86 assembler: AVX, AVX2, AVX-512 and the instruction table●●●●●still the design at go1.17 (asm6.go + generated VEX/EVEX tables from x86avxgen) → continues as amd64 code generation and GOAMD64 microarchitecture levels (1.18+)
ARM64 assembler●●●●still the design at go1.17 (table-driven optab in asm7.go) → continues as arm64 code generation and assembler (1.18+)
32-bit ARM, MIPS and PPC64 assemblers●●●●still the design at go1.17 → continues as PPC64 / Power10 backend (GOPPC64, prefixed instructions, PC-relative code) (1.18+) and s390x, MIPS, 386 and 32-bit ARM backends (1.18+)
Prologues, frame pointers, NOFRAME and tail calls●●●●still the design at go1.17 → continues as Prologue, frame pointer, stack split and function alignment (cmd/internal/obj) (1.18+)
Backends and assemblers (Go 1.12–1.17)
Register-based ABI (ABIInternal) and ABI wrappers●●●●amd64 passes arguments and results in registers by default, with ABI0↔ABIInternal wrappers generated by the compiler; R14 = g, X15 = zero, R12/R13 = entry scratch; other architectures still stack-based → continues as Register-based calling convention (ABIInternal) ports beyond amd64 (1.18+)
Late call expansion, AuxCall and the expand_calls pass●●default design: calls carry ssa.AuxCall, and expand_calls lowers arguments/results (including registers) after generic optimization → continues as Register-based calling convention (ABIInternal) ports beyond amd64 (1.18+)
dev.regabi package decomposition (ssagen, liveness, objw, types, abi)●done; gc no longer holds SSA generation or liveness → continues in the 1.18+ compiler layout
Rewrite-rule generator (rulegen): ellipses, commutativity loops, typed aux●●●●●all rule files use typed aux/auxint (=>); commutative ops use nested loops; no chunking → continues in the 1.18+ SSA rule infrastructure
amd64 lowering: read-modify-write, load-op, addressing modes pass, intrinsic-friendly rules●●●●●●still the design at go1.17 → continues as amd64 code generation and GOAMD64 microarchitecture levels (1.18+)
arm64 backend and assembler●●●●●●mature backend with constant synthesis by MOVZ/MOVK and ADD pairs, frame pointer on by default, ARMv8.1 atomics; → continues as arm64 code generation and assembler (1.18+)
riscv64 port: assembler and compiler●●●●working linux/riscv64 with PIE and cgo, atomics, Duff's devices, branch pseudo-instructions → continues as riscv64 compiler and assembler (RVA profiles, vector, compressed) (1.18+)
PPC64 / POWER backend and assembler●●●●●●power8 baseline with power9 instructions under GOPPC64=power9 → continues as PPC64 / Power10 backend (GOPPC64, prefixed instructions, PC-relative code) (1.18+)
s390x backend: compare-and-branch, intrinsics, assembler●●●●●●still stack ABI → continues as s390x, MIPS, 386 and 32-bit ARM backends (1.18+)
386, ARM (32-bit), MIPS/MIPS64 and the 387 removal●●●●●●maintenance; 387 dropped, GO386=softfloat for no-SSE → continues as s390x, MIPS, 386 and 32-bit ARM backends (1.18+)
WebAssembly backend●●●●stack-based simulated registers with SP cached in locals, optional sign-extension/saturating ops via GOWASM → continues as WebAssembly backend (1.18+)
math/bits, atomic and runtime intrinsics (cross-architecture)●●●●●●intrinsics for bits/atomic/FMA exist on most ports → continues as Atomics and memory-ordering code generation and Multi-word arithmetic, intrinsics and carry-chain support (1.18+)
SSA infrastructure passes: layout, regalloc, scheduling, flagalloc, block shape●●●●●●depth-first layout with exit-block placement, regalloc using phi/desired info → continues as Register allocator, scheduler and block-level backend passes (1.18+)
Safepoints, unsafe points and stack maps for asynchronous preemption●●●●stack maps only at call sites; register maps removed; unsafe-point info in PCDATA → continues as Stack slot merging, liveness and stack maps (1.18+)
Debug info generated by the backend: statements, location lists, line tables●●●●●●is_stmt marks with minimal-harm heuristics; debug_lines generated by walking Progs in the compiler → continues in 1.18+
New object file format and content-addressable symbols (dev.link, assembler side)●●●●done; the linker consumes indexed objects (goobj2) → continues in the linker threads (1.18+)
Assembler directives and tooling: PCALIGN/ALIGN, FuncInfo flags, prologue handling●●●●●●still the design at go1.17 → continues as Prologue, frame pointer, stack split and function alignment (cmd/internal/obj) (1.18+)

Thread map, Go 1.18 – 1.28

Thread1.181.191.201.211.221.231.241.251.261.271.28Status at HEAD
Compiler backend: architectures, register allocation, ABI and assemblers (Go 1.18–1.28)
Register-based calling convention (ABIInternal) ports beyond amd64●●●●●●register ABI enabled on amd64, arm64, ppc64x, riscv64, loong64, s390x; 386, arm and mips still stack-based
Register allocator, scheduler and block-level backend passes●●●●●●●●●●ongoing tuning each cycle
amd64 code generation and GOAMD64 microarchitecture levels●●●●●●●●●●●GOAMD64=v1 default; v3 unlocks BMI1/BMI2/LZCNT/MOVBE/FMA codegen; constant churn of peephole rules
arm64 code generation and assembler●●●●●●●●●●●active; assembler gaining a table-driven SVE/ASIMD path; SVE under GOEXPERIMENT=simd
riscv64 compiler and assembler (RVA profiles, vector, compressed)●●●●●●●●●●●GORISCV64 rva20u64/rva22u64/rva23u64 profiles drive Zba/Zbb/Zbs/Zicond codegen; assembler supports V, crypto vector suites and compressed (RVC) emission
loong64 backend and assembler●●●●●●●●●●feature-complete port with LSX/LASX assembler coverage; very active
PPC64 / Power10 backend (GOPPC64, prefixed instructions, PC-relative code)●●●●●●●power8/9/10 levels; PC-relative (NOTOC) code on power10/linux
Duff's device replaced by generated loops; Zero/Move lowering●●●●●done for amd64, arm64, loong64, riscv64; runtime duff code removed for arm64, loong64, riscv64
Bounds-check calls: new "panicBounds" strategy●all architectures converted; old style removed
Late-lower pass, memory-op combining and arch-independent rule migration●●●●●●late lower on amd64, arm64, ppc64, riscv64; memcombine arch-independent
Stack slot merging, liveness and stack maps●●●●merge-locals on by default; analysis tightened in 1.28
Prologue, frame pointer, stack split and function alignment (cmd/internal/obj)●●●●●●per-arch; amd64 uses PUSH/POP BP and LEAVE
Jump tables for switch statements on more architectures●●●amd64, arm64, loong64, riscv64, mips64x, mipsx
Multi-word arithmetic, intrinsics and carry-chain support●●●●●●●Add64/Sub64/Mul64 intrinsics on all 64-bit ports; 32-bit support for Mul64 added in 1.26
Atomics and memory-ordering code generation●●●●●●intrinsics for sync/atomic And/Or across several ports
WebAssembly backend●●●●●●active small-scale improvements; vector ops landing with simd
s390x, MIPS, 386 and 32-bit ARM backends●●●●●●●maintenance; MIPS gets jump tables, s390x gets regabi
Backend hooks for GOEXPERIMENT=simd (amd64 AVX-512, arm64 SVE, wasm)●●●part of the simd area; backend plumbing only here
SSA package split into subpackages (issue #80409)●in progress
SIMD: the simd packages and their compiler support (Go 1.18–1.28)
Compiler SIMD type system, ABI and register-allocation support●●●SIMD types, vec/mask register classes, ABI passing, X15 zeroing, CPU-feature-aware block analysis and VEX encodings exist on master behind GOEXPERIMENT=simd (X15 whole-register zeroing is unconditional); RegMask widened beyond 64 bits for SVE predicates.
simdgen: the instruction-table-driven generator●●●lives in src/simd/archsimd/_gen (separate module) with simdgen, unify, tmplgen, wasmgen, midway, specgen, gentools; one go generate regenerates everything.
AMD64 archsimd API: AVX, AVX2 and AVX-512 operations●●●simd/archsimd provides 128/256/512-bit vectors on amd64 with operations that map to AVX/AVX2/AVX-512 instructions; experimental (GOEXPERIMENT=simd), not under Go 1 compatibility.
Mask representation, masked operations and merge peepholes●●●masks are opaque MaskNxM types in archsimd; amd64 uses K registers where AVX-512 is available and vector registers otherwise; arm64 SVE uses predicate registers; IfElse/Masked replace Merge.
Memory operands, constant-immediate forms and jump tables●●generated load-op and constant-load rules for AVX/AVX-512; broadcast and non-broadcast memory operands decoded from XED.
dev.simd branch, GOEXPERIMENT=simd lifecycle and merges to master●●●GOEXPERIMENT=simd exists in internal/goexperiment/flags.go, off by default in buildcfg; dev.simd reverse-merged to master repeatedly (development continues "only on dev.simd" and merges "when necessary").
Portable simd package and the "midway" source translator●●simd (vector-length-agnostic types Int8s..Float64s, Mask8s..Mask64s) exists behind GOEXPERIMENT=simd, implemented by rewriting user code in the compiler front end; the GODEBUG simd setting selects emulation and vector length.
ARM64 NEON archsimd●●128-bit NEON types and a large op set in ops_arm64.go; some constant shift ops postponed.
ARM64 SVE: scalable vectors and predicate masks●minimum-viable archsimd SVE types and a growing op set (types_sve.go, ops_sve.go); scalable vector length capped at 32 bytes in model, mask loads/stores removed.
WebAssembly SIMD●128-bit vectors on wasm in types_wasm.go/ops_wasm.go, with Wasm SSA ops, simdssa.go and rules.
Spec-driven API generation (internal/spec, specgen, specfill)●in progress; simd/internal/spec is an executable Go specification of all possible SIMD operations, with specgen, specls, refgen, specfill, specstats tools; plan tracked in SPEC-TRANSITION.md.
API shaping and renames before stabilisation●●●API still experimental; renames continue.
Emulation of missing instructions and test infrastructure●●●ops_emulated_.go per arch; generated test helpers.

Backends and assemblers (Go 1.0–1.6)

Reader: Claude Sonnet. Cites 1,322 of 1,498 commits in this slice; every non-noise commit is cited, plus 25 larger commits the noise filter had set aside, which a separate gap reader described. Skimmed the design-heavy messages; the ppc64 and nacl threads rest mostly on subject lines. About 500 small commits are cited in grouped items.

Release digest for this slice
  • Go 1.0 — Everything from the first 6g (2008) through the 2012 release: 6g, 8g and 5g become full compilers with a first generation of registerization and peephole optimization, complex numbers, ARM hardware float, and the C compilers/assemblers are imported from Plan 9/Inferno. Threads: per-architecture compilers, register allocator/peephole, C compilers, assemblers.
  • Go 1.1 — int becomes 64 bits on amd64; a long series of 6g/8g/5g code-quality changes (out-of-line panicindex, componentgen, rotates, SSE register use, GO386), the first GOEXPERIMENT, and explicit type information for locals. Threads: expression codegen, register allocator, C compilers.
  • Go 1.2 — Groundwork for precise GC: funcdata/pcdata, pointer maps from cc and gc, zeroing of pointers on entry, proginfo, portable optimizer code and temporary merging. Threads: pointer maps/liveness, register allocator.
  • Go 1.3 — liblink: assemblers and compilers emit machine code and a new object format; liveness-based stack maps; NaCl and amd64p32; Duff’s device in the runtime. Threads: liblink, pointer maps/liveness, NaCl, zeroing/copying.
  • Go 1.4 — Preparation for translation (c2go annotations), plan9/amd64 and nacl/arm in liblink, assembly-function stack maps, small-struct registerization. Threads: liblink, C compilers, stack maps.
  • Go 1.5 — The toolchain becomes Go: liblink to cmd/internal/obj, cmd/asm, all compilers and linkers translated, C compilers deleted, go tool compile/link; ppc64 and arm64 ports; shared-library codegen; frame pointers; portable cgen. Threads: C→Go, assemblers, obj normalisation, ppc64, arm64, PIC/TLS.
  • Go 1.6 — mips64 port; position-independent code on ppc64le, arm, arm64, 386; AVX/VEX assembler support and AVX2 runtime assembly; code layout split from object writing. Threads: mips64, PIC/TLS, assemblers, zeroing/copying.

Per-architecture compilers 6g/8g/5g/9g and the shared gc code generator

Span: Go 1.0 → Go 1.6 · Status at go1.17: the Prog-list code generators were replaced by the SSA backend (1.7 onward, other area); in 1.6 they still existed as Go packages cmd/compile/internal/{amd64,x86,arm,arm64,ppc64,mips64} → continues as Register-based calling convention (ABIInternal) ports beyond amd64 and amd64 code generation and GOAMD64 microarchitecture levels (1.18+)

Continues in: Removal of the legacy backend and decoupling gc from the code generators (Go 1.7 → Go 1.9)

Design. The compiler was one front end (src/cmd/gc, C plus yacc) and one back-end directory per architecture, each a copy of the same structure: gsubr.c (instruction emission gins, regalloc/regfree, gmove, optoas mapping Go operators to machine opcodes), cgen.c (expression code generation: cgen, agen, bgen, sgen), ggen.c (calls, clearfat, divide, shift), galign.c, and reg.c/peep.c for the optimizer. Code generation was a Sethi-Ullman tree walk: ullman numbers decide evaluation order (0eda21a2fd made the minimum 1 so “not computed” and “trivial” differ), registers come from regalloc, and running out gives “out of registers” (made a fatal error rather than a recoverable path in 5g/8g in 1.4, c1c5d479bd). Many bug-fix commits are the same fix applied three times (6feb61325a, 7e144bcab0, 0b2353edcb, 46fcfdaa7d).

6g (amd64) came first (0cafb9ea3d; div/mod d83b994da6; shifts and multiplies f7753f1687; floating point 36bfd2a906). 8g (386) started as “minimal 8g” on 2009-03-31 (dc5b4678e2), grew 64-bit arithmetic through register pairs (a8e4ed6a3d, split into cgen64.c by ab3d40b271) and x87 float with float→integer conversions that emulate the x86 overflow results 1<<15/1<<31/1<<63 (a00bfb5b49). 5g (ARM) was copied from 6g on 2009-05-01 (eac5db7f4c), “rebooted” 2009-05-28 (63e1b714de), and stack splitting for ARM arrived in a grab-bag change (1ac2cfc720); function literals b4896b496e; hardware float came later (9c6df3ca13, after software float 69188ad9bb).

Shared logic was hoisted into cmd/gc over time: portable statement generation and signature generation (bac922c6e1, 5e792b6c09, d30285a6f5, 531f242f5d), static initialisation (62231e91d0, da49bfe664, moved to gc cb49a999d1), division by constants (34f76b5de5), the walk-pass division rewrite (4cc9de9147), proginfo tables (1.2), and in 1.5 the wholesale unification of cgen/regalloc (b115c35ee3, “cutting 5 copies to 1”; motivation: a following change would implement x.(*T) without a runtime call, now 5x easier). Alignment became explicit and per-type in 2010 (e7a0f67603, groundwork for max-field alignment, issue 482).

Timeline.

Register allocation and the peephole optimizer (reg.c, peep.c)

Span: Go 1.0 → Go 1.6 · Status at go1.17: replaced by the SSA register allocator; the old optimizer was gone before go1.17 → continues as Register allocator, scheduler and block-level backend passes (1.18+)

Continues in: Register allocation, flag allocation and stack slots (Go 1.7 → Go 1.11)

Design. On top of the on-the-fly regalloc, each back end had reg.c (called regopt) and peep.c. regopt builds a flow graph (Flow/Reg) over the Prog list, assigns each local/arg/global a Var (a bit in Bits sets), solves liveness, and “paints” (paint1/paint2) non-address-taken variables into free registers when width, stack offset and etype agree. peep does copy propagation (copyprop/copyu/subprop), redundant-move removal and fixjmp. First cut 2dd16a3208 (2008-11-18) and e081f25c3e; registerization of builtin structures (strings, slices, interfaces) 3dc3ef4cf7, 96cbdd62b6; large functions skipped (bc15b94181).

Correctness drove most of the work: liveness for registers was added to 8g in 2011 by modelling each machine register as a pseudo-variable (84f291b1bd), so a temporary whose range was nested in a live BX span could no longer receive BX; one commit (e419535f2a) repaired a regression that registerized nothing (“2-3x slower”); loop detection was confused by dead code with rpo 0, fixed together with a JMP-to-JMP squashing pass (e2d326b878). In 1.2 the per-opcode switches duplicated across reg.c/peep.c were replaced by proginfo (instruction flags plus registers set/used/indexed): the 6g version (24c8035fbe) found that copyu listed neither INCB, JPS, MOVAPD, MOVBWSX, MOVBWZX, PCDATA, POPQ, PUSHQ, STD, TESTB, TESTQ nor XCHGL and needc only a handful of carry-using instructions; 8g and 5g followed (ac0df6ce89, a07218385c), flow-graph code moved to portable popt (b3b87143f2, dbf96addfb).

Timeline.

Expression and statement code quality in 6g/8g/5g (division, bounds checks, loops, slices, interfaces)

Span: Go 1.0 → Go 1.6 · Status at go1.17: superseded by SSA rules and lowering → continues as amd64 code generation and GOAMD64 microarchitecture levels (1.18+)

Continues in: amd64 and 386 instruction selection (Go 1.7 → Go 1.11)

Design. A long run of targeted improvements, each tied to a benchmark. Integer division by constants was implemented in 6g in August 2009 (f1baf78247 powers of two; 34f76b5de5 “integer division by a constant done”, with rotate-carry instructions) and in 1.1 moved into the walk pass (4cc9de9147) so 5g and 8g got shifts and magic multiplies; 64-bit division stayed in 6g, and restore magic multiply for /=, %= (061061e77c). Bounds checks: panicindex calls moved out of line so hot loops hold more useful code per cache line (fefae6eed1; two new instruction forms with expected-not-taken branches reversed); loop conditions marked likely so loops are laid out contiguously (001b75c942, replacing expecttaken); conditional branches gbranch take a likely argument. Slicing: x[lo:hi] expanded in the front end (40af78c19e), copy to a direct memmove call with a new OSPTR node for the data pointer (ff416a3f19), convT2E inlined for uintptr-shaped T (8f84328fdc).

Timeline.

Stack frames, pointer maps and liveness analysis in the compiler

Span: Go 1.2 → Go 1.6 · Status at go1.17: liveness analysis (plive) and per-call-site stack maps remain; pointer bitmaps are still 1 bit per word → continues as Stack slot merging, liveness and stack maps (1.18+)

Continues in: Liveness, stack maps, safepoints and write barriers (Go 1.7 → Go 1.11)

Design. Precise stack scanning needed, for every call site, a map of which frame slots hold live pointers. Go 1.2 built the plumbing: the C compiler emits argument pointer maps (9b1f1833de) and locals bitmaps (73c93a404c); a dedicated FUNCDATA number carries the runtime’s GC metadata, and the old pseudo-instructions and linker code that handled them are removed (48769bf546); the assemblers learn PCDATA/FUNCDATA (47b89a37d9, 8c741c97f7); cmd/cc records the size of locals (e4b5cbde46) and the argument size for every call (4e141145b7, 7b3c8b7ac8); interface and eface words are encoded distinctly in the map (abc516e420) so the collector can use the type word to decide whether the data word is a pointer (21ea5103a4). Because the maps could still see stale slots, pointers in the frame and the results are zeroed on entry (7910cd68b5, “up to 30% slowdown”, an “intermediate step”), with “has pointers” separated from “needs zeroing” (3b4d792606).

Go 1.3 computed the maps by dataflow liveness (f056daf075): a backward analysis over the flow graph yields, at each safepoint, the set of live pointer-bearing slots (“mean reduction of 65% in the number of stack slots scanned” under GOGC=1 all.bash), but the commit notes scanning was still not precise because pointers confound reaching definitions. Variables are tracked as whole units; compiler-inserted VARDEF marks the start of life for fat variables, placed “before the initialization but after any final use” (7a7c0ffb47), and VARKILL ends the life of temporaries whose address went to runtime map/chan calls (b700cb4974). Variables that are “ambiguously live” (live on some paths, uninitialised on others) are zeroed on entry; only those are zeroed after 6722d45631 rather than all pointer-bearing locals, with address-taken-but-non-escaping variables handled in ca9975a45e. In 1.4 assembly functions get maps derived from their Go prototypes (myfunc.args_stackmap, 99f7df0598), slices and strings are treated like pointers (613383c765), and argsize PCDATA is no longer generated (68c1c6afa0). In 1.5 the stack, data and bss maps shrink to 1 bit per word because there are no multiword GC types left (4d0f3a1c95).

Timeline.

Stack frame layout, prologues, stack split and frame pointers

Span: Go 1.0 → Go 1.6 · Status at go1.17: prologue generation per architecture in cmd/internal/obj/*; frame pointers kept on amd64 (default) and arm64 → continues as Prologue, frame pointer, stack split and function alignment (cmd/internal/obj) (1.18+)

Continues in: Prologues, frame pointers, NOFRAME and tail calls (Go 1.7 → Go 1.11)

Design docs & discussion

Design. Segmented-stack prologues are emitted by the assembler/linker pass (originally in the linkers, from 1.3 in liblink and then cmd/internal/obj/<arch>/obj*.go): compare SP (or SP minus frame) with g->stackguard and call runtime.morestack, with the frame and argument sizes passed via a magic $a-b operand (3f982aeaf6, 2008). The compilers lay out locals: 2011 saw compaction by sharing slots (2ac375b2df, ARM 2ad42a8249) and a unified layout (5ddf6255a1); 6c/8c frames were cut about in half in 1.1 (139448fe95). In 1.5, with the ARM and arm64 and x86 prologues, “static branch prediction guesses forward branches aren’t taken”, so the forward branch now means “grow” (f4b48de3ad x86, 54789eff38 arm64) and ARM executes one conditional branch rather than three conditional instructions, adding 4 bytes per function (5353cde080). Knowledge of the fixed part of the stack frame was centralised (97055dc1f1) because ppc64le shared libraries need the TOC pointer at 24(R1); larger frames raised the nosplit limit (c1b6e392f5).

The framepointer GOEXPERIMENT (1.5) makes the amd64 toolchain keep BP chains like gcc -fno-omit-frame-pointer so Linux perf/VTune can unwind Go stacks: compilers don’t clobber BP and liblink emits the saving prologue (3c0fee10db); duffcopy/duffzero are fixed for it (08af4c3a79).

Timeline.

Zeroing and copying: Duff’s device, REP, unaligned moves and AVX2 runtime assembly

Span: Go 1.0 → Go 1.6 · Status at go1.17: duffzero/duffcopy still used by amd64 at go1.17 → continues as Duff’s device replaced by generated loops; Zero/Move lowering (1.20+)

Continues in: Memory clearing and copying: Zero, Move and Duff’s device (Go 1.7 → Go 1.11)

Design. Compiler-generated copies and clears (sgen, clearfat) went from REP MOVSQ/STOSQ to straight-line code and Duff’s device. In 1.0 the convention was that the direction flag is always clear (8f53bc0612) so the compiler could emit memcpy/memset as REP. In 1.3, since REP has “really high startup overhead”, 6g inlines copies up to four words (0ea20bc981, after reflect.Value grew to 4 words and json went ~50% slower), uses overlapping unaligned loads/stores for trailing bytes (2b93c4dd06), zeroes small objects in straight-line code (14664050b9), and the runtime uses a Duff’s device instead of REP (6c7cbf086c, 51b72d94de, 383963b506). In 1.4 repeated STOSQ became MOVs (3c40ee0fe0). In 1.5 2- and 3-word sgen were special-cased because they are 85% (6g) / 70% (8g) of sgen calls (747c849833), and duffzero used MOVUPS to zero 16 bytes at a time (2421c6e3df). Duff’s device is disabled on NaCl (c6a41a3559) and on Plan 9 for duffcopy (50ad337238) and ppc64 duffzero (cda1fc0071).

Timeline.

liblink: a shared assembler/linker library

Span: Go 1.3 → Go 1.5 · Status at go1.17: converted to Go as cmd/internal/obj in 1.5 (this thread); the C library itself was deleted (3317e7f19a) → continues as Prologue, frame pointer, stack split and function alignment (cmd/internal/obj) (1.18+)

Continues in: cmd/internal/obj: Prog/LSym refactoring, concurrent backend and object-writing (Go 1.7 → Go 1.11)

Design docs & discussion

Design. For the Go 1.3 linker plan, the per-architecture logic that lived in 5l/6l/8l (instruction selection, span layout, prologue/stack-split rewriting, TLS and branch fix-ups) was extracted into src/liblink, linked into compilers (5g/6g/8g), C compilers (5c/6c/8c) and assemblers (5a/6a/8a) (f7aaa553da, 76a8c873cf, f606c1be80; the C-compiler change also updated cc/gc: 870e821ded). The big commit a9f6db58ce (“move instruction selection + layout into compilers, assemblers”) added a new object file reader/writer (liblink/objfile.c), a pcdata iterator, removed the “line number stack” and “path fragments” from object files and the linker, made DWARF a single compilation unit, and disabled the check for chains of nosplit functions (issue 6931). The point is that each tool emits finished machine code and the linker only relocates and lays out symbols. Object files gained a version number so later format changes are easy (0e8de61d73, “changes we know we want to make, but not before Go 1.3”), the arch-specific constants were removed from the format (8d39e55c65), and a leaf bit so the linker records correct frame sizes (needed for ARM tracebacks, cc08d9232c). A direct consequence: liblink can no longer depend on link-time decisions. linkmode was removed from the ARM (dab127baf5) and x86 paths, which broke cgo TLS on ARM until b377c9c6a9 and 1dd4da1459 (adding an AMRC instruction for MRC/MCR so rewriting no longer matches bit patterns of AWORD).

Timeline.

Normalising Prog/Addr in cmd/internal/obj (the portable instruction representation)

Span: Go 1.5 → Go 1.6 · Status at go1.17: the design (portable Prog, Addr with TYPE_*, From3/RegTo2, shared A* numbers, Link context) is still in place → continues as Prologue, frame pointer, stack split and function alignment (cmd/internal/obj) (1.18+)

Continues in: cmd/internal/obj: Prog/LSym refactoring, concurrent backend and object-writing (Go 1.7 → Go 1.11)

Design. Before 1.5, each liblink architecture had its own Prog/Addr conventions. In January 2015 Russ Cox made them uniform so that one set of architecture-independent passes (and cmd/asm) could operate on them: the zero Prog is valid and has as set to AXXX instead of AGOK (76fed5426c); common instructions get fixed numbers ANOP, ATEXT, AGLOBL, ACALL, AJMP, ARET with arm/ppc64 aliases (2e5b065ac2); TEXT $frame-arg is encoded the same way on all architectures (72521319c7); TEXT/GLOBL flags live in p->from3 (77a415e5a6) and DATA size in from3.offset (3ac37c72ae); constants must use TYPE_ADDR not TYPE_CONST, with an input checker in liblink/pass.c (1f2d7bf44f); ARM conditions renumbered so C_SCOND_NONE == 0 (e66ad16ddb); dead p->back computation removed (6654188190). The portable Prog/Addr layer was ported to every back end in five parallel commits (5a2771e286, 0d599909e9, 604138e87b, a5b1baeed7, da47902d02). Later cleanups: Addr.U struct replaced by Val interface{} (532ccae154), Prog.From3 a pointer (c413c45e6d), two unused fields removed (2286e452bd, 1378173785, 4ed7d06a87), Pcrel and Comefrom combined (c04813e130), global Aconv/Dconv/Rconv and Prog.String replacing per-arch Pconv (74e88dfdee, 29421cbb5b, daddeb2686, 24a43e6a75, 40ff393e4c), go generated Anames (4b981ed3e6), unary-destination tables moved from asm into obj (8974fb9ba5), symbol table as a map (049eee6806), line history reimplemented with O(log n) lookup (861546543a), Ctxt.Endian deleted (ea7be5170a), Link.Tlsoffset removed (7bf959c67a), ProgInfo layout (b795ce9ab0, 30b966307f), keyed ProgInfo literals (2468227038).

Timeline.

The Plan 9 C compilers (cc, 5c/6c/8c) in the Go toolchain

Span: Go 1.0 → Go 1.5 · Status at go1.17: removed in 1.5 (4a42fae2cd on dev.cc); runtime was rewritten in Go and assembly

Design docs & discussion

Design. src/cmd/cc plus 5c/6c/8c compiled the runtime’s C code and cgo helpers; the tree adopted Plan 9’s lib9/libbio in 2008 (69b74c3953) and brought in 8c/8l/8a (4dee747fb9, af0143ce03) and 5a/5c/5l (db3a21d7c6) from the Inferno distribution. The compilers shared cc/pgen.c/pswt.c (27432d67ec) and their own register allocator (reg.c) and peephole (peep.c), with alignment rules kept consistent with the matching Go back ends (7fbf9fcba9, b331f3cfd0). Go-specific extensions: #pragma textflag, dataflag, dynexport, varargck countpos/type for the Windows stdcall helpers (fe2ccb53c0), NOPTRBSS (5bcad92f07), 21-bit rune wide strings (781b2a2519), PREFETCH builtin (d42495aa80, ARM 278d4a583d), nested package calls like sync»atomic·LoadInt32 (8be5e8a419), and a mode that emits Go definitions of C types and variables (efda876a94) used to generate defs_*.go; in 1.4 Eface maps to interface{} (948bb9150e) and structs without tags are disallowed (3ffd29fb2c). For precise GC cc emits argument pointer maps and locals bitmaps (9b1f1833de, 73c93a404c), gc bitmaps in read-only memory (a26c01ad44) and gains the same linkarchinit as gc for amd64p32 (d4896fb876). Optimizations in 6c: avoid long multiplication (2c0b00744f), rotates (bb192d1399, 41ec481a53), NaN-safe floating point (109a976355), fixjmp in regopt (5cb1ed2189), 64-bit switch values (6481e37d28, d89b7173c2).

Timeline.

Assemblers: 5a/6a/8a/9a and the Go cmd/asm

Span: Go 1.0 → Go 1.6 · Status at go1.17: cmd/asm (cmd/asm/internal/{arch,asm,flags,lex} + cmd/internal/obj) is the assembler → continues as arm64 code generation and assembler (and the other per-arch assembler threads) (1.18+)

Continues in: x86 assembler: AVX, AVX2, AVX-512 and the instruction table (Go 1.7 → Go 1.11) · ARM64 assembler (Go 1.7 → Go 1.11) · 32-bit ARM, MIPS and PPC64 assemblers (Go 1.7 → Go 1.11)

Design docs & discussion

Design. The Plan 9-derived assemblers were yacc programs (a.y, lex.c) per architecture. They gained x86 coverage over time (386 SSE2 5da37c0901, XMM registers 755e13877f, AES 297bb12809, CMPPD e5009e29ea, IMULL grammar 1738dc0e71, $(-1) forms 6d9c02aea3, MOVQL ea86752206, amd64p32 e509bbc943, SETPC a6feaf3aff), ARM (PLD 2031252fb8, MULW{T,B} a0084b3494, m/g registers 5d081792b6) and pseudo-ops (PCDATA 47b89a37d9, FUNCDATA 8c741c97f7). In 1.3 they used liblink (f7aaa553da) and in late 2014 labels became function-scoped (b55791e200).

For Go 1.5 cmd/asm replaced them. Rob Pike’s design (dev.cc): a single assembler driven by cmd/asm/internal/arch tables (register names, instruction aliases, per-arch operand rules), lex (a macro-processing tokenizer with #define, #ifdef, #include; d6eb21e331 rewrote the macro processor), asm (parser and instruction builder), flags, producing obj.Prog lists consumed by cmd/internal/obj. Development was bootstrapped by converting the old assemblers mechanically to Go (9e2f8fdb6f, 4ca2fc4d8b “new5a etc, cmd/internal/asm”) and verifying the new assembler byte-for-byte against them: cmd/asm was first the “verify” pass in cmd/go (“verifyAsm”), then made the first assembler (4100f7d95c), and the runtime was the last package verified (e59ed477c3). Once identical, the C assemblers and cmd/internal/asm were deleted (3af0d791be, ba0c142d32, 861af80458).

Timeline.

C→Go translation of the toolchain (liblink, assemblers, compilers; dev.cc)

Span: Go 1.4 → Go 1.5 · Status at go1.17: complete; all tools are Go

Design docs & discussion

Design. The dev.cc branch used Russ Cox’s rsc.io/c2go translator to convert the C toolchain mechanically, then hand-fixed leftovers. Preparation (1.4) was annotation-driven: #define to enum, /*c2go */ comments giving the effect of macros, braces in struct initialisers (ebce79446d). Conversion order: first liblink as cmd/internal/obj with a temporary dual mode where C liblink calls go tool objwriter to write objects, selected by $GOOBJ and $GOOBJWRITER and verified against the C output (be818361b9, 725e3a7afb, 283b23297a, d6f6e420fc, 24dfaba6d1, 1fc330d8fe, 2ecefd41fa disabled the GOOBJ=2 default, 25c140aaa0 turned off the verification), then the assemblers (9e2f8fdb6f, 4ca2fc4d8b, 67e7ded029), then the compilers and linkers into cmd/internal/gc, cmd/internal/ld, cmd/new6g etc (8c195bdf12: 88448 lines added). Repeated reconversions (dc7b54bed2, f6791da7ad, 53d4123fbc adding profiling) followed C-side changes until the freeze, when the C implementations were replaced by the Go ones (3af0d791be: 170 files, +9484/-93906) and then the C libraries and their dist code were deleted (3317e7f19a). Source simplifications for translation went into mainline: cmd/gc as a real library (349ecfb0d6), simplifying code (e82003e750, 5c87cf7608, 2286989912, 3be0a0ef6f), and removal of C tricks (subclassing in popt.c 1250d2e374, pointer arithmetic acba34e45f, qsort order 90965718a8). After the switch the Go source was cleaned: goto removal (4492811c05), idiomatic bool fields (25da594c6a, 38a61ff41f, 75883bae28), unembedding Node.Func/Name (3ed9e4ca3c, 8fa14ea8b4), Fprintf-style cleanups (494e317fbf), destuttering (ce469fadd8).

Timeline.

The ppc64 / ppc64le port (dev.power64, 9g/9a/9l)

Span: Go 1.5 → Go 1.6 · Status at go1.17: production port with shared-library support → continues as PPC64 / Power10 backend (GOPPC64, prefixed instructions, PC-relative code) (1.18+)

Continues in: 32-bit ARM, MIPS and PPC64 assemblers (Go 1.7 → Go 1.11) · Multi-architecture SSA ports: PPC64, MIPS64, MIPS32, s390x (Go 1.7 → Go 1.11)

Design. The port started on 2014-08-06 by importing the Vita Nuova 9a/9c/9l code without modification, “concatenating several cmd/9l files together to form the liblink files” (117b1d4685), then converting 9c/9a to liblink with Go argument passing and both endiannesses (83f6aadf16, bcf7f61f83, 4be75c8f26) and writing 9g from the 6g/5g model (“first check point” 4928b2074b). The code generator copies the template: zerorange (b1c9a8bce3), loops for memmove in sgen (372b5cebe7), ginscon2 for CMP/CMPU (893f28ca16), NaN-correct float compares (27e657ef68, aa739b8d03), MOVD $con(Ra), Rb → ADD (f375c0eb0d), large constants (ba9a11e557), stack split and long conditional branches in liblink (f4529adabe). Bits became a uint64 array (fa32e922d5); progtable gets CC/V/VCC variants (e78777ebfe) and proginfo for DUFFZERO/DUFFCOPY (ee853dacf5, 60f66aa817); regopt (743bdf612a) and a peephole (ed5488eece) were implemented, R_CALLPOWER like ELF R_PPC64_REL24 (6f755f2f8f), R13 avoided (bbd2127909). Frame alignment: 8-byte argument size on power64 (93341e8664, bc8d35a9cf). ARETURN was unified with obj.ARET (9389a86b01, 16310571d1) and ppc64 DS-form loads/stores handled in obj and the linker (5e1d0fcbed).

Go 1.6 added position-independent code: -shared/-dynlink on ppc64 (c23c8d58d6), a TOC pointer maintained in R2 following the ELFv2 ABI: global entry point instructions, local-entry metadata for the system linker, save/restore around calls (368d548417), TOC-relative loads of static data (bd329d47d9), GOT access (2ac993107f), IE-model TLS (a35c85c0cc, further “like a platform linker” c9b8cab16c), no PLT call to morestack (64fbca41c8), stack barriers in ppc64le shared libs (3a1bed82a6), larger minimum frame (97055dc1f1, c1b6e392f5). Regopt must not use REGTMP (0357c38adf).

Timeline.

The arm64 (ARMv8) port (7g/7a/7l)

Span: Go 1.5 → Go 1.6 · Status at go1.17: production port → continues as arm64 code generation and assembler (1.18+)

Continues in: ARM64 assembler (Go 1.7 → Go 1.11) · Multi-architecture SSA ports: ARM, 386, ARM64 (dev.ssa) (Go 1.7 → Go 1.8)

Design. Aram Hăvărneanu’s port landed in March 2015 as three parts: the assembler/obj package (26bbe7ac9b: 32 64-bit integer registers, 32 128-bit vector registers (unused), R31 is either hardware stack pointer RSP or zero register ZR depending on the instruction; hardware SP must be 16-byte aligned), cmd/asm support (18d9ddc35c) and the 7g compiler “based on 9g” (02c1a9d87d, “No peep optimizer yet”). Because ARM64 has no absolute addressing for text, 6e3a2a3f9f removed it from .text; ef49b4ca78 added CSEL; a175fa3bc3 parses register pairs for LDP/STP; 888d44d3c1 uses the zero register in componentgen; e040fd4654 raised registerization; prologues were made predictable (54789eff38); frames must be aligned (3952057cf6) and misaligned frames are rejected except empty ones (034a10d44c); 0f27b91522 introduces RegTo2 instead of a full Addr; 467a2cb73c drops the Reginuse check in clearfat. In 1.6: loads from memory use two instructions rather than three (3a9bc571b0), TLS (72180c3b82 “native-ish”, 80d9106487 initial-exec), GOT access in -dynlink (3534e2bef4), branch enlargement (2a031e6a2a, literal pool flush 73d590b4bd), CASE/BCASE removed (3578bdf3c3, ARM 8d478e845c).

Timeline.

The mips64 port and the mips/mips64 register space

Span: Go 1.6 → Go 1.6 · Status at go1.17: production port → continues as s390x, MIPS, 386 and 32-bit ARM backends (1.18+)

Continues in: Multi-architecture SSA ports: PPC64, MIPS64, MIPS32, s390x (Go 1.7 → Go 1.11)

Design. The port was built the same way as arm64 by copying ppc64: cmd/internal/obj/mips first as a mechanical copy “to reduce code difference when adding the mips64 port” (5cc86c6f55), cmd/compile/internal/mips64 likewise (14f919578d), then real support for GOARCH=mips64{,le} in obj (fa6a1ecd63), cmd/asm (43ea305435) and the compiler (d5cd4ab46a, 689 insertions / 1129 deletions relative to the copy). Register space was reduced afterwards (1b6d55acab).

Timeline.

Native Client (nacl/386, nacl/amd64p32, nacl/arm) and amd64p32 in the toolchain

Span: Go 1.3 → Go 1.6 · Status at go1.17: not covered by this range’s commits after 1.5

Design docs & discussion

Design. The first merge was a mechanical import of Russ Cox’s NaCl branch (7c8280c9ef, per golang.org/s/go13nacl), followed by assembler/compiler support (7954b2b90b): consult $GOARCH, and in 6c under NaCl “some of the more complex addressing modes must be disabled, and the BP and R15 registers must not be used”. amd64p32 (32-bit pointers on amd64) required linkarchinit in cc and gc (d4896fb876, e31a1ce109), 6a support (e509bbc943), morestack fixes (9460cf7825), stack alignment fixes (3d5e219e02), correct stack zeroing (741244e433, bfbb2e827b, ac0e12d158), and Duff’s device disabled (c6a41a3559). nacl/386 (88f84b3e41) and nacl/arm (783bcba84d, large stack offsets 782eea0190) followed.

Timeline.

Position-independent code, dynamic linking and TLS in the assemblers

Span: Go 1.3 → Go 1.6 · Status at go1.17: working on amd64, 386, arm, arm64, ppc64le → continues as Prologue, frame pointer, stack split and function alignment (cmd/internal/obj) (1.18+)

Design. With liblink, TLS and shared-library rewrites became explicit assembler passes. 1.3: a TLS pseudo-register introduced for 386/amd64 so 4(GS)-style operands are rewritten in one place (90093f0634); 1.5: -shared re-enabled on amd64 with -shared flags in cmd/asm and 6g (09d9520be9), -shared TLS base computation on x86 (e73981512f, 14fbec49dc), -dynlink access to global data through the GOT (84207a2500), duffcopy/duffzero called via GOT (1d1c61ba7d), R15 freed when not needed for GOT indirection (8a3132dd5e), framepointer and prologue changes. 1.6: GOT access on arm (e6ceb92e1c), arm64 (3534e2bef4), ppc64le (2ac993107f) and 386 (cb0393866a, 342f17eaf7), independent bits of toolchain TLS support (9e6ba37b86, 7bf959c67a), “a saner model for TLS on arm” (b0344e9fd5), native-ish TLS on arm64 (72180c3b82), initial-exec (80d9106487), IE on ppc64le (a35c85c0cc), regopt must not use CX in PIC on 386 (61da0e92d0, 9514285da9) and android/386 local-exec (0fbf0955d4), -shared/-dynlink on ppc64 (c23c8d58d6), x86 GOT rewrite factored (3c85e1b186). Solaris needed a way to avoid pc-relative addressing (4516a60062, 1.4).

Timeline.

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

Backends and assemblers (Go 1.7–1.11)

Reader: Claude Sonnet. Cites 1,255 of 1,416 commits in this slice; every non-noise commit is cited, plus 16 larger commits the noise filter had set aside, which a separate gap reader described. Read the opening lines of the design-heavy messages and 11 full messages, but no diffs. Many timeline entries are hash lists with brief explanations.

Release digest for this slice
  • Go 1.7 — The dev.ssa SSA backend for amd64 lands (IR to SSA, generic and AMD64 rules, global register allocator, tighten, flag recomputation, scheduler), together with intrinsics, load combining, and a slew of cmd/internal/obj cleanups (As type, ADATA removal, cmd/internal/sys); s390x object/assembler support arrives and the ARM SSA port is started. Threads: SSA bring-up, register allocation, scheduling, obj refactoring.
  • Go 1.8 — SSA becomes the only backend for every architecture: ARM, 386, ARM64, PPC64/le, MIPS64, s390x and the new MIPS32 port, with the legacy cgen/gins code deleted (167e381f40, e7cc9c4f4d); new passes (writebarrier, nil-check redo, stack layout in SSA), atomic intrinsics, link register allocation, and PPC64/s390x assembler growth.
  • Go 1.9 — Cleanup and speed: liveness ported to SSA, math/bits and rotate intrinsics, automatic commutative rule generation, chunked rewrite functions, *types.Type in SSA, XPos/src.Pos, inlining PC tables, Follow removal, concurrent-safe assemblers and Prog allocation for the concurrent backend, ARM/ARM64 assembler hardening, load+op fusion on amd64.
  • Go 1.10 — AVX/AVX2 assembler support via RestArgs, DWARF location lists and -gendwarfinl, math rounding and Abs intrinsics, ARM fused-op patterns, SSE zeroing on amd64, buffered write barrier compiler support (amd64), memory-op combining.
  • Go 1.11 — Safepoints almost everywhere with register liveness maps, buffered write barrier on all runtime architectures, location-list redesign and statement boundaries, branchelim/CMOV on amd64 and CSEL on arm64, ARM64 fused operations and bitfield patterns, AVX-512 in the assembler, | rule alternation, and the new wasm architecture.

SSA backend bring-up on amd64 (dev.ssa merge)

Span: Go 1.7 → Go 1.7 · Status at go1.17: still the design at go1.17 (the same cmd/compile/internal/ssa package: Value/Block/Func, generic ops lowered by gen/*.rules, passes list in compile.go) → continues as Register allocator, scheduler and block-level backend passes (1.18+)

Design docs & discussion

Design. Every commit in this thread is a [dev.ssa] commit with a 2015–2016 author date but first shipped in Go 1.7. The compiler front end (gc) converts its Node IR into SSA form (d2fd43aa77, cmd/internal/gc: convert standard IR into SSA) and then generates machine code straight from SSA values (083a646f63, Generate code from ssa form). The SSA package represents a function as blocks of Values with an opcode, a type, argument list, AuxInt and Aux fields (8f22b5292f added AuxInt; 16b1fce921 added aux typing and per-op flags; 4e95dfed01 added a max argument length per opcode). Opcodes and the lowering rules are described by tables and rule files under ssa/gen and are autogenerated (0dca7351e9 autogenerates opcodes; a9cec30fdc adds block rewriting rules; 177b697ba5 lets a rule choose a target block; 0d91018082 adds simplifying block rules).

A second early design decision concerns narrow integers. Initially values of sub-register width kept their high bits correctly sign/zero extended; 2a5e6c47bc redid this so the compiler treats the high bits of a register as junk, so ops producing subword results need not extend, at the cost of extending when a wider use appears. 67fdb0de86 made opcodes width- and sign-specific, 9cb332efd4 split OpConst into Const8/16/32/64, and 7fc5621991 later defined the high bits of AuxInt to be the sign-extended version of the low bits, so that (Const8 [0x1]) and (Const8 [0x101]) become the same value for CSE.

Memory is modeled as a single linear chain: 6241a41e33 enforces one live memory state, and e98edc88c9 makes the scheduler enforce load/store ordering. Panics and calls were first special ops and then ordinary calls to runtime functions (3a70bf9c0c, d24768e14d completing call ops). Nil checks, bounds checks (3e7904b648 improved inBounds rules), go/defer (9569b957cb), closures and addressed variables (956f3199a3), complex arithmetic (3a9d0ac3c8), string ops (f7f604e284), FP support (997a9f32b0, 8e601b23cd, 4282588694), and GC bitmaps for SSA-compiled code (d2107fc987) were added one language feature at a time. Development was driven by GOSSAFUNC/GOSSAPKG environment variables so single functions or packages could be compiled by SSA without rebuilding the compiler (d298209b1c), and the HTML dump of every pass was added around GOSSAFUNC (later made readable by b13b249f43).

Timeline.

Register allocation, flag allocation and stack slots

Span: Go 1.7 → Go 1.11 · Status at go1.17: still the design at go1.17 (global allocator in regalloc.go, separate flagalloc, stackalloc) → continues as Register allocator, scheduler and block-level backend passes (1.18+)

Continues in: SSA infrastructure passes: layout, regalloc, scheduling, flagalloc, block shape (Go 1.12 → Go 1.17)

Earlier history: Register allocation and the peephole optimizer (reg.c, peep.c) (Go 1.0 → Go 1.6)

Design. The first SSA backend used a local, per-basic-block allocator. 0b46b42943 replaced it with a global (whole-function) register allocator in which clobbers are handled properly. Its structure persisted through 1.11: values are allocated in block order using use distances computed backwards from block ends, with phi values given the registers of their inputs, and merge edges repaired by a “shuffle” pass that inserts copies/loads/stores. ddeee0eed3 enforces that phis come first, f206b16ff7 assigns unused registers to phi ops, d7ad7b9efe zeroes register masks per edge, and 7d61246972 introduces reserved registers (SP, SB, g, etc.).

Flags are not allocated like normal registers: 9f8f8c27dc took a simple approach: never spill flags, and when a flag value is needed again, recompute it by reloading its arguments; fewer than 200 flag spills occurred during make.bash. d04f38e3ee fixed the “find original inputs” step after rematerialization. c140df0326 moved flag allocation into a separate pass (flagalloc), and 0484052358 removed flags from regMask. Because rematerializable ops can be inserted after flagalloc, 2cb61aa3f7 (1.10) added a check that they never clobber flags and fixed the offenders, which is why ADDQconst could not be made rematerializable and 9916feeddf (1.11) rewrites (ADDQconst SP) into a LEA instead.

Spill and stack-slot placement evolved: 366dcc4529 reuses stack slots for spill locations; 7d9f1067d1 reordered register and stack allocation (previously registers, then merge-edge fixup, then stack slots, which produced LoadReg/StoreReg pairs through the same slot that could not be seen until too late); c64a6f6362 added rematerialization of constants and addresses; 02f4d0a130 starts arguments as spilled; 75102afce7 and 7d9f1067d1 are the “better register allocator” rounds; 9d6e605cf7, b57ac33331 and 815c9a7f28 add forward-looking desired-register biasing and use of loop information. Instruction-specific needs: 369f4f5de5 and 0004f34cef allocate for two-address instructions (resultInArg0) and enforce them. 1.8 refined: f876fb9bae copies the value being evicted to a spare register before kicking it out (helpful for x86 DIV and MIPS MUL/DIV, reducing “spilled value remains” by 1% for cmd/go); 348275cda6 copies a still-live Phi input to a spare register to avoid a spill; 1bddd2ee6a does not shuffle rematerializable values around; 7bd88a651d does not sink a spill out of a loop if it satisfies a merge edge; 6dc356a76a erases register copies deterministically; 833ed7c431 reorganizes SSA register numbering; 15817e409b makes the link register allocatable in non-leaf functions (s390x first, then ARM64 5c59cb4aa3, MIPS64 4f6d479186, ARM f9238a76ff), with RC4 on s390x improving up to 40%.

In 1.9 886e9e6065 pushes spills farther from their generator into unlikely branches; 8a44c8efae stops spilling rematerializable values when resolving merge edges; 8a5175df35 improves the startRegs calculation (a value used only after a call need not be live at block entry); 9664bc1d1f fixes phi updates for preemptible loops; cfb0d34992 lets XCHG use stack pointers. In 1.10 7eaa8efbe4 stops spills clobbering differently-typed views of arguments. In 1.11 dafca7de0f prefers rematerialization to copying, 014a9048d4 resolves a long-standing TODO by evicting a register holding a rematerializable value first, 840f25bedd introduces dense numbering for GP registers, 31e1c30f55 forbids LoadReg of the g register, 2ba98f1ae9/377a2cb2d2 remove regalloc allocations.

Timeline.

Scheduling, tighten, block layout and CFG passes

Span: Go 1.7 → Go 1.11 · Status at go1.17: still the design at go1.17 (schedule, tighten, layout, looprotate, branchelim, fuse) → continues as Register allocator, scheduler and block-level backend passes (1.18+)

Continues in: SSA infrastructure passes: layout, regalloc, scheduling, flagalloc, block shape (Go 1.12 → Go 1.17)

Design. The scheduler orders values inside a block subject to phis first, control values last, and memory-ordering constraints. Early commits fixed correctness: phis scheduled first (4e204b42f5, f1401f1a10), control values live at the end of the block (573c791e81), values depending on the control scheduled later (21e6a055c1), load/store ordering (e98edc88c9). b1785a5065 replaced ad hoc ordering with a heap-based priority queue; after trying several heuristics the winning one for the Go tools was “line-number order, then more args before fewer, then variable ID order”. Later fixes: tuple ops (225ef76c25, 1c53a1b197), storeOrder (a355639c60, 978af9c2db, 1a27f048ad), and allocation cuts (41e176fbe0, 00c8e149b6, 4ed94ef1a8).

tighten (f91ff1a509) moves values closer to their uses: in the initial restricted version it moved 84,437 values during make.bash. Special cases were added (not moving SP/SB 6548b63d6b, respecting phis e5fe33e546, not moving mem-using values 65df9c4c2b, aggressive for booleans fe5619b479). In 1.11 3a9e4440fd runs tighten again after lowering, reducing spills.

Layout changed several times. 4b0d74f89d (1.9) lays out exit blocks last (restoring the 1.8 property that panics sit at function end; Fannkuch11 -7%), 638ebb04f2 extends looprotate to move the entire run of contiguous header blocks, and 73f1a1a1a7 (1.10) uses a reverse postorder traversal. In 1.11 c18ff18465 decouples the emitted block order from the regalloc block order and ab48574bab uses Block.Likely to put the likely branch first when neither successor follows. Branch elimination: 2d0172c3a7 adds a branchelim pass producing CondSelect and the arm64 CSEL lowering (go1 Gzip more than 10% faster), and 080187f4f7 / a35ec9a59e (a duplicated subject) enable it on amd64 as CMOV with special NaN handling for FP. CFG editing became constant-time with edge indexes in Preds/Succs (4fa050024f, fixing O(n^2) in deadcode and fuse); 10f38f51ef distinguishes exit and return blocks.

Timeline.

Rewrite-rule machinery and compile-time performance of the SSA compiler

Span: Go 1.7 → Go 1.11 · Status at go1.17: still the design at go1.17 (rulegen.go, | alternation, automatic commutation) → continues as Late-lower pass, memory-op combining and arch-independent rule migration (1.18+)

Continues in: Rewrite-rule generator (rulegen): ellipses, commutativity loops, typed aux (Go 1.13 → Go 1.17)

Design. Rules in gen/*.rules are compiled by rulegen.go into rewriteARCH.go. In 1.8 6a1153acb4 extracted parseValue from genMatch0/genResult0 to share parsing and validation; e4355aeedf added sanity checks on rules (1.7). In 1.9: 041ecb697f (reverted and then relanded as 53f8a6aeb0: “a redo of an undo of the original buggy CL”) makes the generator automatically generate both argument orders for commutative ops, eliminating pairs like (ADDL x (MOVLconst [c])) / (ADDL (MOVLconst [c]) x); fc327a14c4 removes order canonicalization rules from MIPS. 193510f2f6 evaluates config lazily in rewrite functions; eaa198f3d1 stops generating block successor variables; fc7b83d192 breaks up large value rewrite functions into chunks of ten cases (cmd/compile/internal/ssa compiles 23% faster: 7.33s to 5.64s); 5548f7d5cf removes bounds checks from generated code; 69fe9ea43e adopts the “generated code header”; 5e4a958351 refactors portable Op handling. In 1.11 rules gained | alternation (ec62ee7f6d, cab7ba0b28, 3553262248, 8993e16932, ebeea20a49, 1e05924cf5, af11038428, c284171bbb, 0a4c439d3f aggregate AMD64 and other-arch rules), and aff222cd18 trims PPC64 rules to shrink rewritePPC64.go.

Compilation speed of the compiler itself was part of this work. Per-func caches (f962f33035), ssa.Config/ssa.Func/ssa.Cache roles defined in 1.9 (43afcb5c96, a5e3cac895, 88e47187c1, 2cdb7f118a moving Frontend to Func, aea3aff669 separating ssa.Frontend and ssa.TypeSource), where cached values/blocks receive IDs without coupling Func and Config; not creating duplicate constant OffPtr SP values (3dcfce8d19, which were almost 30% of CSE’d values), CSE allocation reductions (f791b288d1), reuse of sparse maps (cd2cb6e3f5), stack-allocated worklists (00c8e149b6, 4ed94ef1a8). The ssa.Type interface was replaced by *types.Type (46b88c9fbc) once types was extricated from gc, reversing the dependency direction. The 1.7 commit 0ac0e22fa7 shrank compiler tables, cutting the cmd/compile binary from 14.41 to 11.42 MiB.

Timeline.

Multi-architecture SSA ports: ARM, 386, ARM64 (dev.ssa)

Span: Go 1.7 → Go 1.8 · Status at go1.17: still the design at go1.17 (each architecture owns ssa/gen/ARCH.rules, ARCHOps.go and cmd/compile/internal/ARCH/ssa.go) → continues as s390x, MIPS, 386 and 32-bit ARM backends (1.18+) and arm64 code generation and assembler (1.18+)

Continues in: 386, ARM (32-bit), MIPS/MIPS64 and the 387 removal (Go 1.12 → Go 1.17)

Earlier history: The arm64 (ARMv8) port (7g/7a/7l) (Go 1.5 → Go 1.6)

Design docs & discussion

Design. Per-architecture backend = ARCHOps.go (op table with register masks and flags), ARCH.rules (lowering and peepholes), rewriteARCH.go (generated), and ARCH/ssa.go (emission from SSA values to obj.Prog). 1.7 had a4e31d42ee removing amd64 code from package gc and the core gen tool, making room for other arch backends, and 4c9a470d46 (“start on ARM port”). The 1.8 cycle is almost entirely about having every Go architecture generate code through SSA. ARM was staged in the open on dev.ssa: symbolic constants e1a2ea88d0, bools ccaed50c7b, shifts and multiplies 8f72690711, Zero/Move/Copy 8357ec37ae, Defer/RetJmp d108bc0e73, 64-bit decomposition 8756d9253f, 64-bit shifts 4636d02244, Div/Convert/GetClosurePtr e78d90beeb, floating point 59e11d7827, GetG 93b8aab5c9; the optimizations 8599fdd9b6, 8cc3f4a17e (shifted operands and indexed loads/stores, split shifts into several ops so constant folding and CSE work, conditional moves), df43cf033f (NilCheck), f55317828b (alignment for Zero/Move), 7d70f84f54 (FP, and teaching the runtime softfloat simulator to compare against 0), NaCl 6b6de15d32, and cf92e3845f (use a 2-result divide op).

386 got an initial port 14cf6e2083, with 64-bit ops aee8d8b9dd, “now works” df2f813bd2, GO386=387 c069bc4996, and PIC handling: 2cbdd55d64 handles that globals on 386 PIC need a two-instruction sequence with a CALL to a PC-getter into a tmp register, requiring careful register allocation; 77ef597f38, 8f955d3664, 94c8e59ae1 complete it. amd64p32 was ported at 69a755b602. ARM64 started with ae9570a5b9, 83208504fe, ed1ad8f56c, 659dd4f1d7 and d99cee79b9, which enables SSA by default for ARM64 after adding zero-register use, shifted ops, and fast division (previously disabled because the old backend generated slower code with it).

Timeline.

Multi-architecture SSA ports: PPC64, MIPS64, MIPS32, s390x

Span: Go 1.7 → Go 1.11 · Status at go1.17: still the design at go1.17; ppc64x, mips64x, mipsx and s390x all remain stack-ABI backends → continues as PPC64 / Power10 backend (GOPPC64, prefixed instructions, PC-relative code) (1.18+) and s390x, MIPS, 386 and 32-bit ARM backends (1.18+)

Continues in: PPC64 / POWER backend and assembler (Go 1.12 → Go 1.17) · s390x backend: compare-and-branch, intrinsics, assembler (Go 1.12 → Go 1.17)

Earlier history: The ppc64 / ppc64le port (dev.power64, 9g/9a/9l) (Go 1.5 → Go 1.6) · The mips64 port and the mips/mips64 register space (Go 1.6 → Go 1.6)

Design docs & discussion

Design. The same four-file scheme as above. PPC64 has no store-immediate, so 806cacc7c6 replaced the pseudo-instruction with storezero (R0 is reserved as zero in Go). The PPC64 SSA port started at 03d152f36f, with calls 2d16e43158, zero/move dede2061f3, compare-to-bool, shifts and high multiply dd1d9b36c6, loads/stores by type, shifts, divisions 01ae4b1da4, FP ff37d0e681 and d08010f94e, then 5b9ff11c3d “ppc64le working, not optimized enough” and eed061f89c enabling big-endian; address folding a1dc9465a1; nil-check removal 0e0ab20334. MIPS64 followed (310a40b4f2 start, e71e1fe87e working, 7f27f1dfdd optimizations plus SSA on by default). MIPS32 arrived as a new port 66fdf433b0 (cmd/internal/sys), 5d28bc58b6 (obj/mips), f58ce7fe79 (asm), 247fc4a98e (ssa), f4c997578a (compile). Big-endian 4-byte unaligned load rules were fixed in 26a6131bac.

s390x is the one wholly new architecture in the range: obj and asm in 1.7 (2a7e85f162, 13f97ea456, 85bbabd9c4, 354e9778a3), SSA lowering a223ccae38 and 613ba6cda8, and in 1.8 6ec993adc3 adds the SSA backend and enables it by default; 1cfb5c3fd5 adds canMergeLoad to decide whether a load can be merged into an operation without reordering relative to stores; 962dc4b44d improves load/store merging; 122abe6b12 folds extensions into constants; 3202aa7800 leaves constants visible to the compiler instead of hiding XOR -1 in the assembler; bd8a39b67a adds fused multiply-add (blocking fusion when a cast intervenes, with explicit float/complex OCONV kept as rounding operations); 8afa8a3374 uses 32-bit comparisons; 5d9c78201f allows R11.

Timeline.

WebAssembly backend

Span: Go 1.11 → Go 1.11 · Status at go1.17: still the design at go1.17 → continues as WebAssembly backend (1.18+)

Continues in: WebAssembly backend (Go 1.12 → Go 1.16)

Design docs & discussion

Design. 3b137dd2df adds the wasm architecture to the compile command (design doc referenced from the message; generated opGen.go, rewriteWasm.go, obj/wasm/anames.go; linker support in a later commit). Go SSA instructions operate on registers while WebAssembly instructions operate on an operand stack, so one Go add would naively map to four WebAssembly instructions (push local A, push local B, add, store to local). 482d241936 “add wasm stack optimization” addresses that: it keeps single-use intermediate values on the wasm stack instead of round-tripping through locals. The remaining wasm-specific issues in 1.11 are fixes: valid offsets for Load/Store in the assembler (bf23a4e61d), write-barrier control value (8aa57f1e9d), OffPtr with negative offset (88dc4aee7c), and folding the offset into LoweredAddr (1d303a0086) so the address is rematerializable and not materialized before the variable becomes live (otherwise liveness complained about a variable live on entry).

Timeline.

Removal of the legacy backend and decoupling gc from the code generators

Span: Go 1.7 → Go 1.9 · Status at go1.17: done; the per-architecture ssa.go files are the only code generators → continues as Prologue, frame pointer, stack split and function alignment (cmd/internal/obj) (1.18+) (frame handling)

Earlier history: Per-architecture compilers 6g/8g/5g/9g and the shared gc code generator (Go 1.0 → Go 1.6)

Design docs & discussion

Design. Until 1.7 each architecture had a cgen-style generator (5g/6g/8g/9g descendants) with gins, Naddr, Betypeinit, Thearch-driven register allocator reg.go and peep.go. In 1.8 167e381f40 made SSA compilation unconditional (removing ssa=0, GOSSAHASH, GOSSAPKG, SSATEST; GOSSAFUNC now only controls printing), e7cc9c4f4d “delete lots of the legacy backend”, followed by f03855f40e, 838eaa738f, ad7732a9ac (gins), 1f2930cb5c (old register allocator), 544010a05a (Betypeinit), 8a0bd5dc01/78c46581f4 (appendpp dedup), e6134702bb (simplify obj.ProgInfo). 882dd1c3ba then enabled rewritings that the old backends had blocked. In 1.9 the remaining dependencies on gc were cut so that the backend can run concurrently and on its own types: 3e2f980e27 passes gc.Thearch into GOARCH.Init, 352e19c92c eliminates Gins and Naddr, c3a50ad3c7 removes Prog-related globals, 0a94daa378 funnels SSA Prog creation through SSAGenState, 263ba3ac7b makes defframe arch-independent, da8e939ba9 threads Curfn through SSA and ef1ab0f0f4 forbids Curfn use in the backend. The stack frame itself is laid out during SSA (8c24bff52b, 1.8).

Timeline.

Liveness, stack maps, safepoints and write barriers

Span: Go 1.7 → Go 1.11 · Status at go1.17: still the design at go1.17 (liveness over SSA, asynchronous-preemption-capable register maps) → continues as Stack slot merging, liveness and stack maps (1.18+)

Continues in: Safepoints, unsafe points and stack maps for asynchronous preemption (Go 1.14 → Go 1.16)

Earlier history: Stack frames, pointer maps and liveness analysis in the compiler (Go 1.2 → Go 1.6)

Design docs & discussion

Design. Liveness analysis (plive.go) originally ran over the legacy Prog list. The SSA backend initially kept it working by emitting Progs that it analysed afterward, with workarounds such as teaching the analysis about pxor and LEAW (73024083b0, 617e892b87), and splitting named compound AUTOs into one-word variables (4a7aba775d: liveness could not handle spilling to multiword types because there was no valid place for VARDEFs). Correctness items in 1.7/1.8: keeping heap pointers live for escaping output parameters (6effdd28de, 519b469795), args alive in tail calls (e3f1c66f31, 3dfb92f254), relaxing “ambiguously live” (6c5e377d23), args no longer live to end of function (ca4089ad62).

The big change was in 1.9: liveness was refactored (2d78538c12) and ported to the SSA CFG (325904fe6a, 1298 lines changed in plive.go), instead of analysing Progs. This depended on SymEffect attributes added to SSA ops (1cdf4bf33f, 691755304c), a map instead of Node.Opt for variable indices to avoid races (794d29a46f), and PC-order block visiting (ea8c7dae4f). b612ab3acb made liveness cheaper with many variables via sparse progeffects; 38dee12dea zeroes ambiguously live variables at VARKILLs; 7cd6310014 skips maps when the stack is too large. 1.8’s f6aec889e1 added the writebarrier phase: the front end emits StoreWB/MoveWB ops, which the phase expands to branches and runtime calls after writes to static stack locations have been recognized and their barriers removed (all instances found by the #17330 script eliminated except two false positives). 1.10’s 7e343134d3 adds compiler support for the buffered write barrier (initially amd64 only; old eager barrier kept so tests that need liveness maps at barrier calls can opt in); in 1.11 the runtime and compiler support for the buffered barrier is added for 386 (252f1170e5), amd64p32 (24dd83d7eb), arm (1de1f316df), arm64 (79594ee95a), mips64 (a39de96438), mips (313a4b2b7f), ppc64 (ae7d5f84f8) and s390x (2ae1e1ae2f).

In 1.11 liveness was generalized to all instructions: a367f44c18 makes almost every operation a safepoint (excluding runtime code other than calls, nosplit functions other than calls, instructions between the write-barrier flag load and the write, and instructions leading up to a uintptr→unsafe.Pointer conversion), 61158c162f tracks which registers contain live pointers (bitmaps in parallel with stack variables), ed7a0682e2 emits stack-map indexes wherever they change instead of only at CALLs, and 9f95c9db23 records register maps as FUNCDATA/PCDATA with an independent index. The cost was then reduced: d7d9df8a5b introduces LivenessMap/LivenessIndex, 75dadbec1e replaces a per-Value map with a dense slice, 3c36b8be66 compacts maps incrementally per block, 1e27e4807b trims trailing non-pointers in args maps, f11e9aac4f reuses structures via ssa.Cache, 577c05ca1c/60c76f7656/3c4aaf8a32 speed up bit-vector code; 132900982c ignores g in the analysis and 97bea97065 fixes the unsafe-point analysis with -N.

Timeline.

DWARF variable locations, statement positions and inline info from the backend

Span: Go 1.9 → Go 1.11 · Status at go1.17: still the design at go1.17 (location lists generated from SSA LocalSlot tracking; is_stmt boundaries) → continues as Register allocator, scheduler and block-level backend passes (1.18+)

Continues in: Debug info generated by the backend: statements, location lists, line tables (Go 1.12 → Go 1.17)

Design docs & discussion

Design. Position representation first changed on [dev.inline]: 82d0caea2c made src.Pos abstract, ad4efedc6c made obj.Prog use src.Pos, 4808fc4443 replaced it with syntax.Pos (the old LineHist table unused), and 472c792e0a introduced the compact 8-byte XPos (global translation table, O(1) conversion). 33c036867f removed LineHist remnants. For tracebacks with inlining, 699175a11a created two tables per function: an inlining tree stored in funcdata and a PC-value table mapping PCs to tree nodes (or -1); 4435fcfd6c (1.10) added DWARF inlined-instance records gated by -gendwarfinl=N (0 off, 1 without inlined-variable tracking, 2 with).

Optimized-code debugging came from [dev.debug]: 2d57d94ac3 tracks variable decomposition in LocalSlot, and 4c54a047c6 makes the compiler represent variable locations in registers/stack as DWARF location lists (previously all variables claimed to be on the stack, so debuggers showed stale data); limitations of SSA make it imprecise. 6bbe1bc940 extends ranges over control-flow instructions that do not come from Values; d58d90152b fixes RegKill timing; 2075a9323d (1.11) completely redesigns generation: no RegKills; liveness is recomputed from Op clobber information, avoiding adding RegKills to empty blocks. Memory use was cut (39eea62340, a306341db7, 9c854d65a3, 108efc79c7, e181852dd4, 438a757d73), stack-only variables tracked from VarDef/VarKill (44e65f2c94), and c2c1822b12 adds a pass to identify “first” SSA node per statement with never-boundary kinds (VarKill, VarDef, Phi), moving boundaries forward when values are removed, while dead03b794 makes the linker process is_stmt into line tables; b9a365681f adjusts is-statement flags. Related: ca360c3992, a042221cdb, 7bac2a95f6 plumbs prologueEnd, 3d6e4ec0a8-era arm64 DWARF register definitions (9b95611e38, 0eacf8cbdf).

Timeline.

Intrinsics for atomics, math/bits, math and runtime helpers

Span: Go 1.7 → Go 1.11 · Status at go1.17: still the design at go1.17 (intrinsics table in ssa.go, SSA ops with per-arch lowerings) → continues as Atomics and memory-ordering code generation (1.18+) and Multi-word arithmetic, intrinsics and carry-chain support (1.18+)

Continues in: math/bits, atomic and runtime intrinsics (cross-architecture) (Go 1.12 → Go 1.17)

Design docs & discussion

Design. Intrinsics are recognised in the front end by package/function name and replaced by generic SSA ops (or by inline code with explicit control flow), which each architecture lowers to one instruction. 1.7 started with 8eec2bbfbc (a first set of intrinsics; one was needed for the future GC, Bswap added as a convenience; the bit-scan family was left unfinished because “scan forward” and “count leading zeroes” don’t match). In 1.8, 320ddcf834 inlines atomic reads and writes on amd64; to keep atomic loads from being reordered they return a <value, memory> tuple, and the meaning of resultInArg0 for tuple ops was changed to mean the first part of the tuple. 84aac622a4 finishes the rest of runtime/internal/atomic on amd64. Other architectures: Ctz/Bswap on ARM64 (4354ffd38b) and ARM (8ff4260777), And8/Or8 on ARM64 (38d35e714a), s390x Ctz/Bswap (e94c52933b) and atomics (517a44d57e), ppc64x atomics (95c9583a18, 1.9), MIPS64 atomics (d63de28711, 1.10), 2756d56c89 math/big.mulWW/divWW on AMD64, d7c7d88b2c mulWW on ARM64.

1.9 introduced math/bits intrinsics: 495b167919 Len/LeadingZeros (the 8-bit case disabled because the Go table lookup was faster), 5cadc91b3c OnesCount on amd64 (guarded by a CPU-feature check because POPCNT may be missing; “Rewrite rules can’t generate control flow at the moment, so the intrinsifier needs to generate that code”), 42e97468a1 Reverse on ARM64, 8304d10763 OnesCount/Len/TrailingZeros on ppc64x, rotates 7e07e635f3 (non-constant, amd64), fa3fe2e3c6 (PPC64 rules matching the RotateLeft code), 01c8719f8b. 1.10 adds math functions: 94484d8ed5 Trunc/Ceil/Floor on amd64 (SSE4.1 round), 0f19e24da7 ppc64x, 7582494e06 s390x (and Round), 2f1f607b21 and 4745604bcb RoundToEven, fb05948d9e Abs on amd64 via int/fp register moves, 4d0151ede5 Abs/Copysign ppc64x, 96cdacb971 s390x. 6cac100eef (1.10) adds the caller-pc intrinsic; 6f3e5e637c getcallersp; and in 1.11 bd8a88729c, 20102594a0 extend getcallerpc to arm64 and all LR architectures. 1.11 additionally: ARMv8.1 LDADDAL for atomic add behind a CPU feature check (0a7ac93c27, Xadd 98% faster), fcba05148f OnesCount arm64, 07f0f09563 Ceil/Floor/Round/Trunc arm64, ebb67d993a Round ppc64x, 6aeddb1b57 Sqrt on mips64, 54dbab5221/1d321ada73 Ctz8/16 and Len8/16 ops on amd64, d9a50a6531 CtzNonZero from the prove pass, 8bf4b7e673 BSFL/BSRL as result-only ops.

Timeline.

Memory-op combining, byte-order idioms and load/store folding

Span: Go 1.7 → Go 1.11 · Status at go1.17: still the design at go1.17 (rewrite-rule based) → continues as Late-lower pass, memory-op combining and arch-independent rule migration (1.18+)

Design. Loads: 12e60452e9 combines smaller loads into a larger load using AMD64 rules (not stores: “harder because we need to make sure intermediate memory states aren’t observed”); 7294ccb54e joins indexed byte loads; da19a0cff4 and 481fe59012 fix address and offset bugs; b81f2f106f places the combined load at the location of the last byte load. Stores: 217c284995 (1.7) combines stores into larger widths and da34ddf24b (1.10) combines more constant stores. Byte-order idioms: 10f757486e (1.8) generates bswap+load/store for big-endian reads/writes (helping encoding/binary), bd91e3569a handles indexed stores, 4477fd097f handles 2-byte loads as a word load plus rolw 8, 0f2ef0ad44 (1.11) adds the missing LittleEndian.PutUint{16,32} rules, ARM64 mirrors these for little-endian (77ba071ec6) and big-endian stores (39fefa0709, 05962561ae), 9c99512d18 (1.10) combines consecutive loads and stores on amd64 (67fdf587dc then stops 64-bit combining), and PPC64 little-endian (bb1fd3b5ff). Load/op fusion: f94e0745b3, 9278a04a8f (1.7), 21c71d7788 (1.9, combine load+op on AMD64), 86dc86b4f9 (do not merge load+op if the other op arg is still live: 86dc86b4f9), 0fe58bf650; SETcc to memory (6b8a3c8889, 624630b824); ac29f4d01c ADD[Q|L]constmem; 1.11 4b00d3f4a2 compares directly with memory, 20cf5c4962 386 binary ops with a memory operand, cd65bbc01b 386 SUBL, 098ca846c7 ops with memory destination on 386; 29fcd57a9f folds offsets into memory ops; 983fca55c0 prefers non-indexed stores on amd64; a4e1a72f0a adds MOVLloadidx8/MOVLstoreidx8; 22115859a5/b1df8d6ffa LEAL{1,2,4,8}. Subword elision bugs: 9613a638a9, 1e0819101b.

Timeline.

amd64 and 386 instruction selection

Span: Go 1.7 → Go 1.11 · Status at go1.17: still the design at go1.17 → continues as amd64 code generation and GOAMD64 microarchitecture levels (1.18+)

Continues in: amd64 lowering: read-modify-write, load-op, addressing modes pass, intrinsic-friendly rules (Go 1.12 → Go 1.17)

Earlier history: Expression and statement code quality in 6g/8g/5g (division, bounds checks, loops, slices, interfaces) (Go 1.0 → Go 1.6)

Design. The AMD64 rules grew from the straight lowering in 1.7. 1.7: sub-word ops became 32-bit ops (036d09d5bf, dc5a7682f0, 9e3c68f1e0, e96b232993 promote byte/word operation, 04945edd40 ANDQ to MOV?ZX); multiply strength-reduction (d8f1f8d856); constant folding (7a4211bc1f, d8ee180ab2 CMPQ/ANDQ, 8448d3aace CMPconst and SHR, afafab3b97 shifts with 6-bit counter); 7a60a962b9 ECX reserved for PIC. 1.8: 8589049d5c breaks false dependency of CVTSL2SS etc. by XORing the destination (and fixes a regression since 1.7), cc0248aea5 stops reserving X15 for float sub/div, c53879512f missing float indexed loads/stores, d251030fa6 no >32-bit constants folded into MULQ, 7ff0c8267f. 1.9: f9bec9eb42, 3e63cdf850, 9d5987d79f (upper-bit-clear idiom via shift-rotate), cfb0d34992. 1.10: df70982825 (SSE 16-byte stores to zero small blocks; duffzero only for 65+ bytes; duffzero now uses leaq to avoid clobbering flags), 14cb41584d/adbfdfe377 useSSE flag so Plan 9 avoids MOVO (no FP in the note handler), 18b48afec9 MOVQconvert as resultInArg0, 03614562ca, a5868a47c6 moves MOV→XOR rewriting for zero from the assembler into the compiler, 40649e6979. 1.11: bit-test BT/BTS/BTR/BTC patterns (79112707bb, eb44b8635c removes BTQconst rule), CMOV (080187f4f7), f4d9c30901, 85a8d25d53 IMUL3, 6a74fe2f15 more constant multiplication, d292f77e95 2*x+c as LEAx1, 54dbab5221, 3f483e6520, 24cd112086 double NEG, 4f7b774822 Move specialization, 098ca846c7/cd65bbc01b/20cf5c4962 for 386, 4c800f03c9 large offsets on 386 (1.10), d3026dd30a, f5d4863130. 386 SSE vs 387 chosen once in Init (3e2f980e27).

Timeline.

ARM and ARM64 instruction selection and fused operations

Span: Go 1.8 → Go 1.11 · Status at go1.17: still the design at go1.17 → continues as arm64 code generation and assembler (1.18+)

Continues in: arm64 backend and assembler (Go 1.12 → Go 1.17)

Design. Both ARM backends gained patterns for instructions that combine several operations, mostly contributed in 1.10–1.11 with each CL citing a micro-benchmark. 32-bit ARM: MULS (64607dbd26), MULAF/MULSF/MULAD/MULSD (a07176b45a), NMULF/NMULD (2899c3e8cb), BFX/BFXU bit-field extract (9732485851), CMN/TST/TEQ (1ec78d1dd1), single-instruction MOVB/MOVH sign/zero-extensions on ARMv6/7 (78ea9a7129), indexed byte/half loads (a2f22a6803), bc723cf340 AuxInt must fit int32, b08a9b7ecc switching to portable softfloat for GOARM=5 (the compiler generates runtime calls for FP rather than the assembler inserting _sfloat calls). ARM64: CSEL (2d0172c3a7), TBZ/TBNZ when comparing with zero (cdd961630c), FNMUL (ebb77aa867), MNEG (3c8b824453, 20046020c4), FMADD/FMSUB/FNMADD/FNMSUB (f4c3072cf5, with a FP accuracy bug fixed by 7113d3a512), bitfield opcodes EXTR/BFI/BFXIL/SBFIZ/SBFX/UBFIZ/UBFX (e244a7a7d3), EON/ORN (1057624985), CMN/TST (f524268c40), shifted/indexed memory ops (34f5f8a580, aaf73c6d1e), 51932c326f fixing shift absorption leaving dead Values that inflated use counts, RBIT/REV (8577f81a10), 257b01f8f4 ANDconst to mask leading/trailing bits, ARMv8.1 LSE atomics (0a7ac93c27), fb0ccc5d0a offset folding. c02fc1605a zeroes memory with STP of ZR pairs; b46d398887 memclr with ZVA; 8916773a3d DUFFCOPY with LDP.

Timeline.

Memory clearing and copying: Zero, Move and Duff’s device

Span: Go 1.7 → Go 1.11 · Status at go1.17: still the design at go1.17 (Duff’s device for large Zero/Move, inline stores for small) → continues as Duff’s device replaced by generated loops; Zero/Move lowering (1.20+)

Earlier history: Zeroing and copying: Duff’s device, REP, unaligned moves and AVX2 runtime assembly (Go 1.0 → Go 1.6)

Design. SSA Zero and Move ops lower to straight-line stores below a size threshold, DUFFZERO/DUFFCOPY entry points for mid-size blocks, and REP string ops for large blocks. 1.7: 66b47812eb initial zeroing, f8fc3710fd mem copies in the amd64 backend, 039d455f9d and d55a099e22 don’t use duffzero/duffcopy on Plan 9, 111d590f86 fixes a possible spill of an invalid pointer with DUFFZERO on AMD64 (Duff device clobbers registers the allocator thinks hold live pointers), 4219aba5db REPSTOSQ clobbers flags. 1.8: 9d4b40f55d implements and uses DUFFCOPY on ARM64, 1e94d79f9d disables Duff on darwin/arm64, 114c05962c fixes the invalid pointer spill in large Zero/Move on ARM, c69dd3f054 enables DUFFZERO in defframe on MIPS64, f5516559e6 fixes zero-range merging. 1.9: ppc64x LoweredZero/LoweredMove improvements (23bd919136: ClearFat12 -71%, ClearFat16 -91%; a8b2e4a630). 1.10: df70982825 16-byte SSE zeroing (duffzero only 65+ bytes, since each duffzero call also saves/restores BP), c02fc1605a STP of ZR on arm64, da34ddf24b. 1.11: 4f7b774822 direct movs for Move up to 79 bytes on amd64 (sizes 32/48/64 with SSE), 094258408d overlapping MOVDs for fractional words on arm64, ecd9e8a2fe, 8916773a3d uses LDP in DUFFCOPY on ARM64, b46d398887 ZVA memclr.

Timeline.

cmd/internal/obj: Prog/LSym refactoring, concurrent backend and object-writing

Span: Go 1.7 → Go 1.11 · Status at go1.17: still the design at go1.17 (assemblers take a newprog allocator, per-function FuncInfo) → continues as Prologue, frame pointer, stack split and function alignment (cmd/internal/obj) (1.18+)

Continues in: New object file format and content-addressable symbols (dev.link, assembler side) (Go 1.14 → Go 1.16)

Earlier history: Normalising Prog/Addr in cmd/internal/obj (the portable instruction representation) (Go 1.5 → Go 1.6) · liblink: a shared assembler/linker library (Go 1.3 → Go 1.5)

Design docs & discussion

Design. The Go-ified liblink (cmd/internal/obj) was reshaped over this range to make the assembler stage independent of global state, as a prerequisite for the concurrent backend (-c). 1.7: 0d9258a830 introduces a distinct As type for opcodes (e853131699 stops using as+ALAST as an opcode), 2dcbbbd193/4f753e77f1 remove obj.ADATA (data now written directly into LSyms 7c18f8cdc3), symbol references added to the object file (3648d2d4cd, 16029babe2 dedup), 24fc323442 splits plist flushing from object writing, 014f3e1e09 an idiomatic object writer, 8262564971 estimates x86 text size, fe5eac63c4 hoists fieldtrack. 1.8: 1bd91d4ccc removes Addr.Etype/Width, 7124056f7e drops Gotype, d391dc260a makes LSym attributes a bitfield (LSym 136 to 128 bytes on 64-bit), 6fe1febc86 replaces AGLOBL with (*Link).Globl, 246074d043 removes ACHECKNIL, 9b88fac00c/0cd8faf744 reduce per-arch opcode space with a safety check, e6134702bb extracts ProgInfo, 4c9a372946 removes rewindmorestack, d61c07ffd8 introduces ctxt.Logf. 1.9 is the big cleanup: bed8129ee6 removes the Follow pass (which reordered/copied even hand-written assembly; SSA now lays code out well), 7c84dc79fd bumps the object magic to go19ld, 9cb2ee0ff2 moves STEXT-only fields into a FuncInfo struct (1747078695 un-embeds it), ce3ee7cdae moves Text flags from From3.Offset into From.Sym.Attribute, Prog allocation moved to clients (5b59b32c97, 5c359d8083), eager initialization (3b251e603d, 63c1aff60b), 735fe51a4b adds LookupInit for lazily initialised LSyms, hash split by version (6e97c71cb7, then 405a280d01 eliminates LSym.Version), 26308fb481 tracks pcln filenames as strings (removing 85% of Ctxt.Lookup calls), assemblers made concurrency-safe (x86 2ebe1bdd89/4f122e82fe AsmBuf; arm dcf643f1fd; mips c4135d61bb; arm64 b5931020b7; ppc64 f95de5c679; s390x 1e69245418), removal of Curp/Cursym/Mode/Asmode globals (7e817859b3, 166160b446, a470e5d4b8, cfb3c8df62, cf2b32e719), 826a09cd65 SortSlice, da15fe6870 gclocals handling, 18fb670e5e fixing LSym.Type at compile time instead of in the linker, 1e3570ac86 extracts shared objabi, and in the compiler 6664ccb453 compiles more complex functions first (statement count as a proxy) and 1213776650 uses a buffered channel for the function queue. 1.10: 8c67f210a1 changes Prog.From3 to RestArgs []Addr so instructions can carry many operands (needed for AVX/AVX-512).

Timeline.

x86 assembler: AVX, AVX2, AVX-512 and the instruction table

Span: Go 1.7 → Go 1.11 · Status at go1.17: still the design at go1.17 (asm6.go + generated VEX/EVEX tables from x86avxgen) → continues as amd64 code generation and GOAMD64 microarchitecture levels (1.18+)

Earlier history: Assemblers: 5a/6a/8a/9a and the Go cmd/asm (Go 1.0 → Go 1.6)

Design. In 1.7 the amd64 assembler lost invented instructions (76f272d717 removes 3DNow!, 0b4f578266 removes nonexistent ones) and gained the AVX2 set needed for SHA acceleration (009c002c92). 1.8 adds missing instructions (5ce715fdfe), VPSHUFD operand fixes (4e24e1d999, 04ade8e428), removes pointless NOPs from prologues (64e152910e) and makes indirect calls pc-relative (13c829e5f6). 1.9: VEX relocation fix (7f9832254c), 92d918da03 fix for adcb, ANDPS (a2da2108ee), LEAx instead of ADDx when calling DUFFxxxx via the GOT (1f85d3ad09). 1.10 is the AVX/AVX2/AVX-512 groundwork. 8c67f210a1 changes Prog.From3 to RestArgs (up to 6 explicit operands; “operand hiding does not scale: AVX and especially AVX-512 have many 3+ operand instructions”; also fixes the 4th immediate type check). 41fa265db0 makes the VEX-specified register explicit, c35f239d52 moves opcode constants to aenum.go and VEX optabs to vex_optabs.go so adding VEX instructions does not edit asm6.go; 1e83f883c5 makes it possible to have all AVX1/2 instructions, 39de58bad7 adds most, 4e19cfcdf2 adds AVX2 gather with VSIB (vm32/vm64 x/y operands), 49322ca9ed fixes /is4 for VBLEND; SSE/SSE4 additions (1e607f225e, 26dadbe32c, 4074e4e5be, b15e8babc8, 50f1f639a4, ca5127cbcd, 6367c19f26, 44943aff33 ADX). 1.11 finishes the set: 5437cde96c “enable AVX512” (EVEX forms strict; 67fe8b5677 keeps Yi8 forms for backwards compatibility), b80b4a23d1 the missing legacy instructions (minimizing TODOs in amd64enc.s tests), fixes (d2c68bb65f VPERMQ/VPERMPD ytab, 1178e51a37 VPERMQ imm8 unsigned), and assembler cleanups: named consts for movtab and Prog.Back (e405c9cca3, 21fa1481c3, 514018c0aa), 3db3826a57 PutOpBytesLit, d487488135 disallow PC/FP/SB scaled index, 70c5839fe0 forbid mem args for MOV_DR/MOV_CR, 8e3dd8ab88 faster Assemble (skip the second round on non-NaCl hosts: about 50% of functions are encoded in one trip), 9b331189c1 tail-call SP adjustment, a5868a47c6.

Timeline.

ARM64 assembler

Span: Go 1.7 → Go 1.11 · Status at go1.17: still the design at go1.17 (table-driven optab in asm7.go) → continues as arm64 code generation and assembler (1.18+)

Continues in: arm64 backend and assembler (Go 1.12 → Go 1.17)

Earlier history: Assemblers: 5a/6a/8a/9a and the Go cmd/asm (Go 1.0 → Go 1.6) · The arm64 (ARMv8) port (7g/7a/7l) (Go 1.5 → Go 1.6)

Design docs & discussion

Design. Much of the ARM64 assembler work in 1.9–1.11 came from one team and follows a fix–extend pattern. 1.8 first handled BITCON (748aa84424: encode constants that fit a logical-immediate directly rather than via the constant pool; the BITCON test is 4ns vs 27ns for a map lookup, and verified against a lookup table with a model checker), then shifted ops (d99cee79b9), a866df2671 materialize float 0 from the zero register, 1e3c57c2cc/911839c1f4 fix “branch too far” for CBZ/TBZ-like instructions. 1.9: shift operands (579297e1e1), TBZ/TBNZ (d039d01fe9), AND MBCON (0bae9b083b), condition encoding (2b6c58f6d5), LDXP (990dac2723), offset support (fb0ccc5d0a). 1.10: 531e6c06c4 “refine Go assembly for ARM64” adds extended register operands (ADD Rm.<ext>[<<amount], Rn, Rd with UXTB..SXTX) and SIMD arrangement suffixes (VADDP Vm.<T>, Vn.<T>, Vd.<T>), then bug-fix commits on movk, fcmp, msr/mrs, stxr, fcsel, hlt/hvc/smc/brk/clrex, VLD1/VST1, extended register arithmetic (bdd7c01b55, 8f1e2a2610, 556fb16bbd, 6e8b10397b, aea286b449, 436f2d8d97, cafb36bf11, a0222ec518), 4282ba0a65 improves the wrapper prologue, 083338cb97 global address in LDP/STP, a92a77c56f unaligned offset handling. 1.11: load with register offset (31700b83b5, 604028568e), many new instructions for crypto/atomics (ef9bdd11e8 AES-GCM, c789ce3f75 ChaCha20Poly1305 vector instructions, fc48dcb15f atomic instructions, e1040d7955 SWP*, ebd4950e3b PRFM, 206ed90eaf REV64, bf6530a25b VSRI, f5de42001d for math optimisation), fc7a72596b LDPW/LDPSW/STPW, 518e6f0893 logical instructions targeting RSP, 236c567ba9 moves extended/shifted register encoding into the backend, 9be1921042 and be88d117bd optimise the constant pool (32-bit constants), 910c3a9dfc and 9d72e8c686 reject incorrect inputs, d7f5c0360f reorders optab entries, 9f10d28304 documents mapping to GNU syntax, 75b56a0895 and 495174302f NOFRAME/no stack check for no-frame leaves.

Timeline.

32-bit ARM, MIPS and PPC64 assemblers

Span: Go 1.7 → Go 1.11 · Status at go1.17: still the design at go1.17 → continues as PPC64 / Power10 backend (GOPPC64, prefixed instructions, PC-relative code) (1.18+) and s390x, MIPS, 386 and 32-bit ARM backends (1.18+)

Continues in: PPC64 / POWER backend and assembler (Go 1.12 → Go 1.17) · 386, ARM (32-bit), MIPS/MIPS64 and the 387 removal (Go 1.12 → Go 1.17)

Earlier history: Assemblers: 5a/6a/8a/9a and the Go cmd/asm (Go 1.0 → Go 1.6) · The ppc64 / ppc64le port (dev.power64, 9g/9a/9l) (Go 1.5 → Go 1.6)

Design. PPC64: vector registers and instructions (f1973fca71, VMX), VSX (0acefdbea0), ISEL for SSA (b6946fb120), CR1–CR7 on branches (6da8bdd2cc), ISA 2.05/2.06/2.07 (85ecc51c48, which also fixes RLDICR/RLDICL masks and removes two non-existent instructions), ISA 3.0 (526f3420c2), 7d4cca07d2 DS-form offset check (offset must be a multiple of 4), data cache instructions (3a165bba34), shifted-immediate opcodes (9a9a8c0165; old -AADD mnemonics removed), operand support for l*arx (e1f8fe8dff), constants needing one instead of two instructions (5b14c7b324), vector multiplies (4c15fdb32b), function alignment 16 (09b71d563a), leaf-function MOVDU (6910e1085b), faster atomics using LWSYNC (6633bb2aa7, ISA 2.07B Appendix B.2). Runtime assembly benefits: 4955147291 math/big arith, 1e28dce80a bytes.Compare, be943df588 IndexByte, b4b2ddb867 sqrt, 3311275ce8 floor/ceil/trunc.

32-bit ARM: 1.9 added many instruction forms and error checks (c5ddc558ba ARMv5/6/7 instructions, a041806335 MOVW $imm16 from ARMv7 avoiding constant-pool entries, 14f3ca56ed immediates instead of constant pool entries, 6897030fe3 BIC/BIC-pair decomposition, c8ab8c1f99 constant decomposition, many encoding fixes such as 3785457c76 MULBB, e00a38c89a, a38c8dfa44, d3d5489135, b8a4eb4bd8), concurrency-safe assembler (dcf643f1fd), NaCl instruction splitting (0aede73917); 1.10: 75cb22cb2f, 074547a5ce, f727fa7939 (VFP), 9bf521b2b4 BFX/BFXU, 0ed1c38017 ARMv6, 1ac8846984 BFC/BFI, 47193dcc0c suffix checks; 1.11 99e37e98b4 NOFRAME, 0a5be12f5c DMB, 1b44167d05 checkpool distance. MIPS: new MIPS32 port (above), LLV/SCV/NOOP (b53acd89db), REGTMP R23 (a409fb80b0), saving LR before SP update (b55cee1893), float zero from the zero register (4f1ca8b6f9), 423111081b load/store even float registers first, NOFRAME (e940358fc9), LR as temp in RET (1e0ef6601d), NEG (0938e4cf90).

Timeline.

Prologues, frame pointers, NOFRAME and tail calls

Span: Go 1.7 → Go 1.11 · Status at go1.17: still the design at go1.17 → continues as Prologue, frame pointer, stack split and function alignment (cmd/internal/obj) (1.18+)

Continues in: Assembler directives and tooling: PCALIGN/ALIGN, FuncInfo flags, prologue handling (Go 1.12 → Go 1.17)

Earlier history: Stack frame layout, prologues, stack split and frame pointers (Go 1.0 → Go 1.6)

Design. Frame pointers: 7fdec6216c (1.7) enables framepointer mode by default in the build and d9557523c2 makes it safe on Windows. In 1.8 c96e94e69d generates frame pointers for otherwise frameless functions; b92d39ef69 eliminates function prologues for functions that need no stack but called a method only through the stack-check prologue. LR architectures: 698bfa17a8 saves LR in leaf functions with non-empty frames (PPC64, ARM64, S390X), 430b82009c marks small-stack functions NOSPLIT on ppc64/s390x, b55cee1893 mips stores LR before SP update. 1.9: 41fd8d6401 makes morestack cutoff identical on all architectures (128 bytes always available below the stackguard can be used by medium frames), 88f423edac and a143f5d646 move the wrapper-prologue rare case to the end of the function (static prediction assumes forward branches not taken); 450472989b disallows combining nosplit and systemstack. 1.11: 99e37e98b4, 75b56a0895, e940358fc9 support NOFRAME on arm, arm64, mips; a7f73c436d eliminates NoFramePointer; 495174302f omits stack check for no-frame leaves on arm64; 9b331189c1 changes x86 tail calls to adjust SP on return like LR machines (no longer requiring a zero-sized frame and disabling the frame pointer).

Timeline.

Changes in this slice that are not part of a thread are listed on the release pages: 1.7 · 1.8 · 1.9 · 1.10 · 1.11

Backends and assemblers (Go 1.12–1.17)

Reader: Claude Sonnet. Cites 883 of 988 commits in this slice; every non-noise commit is cited, plus 4 larger commits the noise filter had set aside, which a separate gap reader described. Read the design-heavy messages and subject lines. Most “Left open” entries say “nothing noted” because peephole commits rarely mention follow-ups.

Release digest for this slice
  • Go 1.12 — Dominated by incremental lowering work (amd64/386/ARM/arm64/ppc64/s390x peepholes), arm64 constants without the literal pool, arm64 frame pointers and the first symabis plumbing for the future register ABI.
  • Go 1.13 — Intrinsics and ports: Add64/Sub64 carry chains, wasm intrinsics and options, ABI wrappers for linkname’d symbols, index-carrying bounds failures, s390x sequentially consistent atomics.
  • Go 1.14 — riscv64 assembler and compiler arrive; multi-control blocks and s390x compare-and-branch; FMA intrinsics; unsafe-point marking for async preemption; compiler-generated debug_lines; dev.link object format begins.
  • Go 1.15 — rulegen reshaped (ellipses, commutative loops, typed aux); separate addressing-modes pass for amd64/386/s390x; stack maps only at calls; spectre mitigation; alignment directives; new object format re-enabled.
  • Go 1.16 — typed aux finished everywhere; late call expansion (AuxCall); 387 removed, softfloat added; arm64 SHA/atomics/SIMD; riscv64 g moved to X27 with PIE; content-addressable symbols; register maps deleted.
  • Go 1.17 — Register-based ABI on amd64 (X15, R14, wrappers, expand_calls, spills) delivered with dev.regabi package split; depth-first block layout; arm64 conditional-select rules; memmove inlining; large clean-up of ppc64 assembler optab.

Register-based ABI (ABIInternal) and ABI wrappers

Span: Go 1.12 → Go 1.17 · Status at go1.17: amd64 passes arguments and results in registers by default, with ABI0↔︎ABIInternal wrappers generated by the compiler; R14 = g, X15 = zero, R12/R13 = entry scratch; other architectures still stack-based → continues as Register-based calling convention (ABIInternal) ports beyond amd64 (1.18+)

Paths not taken: Register arguments prototype (2016) (superseded) · A dedicated g register on amd64 (2014–15) (superseded)

Design docs & discussion

Design. The work starts from a naming problem rather than a register file: assembly functions are ABI0 (all arguments on the stack), Go functions are to become ABIInternal, and the toolchain must know which symbol is which. In 1.12 Austin Clements adds the plumbing: cmd/asm -symabis runs the assembler in a fast, lax “collect symbol definition/reference ABIs” mode (ba2e8a629b), the compiler accepts and parses the resulting symabis file (97e4010fd4), and later the compiler stops conflating data and function LSyms so that a symbol looked up before it is known to be a function is not silently ABI0 (a2e79571a9). In 1.13 the compiler generates ABI wrappers for //go:linkname’d symbols (b402bd4499), replacing a hack in cmd/go that scanned related packages when building the runtime’s symabis, and the -allabis mode is removed (74d92db8d7). In 1.16 the assembler learns explicit <ABI0>/<ABIInternal> selectors on definitions and references, enabled only for runtime-related packages (dd58239dd2), and cmd/dist/cmd/go define an asm macro GOEXPERIMENT_REGABI for the runtime, reflect, syscall and runtime/internal/* (4d1cecdee8).

The real implementation happens on dev.regabi and ships in 1.17. Wrappers are created as real IR (not ABI aliases): initially “no-op” wrappers that call the target using the existing ABI, on the assumption that late call expansion can later expand the call for both ABI0 and ABIInternal (cb28c96be8). GOEXPERIMENT=regabi is then split into regabiwrappers, regabig, regabireflect, regabidefer, regabiargs (eaa1ddee84), replacing the -abiwrap flag, so that wrappers can be benchmarked without the new register convention (bf5fa2d198). Register assignment lives in abiutils (refactored out of gc into package abi, c41b999ad4; updated for the revised ABI, 5a76c3d548; ABI info plumbed into ssagen/ssa and expandCalls, 7a2f3273c5, d0d21b7c4c). AMD64 parameter registers and OpArgIntReg/OpArgFloatReg ops were plumbed into ssa.Config (74cac8d479), with morestack spill support copied from the original experiment CL (c1370e918f).

Special registers: X15 is reserved as constant zero in ABIInternal and used to zero memory, re-zeroed on ABI0→ABIInternal transitions (401d7e5a24; fixed use for frame zeroing 1678829d95, zerorange made regabi-friendly 3300390ec7, c26f954a54, b2389ad3ce); R14 is the g register (5d7dc53888) with the assembler told about it (397a46a10a) and the obj/x86 stack-bound check reading it (2e60c00f56); R12/R13 become REGENTRYTMP0/1 for WRAPPER prologues because BX/DI are now argument registers (87c6fa4f47, 2ebe77a2fd). The assembler requires NOSPLIT for ABIInternal asm functions (25b25a9ed7) and omits args_stackmap for them (4480c822ba). Functions with cgo_unsafe_args are pinned to ABI0 (6996bae5d1). Wrapper vs. ABIWrapper flags are untangled (5cc5576a9c).

Timeline.

Late call expansion, AuxCall and the expand_calls pass

Span: Go 1.16 → Go 1.17 · Status at go1.17: default design: calls carry *ssa.AuxCall, and expand_calls lowers arguments/results (including registers) after generic optimization → continues as Register-based calling convention (ABIInternal) ports beyond amd64 (1.18+)

Design docs & discussion

Design. Before 1.16, SSA call values carried an *obj.LSym (static) or nothing (closure/interface) in Aux, and argument/result marshalling was built during SSA generation as explicit stack stores/loads. To make the register ABI possible and to allow interprocedural SSA optimization, David Chase moves the lowering of call ABI details later. First a dedicated Aux type: *ssa.AuxCall substitutes 1-for-1 for the LSym in static calls (b4ef49e527), is extended to closure and interface calls (3c85e995ef) and populated for OpClosureCall (39da81da5e). New ops for the experiment — Dereference, StaticLECall, SelectN, SelectNAddr — arrive in 35e413c537; 44c0586931 adds the code to expand calls just before “late opt”. 15f01d6ae9 delays expansion of OpArg itself until expand_calls, merges two overlapping functions in expand_calls.go into one called twice, drops the per-function GOSSAHASH dependence and keeps a go116lateCallExpansion kill switch.

In 1.17 the pass is extended in step with regabi: StaticCall returns a “Results” value (e25040d162), return values are expanded late (92cb157cf3), ABIInfo is plumbed through (d0d21b7c4c), expandCalls preserves types of pointer stores (ca3aefc4a9), and a recursive-invalidate Value method is added for it (a9e475a15a). A kill switch (c78c79f152) was activated after a regression in 1.16 (the “revert switch”); a separate fix addresses interface-typed OpIData loads (92c732e901), a “leaf type” bug for singleton register-sized structs (f8d80977b7), and Arg scheduling (cb42e3e979). OpArg values that arrive in registers are scheduled first.

Timeline.

dev.regabi package decomposition (ssagen, liveness, objw, types, abi)

Span: Go 1.17 → Go 1.17 · Status at go1.17: done; gc no longer holds SSA generation or liveness → continues in the 1.18+ compiler layout

Design. In the 1.17 cycle, the [dev.regabi] branch used mechanically generated (rf, [git-generate]) refactors to break package gc apart, so the backend could be handled without import cycles (“gc ↔︎ ssa cycle hacks” removed, 048debb224). Known symbols/packages/names move to package ir (65c4c6dfb2), type size computation moves to package types (dac0de3748), object writing to objw (0ced54062e), liveness to its own package (071ab0a14c, with ir.Node use eliminated in b75f51c645, 9eeed291bc), SSA generation to ssagen (6c34d2f420), and dependencies flattened (3383b5c74a). Stack objects move into liveness (422f38fb6c); Addrconst/Patch set up to move into obj (2153a99914). LocalSlot.N becomes *ir.Name (dcec658f6c). Fixes for the big rewrite are in d1d1099c91.

Timeline.

Rewrite-rule generator (rulegen): ellipses, commutativity loops, typed aux

Span: Go 1.13 → Go 1.17 · Status at go1.17: all rule files use typed aux/auxint (=>); commutative ops use nested loops; no chunking → continues in the 1.18+ SSA rule infrastructure

Earlier history: Rewrite-rule machinery and compile-time performance of the SSA compiler (Go 1.7 → Go 1.11)

Design. cmd/compile/internal/ssa/gen/*.rules compile into huge Go switch functions. By 1.14–1.15 the generated files (about 190 KiB less in 870080752d) were hurting editor and build time. The series by Josh Bleecher Snyder shrank them: more precise prediction of “magic” variables b, config, fe, typ removed ~3000 pointless _ = lines (53948127d3); early tweaks to for-loop shape (0fbf681840, 04b8a9fea5), package-level shadowing fixes (9f56845f2c), block rewrite code size reduced with Block.Reset (c7d81bc086, 5e4da0adac). Commutative ops: previously one rule per argument permutation; bd6d78ef37 instead generates nested loops that try both orderings, preceded by the fix that generates commutative variants when a condition mentions the arguments (73269b8e75) and a63ed10d44 skipping (Op x x). Bounds checks in generated code are cut by preloading v.Args[0]/[1] and swapping in the loop post statement (a3f234c706, a2bff7c296); chunking is removed (6dd11bcb35); AddArgN helpers shrink the compiler binary by about 192 KB (7913f7dfcf), and d98e0720bc regenerates. Also “ellipsis” rules (Op ...) => (Op2 ...) for ops that map one-to-one are adopted architecture by architecture: 386 (7c0f540b00), ARM (5749c0eb5b), ARM64 (2bfa8c37c3), MIPS (1bda9b001f), wasm (088aad5ff6), dec64 (5abab1da2c), PPC64 (3abec31097), S390X (3a9381c6b0), MIPS64 (49db4ae40f), RISCV64 (2ed96d0958), AMD64 (a37bbcecca). RHS common subexpressions are CSE’d (67a8660b5a). 390c096ee9 makes clobber variadic; 5437b5a24b rejects rules that declare reserved names; 4ebb6f5110 automates resultInArg0 register checks; bd8b3fe5be shrinks no-op rewrite funcs.

Typed aux (Keith Randall): Aux and AuxInt were interface{} and int64; every rule cast. 28157b3292 makes the generator declare the real types per opcode, using => for strongly typed rules and -> for legacy rules so that migration can proceed rule file by rule file. It introduces a Sym type instead of interface{} for symbolic offsets (ea7126fe14) and ARM64BitField aux (2cf3ebaf3d), ValAndOff accessors return int32 (b70a2bc9c6), CCop aux for arm64 conditional ops (ffbd8524ac, ae658cb19a). Migration commits: amd64 (2f84caebe3, dc9879e8fd, f0c7e3e946, 0f82e58392, fa63d24333, 66fbb80b72, b6632f770f, 83e8bf2e7d, 0f55d37d44, 9a7a981ab7, 39a426d356), 386 (bbaae9c43d, 17fbc818ff, a95bf77e1a), wasm (82f29898ea), riscv64 (7004be998b), mips64 (67d40873ad, 4e4d5df0b0), mips (81df5e69fc, e47a17aeee), s390x (b1cae8cd1d, b7a5e7ae92, 925d6b31b0, f639a81cd5, e1a96b82df, 1d9801223e, 03eb7e20e4), arm64 (ac211c037d, c53d1236df, 70d9b72a87, bd01a1b756, c8b42f9aa5, 666c9aedd4, b3c0fe1d14, 328214e85c, 85902b6786, ea33523877), ARM in eight parts (c9551f9c19, 7f7184686b, be01f54c24, bfe72ec56a, d2bd93a010, 150bd4ffd4, 1f41f04d2c, aacbd7c3aa, 58fa8075f5, f5c3eda4c9, 3ca3ca51d7, 9ec0a46a15, 0eb52ac250), dec/dec64/decArgs (917aa72c14, 7f0c479fff, f1a2a0e0bf) and splitload (bf5b83a835). 1e820a3432 ensures ... rules have compatible aux types. Cleanups: 5c1122b528, bc0d7fd9b7, de2d1c3fe2, 1af388f1c0.

Timeline.

amd64 lowering: read-modify-write, load-op, addressing modes pass, intrinsic-friendly rules

Span: Go 1.12 → Go 1.17 · Status at go1.17: still the design at go1.17 → continues as amd64 code generation and GOAMD64 microarchitecture levels (1.18+)

Earlier history: amd64 and 386 instruction selection (Go 1.7 → Go 1.11)

Design. The amd64 rules fuse loads and stores into x86’s memory-operand forms. In 1.12 Ben Shi and others add DIVSSload/DIVSDload (705f3c74e6), more read-modify-write ops (b75c5c5992, 3bc34385fa: AND/SUB/OR/XOR with memory destination and constant sources), BTSLconstmodify-style bit ops (c6bf9a8109), ADDQconstmodify to DEC (5f5ea3fd4d), load/store combining on 386/amd64 (95dda75bde), “prefer rematerializeable arg0 for HMUL” (24e51bbe64), splitting slow 3-operand LEAs: objdump counts LEA.*(.*(.* 1012 → 20 in go (b40db51438), and folding address calculations into CMPload[const] ops (b1f656b1ce, with 623c772814 fixing the CMPconstload rule and 9f291d1fc3 a load/compare combining rule).

By 1.15 the explosion of rule orderings needed to find complicated addressing modes (x+y, x+2*y) is replaced with a separate pass: ordinary load/store rules only carry a constant offset and symbol; 98cb76799c adds a pass that combines loads with LEAQx ops, shrinking the diffstat by ~5000 lines (3145 insertions, 8176 deletions). The same pass was later applied to 386 (reverted and retried because it created transient out-of-bounds pointers: af7eafd150, d84cbec890, 3adbdb6d99, 5b897ec017), and s390x (17ae587383). Indexed load+op (882ec701d2), indexed memory modification (9ed0fb42e3), indexed CMP loads (bba88467f8, e4e192484b), faultOnNilArg0 forbidden for indexed forms (287d67e3dd). The pass was extended with floating-point load+op (c4fed25553).

Other amd64 work: LoweredHasCPUFeature intrinsic (fff7509d47, MOVBQZX version 7ee8467b27), preferring AND to SHR+SHL pairs when masking (4e2b84ffc5, f41451e7eb), unsigned compares to 0/1 (18053b7131), shift pairs merged into stores (f53f987ebf), ADDQ instead of LEAQ (1cfe8e91b6), constant store merging (9c60094986, f510cddcd1, ba0e651130), MOVO of readonly sym with immediates (f5b5d77ee2), multi-register shifts SHLD/SHRD (43d5f213e2), arch-specific runtime.memmove inlining on amd64/arm64 (3b321a9d12), adding more registers for write-barrier calls (8114242359, with wrapper variants of runtime.gcWriteBarrier per second-argument register), readonly-global loads into constants (653a4bd8d4), x & 1 != 0 (e7c1873691), and a correction for long RMW bit ops (359f44910f) after which the BT{S,R,C}modify ops are removed as slower than the fallbacks (b211fe0058). Spectre mitigation -spectre=index uses CMOV to prevent speculation past bounds checks (877ef86bec), and -spectre=ret adds return-trampoline mode (fc8a6336d1).

Timeline.

arm64 backend and assembler

Span: Go 1.12 → Go 1.17 · Status at go1.17: mature backend with constant synthesis by MOVZ/MOVK and ADD pairs, frame pointer on by default, ARMv8.1 atomics; → continues as arm64 code generation and assembler (1.18+)

Paths not taken: A new arm64 assembler encoding path (still open)

Earlier history: ARM and ARM64 instruction selection and fused operations (Go 1.8 → Go 1.11) · ARM64 assembler (Go 1.7 → Go 1.11)

Design. Two parallel streams: assembler improvements in cmd/internal/obj/arm64 (asm7.go, literal pools) and compiler rewrite rules. The assembler stopped using the constant pool for most values: large 64-bit constants are encoded as MOVZ/MOVN plus MOVK sequences (644ddaa842), 32/64-bit constants are reclassified by opcode width (86ce1cb060), float constants that chipfloat7() can encode go to FMOVS/FMOVD immediates (c430adf136), ADD $0x123456 is split into two shifted-immediate ADDs (808186203b), and stack addresses with big offsets are done with two ADD/one SUB rather than a pool load (a86b6f23f0). Pool size accounting is fixed in 1.17 (d25476ebb2, 9c7207891c). Frame pointers are supported on arm64 (8f4fd3f34e): FP saved on the word below RSP; runtime assembly, wrappers and scheduler adjusted; stack-overflow check in the linker takes the extra word into account; nosplit tests changed for 16-byte frame alignment; enabled by default on Linux but disabled with GOEXPERIMENT=noframepointer; later “framepointers are no longer an experiment - hard code them” (5c2c6d3fbf, 1.16). The epilogue is made async-signal safe (47232f0d92), LR is saved after SP decrement on darwin (13fb147e60), small stack functions are marked NOSPLIT (d8394bfc7f) and huge-frame prologue simplified (4702dd67a7).

On the compiler side, arm64 grows MADD/MSUB fusion (b444215116, d60cf39f8e), shifted-operand forms (5aeecc4530), TST/CMN comparison forms (0e9f1de0b7), indexed FP loads/stores (3ca3e89bb6, 84feb4bbb7), float<->int moves for Float64bits (a19a83c8ef), Copysign (2e5c32518c), RoundToEven and Abs (8149db4f64), non-constant ROR (204cc14bdd), FCMP with zero (27cce773d3) and a change of the flag conditions for floating-point comparisons (6efd51c6b7) later followed by a wrong-complement fix for CSEL (d9b79e53bb), REV16 for ReverseBytes16 (192b675f17, b182ba7fab), CLZW for Len32 (dd91269b7c), Add64/Sub64 (5714c91b53, f8f265b9cf, d0cbf9bf53) and Div32 (159b2de442), EON (01aad9ea93), unsigned comparison to 0/1 (06337823ef), conditional-instruction rules CSETM/CSINC/CSINV/CSNEG (f5e6d3e879, bug fix 8d77e45064), single-precision square root (2b50ab2aee), float slice access (3ee32439b5), MOV nop folding (d2911d7612) and rewrites to constant 0 (3a0061822e). Overflow-correctness: comparisons-with-zero rewriting to CMN/CMP is wrong on overflow, so new LTnoov/GEnoov/LEnoov/GTnoov block kinds are introduced that ignore V (e8f5a33191, arm twin e031318ca6). ARMv8.1 atomics: LDADD*/LDAND*/LDEOR*/LDOR*/SWP* (448089854a, 26d62b4ca9, 1018a80fe8), CAS/CASP (15131caeaa), used by intrinsics guarded by a CPU-feature branch (ecc3f5112e, cdb19b4dba). New SIMD/crypto assembler support: VLD/VST 2-4 (8403d4ea90), VLD1-4R (2bf7a92571), SHA512 (93eeb819ca), SHA3 (3036b76df0), system registers (9c67516ed6, 9ce5cad0aa), PCALIGN (2f8798ed07), FLDPQ/FSTPQ (79beddc773), VDUP (9136d958ab), VUMAX/VUMIN (726d704c32).

Timeline.

riscv64 port: assembler and compiler

Span: Go 1.14 → Go 1.17 · Status at go1.17: working linux/riscv64 with PIE and cgo, atomics, Duff’s devices, branch pseudo-instructions → continues as riscv64 compiler and assembler (RVA profiles, vector, compressed) (1.18+)

Design docs & discussion

Design. Based on the external riscv-go port (Updates #27532). The assembler in cmd/internal/obj/riscv was imported in layers: arch definition in cmd/internal/sys (a3ceb57fb4), instruction and register definitions generated from the riscv-opcodes parse_opcodes tables (7ef890db91), consolidated LinkArch handling in cmd/asm (112a72a020), a framework supporting raw WORDs (6c6ad3086e), then computational (c3c53661ba, e29d276d55), environment call/breakpoint and counters (ced2454220), load/store/multiply (dfbc9c83a9), floating point (434e4caf1b), AUIPC/LUI (430d2aa349), control transfer (a37f2b4f60), MOV pseudo-instructions (a04aa1f6d1, 24902c5be6), immediate splitting (8cf5293caa, 1424889921, d8e052667f), call/jmp/branch handling (7cab55dfd3), prologue/epilogue (6718cb617f), archreloc for the linker (1046a9cb1b), shortened call sequence (a50c3ffbd4). The compiler backend landed in one commit with 8680 insertions (98d2717499). 1.15 reworks instruction generation to emit multiple instructions from one Prog rather than rewriting Progs (3e00061f52), stores relocation details in Prog.RestArgs (2b920cba8f), and adds LR/SC (85a8526a7e), AMO (10635921e5), FCLASS (32dbccde78), branch pseudo-instructions (a3d8c210ad), NOT/NEG/NEGW (dc3255391a, 25da2ea72c). Compiler rules: multi-control (compare-and-branch) blocks (40f2dab0e1), branch optimization (57e32c4fbd), absorbing SNEZ (c9ece81cc8), immediates (97585092f5), zero stores (cc6a8bd0d7), atomic intrinsics for Load8/Load32/Load64/Store*/Add/Cas/Exchange (2e918c3aab, ade988623e, efb0ac4ce6), And/Or (497feff168), and ReturnBlock fix (ed91661745). In 1.16 the g register moves from TP/X4 to X27 so that cgo and TLS can coexist (6f45b39e4d), Duff’s devices are used (150d2448e5), PIE and extra build modes are enabled (4f2d213476, 8eb846fd37), sign/zero-extension elimination removes “almost another 8,000 instructions from the go binary” (320cc79002, 8a013233ac, 12a2e72065) by changing Eq16/Neq16 to extend each operand before subtracting, and small zeroing/moves are optimized (0be8280d8d). In 1.17: single const MOV operand (6517844129), >32-bit constants loaded from memory (c2d625168f), sign-correct constants (8fd0f83552), JMP name<>(SB) (6fe8981620), constant load targets (0398a771d2), regabi unaffected.

Timeline.

PPC64 / POWER backend and assembler

Span: Go 1.12 → Go 1.17 · Status at go1.17: power8 baseline with power9 instructions under GOPPC64=power9 → continues as PPC64 / Power10 backend (GOPPC64, prefixed instructions, PC-relative code) (1.18+)

Earlier history: Multi-architecture SSA ports: PPC64, MIPS64, MIPS32, s390x (Go 1.7 → Go 1.11) · 32-bit ARM, MIPS and PPC64 assemblers (Go 1.7 → Go 1.11)

Design. Lynn Boger, Carlos Eduardo Seo, Paul E. Murphy and Cherry Zhang. Compiler: zero/sign-extension elimination and truncate fixes (8dbd9afbb0, d585f04fd3), indexed loads/stores (85c9c85955), shifts using bounded info (d45f24c084), record-form ANDCC/ORCC/XORCC to avoid CMP (4ae49b5921), intrinsics (Mul 9aed4cc395; TrailingZeros16/OnesCount8/16 23578f9d00; RotateLeft f2a18b1456; Add64 50ad09418e; CNTTZW/CNTTZD for POWER9 3023d7da49), VSX loads/stores in LoweredMove/Zero (816ff44479) then LXV/STXV and “Short” variants that don’t loop or clobber address registers on power9 (815509ae31), ISEL for variable shifts (a1550d3ca3), mod instructions on power9 (56933fb838), MADDLD (e7c7ce646f), SUBFIC (7615b20d06), combined rotate-mask shift rules (967465da29, c3c6fbf314), extswsli (a424f6e45e), MULLI (bdab5df40f), trunc fixes (cc2a5cf4b8), DS-form offset handling (ccf9acefa8, 6fbdfe4804, earlier 4-aligned folding). Assembler (obj/ppc64): VPERMXOR, DQ-form (lxv/stxv, 6b67f7d65d), PCALIGN 16/32 (bdba55653f, 37470c0664, c79c5e1aa4, 60a964ea45), float 0 via VSX (f776d51bf7), AND-based remainder (a3ef804891), vmrgow/vmrgew (8f79ca1d1a), lxvx/stxvx (64350f1eab), rlwnm and rldcr encodings (5ed81a3d14, d4aa72002e), 5-operand RLWNM/RLWMI (48ddf70128) and extra optab cleanup in 1.17 (2d30c94874, 1bad3831a0, 7bfe32f39c, 196b104bc1, 747f426944, a1a45afd4a, fca51ba24a, ddd8d7c0a6, 1eca6aa747, 06ac303f6a, 9bfa6f75d5, 4e16422da0). Indirect calls always go through LR so CTR is free for async preemption’s call injection (24e549a717), a hint on bclrl avoids a branch-predictor regression (cb674b5c13), R12 marked clobbered for write-barrier calls (b58d297416). Unsafe points marked (ceca99bdeb), MOVDU SP delta (3c47eada3f).

Timeline.

s390x backend: compare-and-branch, intrinsics, assembler

Span: Go 1.12 → Go 1.17 · Status at go1.17: still stack ABI → continues as s390x, MIPS, 386 and 32-bit ARM backends (1.18+)

Earlier history: Multi-architecture SSA ports: PPC64, MIPS64, MIPS32, s390x (Go 1.7 → Go 1.11)

Design. Michael Munday’s chain: multiple SSA block control values (9c2e7e8bed) so a block can carry two register controls instead of one flags value; numeric condition-code masks stored as a Block field instead of one extended mnemonic per condition (cf03238020); then compare-and-branch rules (6ec4c71eef, object sizes -1.1% geomean, which also deleted loop_test.go in favour of a codegen test) and immediates optimization switching signedness for == and != so values fit an 8-bit immediate (e464d7d797, a58be734ea fixes shift-count type). Floating-point ops FADD/FSUB can yield flags (b2790a2838); float to uint conversions (2962c96c9f); store-multiple clobbers flags (a7a0f03050); int-float conversions clobber flags (377c1536f5). Intrinsics: OnesCount (6f9b94ab66, using popcnt then summing per-byte counts with pseudo ops), RotateLeft (f94de9c9fb), Add64/Sub64 via the condition code (2c1b5130aa, clobberFlags removed from loads/stores), Mul64 via MLGR (98aa97806b, ac2ceba01a), And8/Or8 via ‘rotate then xor selected bits’ with load-and-and/or (b3885dbc93, 646531c52a). Atomic loads/stores made sequentially consistent by serializing after stores (ac8dbe7747). Rotate-then-insert patterns (854e892ce1), addressing modes pass (17ae587383). Assembler: VMSLG variants (3f3142ad99), RNSBG/ROSBG/RXSBG/RISBG (0f79510dc5), IPM (6966b67510), SPM (1da575a7bc), branch-on-count (38c4a73706), masked branch and conditional load (8c99e45ef9), 12-bit load/store forms (90e0b40ab2), 2-byte NOP (11c2411c50), MVCIN (553a8626ba), ADDE memory (a45ea55da7), SYNC encoding (189931296f), inline marker fixes (d0d6593d1d, 268dd2e5a7).

Timeline.

386, ARM (32-bit), MIPS/MIPS64 and the 387 removal

Span: Go 1.12 → Go 1.17 · Status at go1.17: maintenance; 387 dropped, GO386=softfloat for no-SSE → continues as s390x, MIPS, 386 and 32-bit ARM backends (1.18+)

Earlier history: Multi-architecture SSA ports: ARM, 386, ARM64 (dev.ssa) (Go 1.7 → Go 1.8) · 32-bit ARM, MIPS and PPC64 assemblers (Go 1.7 → Go 1.11)

Design. 386: load-op ops MULLload/DIVSSload/DIVSDload (90f2fa0037), OPC $imm,(mem) (a0a7e9fc0c), comparisons with memory (556971316f, 031a35ec84), indexed forms (3933302550, 27965c1436), load/store combination (4b78fe57a8), 387 simplifications (bd483592a2, e03220a594), TrailingZeros16 (22355d6cd2, 8d5197d818), conversion to the addressing-modes pass (above). Cleanup of generator code (9a033bf9d3, 7b8b3f30ed, 30c1ed7d0b, dc492682b6). In 1.16, 387 support is removed (“all: drop 387 support”, fe2cfb74ba) and GO386=softfloat is introduced (04b8a9fea5), which also resolves a pie+float32 register clobber (ea106cc07a); the backend’s “scratch mem” support, last used by 387, is deleted (2a18e37c4e).

ARM: comparisons (2b69ad0b3c, 285747b900, 067dfce21f), ARMv7 constants (0a382e0b7f), BFC (9f2411894b), ReverseBytes16 (fee84cc905), TrailingZeros (156c830bea), FMA via FMULAD (58b031949b), Abs (c683ab8128), RotateLeft32 (3cfd003a8a), NaCl DATABUNDLE removed (f6c624a22a), compare-with-zero overflow fix (e031318ca6), TST/TEQ do not set V (4826abb6c2), ARMv5 special case in regalloc removed (c4190fc34d). MIPS/MIPS64: SGTconst-with-shift fix (6a64efc250), MADD/MSUB (a189467cda), DUFFCOPY on MIPS64 (307544f427), Mul64 (50f1157760), runtime/internal/math intrinsics (ab7ecea0c8), CALLIND relocation fix (5dde10c304), restartable sequences marked (ef3571bf07), unsafe points (69dcdbd2ba, a96cfa75c6), FCR encoding (ba0e651130), MSA LD/ST (68fea523fd), shift fixes (f2eea4c1dc, 399b5d14d4), readonly loads (afe517590c), rotr/drotr (e31e84010e), atomic And/Or (e5ad73508e).

Timeline.

WebAssembly backend

Span: Go 1.12 → Go 1.16 · Status at go1.17: stack-based simulated registers with SP cached in locals, optional sign-extension/saturating ops via GOWASM → continues as WebAssembly backend (1.18+)

Earlier history: WebAssembly backend (Go 1.11 → Go 1.11)

Design docs & discussion

Design. Richard Musiol’s wasm port: the compiler emits Wasm with Go “registers” as Wasm globals, the stack in linear memory, and PC_F/PC_B globals to dispatch between functions. Improvements: locals count (6c631ae227), PC increments at sigpanic (52cae2763e), simpler zero extension (72d24a7484), math and math/bits intrinsics (5ee1b84959), optimized blocks in the binary (2396101e05), removal of the PC_F/PC_B globals (496b8dbbfc, by turning them into parameters and communicating through the Go stack), caching SP in a local per function (203e188837, to avoid V8’s slow global access; updated after each call), GOWASM=signext (4d23cbc671) and GOWASM=satconv (cf8cc7f63c, 42% faster, with helper fallbacks), Select (731e6fc34e), eliminated redundant extension/wrapping (1786ecd502), float32 registers (1c50fcf853), rotate and bounded shifts (b003afe4fe, b38be35e4c, 8fedb2d338, b9ef4c0f56), if-block optimization (9c384cc570), readonly globals (7be97af2ff), fast path for ULEB128 (03bb3e9ad1), deferred nil function panic (c4817f5d4f), refactored variable handling (547021d723), unsigned compare (8acbe4c0b3).

Timeline.

math/bits, atomic and runtime intrinsics (cross-architecture)

Span: Go 1.12 → Go 1.17 · Status at go1.17: intrinsics for bits/atomic/FMA exist on most ports → continues as Atomics and memory-ordering code generation and Multi-word arithmetic, intrinsics and carry-chain support (1.18+)

Earlier history: Intrinsics for atomics, math/bits, math and runtime helpers (Go 1.7 → Go 1.11)

Design docs & discussion

Design. Intrinsics are SSA ops generated from ssagen’s table. Carry chains use per-arch Add64/Sub64 lowerings that keep the carry in flags: amd64 (899f3a2892, dd789550a7: “individual ops are a bit slower but using the ops in carry chains is very fast”), arm64, ppc64x, s390x. A generic FMA op with arch rules (33425ab8db; amd64 7a6da218b1, arm 58b031949b), plus a rule not to use it on plan9 (03fb1f607b). runtime/internal/math.MulUintptr is an intrinsic on 386/amd64 using MUL and the overflow flag (a1ca4893ff: MulUintptr/large 10.7ns to 1.1ns) and on MIPS64 (ab7ecea0c8). Atomics: Load8 (4a4e05b0b1), Store8 (97592b3c14), And/Or on several ports in 1.16 (44dbeaf356, cdb19b4dba, e223c6cf07, 646531c52a, e5ad73508e, riscv64 in 1.17 497feff168). Division invariants: unsigned divisibility via modular inverse (a28a942768, from Granlund–Montgomery and Hacker’s Delight) and signed (6b1d5471b9).

Timeline.

SSA infrastructure passes: layout, regalloc, scheduling, flagalloc, block shape

Span: Go 1.12 → Go 1.17 · Status at go1.17: depth-first layout with exit-block placement, regalloc using phi/desired info → continues as Register allocator, scheduler and block-level backend passes (1.18+)

Earlier history: Register allocation, flag allocation and stack slots (Go 1.7 → Go 1.11) · Scheduling, tighten, block layout and CFG passes (Go 1.7 → Go 1.11)

Design. Block control: from 1.14 a block may have up to two control values (9c2e7e8bed); Block.Reset/AddControl streamline rewrite code (5e4da0adac). Flags: 80b6812d7b moves flagalloc op splitting to rewrite rules; dead values are removed after flagalloc (6303c34d7f); the go115flagallocdeadcode switch removed (91b7619310). Comparison argument order is canonicalized so CSE can merge them (44fe355694), the generic Greater/Geq ops removed (cb74dcc172, bfd569fcb0) to simplify rules and prepare for integer-in-range checks. Layout: exit blocks are laid out at the end (42d4df9459; blocks post-dominated by exits also treated as cold, “empirically yields better register allocation”), ssa/layout replaced in 1.17 by a depth-first topological sort that removes the primary-predecessor mechanism, adapts loop rotation and changes regalloc’s primary predecessor selection (600259b099). Register allocation: dead-slot clobbering only at calls under clobberdead (c8545722a1, 360771e422, extended 6ae3b70ef2, 9fbcba6664), reuse a dead register before one holding a constant (72dc9ab191), phi register choice considering all predecessors (1c0d1f4e97), desired registers for live values (b2a8317b31), valState slice reuse (4ae31dc8c5), copy deallocation fix (382fe3e249), register reuse on edge state becomes an ICE (48403b268b), “desired only if it satisfies register mask” (5b328c4a2f), 8a317ebc0f, mask fixes. Scheduling: OpArg earlier (9a7e70e34f). The scheduler tie-breaks on Value.Uses before ID (943a0d02d1), more non-ID comparisons (80ced39396). Others: pre-existing ssa.html gets CFGs via dot (3068fcfa0d), GOSSAHASH package-sensitive (4220d67084), shortcircuit for blocks with other phis (ade0811dc8), phiopt types (d889f0cb10), proved bounds remove signed-division fix-ups (13d5cd7847), prove ignoring div/mod on non-x86 (2c859eae1d), NaN constants forbidden in SSA (2aa7c6c548, cd9fd640db), nil check ordering by position (ff9481ec0d), 2c423f063b index info on bounds failure.

Timeline.

Safepoints, unsafe points and stack maps for asynchronous preemption

Span: Go 1.14 → Go 1.16 · Status at go1.17: stack maps only at call sites; register maps removed; unsafe-point info in PCDATA → continues as Stack slot merging, liveness and stack maps (1.18+)

Earlier history: Liveness, stack maps, safepoints and write barriers (Go 1.7 → Go 1.11)

Design docs & discussion

Design. To support signal-based preemption, assemblers and the compiler must say which PCs are not safe points. In 1.14 the compiler marks unsafe points (4a7ed1fab7), architecture-specific marking in obj for arm64 (4736088463), mips (69dcdbd2ba, a96cfa75c6), ppc64 (ceca99bdeb), s390x (4751db93ef), x86 two-instruction TLS access (498eaee461); the encoding moves from stack-map index -2 to register-map index because -2 could not mark CALLs (c3f149250e); split-stack prologues marked nonpreemptible (b2482e4817); a missing initial PCDATA bug (initial value is -1, not -2) is fixed (61fa79885b). In 1.15 9d812cfa5c limits stack maps to call sites and replaces register maps with unsafe-point info, behind go115ReduceLiveness: exe size -5.72%, pclntab -21.08%. Calls in nosplit functions are separated from unsafe-point bits (faafdf5115), 6bf2eea62a fixes StackMapDontCare for unused reg maps, 601bc41da2 drops stack maps for write barrier calls, 2bad2f7eba marks PanicBounds/Extend as calls, ee7d9f1c37 makes LivenessMap sparse, f84bbd52b0 marks block control unsafe. In 1.16 84d7a85089 deletes register maps entirely, DUFFZERO/DUFFCOPY become async unsafe because they stash the frame pointer below SP (01f99b4e95), and deferreturn liveness is fixed (32a84c99e1). Related: c8545722a1 clobberdead experiment limited to calls.

Timeline.

Debug info generated by the backend: statements, location lists, line tables

Span: Go 1.12 → Go 1.17 · Status at go1.17: is_stmt marks with minimal-harm heuristics; debug_lines generated by walking Progs in the compiler → continues in 1.18+

Earlier history: DWARF variable locations, statement positions and inline info from the backend (Go 1.9 → Go 1.11)

Design. Statement marks (David Chase): move marks from jumps to targets (82af9e6749), classify poor statement choices (53bd915109), keep marks in rematerialized values (c8e7c53b40), not on Phi (33ab6ccba0), preserve in late nilcheckelim (6adaf17eaa), OpSB/OpSP are poor (975ba6e2b2, 0ecf769633), 7502ed3b90 prefers faulting insn line number. Location lists: OpArg and OpPhi start at block start (2b58ca6e3d), prev block not a pred (b3294d9491), zero-length lists filtered (321a3d6e85), params that are not SSA-able (72496e35ce), PAUTOHEAP (68a98d5279). In 1.14 line table generation moves from the linker to the compiler: ee3dded36d generate debug_lines in compiler, new symbol (376fc48338), linker switched (8506b7d42f), then the isStmt symbol removed (7defbffcda) and Progs walked directly (9e6a84f2b1, reland 46b7557836), file names with ‘/’ (ae0b735369). 1.15: a88c26eb28 and 63fd764502 add prologue_end. 1.17: debug locations for register args (8bf5bf5173, ef8ae82b37, afa58ddf5a, 66ce8aa88d, caf4c9434b, 00d42ffc89). Plus GOSSAFUNC dumps on crash (b345a306a0), -S improvements (01a1eaa10c, 1dcf34f2ec), numberlines (6081a9f7e6, d199369aab, fba6066975).

Timeline.

New object file format and content-addressable symbols (dev.link, assembler side)

Span: Go 1.14 → Go 1.16 · Status at go1.17: done; the linker consumes indexed objects (goobj2) → continues in the linker threads (1.18+)

Earlier history: cmd/internal/obj: Prog/LSym refactoring, concurrent backend and object-writing (Go 1.7 → Go 1.11)

Design docs & discussion

Design. The assembler and cmd/internal/obj write object files in a new format that references symbols by (package index, symbol index) instead of names. 2c484c0356 adds the writer, with cmd/internal/goobj2 created; indices are assigned in compiler/asm and propagated in export data (53b7c18284); d56c8149ac uses index for static symbols; 97e497b253 references by name when linking against shared libraries. The old format is the default in 1.14 (9cf6c65ca3), the new reenabled by default in 1.15 (8cdb769b08), old support deleted from compiler/asm (181faef82c) and entirely (c89251204e). Fingerprints guard index consistency (e08f10b8b5, hash of export data 7795523910). Content-addressable symbols identified by hashes: pseudo-package PkgIdxHashed (27342e5bd9), short hash (4f217d5aaa), applied to GC maps (17344d55d6), constants (3c54069907), read-only static temps (289c238a33), type names (66770f4b1d), with relocations (526d99a49a), trailing-zero trim (b3e3c339ff), length (b249703e3c), Hashed64 size (2ad44158af). Others: DWARF sub-symbols as aux (8634642f9a, ad92148058, 673a02a235, 62b0790a79), LSym.Func moved into Extra (912262b806), FuncID computed in compiler (89cf569a45), per-package filenames (9bdaf99966), -p package path written at compile time (cc5eaf9346, saving ~100MB allocations in the linker), AttrSeenGlobl removed (c2e73fb446), deterministic file order (991adcd21b), large functions aligned (d92a5a80b5).

Timeline.

Assembler directives and tooling: PCALIGN/ALIGN, FuncInfo flags, prologue handling

Span: Go 1.12 → Go 1.17 · Status at go1.17: still the design at go1.17 → continues as Prologue, frame pointer, stack split and function alignment (cmd/internal/obj) (1.18+)

Earlier history: Prologues, frame pointers, NOFRAME and tail calls (Go 1.7 → Go 1.11)

Design. Assembly-visible features: PCALIGN on ppc64 (bdba55653f) and arm64 (2f8798ed07), generic instruction/function alignment for gVisor with ALIGN2048 and per-FuncInfo alignment data (71d477469c, 1dbcbcfca4), support in the linker (d92a5a80b5). FuncInfo flag bits give the runtime exact TOPFRAME and SPWRITE bits derived from the assembly instead of the runtime’s own list (4dd77bdc91). Spectre -spectre=ret (fc8a6336d1). ADJSP pcsp fix (9d032026d6). Huge-frame prologue simplification across x86, arm, arm64, mips, ppc64, riscv, s390x (ef3122e909, 24dd8cfe23, 4702dd67a7, 2b63404ddb, a06b08e7d1, 042f4cbb6f, 191167c2b2), x86 TLS offset (68c664141c), cmd/asm rejects code that uses a clobbered R15 under dynamic linking (c870e86329), refined buildcfg move (95ed5c3800), cmd/asm object header (cef3a442ea), Prog.SetFrom3{Reg,Const} (a61524d103), obj IsAsm flag (f5fcc9b8e0), branch target recording simplification (8247da3662, 9e70564f63), -S consistency (75fab04b83).

Timeline.

Changes in this slice that are not part of a thread are listed on the release pages: 1.12 · 1.13 · 1.14 · 1.15 · 1.16 · 1.17

Compiler backend: architectures, register allocation, ABI and assemblers (Go 1.18–1.28)

Reader: Claude Sonnet. Cites 1,385 of 1,512 commits (all non-noise ones). Based on commit messages only. The reader called it a thorough first draft: wording in per-arch threads is terse in places.

Register-based calling convention (ABIInternal) ports beyond amd64

Span: Go 1.18 → Go 1.26 · Status at HEAD: register ABI enabled on amd64, arm64, ppc64x, riscv64, loong64, s390x; 386, arm and mips* still stack-based

Earlier history: Per-architecture compilers 6g/8g/5g/9g and the shared gc code generator (Go 1.0 → Go 1.6) · Register-based ABI (ABIInternal) and ABI wrappers (Go 1.12 → Go 1.17) · Late call expansion, AuxCall and the expand_calls pass (Go 1.16 → Go 1.17)

Design docs & discussion

Design. The register ABI (ABIInternal) assigns integer/FP arguments and results to fixed register sequences described per architecture in cmd/compile/internal/ssa/_gen/*Ops.go plus cmd/compile/internal/abi (ABIConfig, abiutils). Enabling it on an architecture needs a fixed recipe, visible in every port: (1) define parameter registers and make sure no other fixed-purpose register (closure context register, duff-device temporaries, REGTMP) overlaps them; (2) update the CALL* ops (CALLstatic/CALLclosure/CALLinter) so arguments are register inputs; (3) add “initial spill support” — arg/result load/spill code (ssagen‘s LoadRegResult/spillArgReg), because each arg has a dedicated spill slot in the caller’s frame; (4) make morestack prologue spill/unspill the argument registers (cmd/internal/obj/<arch> sets MorestackArgs / “regabi prologue”); (5) add the arch to buildcfg and the tests’ hasRegisterABI. On amd64 the ABI relies on a dedicated G register and a zeroed X15; on other architectures the G register already existed.

Several ABI-adjacent mechanisms were built along the way: argument-stack-slot liveness (so tracebacks mark possibly-stale register-passed args with ?), clobberdead debugging mode per arch, wrappers using tail calls (OTAILCALL now wraps a real OCALL so args can be placed by SSA), and maymorestack hooks.

Timeline.

Register allocator, scheduler and block-level backend passes

Span: Go 1.18 → Go 1.28 · Status at HEAD: ongoing tuning each cycle

Paths not taken: PGO beyond inlining: block layout, edge profiles, function ordering (dormant)

Earlier history: Register allocation and the peephole optimizer (reg.c, peep.c) (Go 1.0 → Go 1.6) · SSA backend bring-up on amd64 (dev.ssa merge) (Go 1.7 → Go 1.7) · Register allocation, flag allocation and stack slots (Go 1.7 → Go 1.11) · Scheduling, tighten, block layout and CFG passes (Go 1.7 → Go 1.11) · DWARF variable locations, statement positions and inline info from the backend (Go 1.9 → Go 1.11) · SSA infrastructure passes: layout, regalloc, scheduling, flagalloc, block shape (Go 1.12 → Go 1.17)

Design. regalloc.go is a linear-scan-style allocator over blocks in a visit order: each block starts from a chosen predecessor’s endRegs, values get registers using per-value “desired” registers (computeDesired), and a final shuffle pass reconciles each edge. Over the period, work concentrated on (a) giving ops richer register constraints, (b) cheaper/better liveness, (c) smarter spill/rematerialization, and (d) a forward scheduler with arch-specific score tweaks. Scheduling (schedule.go) was converted from backward to forward scheduling so values can be placed as soon as they are ready; scores (ScoreReadFlags, ScoreCarryChainTail, ScoreMemory, …) control flag and carry-chain adjacency.

Timeline.

amd64 code generation and GOAMD64 microarchitecture levels

Span: Go 1.18 → Go 1.28 · Status at HEAD: GOAMD64=v1 default; v3 unlocks BMI1/BMI2/LZCNT/MOVBE/FMA codegen; constant churn of peephole rules

Earlier history: Per-architecture compilers 6g/8g/5g/9g and the shared gc code generator (Go 1.0 → Go 1.6) · Expression and statement code quality in 6g/8g/5g (division, bounds checks, loops, slices, interfaces) (Go 1.0 → Go 1.6) · amd64 and 386 instruction selection (Go 1.7 → Go 1.11) · x86 assembler: AVX, AVX2, AVX-512 and the instruction table (Go 1.7 → Go 1.11) · amd64 lowering: read-modify-write, load-op, addressing modes pass, intrinsic-friendly rules (Go 1.12 → Go 1.17)

Design docs & discussion

Design. GOAMD64=v1..v4 is checked at compile time through buildcfg.GOAMD64 (ssa.Config), and v1 builds are guarded by a test (TestGoAMD64v1) that disassembles to ensure no >v1 instruction slips in (cbd7200167, 5d946f1892, bb1749ba3b, more robust v1 detection 515040548c). Where an instruction is only legal at v3, rules gate on it. The code generator is rules in AMD64.rules/AMD64latelower.rules plus the amd64/ssa.go emitter; the late-lower pass (below) was introduced for AMD64 LEA/shift patterns.

Timeline.

arm64 code generation and assembler

Span: Go 1.18 → Go 1.28 · Status at HEAD: active; assembler gaining a table-driven SVE/ASIMD path; SVE under GOEXPERIMENT=simd

Paths not taken: A new arm64 assembler encoding path (still open)

Earlier history: Assemblers: 5a/6a/8a/9a and the Go cmd/asm (Go 1.0 → Go 1.6) · The arm64 (ARMv8) port (7g/7a/7l) (Go 1.5 → Go 1.6) · Multi-architecture SSA ports: ARM, 386, ARM64 (dev.ssa) (Go 1.7 → Go 1.8) · ARM and ARM64 instruction selection and fused operations (Go 1.8 → Go 1.11) · ARM64 assembler (Go 1.7 → Go 1.11) · arm64 backend and assembler (Go 1.12 → Go 1.17)

Design docs & discussion

Design. Compiler side: ARM64.rules/ARM64latelower.rules with bitfield (UBFX/SBFIZ/BFI), CMP/CMN/TST, CSEL/CSET, conditional-compare, rotate and shifted-operand forms. Assembler side (cmd/internal/obj/arm64): optab classes with a literal pool, REGTMP (R27) use for synthesized sequences, span7 passes, prologue/epilogue emission in obj7.go. In 1.27 a second, data-driven assembling path was added: generated instruction tables (anames_gen.go, encoding_gen.go, goops_gen.go, inst_gen.go) entered through tryEncode, designed for SVE and newer ASIMD forms.

Timeline.

riscv64 compiler and assembler (RVA profiles, vector, compressed)

Span: Go 1.18 → Go 1.28 · Status at HEAD: GORISCV64 rva20u64/rva22u64/rva23u64 profiles drive Zba/Zbb/Zbs/Zicond codegen; assembler supports V, crypto vector suites and compressed (RVC) emission

Earlier history: riscv64 port: assembler and compiler (Go 1.14 → Go 1.17)

Design docs & discussion

Design. The compiler lowers to RV64G with sequences for shifts (go semantics: shift counts ≥64 give 0 or sign), then peephole-folds immediates and sign extensions in RISCV64.rules. The assembler (obj/riscv) originally rewrote obj.Prog into pseudo-expansions; 1.18 reworked it to avoid Prog rewriting and generate machine code directly from instructionsForMOV etc. A generated instruction table (inst.go, regenerated from the spec; make driven in 1.27 d484fb9ddf) holds encodings. GORISCV64 selects profiles (rva20u64 / rva22u64 / rva23u64): at rva22u64+ the compiler may emit Zba/Zbb/Zbs instructions without runtime checks, and at rva20u64 it emits a Zbb runtime check only where profitable (OnesCount).

Timeline.

loong64 backend and assembler

Span: Go 1.18 → Go 1.28 · Status at HEAD: feature-complete port with LSX/LASX assembler coverage; very active

Design docs & discussion

Design. loong64 was added as an architecture in 1.18 (c7f09eeb13), then given SSA rules/ops (ec5bdefd0a, 756fcd8fc2, 2136e15510, a2d12076b9), assembler registers/instructions (c1105cfd43) and tests (335569b598, 0653cb4cf7) in 1.19. Subsequent work: regabi (above), CPUCFG-feature-dependent atomics (LAMCAS/LAM_BH on LA664: AMCAS[DB], AMSWAP[DB]{B,H}) with LL/SC fallback on LA464, and wide use of LSX/LASX instructions in the assembler.

Timeline.

PPC64 / Power10 backend (GOPPC64, prefixed instructions, PC-relative code)

Span: Go 1.18 → Go 1.28 · Status at HEAD: power8/9/10 levels; PC-relative (NOTOC) code on power10/linux

Earlier history: The ppc64 / ppc64le port (dev.power64, 9g/9a/9l) (Go 1.5 → Go 1.6) · Multi-architecture SSA ports: PPC64, MIPS64, MIPS32, s390x (Go 1.7 → Go 1.11) · 32-bit ARM, MIPS and PPC64 assemblers (Go 1.7 → Go 1.11) · PPC64 / POWER backend and assembler (Go 1.12 → Go 1.17)

Design docs & discussion

Design. GOPPC64=power8|power9|power10 gates instruction selection. Power10 adds prefixed loads/stores/addi with 34-bit displacements and PC-relative addressing; the assembler selects them by dynamically configuring optab (72301a9863), emitting new always-PC-relative relocations. Because such code needs no TOC pointer, the compiler/assembler/linker stop maintaining R2 on linux/ppc64 with power10, shrinking PIC binaries ~2% (k8s binaries −1.8…−2.5%) (29d5272da8, linker PCrel ccb8db88c5). The assembler grew carry/XER modelling so bits.Add64/Sub64 lower into SSA with CA tracking.

Timeline.

Duff’s device replaced by generated loops; Zero/Move lowering

Span: Go 1.20 → Go 1.28 · Status at HEAD: done for amd64, arm64, loong64, riscv64; runtime duff code removed for arm64, loong64, riscv64

Earlier history: Zeroing and copying: Duff’s device, REP, unaligned moves and AVX2 runtime assembly (Go 1.0 → Go 1.6) · Memory clearing and copying: Zero, Move and Duff’s device (Go 1.7 → Go 1.11)

Design. LoweredZero/LoweredMove (and …Loop) ops emit inline instruction sequences and a counted loop instead of jumping into the runtime’s hand-unrolled duffzero/duffcopy. Unrolled Duff code needed special registers, nil-check handling and could not be traced well. Generated loops pick unroll size by length, avoid DUFF* registers constraint, simplify zerorange (in ggen), and let the register allocator choose registers. Ops like LoweredMemmoveLoop use wider vector ops (NEON/LSX) where available.

Timeline.

Bounds-check calls: new “panicBounds” strategy

Span: Go 1.26 → Go 1.26 · Status at HEAD: all architectures converted; old style removed

Design. Instead of loading index/length into fixed argument registers and calling panicIndex variants, the failing bounds-check emits a single call instruction whose operands are the registers that already hold index and length, plus a pcdata table so the runtime can recover them (static analysis in the cmd/go sample: 6877 of ~7650 checks are just the call itself; only 139 need a reg-reg move, 602 a constant load). Reduces cmd/go text ~0.8%, total binary ~0.2% after the new pcdata table.

Timeline.

Late-lower pass, memory-op combining and arch-independent rule migration

Span: Go 1.20 → Go 1.28 · Status at HEAD: late lower on amd64, arm64, ppc64, riscv64; memcombine arch-independent

Earlier history: Rewrite-rule machinery and compile-time performance of the SSA compiler (Go 1.7 → Go 1.11) · Memory-op combining, byte-order idioms and load/store folding (Go 1.7 → Go 1.11)

Design. The “lower” pass has no ordering among rules; lateLower is a second round of lowering that runs rules which must fire last (e.g., split constant folding that would materialize a constant repeatedly, or turn 3-operand LEA into simpler forms) ddc7d2a80c, with a “late lower block” function c0f27eb3d5. Memory op combining was removed from arch rules into one arch-independent pass memcombine (cedf5008a8: removed ~900 lines from ARM64.rules, ~490 from AMD64.rules; handles out-of-order writes), later extended to loads that feed stores e713d6f939 and store order/line number fixes 657c885fb9. Rotate detection moved to generic rules 60ad3c48f5; canRotate per arch.

Timeline.

Stack slot merging, liveness and stack maps

Span: Go 1.23 → Go 1.28 · Status at HEAD: merge-locals on by default; analysis tightened in 1.28

Earlier history: Stack frames, pointer maps and liveness analysis in the compiler (Go 1.2 → Go 1.6) · Liveness, stack maps, safepoints and write barriers (Go 1.7 → Go 1.11) · Safepoints, unsafe points and stack maps for asynchronous preemption (Go 1.14 → Go 1.16)

Design docs & discussion

Design. cmd/compile/internal/liveness gained a “live intervals” utility (7979c8f588) and a mergelocals pass that overlaps stack slots of disjointly live locals (e01b1eb289: now allows B smaller than A, non-pointer merged into pointer slot; candidates sorted by pointerness, alignment, size, name; GC shape no longer compared since two vars sharing a slot are never both live across a call; total saved bytes on sweet rose from 91,256 to 124,336). Addr-taken candidates are handled when only the arch memory op’s SymAddr touches the var 82e929e4f8. SIMD vars excluded 1440ff7036. Related liveness/stackmap pieces: argument-slot liveness (see ABI thread), ArgInfo/ArgsPointerMaps implicit setup for asm functions 208f361800, args_stackmap for ABI0 assembly functions regardless of linkname 0b72631a82, GOSSAFUNC liveness output 7472b4c324, sorted liveness reports 4ff074945a.

Timeline.

Prologue, frame pointer, stack split and function alignment (cmd/internal/obj)

Span: Go 1.18 → Go 1.28 · Status at HEAD: per-arch; amd64 uses PUSH/POP BP and LEAVE

Earlier history: Stack frame layout, prologues, stack split and frame pointers (Go 1.0 → Go 1.6) · liblink: a shared assembler/linker library (Go 1.3 → Go 1.5) · Normalising Prog/Addr in cmd/internal/obj (the portable instruction representation) (Go 1.5 → Go 1.6) · Position-independent code, dynamic linking and TLS in the assemblers (Go 1.3 → Go 1.6) · Removal of the legacy backend and decoupling gc from the code generators (Go 1.7 → Go 1.9) · cmd/internal/obj: Prog/LSym refactoring, concurrent backend and object-writing (Go 1.7 → Go 1.11) · Prologues, frame pointers, NOFRAME and tail calls (Go 1.7 → Go 1.11) · Assembler directives and tooling: PCALIGN/ALIGN, FuncInfo flags, prologue handling (Go 1.12 → Go 1.17)

Design. Each obj/<arch>/obj*.go preprocess emits stack-split checks, frame setup and return sequences. Changes are about shrinking the sequence, avoiding writes below SP before the frame exists (signal safety), and making branch layout favour the common path: arm64 STP/LDP and single-instruction LR+SP save for small frames; amd64 PUSH BP/POP BP or LEAVE; mips/s390x/riscv/loong64 save LR after decrementing SP (3562977b6f, 3ed8a1e629); loong64 inverts stacksplit branch (5923a97f43,9350068b23). PCALIGN was extended to amd64/386 and PCALIGNMAX added. Frameless detection became explicit (NOFRAME) rather than heuristic on amd64.

Timeline.

Jump tables for switch statements on more architectures

Span: Go 1.19 → Go 1.28 · Status at HEAD: amd64, arm64, loong64, riscv64, mips64x, mipsx

Design. Following the amd64 jump-table CL (357330) each port adds a JUMPTABLE block kind and obj support for table entries. In 1.28 table entry generation was unified in obj 403b3349ca; 32-bit mips uses four-byte entries and cannot use tables for 64-bit-switch benchmarks (Switch8/Switch32 on uint64 are declined by walk.tryJumpTable on 32-bit).

Timeline.

Multi-word arithmetic, intrinsics and carry-chain support

Span: Go 1.18 → Go 1.28 · Status at HEAD: Add64/Sub64/Mul64 intrinsics on all 64-bit ports; 32-bit support for Mul64 added in 1.26

Earlier history: Intrinsics for atomics, math/bits, math and runtime helpers (Go 1.7 → Go 1.11) · math/bits, atomic and runtime intrinsics (cross-architecture) (Go 1.12 → Go 1.17)

Design. math/bits intrinsics lower to flag/carry-aware SSA ops. Architectures with a carry bit in a flag/special register (PPC64 XER.CA, arm64 flags) need the scheduler to keep carry chains contiguous: a carry-using op group is scored below other ops and carry-generating ops later so only one chain is in flight (c386269ed8), with ScoreCarryChainTail ordered before ScoreMemory but after ScoreReadFlags on arm64 (669ec549b5).

Timeline.

Atomics and memory-ordering code generation

Span: Go 1.18 → Go 1.27 · Status at HEAD: intrinsics for sync/atomic And/Or across several ports

Earlier history: Intrinsics for atomics, math/bits, math and runtime helpers (Go 1.7 → Go 1.11) · math/bits, atomic and runtime intrinsics (cross-architecture) (Go 1.12 → Go 1.17)

Design docs & discussion

Design. Atomic ops are intrinsics (ssagen/ssa.go) mapped to arch-specific LoweredAtomic* ops. Details center on matching the Go memory model’s sequential consistency (riscv64 amo…aqrl/lr.aq-sc.rl 890b96f7ab), correct width extension (LL sign-extends: 0b2ad1d815), and hardware extensions detected by CPU flags (loong64 above).

Timeline.

WebAssembly backend

Span: Go 1.20 → Go 1.28 · Status at HEAD: active small-scale improvements; vector ops landing with simd

Earlier history: WebAssembly backend (Go 1.11 → Go 1.11) · WebAssembly backend (Go 1.12 → Go 1.16)

Design docs & discussion

Design. Wasm “PC” values encode (function index << 16 | block). Backend work covers bulk memory (1.20, 50557edf10), signext and saturating conversions always on (742f92063e), unaligned load/store (53ad68de4b), wasmimport/wasmexport (03e5d83ca7, 5a9aeef9d5, 0b632d26b9, 4ffa2aecc1).

Timeline.

s390x, MIPS, 386 and 32-bit ARM backends

Span: Go 1.18 → Go 1.28 · Status at HEAD: maintenance; MIPS gets jump tables, s390x gets regabi

Earlier history: The mips64 port and the mips/mips64 register space (Go 1.6 → Go 1.6) · Multi-architecture SSA ports: ARM, 386, ARM64 (dev.ssa) (Go 1.7 → Go 1.8) · Multi-architecture SSA ports: PPC64, MIPS64, MIPS32, s390x (Go 1.7 → Go 1.11) · 32-bit ARM, MIPS and PPC64 assemblers (Go 1.7 → Go 1.11) · s390x backend: compare-and-branch, intrinsics, assembler (Go 1.12 → Go 1.17) · 386, ARM (32-bit), MIPS/MIPS64 and the 387 removal (Go 1.12 → Go 1.17)

Design docs & discussion

Design. These ports receive correctness fixes and cheap peepholes rather than redesign. s390x gained a register ABI in 1.26 (above) and crypto/vector assembler instructions. MIPS: hardware NOP as NOOP (68a32ced0f), Float32/64 bits via MFC1/MTC1 (f0d575c266,d9fd19a7f5), math.Abs (574431cfcd,5cad8d41ca), WSBH/DSBH/DSHD and SEB/SEH (83c4e533bc,24f83ed4e2); the HI/LO result registers were dropped from mips regalloc in 1.28 af4b02c461,6db72bb92b.

Timeline.

Backend hooks for GOEXPERIMENT=simd (amd64 AVX-512, arm64 SVE, wasm)

Span: Go 1.26 → Go 1.28 · Status at HEAD: part of the simd area; backend plumbing only here

Design. The backend work needed beneath simd/archsimd: new regalloc register shapes (fp1m1fp1, fp3fp1, fp3m1fp1, fp1gp1fp1), mask (k) registers and upper (X16–31) registers allocatable 8db7f41674, e7ec26cf8f, VZEROUPPER/VZEROALL, X15 for zero in AVX context, SIMD-aware spills/copies/rematerialization, SIMD types non-fat and excluded from mergelocals, peephole load/store folding, mask ↔︎ bits conversion, ClearAVXUpperBits, and a regMask widened beyond 64 registers (1901161d96, 966c78ed74). Cross-area; see the SIMD report.

Timeline.

SSA package split into subpackages (issue #80409)

Span: Go 1.28 → Go 1.28 · Status at HEAD: in progress

Design docs & discussion

Design. cmd/compile/internal/ssa is being broken up so that the generated per-arch rewrite code and op tables can be compiled separately from core IR types: Register moved to ssabase, BlockKind to a block subpackage 088195b6fb, Op and op tables to an op/ssaop package ef0bd1f8df, the core (Func, Value, Block) to ssacore 9b32c1225c, rewrite packages to ssa/rewrite/<arch> (386 → i386) 558faa4c47; supporting renames/exports 0d74cff9d1,8b1f275ac7; RegisterSet widened 966c78ed74; zero-upper-bits as a declared op attribute e8285ccd6e. Done with rf/[git-generate] mechanical refactors.

Timeline.

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

SIMD: the simd packages and their compiler support (Go 1.18–1.28)

Reader: Claude Sonnet. Cites 445 of 538 commits (all non-noise ones). The SVE rules and the midway translator are described from commit messages and file headers.

Compiler SIMD type system, ABI and register-allocation support

Span: Go 1.26 → Go 1.28 · Status at HEAD: SIMD types, vec/mask register classes, ABI passing, X15 zeroing, CPU-feature-aware block analysis and VEX encodings exist on master behind GOEXPERIMENT=simd (X15 whole-register zeroing is unconditional); RegMask widened beyond 64 bits for SVE predicates.

Design. SIMD vectors are ordinary Go structs in the library (type Float32x4 struct { float32x4 v128; vals [4]float32 }, in src/simd/archsimd/types_amd64.go), but the first field is a tag type (v128/v256/v512, each containing an uncomparable [0]func() so vectors are not ==-comparable, 5a78e1a4a1). The compiler’s types package recognises the tag (typeIsSIMDTag) and marks every type containing it typeIsSIMD; simdify assigns size, alignment and register counts. SSA has dedicated types TypeVec128/256/512 and TypeMask (width 8; the comment notes spilling a mask “will be interesting”), so a value is either a vector-register class value or a mask-register class value. IsSIMD() is then consulted by abi/abiutils.go (SIMD params and results are register-assigned like floats, one register per vector), ssagen/abi.go, liveness/plive.go, amd64/ssa.go (simdReg, simdOrMaskReg, simdRegBySize choose X/Y/Z register names by value size) and the inliner.

Machine ops are described to regalloc by generated “register shapes” in ssa/_gen/AMD64Ops.go / simdAMD64ops.go: the shape names (v11, v21, v2k, kv, v2kv, v31, vgpv, …) say how many vector (v), mask (k), gp and fp inputs/outputs an instruction has. Many were added one by one as new instruction classes appeared (fp1m1fp1 for load-op forms 1161228bf1, fp3fp1 6bc3505773, fp3m1fp1 884f646966, fp1gp1fp1 1b87d52549, with a naming cleanup e32488003d/5429328b0c, and removal of fused-op shapes 1313521f75). Masked “merging” ops are resultInArg0 so the destination register doubles as the merge source (86b4fe31d9).

Because AVX2/AVX-512 are not guaranteed on amd64, the compiler runs a cpufeatures pass over SSA blocks (d2270bccbd, ba72ee0f30): it tracks which CPU features are in scope in each block (reached only through a feature check, signature mentions an AVX type, or the block contains an instruction needing one) and records the maximum feature per function. Later consumers: SIMD-typed zero values are placed so as not to leak features (d61b240b11 puts SIMD zeros in the right block since they may need guarding by a feature check; fbea197d32 stops no-code values such as SIMD zeros from implying features, because the map fast path under GOEXPERIMENT=simd was claiming AVX in every block), critical-edge split blocks inherit the features of their edge (0b2fd4aa9c), and spills/moves/copies/Zero/Move use VEX encodings when AVX is proven present to avoid AVX-SSE transition penalties (5763a306d2, c2f76b656d; issue case: variable shifts ~65x slower due to a legacy MOVQ). An attempt to seed entry features from GOAMD64=v3/v4 (bc769ea44b) was reverted because it broke simd builders (4a1a9dfeb7).

X15 is the ABIInternal zero register. Originally only its low 128 bits were zeroed on function entry (f38e968aba: zeroing the upper part makes the CPU consider it “dirty” and slow SSE code; 256/512-bit zeros are built on the fly); later the whole X/Y/Z15 is zeroed on entry, unconditionally, with a note that earlier attempts (CL 698237) were suspected of regressions but the new benchmarks show none (3f1287d96a). Inlining budget gets a multiplier for functions with SIMD parameters/closures because abstractions over vectors otherwise lose to the cost model (simdCreditMultiplier in inline/inl.go; bumped again for rotate emulation in d7b3da6e25). For arm64 SVE, the register set bitmask RegisterSet (one bit per register in a uint64) silently dropped registers >=64 such as P1-P15, so it was widened to ssaop.RegMask (966c78ed74); ppc64 got F31/vector registers in its regMask in preparation for SIMD there (d934b909f6).

Timeline.

simdgen: the instruction-table-driven generator

Span: Go 1.26 → Go 1.28 · Status at HEAD: lives in src/simd/archsimd/_gen (separate module) with simdgen, unify, tmplgen, wasmgen, midway, specgen, gentools; one go generate regenerates everything.

Design docs & discussion

Design. Almost all of src/simd/archsimd/ops_*.go, the compiler’s simdAMD64.rules, simdAMD64ops.go, simdgenericOps.go, ssagen/simd*intrinsics.go, amd64/simdssa.go and (for the other targets) their ARM64/Wasm analogues are generated. simdgen reads Intel XED instruction data (xeddata; fetch-xed.sh), unifies it with hand-written YAML (go_amd64.yaml, types.yaml, categories.yaml) using a constraint-style unify package (values with environments; unify.Value.Decode, bijective field-name canonicalisation a5ed3e905c), and emits Go declarations with doc comments, generic SSA ops, machine ops, rewrite rules and intrinsic registrations. The machine-op generator knows op shapes (the v11... register classes), whether an op is resultInArg0, commutative, has an immediate, has memory forms, and its required CPU feature; features carry an implication table so AVX512 implies AVX2 etc. (9ef1692c93), a feature check API is generated per CPU feature (934dbcea1a) and AVXAES is exposed as a single combined check.

The generator was originally in golang.org/x/arch/internal/simdgen, and commits had to be paired between repos (“PAIRED w/ arch/internal/simdgen CL 681615”, 6c50c8b892; 0870ed04a3 “generated by CL 686215”). It was migrated into the main repo as simd/_gen in 1.26 (b7c8698549) because coordinating x/arch commits with the generated files caused “tremendous process friction”; the migration commit is itself script-generated and builds a self-contained module. Test generation moved from simdgen to the Go repo using generics (88568519b4, *_helpers_test.go generated), and template code generation moved into _gen with go generate generating everything (cf45adf140, 3fe246ae0f); generate.go has a goexperiment tag so that the package does not exist when the experiment is off (61a5a6b016).

In 1.27 simdgen was generalised from amd64 to a per-architecture parametrisation: templates for intrinsics, SSA ops, rules and types take the target (8a1b0c8ecb), an ARM64 ISA XML parser feeds NEON (8437d1b188), YAML configs were reorganised by architecture (951257bd14), the rule-generation template and intrinsic definition were refactored (0442e04934, 593e8bf9b1), output ordering made deterministic (dfd75f82d4, e3afca43e2, b93d81957e), and a tmplgen produces typed test helpers (61a10105e6). In 1.28 common code moved into gentools (GOROOT discovery robust to a broken tree, post-processing hook, 7d634c347a..fe5d9816fd, f619436443, 98283d7ebf), the external data dependencies were redone (babec569e7), SSA generic ops are written separately (6b5417f030), and the tool works with any Go toolchain >= 1.26 (6ddf3bfac0). AGENTS.md/README guidance for tool users appeared (fd36608473, c3cbcf4169).

Timeline.

AMD64 archsimd API: AVX, AVX2 and AVX-512 operations

Span: Go 1.26 → Go 1.28 · Status at HEAD: simd/archsimd provides 128/256/512-bit vectors on amd64 with operations that map to AVX/AVX2/AVX-512 instructions; experimental (GOEXPERIMENT=simd), not under Go 1 compatibility.

Design. Vector types are Int8x16 … Float64x8; each operation is a method (func (x Int32x4) Add(y Int32x4) Int32x4), declared without a body and implemented as a compiler intrinsic; its doc comment names the assembler op and CPU feature (“Asm: VPADDD, CPU Feature: AVX”). The API deliberately does not expose every instruction: “the compiler recognizes certain patterns … and may optimize them to more performant instructions” (e.g. Add followed by Masked becomes a masked AVX-512 add). The surface was built in waves of generated families: arithmetic, comparisons, rounding (34a9cdef87), dot products and fused multiply-add (9ba7db36b5, ded6e0ac71, ca01eab9c7), shifts and rotates (e61ebfce56, 82d056ddd7), Galois-field ops (10c9621936), insert/extract (1fa4bcfcda, 7fadfa9638, 292db9b676, 43a61aef56), permutes and Compress/Expand (01f7f57025, 6d10680141, 5b0ef7fcdc), blend (7ca34599ec), Broadcast (ddb689c7bb), Get/SetLo/Hi (d3cf582f8a), interleave and VSHUFP (b509516b2e, bf00f5dfd6), select-from-pair (25c36b95d1, ea3b2ecd28), integer conversions of every width/saturation flavour (4fce49b86c, f4c41d9922, 7fdb1da6b0, 4fa23b0d29, bc217d4170) plus float<->int (86cd9b5c90, d6564ed088, f3a306527c), VPSADBW, VPLZCNT, AES, SHA, VPTERNLOG, VPALIGNR and carryless multiply (703a5fbaad, cf7c1a4cbb, d03634f807, 95871e4a00, 3417b48b17).

Design decisions visible in the history: FP comparisons involving NaN return false (immediates for Equal/Greater/GreaterEqual changed, 0870ed04a3); FP bitwise logic ops removed from the API (56ca67682b) and bitwise ops made available for all integer vectors (3f92aa1eca); Masked$OP renamed $(OP)Masked (029d7ec3e9); PairDotProdAccumulate->AddDotProd->DotProduct (c2d775d401, 416332dba2); a “best instruction” choice for compare-by-immediate that respects commutativity (608dba4308); IsNaN made unary and renamed (13440fb518; compiles to VCMPPS $3, x, x); String() methods (c75965b666); 128/256-bit FMA no longer needs AVX-512 (e242961960); DotProductQuadruple deleted for now (70c22e0ad7). Fast transposes were demonstrated as a performance example (1c961c2fb2; tiled 8x8 transpose ~4.7x faster than plain on the 88-core Xeon in that message).

Timeline.

Mask representation, masked operations and merge peepholes

Span: Go 1.26 → Go 1.28 · Status at HEAD: masks are opaque MaskNxM types in archsimd; amd64 uses K registers where AVX-512 is available and vector registers otherwise; arm64 SVE uses predicate registers; IfElse/Masked replace Merge.

Design. A mask has an opaque type (Mask8x16 struct with ToBits() uint16, Mask8x16FromBits). In the compiler a mask is TypeMask in the mask register class for AVX-512 (K registers) and an ordinary vector-sized value for AVX/AVX2 compare results. Semantics: converting a vector to a mask sets an element true iff the vector element is nonzero (a034826e26, ToMask, with asMask unexported); mask->vector yields all-ones/zero lanes. Comparisons produce masks (aab8b173a9, 47b07a87a6 fixed Int64x2 Greater to return a mask type). Rather than expose one API per AVX-512 masked instruction variant, the API exposes plain ops plus Masked / Merge (later IfElse) and the compiler pattern-matches: x.Op(y).Masked(m) -> OpMasked machine op (first sample ede64cf0d8, then many ops cf31b15635, 0f660d675f making OpMasked machine-ops only, and for 128/256-bit “under feature check” 771a1dc216, also fixing masked-load variants to be zeroing by default); x.Op(y).Merge(z, mask) -> OpMasked(z, x, y, mask) where the machine op is resultInArg0 (86b4fe31d9). BLEND instructions are marked “not-zero-mask” (8ccd6c2034). Masked loads/stores were first an API (acc1492b7d, AVX2 VMASKMOV; K-mask slice loads c25e5c86b2), but the API was removed (2e71cf1a2a: “mask load and stores could just be hidden behind peepholes”) and replaced by the LoadTPart / StorePart slice helpers that use them internally (2eaa5a0703, 761894d4a5, b70242b3b9, ec5b41b314).

To support AVX2-only machines, emulations for mask/merge methods were added (94d72355f6) and later “amd64 supports IfElse, not Merge”, with the old methods deprecated using go-fix-inline annotations (e115b2f987); Merge was then removed for 1.27 (6f010acf75). Mask <-> bits conversions (6f7a1164e7, f39711a03d) exist because mask load/store of the register were dropped. Mask bitwise logic on amd64 rewrites to K-register ops when AVX-512 is known (e.g. KANDN, see e2889b0bf5).

Timeline.

Memory operands, constant-immediate forms and jump tables

Span: Go 1.26 → Go 1.27 · Status at HEAD: generated load-op and constant-load rules for AVX/AVX-512; broadcast and non-broadcast memory operands decoded from XED.

Design. Instructions with an immediate (VPSHUFD $imm, shifts, VPEXTR, rounding) take the immediate as a “constant” parameter in the API (4993a91ae1 renamed imm to constant). When the argument is a compile-time constant it is encoded directly; when it is not, the compiler emits a jump table over all 256 values (38b76bf2a3, initial exhaustive, bounds-checked version to follow; wasm uses a binary-search switch expansion with retpoline fixes, 8742584113; b84139dfad adds ImmMax to limit table size). Memory forms: simdgen parses and merges memory operands (f42c9261d3, 0b323350a5), adds machine ops with prog writing (5a0446d449, which also fixed a XED merge bug where features were not checked), memop peepholes x.Op(load) -> op-with-memory (48f366d826), load+const-imm8 variants (3ec0b25ab7, 4eb5c6e07b), rewrite rules consistent on CPU features (443b7aeddb) and memory peepholes where the load’s feature level differs (c4fbf3b4cf). Later decoding was extended to non-broadcast memory operands (4136ffed69, fixes #78159), unsigned constants when folding (3f6eabdf09), and broadcast-to-VMOVI folding (098d688071).

Timeline.

dev.simd branch, GOEXPERIMENT=simd lifecycle and merges to master

Span: Go 1.26 → Go 1.28 · Status at HEAD: GOEXPERIMENT=simd exists in internal/goexperiment/flags.go, off by default in buildcfg; dev.simd reverse-merged to master repeatedly (development continues “only on dev.simd” and merges “when necessary”).

Design. All development happened on the dev.simd branch, periodically merged from master. The experiment flag guards the simd and simd/archsimd packages (build tag goexperiment.simd) and the compiler’s SIMD intrinsics. It was introduced as an off-by-default flag (4d2c71ebf9, 2025-05-22) then turned on by default for amd64 on the branch only so developers get it (2ef7106881); before each merge to master the default is turned off (“Preparing for merge to the main branch. Will reenable on the branch”, a9914886da), and re-enabled after (2b91d96941, “turn GOEXPERIMENT=simd back on”). The unused internal/simd directory was deleted (62e1fccfb9). Helper commits: go/doc/comment must not list experimental packages in the std list (f045ed4110), generate.go tag (61a5a6b016), test helpers guarded with the amd64 tag so GOEXPERIMENT=simd go test simd/... works on other architectures (a5fe8c07ae), testdata subprocess tests set GOEXPERIMENT explicitly because Plan 9 does not inherit it (6d1bcd1009). A trial use inside the runtime (Green Tea GC scan kernels using SIMD: ~-19% geomean ScanSpanPacked, +25% throughput) was reverted before merge because “naming is confusing” plus a semantic merge conflict (da92168ec8, 0c69e77343).

Timeline.

Portable simd package and the “midway” source translator

Span: Go 1.27 → Go 1.28 · Status at HEAD: simd (vector-length-agnostic types Int8s..Float64s, Mask8s..Mask64s) exists behind GOEXPERIMENT=simd, implemented by rewriting user code in the compiler front end; the GODEBUG simd setting selects emulation and vector length.

Design. The package simd mirrors Highway: length-agnostic vectors whose width is bound at program run time. Its operations are declared with bodies that dispatch to simd/internal/bridge (types, tofrom_*.go conversions, per-arch decls_*.go, simd_emulated.go). The compiler package cmd/compile/internal/midway (“tiny-highway v0”, f1aa77c984) is a source-to-source translator run after type checking and before unified IR; to type-check the output it resets type info and re-runs the type checker. Phase 1 analysis finds functions, types, methods and variables that depend on simd types; “boundary” functions (simd-free signature but simd-using body) are rewritten into a dispatch function (original name) that chooses among specialised copies named with an @ suffix (illegal in Go identifiers, hence collision-free; methods are specialised by renaming their receiver type). rewriteSizes gives the variants per GOARCH (wasm {0,128}, amd64 {0,128,256,512}, arm64 {0,128}); size 0 means “emulated”. Non-boundary dependent functions are left in the export data with their bodies replaced by panic. The implementation relies on a builder hack to allow rewritten code to import the internal bridge package, and 90e20f8188 adds a hard dependence of simd types on the bridge package so go tools see the import.

Runtime selection: at startup simd checks that the hardware supports the assumed operation set for a vector width and downgrades (e.g. amd64 emulation on Apple Silicon has AVX2 but no 256-bit CLMUL, so it downgrades to 128-bit; NEON without PMULL such as Raspberry Pi gets an “arm64 nclm” variant that emulates only CLMUL, 8dd7aa0168). GODEBUG=simd=0 forces emulation, simd=N forces a length, simd=+N overrides the feature check (a92cf0ee94, which also adds HasVPCLMULQDQ to internal/cpu.X86). Benchmarks show the stakes: on an Apple laptop an inner-product kernel is -91% (arm64 128-bit), -93% (amd64 256), +594% under wasm-0 emulation vs the base (c82958326a); a 3-way CLMUL benchmark shows 2.3 ns SIMD vs 16 ns “clever” vs 67 ns iteration on arm64 (e873c5e929). Doc: ToArch(), Load<Types>Part, etc.

Timeline.

ARM64 NEON archsimd

Span: Go 1.27 → Go 1.28 · Status at HEAD: 128-bit NEON types and a large op set in ops_arm64.go; some constant shift ops postponed.

Design. Reuses the generic IsSIMD compiler plumbing and simdgen, but with a different instruction source (ARM ISA XML via 8437d1b188, arrangement symbols like 4S, 9764721859). Notable lowering tricks: upper-half “2” variants are not API; narrowing/widening are expressed by SSA folding of the lower-half base op with SetHi/GetHi (e.g. SHRN+SetHi -> SHRN2, XTN folded from SHRN shift 0, 6a8b351af9); shifts by vector or by a scalar ShiftAll use broadcast + SSHL/USHL with CSEL clamp for out-of-range amounts (39b11f4b14); Reduce* ops return a scalar via an unexported intrinsic plus GetElem(0) (8d29cf23b1); LookupOrZero (TBL) only for a single-register list (1768c11712; multi-register forms left as follow-ups). Ops added: arithmetic/rounding/min-max (c2f3871e15), shuffles (d2a9f4e364), saturating/pairwise/bit-count (3a6e136c90), bitwise (c473f30297), conversions (43df7ba2f5), comparisons and Masked/IfElse (ae7bac0a4b), MulAdd (555dd4691a), PMULL (627bc968ea), GetElem/SetElem (d62d649e1a), ConcatShiftBytesRight, horizontal reductions (da9cdfcbc0, f6490657e7), Broadcast (ae1c21739d), partial slice ops (399bc412ae), VCMTST peephole (62bb6b4a80), signed GetElem lowered to VSMOV (8db4546f29, with the assembler instruction b01fe21278).

Timeline.

ARM64 SVE: scalable vectors and predicate masks

Span: Go 1.28 → Go 1.28 · Status at HEAD: minimum-viable archsimd SVE types and a growing op set (types_sve.go, ops_sve.go); scalable vector length capped at 32 bytes in model, mask loads/stores removed.

Design. SVE is an arm64 feature, not a GOARCH, but is treated as a fourth generation target. Scalable vectors are modelled as fixed 256-bit values (vector length bounded to 32 bytes at package init; mandatory panic for VL>32 later removed 75a79d1b6f), with LoadTPart/StorePart bounded by a runtime predicate (6fbc254146). The first MVP (68f391a3c2) plumbed types, ops and a test through the assembler. Predicate masks use the P registers: SVE predicate ops produce Mask types (fddf2d237d, first op Greater); Mask is a uint64 with one bit per vector byte (f662110644 removed LoadMask/Store because PSTR writes only VL/8 bytes). IfElse lowers to ZSEL, Masked is select against zero (4756f33d43); simdgen groups predicated and unpredicated encodings into one definition and folds a select over an unpredicated op into its merging form: (ZSELB (ZADDB x y) z mask) => (ZADDMergingPrefixedB z x y mask) with MOVPRFX. Predicated-only instructions (SMAX, UMAX, FMAX, ABS) run under an all-true predicate (PWHILELT, 6017e3eb32), and destructive ops use simdZ2kvPred which swaps sources or inserts unpredicated MOVPRFX (740fcaabb9). A correctness bug showed up at b6376b04a2: merging ops were marked commutative and not resultInArg0, so CSE merged x.Add(y).IfElse(m,x) and y.Add(x).IfElse(m,y) and returned the wrong lanes; fix is to mark merging ops resultInArg0 and non-commutative. Mask queries use PNEXT/PFIRST etc. (ad2488e65c: PNEXT with a false first operand since PFIRST has only a .B form; 7257a19fd5, 8a2ef7eb43, 9bdbfc8fa5, a90c4a7a58, 8dfdca7cf2 FromCount).

Timeline.

WebAssembly SIMD

Span: Go 1.27 → Go 1.27 · Status at HEAD: 128-bit vectors on wasm in types_wasm.go/ops_wasm.go, with Wasm SSA ops, simdssa.go and rules.

Design. Added as another generator target (wasmgen, 8742584113), “most of the operations, and many tests”; vectors are passed as obj.WasmV128 for //go:wasmexport/wasmimport ABI. Instructions with immediates use a binary-search switch expansion (including retpoline and correctness fixes from master). Follow-ups: some emulations (40eb69725e), Swizzle (5b07ed8bfa), MulWiden{Lo,Hi} (9c6b9b2ece, commutative d494f566fb), broadcast (b7327ecacf), carryless multiply (cea3788e05), mul-add removed for hardware lacking it (8c14de9526: “mul-add is not supported on some WASM”), out-of-bounds constant Get/SetElem (48bf92284c), sort-order fix (56355bb195).

Timeline.

Spec-driven API generation (internal/spec, specgen, specfill)

Span: Go 1.28 → Go 1.28 · Status at HEAD: in progress; simd/internal/spec is an executable Go specification of all possible SIMD operations, with specgen, specls, refgen, specfill, specstats tools; plan tracked in SPEC-TRANSITION.md.

Design docs & discussion

Design. Problem: the same operation was described in six places that drifted. spec describes each operation once as a generic Go function over Vec[E, W] (element class constraint such as Nums, width W = 128/256/512/scalable), giving signature, doc and executable semantics; specgen (with a constraint solver specexpr, 2eb6506ad7) expands it into concrete methods, refgen produces a reference implementation (simdref, 26fee28ba5) for conformance testing, and specfill injects spec docs and parameter/result names into generated and hand-written source and verifies signatures (73adfe029b, d73c92c091). It is intended to replace categories.yaml (810fe52b24). The “one meaning” rule (a name means one thing across arch, types and both packages) is the guiding invariant (README). Conformance testing found real bugs: FMA (958aac0d81), KANDN order (e2889b0bf5), integer Div (6acc64e461). spec was moved up the package DAG because the vectors are slices and allocate, so it cannot serve as a runtime-importable emulation (5b8216128d).

Timeline.

API shaping and renames before stabilisation

Span: Go 1.26 → Go 1.28 · Status at HEAD: API still experimental; renames continue.

Design. The public API was repeatedly adjusted for consistency: reordered operands (ead249a2e2/55665e1e37), Shift*AndFillUpperFrom->Shift*Concat (2c25f3e846), DotProd->DotProduct, AES/SHA op names (0978935a99, be9c50c6a0), conversion names (3fdd183aef), loads/stores “scalable first” (b70242b3b9: LoadT->LoadTArray, LoadTSlice->LoadT, Store->StoreArray), RoundToEven->Round (9bc619add4), AddSub->AddOddSubEven (35ea327859), SumAbsDiff->SumOf8AbsDiff (8583ef15c8), TruncateToX->TruncToX (3e04a409d1), all shifts/rotates take uint64 (fed139a1b3), MulEvenWiden->MulWidenEven (3521c10d5e), SaturateToConcat->ConcatSaturateTo (23761e7bb2), As conversions deprecated for factored templates (3d6c5301df). In 1.27 the stated criterion was that removing is easier than adding later.

Timeline.

Emulation of missing instructions and test infrastructure

Span: Go 1.26 → Go 1.28 · Status at HEAD: ops_emulated_*.go per arch; generated test helpers.

Design. Where an operation is not a single instruction on some CPU level, archsimd provides a pure-Go emulation built from vector ops (Not, AVX2 comparisons 2080415aa2, bitwise ops 94d72355f6, rotate-from-shifts d7b3da6e25, FP negation edbc93f594, OnesCount for bytes 385f272158, ReduceSum). Tests are generated with generics (88568519b4), the internal tests do not import testing into simd (95b4ad525f), tests deduplicated across architectures (c554c53ac8..b350da230f), and test-gate and helper packages simd/internal/test_helpers (dd22b9816b). Benchmarks: inner-product kernels (c82958326a) and transposes (1c961c2fb2).

Timeline.

Changes in this slice that are not part of a thread are listed on the release pages: 1.26 · 1.27 · 1.28