Backends and assemblers
Thread map, Go 1.0 – 1.17
| Thread | 1.0 | 1.1 | 1.2 | 1.3 | 1.4 | 1.5 | 1.6 | 1.7 | 1.8 | 1.9 | 1.10 | 1.11 | 1.12 | 1.13 | 1.14 | 1.15 | 1.16 | 1.17 | Status at go1.17 (or at the end of its era slice) |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 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
| Thread | 1.18 | 1.19 | 1.20 | 1.21 | 1.22 | 1.23 | 1.24 | 1.25 | 1.26 | 1.27 | 1.28 | Status at HEAD |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 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 —
intbecomes 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 firstGOEXPERIMENT, 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.
- Go 1.0 — Done:
- 2008: 6g emits amd64 (
0cafb9ea3d,d83b994da6,f7753f1687,36bfd2a906, method/interface code8231e94520); morestack magic number$a-bproduced by 6g and 6c (3f982aeaf6); fat-copy and overlap fixes (30fd44cf9d). - 2009: 8g and 5g created and bootstrapped (
dc5b4678e2,a8e4ed6a3d,3aa006b8cd,eac5db7f4c,63e1b714de,190b5c9ad2,4556c04d8b,86987055a3,6d0983ae46); nil-pointer checks in 8g (9dd2e1e30f); inlinex[lo:]slicing (652f55672d,9bf597a210,bf0130cc0f); Go types in the object symbol table (43f29e64a6); first attempt at bounds-check elimination for range and[256]array[byte](fdb030d86f); 8g bounds failures callthrowindex(dc30800c28). - 2010: complex numbers (
426099f42efor 6g,f229c8b546identical 6g/8g with 5g a stub,b0ad7a42685g regalloc for complex); string index inlined in all three (e48c0fb562); amd64 TLS moved to segment memory so R14/R15 return to the register pool (e473f42b2d); ARM hardware float (9c6df3ca13); O(1) string compare when lengths differ (30dd191171); dead code elided (410927d1ad). - 2011–Feb 2012: explicit alignments (
e7a0f67603), inlineappendwhen len<cap (d6b2925923), compact stack frames (2ac375b2df, ARM2ad42a8249, unified layout5ddf6255a1),f() < g()order (bac8f18035),gotorestrictions (7f4c5ea7d8), interface==/!=fast-path on differing types (f91cc3bdbb, ~3x forerr == io.EOF), correct boundary checks in all compilers (6ed2b6c47d),NOPTRBSSfor large pointer-free data (5bcad92f07). Left open: 6g still had 32-bitint; 5g and 8g trailed 6g in code quality.
- 2008: 6g emits amd64 (
- Go 1.1 — Done: user-visible
intbecomes 64-bit on amd64: the compiler first learns len/cap areint(650160e36a), then flips (10ea6519e4, assembly offsets converted mechanically). 6g may use R14/R15 (8f8640a057) and all 16 SSE registers (57ad05db15); 8g importscomponentgen(ae0862c1ec; LEAL count for cmd/go 89774 → 47548), igen cases (f4e76d5e02), no address-of-slice for&s[i](2de064b63c,acbe6c94d7for 6g); panicindex out of line (fefae6eed1); contiguous loop layout (001b75c942); 5g backports of 6g constant-operand code (7b36acc4ac,ed0c5dd11f,bbccfddb84,01ddc8bd9a,905e8dfa27,d8008a9eef,3dcc63f750,5bdf40dcca,bdd9f29780); user-visibleGO386=387|sseselects FPU flavour, default 387 (9afb34b42e); method values (d3c758d7d2); closures without runtime code generation (9f647288ef) after the indirect func-value representation (1903ad7189); explicit TYPE pseudo-instructions for locals (1d5dc4fd48). Left open: no auto-detection of SSE2 (commit: “no auto-detection is performed”). - Go 1.2 — Done: trampolines moved to portable code (
1f51d27922,31be5deae4),proginfoin every back end (see optimizer thread). Left open: nothing noted. - Go 1.3 — Done: the back ends link against liblink (
f606c1be80) and start emitting code for the new liveness maps (f056daf075); amd64p32 initialisationlinkarchinit(e31a1ce109,d4896fb876), register-width stack alignment (3d5e219e02). - Go 1.4 — Done: registerize small structs/arrays in 5g and 8g (
527ae57e65); explicit zeroing simplified so*x = T{}needs no write barrier (3c40ee0fe0); fatal “out of registers” (c1c5d479bd); 5g does not make register variables for directly called functions (bb4a358af3). - Go 1.5 — Done: back ends converted to Go and moved to
cmd/internal/gc(8c195bdf12,3af0d791be); portable cgen/regalloc/componentgen (b115c35ee3,b960263244),reg.cportable (ad88fd1d4a),cmd/gcmade a library (349ecfb0d6,eb1774fa19); 9g and 7g added (see port threads);go tool compile/go tool linkreplace 5g/5l etc. (17eba6e6b7). Left open: machine-translated C idioms (goto, pointer arithmetic) cleaned only gradually (4492811c05,acba34e45f). - Go 1.6 — Done: mips64 back end copied from ppc64 (
14f919578d,d5cd4ab46a);Etype/Opnamed types and bool fields (8e7a3ea11e,8712e1867b,259466452d,05c35ac5d1); 387 builds stop usingfucomi*(3d01f28e47). Left open: the SSA backend (dev.ssa) was the planned replacement for amd64 in 1.7.
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.
- Go 1.0 — Done: optimizer and indexing/op= improvements (
2dd16a3208,e081f25c3e,a6182dab47,9a5c7eab16,c8a66a98ff); constant propagation for assignments (567a7bf664); 8g optimizer (0eb2a79ff3,4ab0ce107a); aliasing fixes (bedfc2c2f1,cd00bc78da); ARM optimizer (90ca4d75a4,4ed17ceace), disabled for the release (1c99bdc8e5,d88f717da6), peep re-enabled (1f55105f66,2799e0ecb9,dc331e6565,63c24081a0); register liveness (84f291b1bd); “registerize variables again” (e419535f2a); loop finding (e2d326b878); flush modified globals aggressively (8998835543); constant propagation friendly to inlining (fff732ea2c); byte-move rewrite in 8g with up to -69.83% on crc32 (23f6479be6). Left open: ARM peephole disabled in releases; optimizer bugs recurring. - Go 1.1 — Done: 5c
regoptre-enabled (70db440885); 6g peephole (3d3b4906f9: shifts do not stop subprop, redundant MOVLQZX removed, early loads);fixjmpfor 6c/8c (5cb1ed2189); ONAME nodes recycled to denote registers (45fe306ac8); return registers optimizable (b73a1a8e32,bdd9f29780); 8g drops redundant temporaries before regopt (fa316ba4d8,14f3276c9d, extended to SSE2cf77dd37e7: Erf frame 220 → 84 bytes, Lgamma 948 → 44); 5g peephole reactivation incl. EOR → MVN and MOVB elimination (542dd8b9fb,3f26c5e124); short-integer arithmetic eliminated where possible (8f3c2055bd);componentgenfor 5g and for small arrays/structs on 6g (ee3e2ac1a6,16072c7497,022b361ae2: the 64-bitinthad made it ineffective); 6c rotate/shift peephole (41ec481a53,bb192d1399:(x<<a)|(x>>b)becomes a rotate). - Go 1.2 — Done:
proginfoin all three (24c8035fbe,ac0df6ce89,a07218385c,9a0a59f171); portable optimization logic and flow graph (b3b87143f2,dbf96addfb); temporary-merging pass using greedy linear scan (Poletto-Sarkar), cutting the number of functions with too many vars in godoc from 83 to 32 (fa72679f07,72636eb506); O(n) reset loop removed from copyprop (6d47de2f40); faster compilation by memset (06e686def6); MOVBS/MOVHS pseudo-instructions on ARM to make short-integer semantics explicit (3670337097,357f733695). Left open: the back ends only track the first 128 variables per function (commit text,fa72679f07). - Go 1.3 — Done: no credit for NOPs in allocation (
1848d71445); more aliasing by fixing etype mismatches (03c0f3fea9,0a8a719dedpreserve wide values in large functions); globals and results flushed/loaded correctly (f5184d3437); undefined instruction has no successors (85e4cb2f10); liveness-aware regopt fixes (26ad5d4ff0,684332f47c,1afbceb599: fat vardef-initialised variables live at calls; up to 7% slower end-to-end). - Go 1.4 — Done: register variables for small structs/arrays on 5g/8g (
527ae57e65); registerized addr nodes cleared so full registerized temporaries stop reserving stack (be2ad1d7f5); optimizer failure fatal in the C compilers (3147d2c4ee). - Go 1.5 — Done: float-constant propagation and
MOVSD $0→ XORPS (43c87aa481); 9g regopt (743bdf612a) and peephole (ed5488eece);Bitsas uint64 array (fa32e922d5); registerization limits raised (d47fe8092e) and extended to 7g/9g (e040fd4654); cached ProgInfo (fd38dbc8a1);reg.cportable (ad88fd1d4a); debug prints (5b38501a4f). Left open: generated temps in bool codegen (a44becef4a) still produced extra moves. - Go 1.6 — Done: register fix for
==(6ec1809b83), regopt must not use REGTMP on ppc64 or CX in PIC (0357c38adf,61da0e92d0,9514285da9); AVARLIVE in peep for arm/arm64/mips64/ppc64 (5f23bc8903); duffcopy clobbering X0 (ffb20631fc,0bef88aa5f).
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.
- Go 1.0 — Done: divide-by-constant (
f1baf78247,34f76b5de5), 8-bit div/mod (3f91f80a21), inline slicearray/sliceslice (9bf597a210,bf0130cc0f),x[lo:](652f55672d), scaled indexing (c8a66a98ff), 64-bit shift fix (e5bd12ea90), NaN-safe comparisons (109a976355,ef46a9ddac), avoid unneeded conversions in bounds checks (d14baee96e),a[byte(x)]truncation (ba50599e46),bounds checks offsupport (c332ff42fe),8g: float->uint64(a551ffe809,f570d9d765). - Go 1.1 — Done: out-of-line panicindex (
fefae6eed1), contiguous loops (001b75c942), rotate code (c6ce44822c, later partly rolled back for 8g251199c430), float->uint64 truncation fix (e80f6a4de1), inline slice[arr,str] (40af78c19e),convT2E(8f84328fdc), nil-comparison without extraagen(ff642e290f), no LEA of interfaces on method calls (b45b6fd1c7),6gfloat move without MOVSD between registers (a768de8347),sbopswap logic (de96df1b02), simplified integer division (a617d06252), zerostack experiment: user-visibleGOEXPERIMENT=zerostackclears the stack on function entry to avoid GC false positives (b7603cfc2c);cmd/gc: avoid duplicate allocation during inlining(a72f9f46a2); large-model flag for 6c/6g (fe14ee52cc); more nil-pointer-to-large-struct checks (578dc3a96c). Left open: zerostack was expensive (the commit says so) and opt-in. - Go 1.2 — Done: copy inlined as memmove (
ff416a3f19), nil-check on&xand removal of redundant&x.Fieldchecks (999a36f9af,aa0439ba65), 6c/8c avoid long multiplication (2c0b00744f), full 64-bit address in block copy (f268c295a3), very wide offsets (9c21ce54dd), no constant propagation of non-constant LEA (3c3ce8e7fb), clearfat de-interleaved (85a7c090c4),defercaller line numbers via a real NOP (7e97d39879; XCHG AX,AX / AND.EQ R0,R0,R0). - Go 1.3 — Done: 6g inline copies of up to 4 words, after reflect.Value grew to 4 words and json fell ~50% (to ~5%) (
0ea20bc981); unaligned load/stores for trailing bytes in sgen/clearfat (2b93c4dd06); small zeroings in straight-line code (14664050b9); bypass DATA for integer-constant data (4acb70d377); a register for nil-checking constants (52e6d7c622). - Go 1.4 — Done: explicit zeroing without write barrier, MOVs instead of repeated STOSQ (
3c40ee0fe0);extern = <N>code (0edafefc36); no address-as-constant in amd64 instructions (ce35994d4e). - Go 1.5 — Done: float-constant propagation (
43c87aa481), componentgen on string constants (94410c794e), zero via register (01ef6dbfa5,8f734d4e2f), 2- and 3-word sgen (747c849833,c1c3ce6b36), boolean values via SETcc instead of jumps (13cb62c764), hardwaremath.Sqrton amd64/arm (92dba0d278),runtime.getginlined (92c826b1b2, garbage benchmark +2.6%),MOVinstead ofLEA 0(R0)(0f0bc0f016), shifts for power-of-two indexing (135389d0ff), write-barrier-aware slice updatex = x[:i](d447279927) andtypedmemmovefrom sgen (2a73559023,2b7505e28afor RISC), panicindex marked as not returning (fc595b78d2),ginscmphook (f8d14fc3a0), generated temps in bool codegen (a44becef4a), missing write barrier inappend(x, BigStructWithPointers)(366ba526e8,9c9e36b340). - Go 1.6 — Done: test-first-field-of-struct special case for amd64 and 386 (
9aed0b7e3d,f3b064ae01), magic-multiply no longer smashes AX (03c8164849), write barrier on type switch (e3033fc535), Syscall-like functions for liveness (1ac637c766),args_stackmapnot forfunc _(292ad59291).
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.
- Go 1.2 — Done: funcdata/pcdata plumbing (
48769bf546,47b89a37d9,8c741c97f7,de6da4a8bd), C and Go argument/locals bitmaps (9b1f1833de,73c93a404c,2db5e4bb0b), interface encoding (abc516e420,21ea5103a4), zero pointers on entry (7910cd68b5,3b4d792606,d3b04f46b5,390656affd), nosplit pointer maps from the linker (037a1a9f31), . Left open: maps conservative; liveness analysis not written (commit text: “intermediate step”). - Go 1.3 — Done: liveness-based maps (
f056daf075;d4f68f2892,6965a752a7,bc9691c465dead-value maps disabled/renamed), compile-time speedups (0e97f4187e: up to 7x,145edc283f), many correctness fixes: addressed results824e918ca4, block-move init91b1f7cb15, tail-jump wrappers02ae91f342, funcs ending in panicab9e8d068a, non-returning functionsaf545660d5, fat variables7a7c0ffb47, no fallthrough edge after panic28479f2c32, zeroing to enable precise stack accountinga81692e265(only effective with a debug flag), zero only ambiguously live variables6722d45631,1ca1cbea65, VARKILL for runtime-call temporariesb700cb4974, stack map entries redone to avoid false retention1b45cc45e3, don’t zero more than needed47acf16709, relaxed zeroing in 6gf400d9aafc, input variables vs regopt26ad5d4ff0, globals/args/resultsf5184d3437, vardef-initialised fat variables live at calls1afbceb599(slowdown up to 7%), reflect.callXX mapscee8bcabfa. Left open: zeroing “pretty inefficient” (issue 7624) and too many ambiguously live variables (issue 7345). - Go 1.4 — Done: local type information is emitted explicitly (see the 1.1 note on
1d5dc4fd481.1,43c04ba1b81.1,aa3efb28f01.1);args_stackmapfor assembly functions from Go prototypes (99f7df0598), slices/strings scanned like pointers (613383c765), all G frames copyable (00365b13b4),argsizePCDATA dropped (68c1c6afa0), auxiliary symbols marked pointer-free (8b20e7bb7e), runtime package decls never heap-promoted (064acace5b), liveness ~10x faster (7aa3031eba), invalid GC pointer crashes (11e53e46a6), emit code for extern init (0edafefc36). - Go 1.5 — Done: 1-bit stack/data/bss bitmaps (
4d0f3a1c95), fixed-size frames ingoproc/deferproc(53c5226f9f), larger stack guard for unoptimized builds (a55b131393), liveness debugging (fa7efa2cb0),gc: restore stack frame debugging(aed88be021). Left open: nothing noted. - Go 1.6 — Done: Syscall-like assembly functions recognised for liveness so arguments stay live for the whole call (
1ac637c766); noargs_stackmapforfunc _(292ad59291);AVARLIVEin peephole passes (5f23bc8903).
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)
- motivating issue #10587 cmd/gc: stack split check does not follow static branch prediction rules of modern CPUs referenced by 3 of this thread's commits
- discussion get rid of stack check prolog golang-nuts · linked from #10587
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.
- Go 1.0 — Done: morestack magic operand (
3f982aeaf6), stack-split segment size bug (cb15bbe748),argsizerounding smash on 32-bit (de663b2d6b), 5l morestack for ARM (1ac2cfc720), NOSPLIT checks in cc (36c5c5bf40), frame compaction (2ac375b2df,2ad42a8249,5ddf6255a1), . - Go 1.1 — Done: 6c/8c stack frames cut by about half (
139448fe95),clearstkdeleted (96b0594833), fix for large stacks (d127ed5378). - Go 1.2 — Done: pointer-map-aware zeroing of frames and
stkptrsize(d3b04f46b5,390656affd). - Go 1.3 — Done: register-width stack alignment for amd64p32 (
3d5e219e02), zero at start of frame more efficiently (383963b506), NaCl zeroing (bfbb2e827b). - Go 1.4 — Done: ARM wrapper prologue (
fb818eab7f). - Go 1.5 — Done: frame pointer experiment (
3c0fee10db,08af4c3a79,90c0fefe51), predictable prologues (f4b48de3ad,5353cde080,54789eff38), correct pc/sp info for the prologue (0a3c991fd3), arm64 frame alignment (3952057cf6,034a10d44c), stack guard (a55b131393),Link.Symmorestackremoved (4b23b50fb4), 386 prologue fixes in position-independent mode. - Go 1.6 — Done: x86 functions aligned and padded with INT $3 (0xCC) rather than 0x00 which can decode as valid instructions (
7d2c6eb3f5); PCSP table fixed atmorestackcalls (66f1f89dc0); fixed stack part centralised (97055dc1f1); ppc64x stack limit raised (c1b6e392f5); stack barriers in ppc64le shared libs (3a1bed82a6).
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.
- Go 1.0 — Done: direction-bit convention (
8f53bc0612), fat copy overlap (30fd44cf9d), reverse character copy insgen(1d0b65d33f). - Go 1.3 — Done: the changes listed above (
0ea20bc981,2b93c4dd06,14664050b9,6c7cbf086c,51b72d94de,383963b506);Duff off on NaCl(c6a41a3559). Left open: “TODO: REP still used for reverse copying when regions overlap”. - Go 1.4 — Done:
3c40ee0fe0; - Go 1.5 — Done:
747c849833,c1c3ce6b36,01ef6dbfa5,8f734d4e2f(zero via a register in componentgen), fasteraeshashhashing several streams in parallel (7a4a64e8f3). - Go 1.6 — Done: MOVUPS-based duffzero (
2421c6e3df), AVX2 versions ofCompare, string compare,IndexByteandmemclr(0e23ca41d9,967564be7e,321a40721bup to -49% at 4K,b597e1ed54), SSE4.1Floor/Ceil(37cfb2e07e, ~-30%), duffcopy clobbers X0 (ffb20631fc,0bef88aa5f).
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 doc Go 1.3 Linker Overhaul cited in this thread's commits
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.
- Go 1.3 — Done: adoption by assemblers, C compilers and Go compilers (
f7aaa553da,76a8c873cf,f606c1be80), new object format (a9f6db58ce), many Plan 9 / warning fixes (b9bed39714,52ee63f544,6e97513012,0830f64bf0), explicit field for duplicate globl detection (4890502af6), either of conflicting symbols may say dupok (c9f12d2289),symgrowcheck (6111dc4e71), TLS register on 386/amd64 so4(GS)rewrites are explicit (90093f0634; the commit says rewrites “had gotten out of control”),linkmoderemoval and ARM cgo fixes (dab127baf5,b377c9c6a9,1dd4da1459,9ed5995cfe), object version (0e8de61d73), leaf bit (cc08d9232c), NaCl morestack (9460cf7825,88f84b3e41), field tracking (d9c9665f1d), remove never-executed code (f4ecfaa442). Left open: nosplit-chain check disabled (issue 6931). - Go 1.4 — Done: preliminary plan9/amd64 (
decd810945), nacl/arm (783bcba84d),LinkArch.textflag()instead ofthecharswitches (3c24b60fd3), big-endian support (08ee2661f2), shorter zeroing-register encoding (24db881689), MOVBQZX as MOVZBL (bbc6bb9a99), literal-word comparison on ARM (b916dc1992), way to avoid pc-relative addressing for Solaris (4516a60062), DATA must be offset-ordered with no overlap (5e6bd29c2c),%^verb andC_*constants moved (d69d0fe92b), arm MRC replacement generated in liblink (0b36211cfb),gotosrewritten for translation (8e0e09ae7d),fb818eab7f,20a5de9eb6,a6feaf3aff,ae027f52fe. - Go 1.5 — Done: the C library is converted to Go (see C→Go thread), made uniform (see
cmd/internal/objthread) and finally deleted (3317e7f19a); liblink fixes during the transition (0185ba76ed,e04c8b063fdo not patch jumps to jumps to symbols,3be0a0ef6f,52d277906d,dba9eb3369GOEXPERIMENT in runtime and liblink,281ae92881SETcc encoding,ab9ec2e401,6c78443b3ecode for debugging bad returns,9b1b0a46ddfail for too-large register offset).
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.
- Go 1.5 — Done: the list above;
cmd/internal/objcreated from liblink (d6f6e420fc,24dfaba6d1,1fc330d8fe,dc7b54bed2), shift of x86SHRQsecond source toFrom3(3d0397a4f4),CALL *name(SB)(b66a509992),PINSRD(29f18f2800),CMPPSmemory third arg (59584ede73), cleanup (ad49c7bb62,fc28fd1eb8,e7ffafdb6e,0088ddc3a0,873483c682,8d16253c90). Left open: the code generator still producedProglists per function and wrote objects at the end. - Go 1.6 — Done: code layout (
span) separated from object writing so the compiler could lay out each function as it is compiled, “should reduce the working set of the compiler. But not until Go 1.7” (ed03dab853); Tlsoffset removal (7bf959c67a); ProgInfo layout (b795ce9ab0).
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 doc Go 1.3+ Compiler Overhaul listed in Go: A Documentary
- design doc dev.cc branch plan listed in Go: A Documentary
- design doc Go 1.5 Bootstrap Plan listed in Go: A Documentary
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.
- Go 1.0 — Done: the imports and 8c/5c bring-up above; many
6c/8ccode-gen fixes (902a4d5f29large constants,5e5476c2febyte* difference,d4ad8e8ce3misaligned arguments,494bcc80e4extern static registers in 5c,ee7c64efe5extern register vars only in MOVL,3e0adc9ffbvlong condition,5124e66fca), removal of fixed-size buffers (139a053733,77053797e0,0e47c75f60),-Sline numbers (2cda191aef), 5c do not use R9/R10 (c475c3071a) and gcc/Plan 9 build fixes (b0449c50f7,5ea4d73b17,4c6280b0f1,3f2cc8ba7e,c8c6e1961d),b0449c50f7. - Go 1.1 — Done:
GOROOT_FINAL(bcdafaa582), 6c rotates (bb192d1399,41ec481a53),MOVQLfor explicit truncation (e530d6a1e0), 5cregoptre-enabled (70db440885), R12 and F8–F15 usable on ARM (d87bc2f0c0),uintright-shift constant eval (34ad3995e0), large model flag (fe14ee52cc), dynamic-linker autodetection (dac4c3eee9), struct literal side effects (c13866db7f). - Go 1.2 — Done: cc rejects unions with pointers (
dbee8ad0f9), isolated textflag/dataflag (2449909d81), wide-char fix (5b15b44434), full 64-bit block copy (f268c295a3), pointer maps (9b1f1833de,2db5e4bb0b),%Q(237e7c134c), AFUNCDATA PC fix (de6da4a8bd), fix undefined behaviour (8d5ec52e6c). - Go 1.3 — Done: liblink (
76a8c873cf), NaCl (7954b2b90b), amd64p32 (d4896fb876), bitmaps in rodata (a26c01ad44). - Go 1.4 — Done:
Eface→interface{}(948bb9150e), no tag-less structs (3ffd29fb2c), make optimizer failure fatal (3147d2c4ee), diagnose Go calling C (0bb14d74f1), stop emittingargsizePCDATA (68c1c6afa0),#pragma pack offerror (86c4c4f00c). Left open: the runtime still contained C (c2go translation was the plan). - Go 1.5 — Done: cc, 5c, 6c, 8c removed on dev.cc (
4a42fae2cd), 9c removed (1a68ac2538, an earlier 9c existed on dev.power64:83f6aadf16,95e8a3a1da,860f941e7f,25bde37af4),lexbody/macbodydeleted after the assemblers were replaced (184eb0ac9e).
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)
- talk The Design of the Go Assembler. GopherCon 2016. Aug 18, 2016. listed in Go: A Documentary
- design doc Go 1.3 Linker Overhaul cited in this thread's commits
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.
- Go 1.0 — Done: documentation of the assemblers and loaders (
b5ab5d4f12), Windows paths (aed2c06dcb), fixed buffers (07d3e0dce4,0e47c75f60,77053797e0), delete 8a’sdreg l.s(783c46b197). - Go 1.1 — Done: PLD (
2031252fb8), MULW (a0084b3494), SSE2 and XMM (5da37c0901,755e13877f), AES (297bb12809),GOROOT_FINAL(6af069f3e1), delete dead code (6a22e2fb3f). - Go 1.2 — Done: flag parsing (
8124a02cb2), PCDATA/FUNCDATA (47b89a37d9,8c741c97f7),m/gfor ARM (5d081792b6), MOVBS/MOVHS (3670337097). - Go 1.3 — Done: liblink (
f7aaa553da), generated.yfixes (426b48a775), MOVQL (ea86752206), amd64p32 (e509bbc943), link to the new assembler document (0bdd90bc0a). - Go 1.4 — Done:
AJxxcondition-code docs (c6442c994a), SETPC encoding (a6feaf3aff). - Go 1.5 — Done: the new assembler: arch, addr and lex packages and the assembler proper (
6017f68029,40eb0ed0d8,8642639575,fdeee3a538), identical output tonew6a(89162307cd),TEXT $3-2parsing (92e5120c77), macros (d6eb21e331,5beec6a699), rewrite for the new obj API (cb19a20121), runtime final fixes (e59ed477c3), architectures: 386 (ef664199cb), ARM (c497349a5b,5d111b898a,5d987f1513MCR), ppc64 (e559c5cce2,5b9429d122,adff896e4c), ppc64le (9c0c050773), arm64 (18d9ddc35c,a175fa3bc3,ef49b4ca78);4(SP)illegal except on 386 (345350bf07);LOOPas a branch on x86 (9a420f4cf3); order of assemblers (4100f7d95c); removing the objects when assembly fails (3b1d0d0f07); function-scoped labels (b55791e200,b528063099); old assemblers deleted (621d75999frename toold5a,ba0c142d32,861af80458). Left open: shift handling and macros had follow-up bugs (58d177c63e,989b372a5c,d6eb21e331). - Go 1.6 — Done: robustness against fuzzing (
7437e3f02e,df37c4b987,63e2bed0ab,5e89acb580,980cb0caf2,173bf3487f,49580db149,49065cbfe4), nested#if(eaea5ade2b), more than one encoding error reported (36edf48a10),-eflag (02717bdc20), mips64 (43ea305435), ARMCMPF/CMPD(36af087aef), arm64 condition-code aliases (61a3ebed55); x86: YMM registers (d2b0c387b2), VEX encodings VZEROUPPER, VMOVDQU, VMOVDQA, VMOVNTDQ (9dd81d6227), then “correct, complete” AVX (8d881b811d), BMI-stylePDEP/PEXT(0bae38e094),POPCNT(d3ff40fb1f,7f620a57d0),HADDPD/HADDPS(e634765941), requested amd64 instructions (863d9b66f8), TSX (e9081b3c76), PSRLW fix (d96b4c494f), rejectfoo(SB)(AX)(9d6427d899).
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 doc dev.cc branch plan listed in Go: A Documentary
- design doc Go 1.3+ Compiler Overhaul listed in Go: A Documentary
- design doc Go 1.5 Bootstrap Plan listed in Go: A Documentary
- talk The move from C to Go in the toolchain. GopherFest 2015. Jun 10, 2015. listed in Go: A Documentary
- design doc Go 1.3 Linker Overhaul listed in Go: A Documentary
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.
- Go 1.4 — Done: c2go annotations (
ebce79446d). Left open: the actual conversion. - Go 1.5 — Done: conversions and the switch (above),
go tool compile/go tool link(17eba6e6b7), old assemblers deleted (ba0c142d32),cmd/distGo constant for external linking (7934199877). Left open: generated code still C-flavoured (acba34e45f). - Go 1.6 — Done: janitoring of map/slice literals and gc (
3c37a61455,7d48573d46,90f69259ec,932c1e3dd3,dd42eff8fe,3e476827a6,6d0178359f,ea4e321d4d).
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.
- Go 1.5 — Done: the port as above, plus
cmd/asmsupport (e559c5cce2,9c0c050773),9g: fix nilopt(810019286f),fix under-zeroing in clearfat(6e86003651),fix addr width and enable MOV* width check(22c929f538,c1e8c57c3d), nilopt (5e4989cf0adisables,810019286ffixes), REGZERO for the zero register (1e1c9dc2c8). Left open: “disable stack split for now” at the start (05d670f757), “No peep optimizer” on 7g; nilopt disabled initially (5e4989cf0a); suboptimal copy code noted (36f879780a). - Go 1.6 — Done: the PIC/TOC work listed above,
SRADCCimmediate assembly (10c0753761), epilogue for non-leaf generated methods (710b9ad617),archrelocsimplification (d821ae2a9e).
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.
- Go 1.5 — Done:
26bbe7ac9b,18d9ddc35c,02c1a9d87d,6e3a2a3f9f,ef49b4ca78,a175fa3bc3,72f8102244,888d44d3c1,0f27b91522,54789eff38,3952057cf6,034a10d44c,467a2cb73c. Left open: “No peep optimizer yet”, no shared linking. - Go 1.6 — Done:
3a9bc571b0,72180c3b82,3534e2bef4,80d9106487,2a031e6a2a,73d590b4bd,3578bdf3c3,61a3ebed55,5f23bc8903.
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.
- Go 1.6 — Done: the above. Left open: nothing noted besides that the port starts with the ppc64 structure.
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 doc Go 1.3 Native Client Support cited in this thread's commits
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.
- Go 1.3 — Done:
7c8280c9ef,7954b2b90b,d4896fb876,e31a1ce109,e509bbc943,9460cf7825,88f84b3e41,741244e433,bfbb2e827b,c6a41a3559,3d5e219e02. Left open: working NaCl support not included in the first merge (“does not include working NaCl support”). - Go 1.4 — Done: nacl/arm (
783bcba84d),ac0e12d158,8a7597fd7a. - Go 1.5 — Done: amd64p32 build fixes (
12079acaa0,cbc854a799), nacl/arm stack offsets (782eea0190).
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.
- Go 1.3 — Done:
90093f0634. - Go 1.4 — Done:
4516a60062. - Go 1.5 — Done:
09d9520be9,e73981512f,14fbec49dc,84207a2500,1d1c61ba7d,8a3132dd5e,264858c46e,0661143447. - Go 1.6 — Done: the list above. Left open: “the make TLS make sense project has quite a few more steps to go” (
9e6ba37b86).
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/ginscode 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.Typein 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 doc New SSA Backend for the Go Compiler listed in Go: A Documentary
- talk Generating Better Machine Code with SSA. GopherCon 2017. Jul 24, 2017. listed in Go: A Documentary
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.
- Go 1.7 — Done: user-visible the SSA path exists and generates amd64 code: conversion from IR (
d2fd43aa77), codegen from SSA (083a646f63), memory zeroing (66b47812eb), static data (6b41665039,7c18f8cdc3writes static data straight into the linker symbol instead of ADATA Progs), strength reduction of constant multiplies/divides (3e7e519c36,d8f1f8d856,a3055af45efor 64-bit constant divides,77b527e283for 24), const folding for floats (f6ceed2cab), bool ops (8b92397bcd), intrinsics (8eec2bbfbc). The compilation trigger changed from “compile everything with SSA and decide whether to use it” to deciding up front which functions go through SSA (5b355a7907). Compile-time work made the pass pipeline cheap: sparse sets cached on Func and reused across passes (f962f33035,3297a4f5f3), preallocated values/blocks and backing store for Preds/Succs/Values (2f57d0fe02,056c09bb88), faster CSE (6a96a2fe5a,88c1ef5b45handling commutative ops), value use counts kept in SSA (56e0ecc5ea), critical-edge pass tweaks (50bc546d43,b2dc1f82a5). Bugs of the period (flags clobbered by SETcc/REPSTOSQ/LEA, e.g.8f51ae8ba5,f3171994e9,4219aba5db,2511cf03b9) show the flag handling being worked out empirically. Also:ba8a146af4prints register names,288817b05areduces line-number churn,686fbdb3b0makes compilation deterministic. Left open:* only amd64 is supported (4c9a470d46“start on ARM port” at the end of the cycle); the oldcgen-based backend still exists and is the fallback, andUnimplementedFstubs remain (5b355a7907TODO: “get rid of all the UnimplementedF stuff”).
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.
- Go 1.7 — Done: global allocator (
0b46b42943), reserved registers (7d61246972), rematerialization (c64a6f6362), stack-slot reuse (366dcc4529), separate flag allocation (c140df0326) and spilling flags by recomputation (9f8f8c27dc,d04f38e3ee), allocation-order change (7d9f1067d1), two-address instruction allocation (369f4f5de5,0004f34cef), loop-aware and forward-looking tweaks (815c9a7f28,9d6e605cf7,b57ac33331), moving constant phi arguments closer to the phi (305a0ac123),3572c6418bkeeping pointer input arguments live. Left open: spills always placed right after the defining op; stack-slot assignment still coarse. - Go 1.8 — Done: copy-before-evict (
f876fb9bae), Phi-input copies (348275cda6), LR as allocatable register on s390x/ARM64/MIPS64/ARM (15817e409b,5c59cb4aa3,4f6d479186,f9238a76ff), flags removed from register masks (0484052358), SSA register numbering (833ed7c431), do not sink spills used for merge edges (7bd88a651d),d0fa6c2f9eaddingSSAReg,a2beee000bspecial-register checks, regalloc checks only when enabled (93d5f43a29), PPCCheckLoweredPhi(4c8baa0ad0). Left open: nothing noted beyond later spill placement. - Go 1.9 — Done: better spill placement (
886e9e6065), no spill of rematerializable values at merge edges (8a44c8efae), startRegs fix (8a5175df35), preemptible-loop phi fix (9664bc1d1f), rewrite of rematerializable/flag interplay discussed in2cb61aa3f71.10’s predecessor work. Left open: ADDQconst could not be marked rematerializable because it clobbers flags (documented in2cb61aa3f71.10 the next cycle). - Go 1.10 — Done: rematerializable ops must not clobber flags, with a checker (
2cb61aa3f7);7dd279013bmarks ADD[Q|L]const rematerializable (reverted in the same cycle per2cb61aa3f7);e01eac371amarks LoweredGetCallerPC rematerializable;7eaa8efbe4. Left open: nothing noted. - Go 1.11 — Done: preferring rematerialization (
dafca7de0f,014a9048d4), LEA of SP instead of ADDQconst (9916feeddf), dense GP register numbering (840f25bedd), g-register guard (31e1c30f55), allocation reductions (2ba98f1ae9,377a2cb2d2,cd2cb6e3f5caching sparse maps),3a9e4440fdmoving tighten after lowering so that rematerializable/flag-generating decisions benefit from lowered CSE. Left open: nothing noted.
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.
- Go 1.7 — Done: scheduler fixes (
4e204b42f5,a678a5c7a5,e98edc88c9), tighten (f91ff1a509), priority-queue scheduler (b1785a5065), critical edges (a2f8b0d0e4), constant-time CFG editing (4fa050024f), exit vs return blocks (10f38f51ef), load-of-store replaced with copy (5c5fa3628c), prove pass extended to constant comparisons (47c9e139ae). Left open: tighten ran only before lowering. - Go 1.8 — Done: tuple scheduling fixes (
225ef76c25,1c53a1b197), aggressive tighten for booleans (fe5619b479),c345a3913fremoves BlockCall, reverse of nil-check blocks (see nil check thread),8c24bff52bstack layout in SSA. Left open: nothing noted. - Go 1.9 — Done: exit blocks last (
4b0d74f89d), contiguous looprotate (638ebb04f2), storeOrder fix (a355639c60,978af9c2db), boolean simplification rules (6a24b2d0c1),27bc723b51loop depth init,1213776650/6664ccb453concurrency tuning (see obj thread). Left open: nothing noted. - Go 1.10 — Done: RPO traversal (
73f1a1a1a7),770d8d8207free values earlier in nilcheck,18b48afec9. Left open: nothing noted. - Go 1.11 — Done: CondSelect/CMOV (
2d0172c3a7,080187f4f7,a35ec9a59e), likely-first layout (ab48574bab), decoupled emission order (c18ff18465), tighten after lowering (3a9e4440fd),f7404974dafinishing GOEXPERIMENT=preemptibleloops (likely bit bug),d9a50a6531and9eb4590ad2use prove-pass facts for CtzNonZero and bounded shifts,bff29f2d17/caa1b4afbd/9dc351bebahandling zero-width ops. Left open: nothing noted.
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.
- Go 1.7 — Done: rule sanity checks (
e4355aeedf), strength-reduction rule generalizations (3e7e519c36,d8f1f8d856),1cc5789df9many small rewrites,0fb818a424copy values during rewrites,4989337192allow control values to be CSEd,16b1fce921aux typing. Left open: both operand orders written out by hand. - Go 1.8 — Done:
parseValuerefactor (6a1153acb4), rulegen/gen directory split from main build (7208a2cd781.7, 1.7),ad8b8f644edead-rule removal. Left open: rule duplication for commutative ops. - Go 1.9 — Done: automatic commutation (
041ecb697fthen relanded53f8a6aeb0), chunked rewrite functions (fc7b83d192), no successor variables (eaa198f3d1), lazy config (193510f2f6), cheaper CSE (f791b288d1), Func/Config/Cache roles (43afcb5c96,a5e3cac895),*types.Typein SSA (46b88c9fbc),d11a2184fbearlier GC of freed constants,3dcfce8d19. Left open: rules for different operand widths still repetitive. - Go 1.10 — Done:
1787ced894removes Symbol wrappers from Aux fields;ca360c3992better XPos for rematerialized values and JMPs;f22cf7131ausessrc.NoXPosfor entry-block constants. Left open: nothing noted. - Go 1.11 — Done:
|alternation across 386, ARM, MIPS, MIPS64, PPC64, s390x and AMD64 rule files (ec62ee7f6d,cab7ba0b28,3553262248,8993e16932,ebeea20a49,1e05924cf5,af11038428),4f5389c321moves SSA local type definitions to one location,aad71d3163rewrites ssa/README.md,398a8ed653removes broken rules,0029cd479eaddsLocalAddr(SP,mem operands). Left open: nothing noted.
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)
- tracking issue #15365 cmd/compile: ARM SSA support referenced by 19 of this thread's commits
- tracking issue #16358 cmd/compile: 386 SSA support referenced by 2 of this thread's commits
- design doc New SSA Backend for the Go Compiler listed in Go: A Documentary
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.
- Go 1.7 — Done:
4c9a470d46starts ARM port;7c6b48ffbafixes comparison and conditional branch for SSA on ARM;74a9bad638enables const division on arm64 (-67% for DivconstI64);52fe472472zeroes unaligned memory a byte at a time on ARM;f3d54789f7power5 instructions for uint64 to float on ppc64;1f4f2d0d39,bf151cc2aaunsigned to float fixes. Left open: all ports other than amd64 still on the old backend. - Go 1.8 — Done: ARM (series above,
d8181d5d75simplify MOVWreg,7b9873b9b9NEGF/NEGD), 386 (14cf6e2083..df2f813bd2,c069bc4996,2cbdd55d64), ARM64 (ae9570a5b9..d99cee79b9,ed1ad8f56c,b662e524e4CBZ/CBNZ,4d5bb76279), amd64p32 (69a755b602), many intrinsics. Left open: ARM64’sDuffdevice disabled on darwin (1e94d79f9d); ARM and ARM64 offset folding limits (see 1.9). - Go 1.9 — Done: ARM64 address folding refactor
4eb48a336e,b53f0f8c96,6464e5dc4bnot folding offsets for args because args are not 8-byte aligned to SP;fb0ccc5d0aadds assembler support for large/unaligned loads and stores so folding can resume;38fbada557more ARM constant folding;01b1a34aacrework of udiv on ARM;a041806335,14f3ca56ed,6897030fe3assembler constant-pool avoidance. Left open: ARM64 assembler still rejects many forms (see assembler thread).
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)
- tracking issue #16010 cmd/compile: PowerPC (LE) SSA support referenced by 8 of this thread's commits
- tracking issue #16359 cmd/compile: SSA backend for MIPS64 referenced by 3 of this thread's commits
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.
- Go 1.7 — Done: s390x assembler/object/asm/ssa/gc support (
2a7e85f162,13f97ea456,85bbabd9c4,354e9778a3,a223ccae38,613ba6cda8,cd6b2b7451,3c8ef0e0c964-bit multiplication with immediates); MIPS64 REGTMP/SB/TLS work (a409fb80b0,8dc0444a04,981395103e,3b0b30727c); ppc64x atomics (eeca3ba92f, with sync moved outside the lwarx/stwcx loop). Left open: s390x and MIPS64 only reach SSA in the next release. - Go 1.8 — Done: PPC64 and PPC64le SSA, MIPS64 SSA (
7f27f1dfdd), s390x SSA (6ec993adc3), MIPS32 new port (66fdf433b0..f4c997578a), SSA unconditional (167e381f40). Left open: PPC64 load/store merging and several float conversions still go through memory (44053de3651.9 and40e25895e31.10 fix this in 1.9/1.10). - Go 1.9 — Done: PPC64 rotates (
9248ff46a8,01c8719f8bmoves rotate generation to SSA), ANDN (95f3e474561.8,695f12c21a), fused multiply-add (17570a9afb), LoweredZero/LoweredMove (23bd919136,a8b2e4a630,b8d327a438), register-based int/float conversion (44053de365, requiring Power8),13c35a1b20no scratch stack location,c644a76e1fremoving OldArch checks,4dbcb53d0b,ddf807fce8,f75b5e99eds390x rule fixes,93b7eb973f. - Go 1.10 — Done: PPC64 math/Float64bits conversion rules (
40e25895e3), Abs/Copysign (4d0151ede5),622cfd8833not folding global addresses into load/store on PPC64 so temp register reloads are not repeated, little-endian consecutive-byte load/store merging (bb1fd3b5ff),b74b43de68requests R12 for indirect calls on ppc64le, s390x Abs/Copysign (96cdacb971) and rounding (7582494e06,4745604bcb),33c246ff92immediate sign extension fix. Left open: big-endian ppc64 merging “will be done later” (bb1fd3b5ff). - Go 1.11 — Done:
ec62ee7f6d-style rule compaction,aff222cd18smaller PPC64 rewrite,8af0c77df3,331c187b17,8afa8a3374s390x simplifications,ae10914e67LAA/LAAG clobber flags,de186c63cecalls clobber g on s390x,ebb67d993aRound intrinsic on ppc64x,6aeddb1b57Sqrt on mips64,30311e8860loads without DS relocation for go.string on ppc64le,1566bf9025. Left open: nothing noted.
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 doc WebAssembly architecture for Go cited in this thread's commits
- tracking issue #18892 all: WebAssembly ("wasm") support referenced by 2 of this thread's commits
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.
- Go 1.11 — Done: user-visible new architecture in the compiler (
3b137dd2df), stack optimization (482d241936), rule file name fix (cc0aaff40e), assembler offset fix (bf23a4e61d), write-barrier control (8aa57f1e9d), negative OffPtr (88dc4aee7c), address offset folding (1d303a0086). Left open: the linker half is separate (“A later commit will contain the corresponding linker changes”,3b137dd2df).
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)
- tracking issue #16357 cmd/compile: delete the legacy compiler back end referenced by 4 of this thread's commits
- tracking issue #15756 cmd/compile: parallelize compilation referenced by 7 of this thread's commits
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.
- Go 1.7 — Done: amd64 code removed from
gc(a4e31d42ee);a40b27e304eliminates arch-specific typedefs;cea5d26aa2removesThearch.Linkarchinit. Left open: old backend still compiles everything not in SSA. - Go 1.8 — Done: unconditional SSA (
167e381f40), legacy-backend deletion (e7cc9c4f4d,f03855f40e,838eaa738f,ad7732a9ac,1f2930cb5c,544010a05a,ae7e0ad7b8,24965bc96c),d6194c1fd1changesgoarch.Mainintogoarch.Init,08feadbd6eremovesArch.REGCTXT,dd748cf314/bb2bbfa086/79db1625b9simplify address generation,8c24bff52bstack layout in SSA,c345a3913fremoves BlockCall. Left open: the front end still reaches backend through globalCurfn,Thearch. - Go 1.9 — Done:
Thearchplumbing (3e2f980e27,3e63cdf850),Curfnremoval (ef1ab0f0f4,6652572b75,1911087dee), Prog globals gone (c3a50ad3c7),0a94daa378SSAGenState,263ba3ac7bdefframe,174b858f78frame size passed to defframe. Left open: nothing noted.
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)
- proposal #24543 runtime: non-cooperative goroutine preemption referenced by 12 of this thread's commits
- design doc Proposal: Non-cooperative goroutine preemption Austin Clements, 2019-01-18 · linked from #24543
- motivating issue #22460 runtime: optimize write barrier referenced by 9 of this thread's commits
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.
- Go 1.7 — Done:
d5297f7261fixes quadratic regalloc liveness (slice3.go >10m to ~20s),d2107fc987GC bitmaps for SSA code,4a7aba775dsplitting compound autos,3572c6418bpointer args live throughout,6effdd28de,519b469795,f4d38a8792de-dups gclocals symbols. Left open: liveness still operates on Progs. - Go 1.8 — Done: writebarrier phase (
f6aec889e1), nil-check redo (3134ab3c2d:BlockCheckremoved, multipleNilCheckops per block, arch-specific elimination moved into a pass using new opcode flags), stack frame layout during SSA (8c24bff52b),943f5afe22stackmap dumping refactor,ca4089ad62. Left open: nothing noted. - Go 1.9 — Done: SSA liveness (
325904fe6a,2d78538c12,ea8c7dae4f),1cdf4bf33f/691755304cSymEffects,b612ab3acb,38dee12dea,7b8f51188bbitmap generation refactor,8179b9b462output params live with defer,7cd6310014. Left open: nothing noted. - Go 1.10 — Done: buffered write barrier compiler support (
7e343134d3),0153a4130dfixes runtime.KeepAlive,e130dcf051aborts earlier on huge frames. Left open: buffered barrier only implemented on amd64 at this commit. - Go 1.11 — Done: buffered write barrier on all remaining runtime architectures (
252f1170e5,24dd83d7eb,1de1f316df,79594ee95a,a39de96438,313a4b2b7f,ae7d5f84f8,2ae1e1ae2f), safepoints almost everywhere (a367f44c18), register liveness maps (61158c162f,9f95c9db23,ed7a0682e2), memory optimization (d7d9df8a5b,75dadbec1e,3c36b8be66,f11e9aac4f,1e27e4807b,bdb65da049),d15d055054/06b326054dreject huge argument areas,85cfa4d55edegenerate write barrier case. Left open: optimized in later CLs (61158c162f: “We’ll optimize this in later CLs”).
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)
- proposal #22080 cmd/compile: generated DWARF inlining info referenced by 1 of this thread's commits
- design doc Proposal: emit DWARF inlining info in the Go compiler Than McIntosh, 2017-10-23 · listed in Go: A Documentary
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.
- Go 1.9 — Done: XPos and src.Pos plumbing (
82d0caea2c,ad4efedc6c,4808fc4443,472c792e0a), inline PC-value tables (699175a11a,0824ae6dc1), dwarf.Var generation moved into the compiler (68177d9ec0),1a7582f5e9,00263a8968line-number churn reduction. Left open: variables always described as being on the stack. - Go 1.10 — Done: dev.debug location tracking (
2d57d94ac3,4c54a047c6),-gendwarfinl(4435fcfd6c),3ba818c894distinguishing args from return values,6bbe1bc940,0b6b5641d7correct stack slots,d58d90152b,87f83eac7f,4a1be1e1daDW_AT_decl_line. Left open: location lists computed from regalloc liveness (replaced in 1.11). - Go 1.11 — Done: location-list redesign (
2075a9323d), memory reductions (39eea62340,a306341db7,9c854d65a3,108efc79c7,ac81c5c402), stack-only vars (44e65f2c94), statement boundaries (c2c1822b12,b9a365681f,dead03b794), line directives with columns (515fa58ac9,b3cb740be5),eca41af012base address selectors for dsymutil. Left open: nothing noted.
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)
- proposal #18616 math/bits: an integer bit twiddling library referenced by 5 of this thread's commits
- proposal #17895 spec: allow the use of fused multiply-add floating point instructions referenced by 2 of this thread's commits
- discussion Fused multiply-add? golang-dev · linked from #17895
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.
- Go 1.7 — Done: first intrinsics (
8eec2bbfbc). Left open: bit-scan family incomplete (8eec2bbfbc). - Go 1.8 — Done: amd64 atomics (
320ddcf834,84aac622a4), ARM64/ARM/s390x intrinsics (4354ffd38b,8ff4260777,38d35e714a,e94c52933b,517a44d57e), big arithmetic helpers (2756d56c89),10f757486ebswap for big-endian reads. Left open: nothing noted. - Go 1.9 — Done: math/bits (
495b167919,5cadc91b3c,42e97468a1,8304d10763), rotates (7e07e635f3,fa3fe2e3c61.10), ppc64x atomics (95c9583a18), fused multiply-add (bd8a39b67a,17570a9afb). Left open: Len8 is disabled (495b167919). - Go 1.10 — Done: math rounding and Abs intrinsics (
94484d8ed5,0f19e24da7,7582494e06,2f1f607b21,4745604bcb,fb05948d9e,4d0151ede5,96cdacb971), caller pc/sp (6cac100eef,6f3e5e637c), MIPS64 atomics (d63de28711). Left open: nothing noted. - Go 1.11 — Done: the 1.11 items above (
0a7ac93c27,fcba05148f,07f0f09563,ebb67d993a,6aeddb1b57,54dbab5221,1d321ada73,d9a50a6531,d7c7d88b2c,bd8a88729c,20102594a0,44b826bb28(AtomicAnd8/Or8 register fix on ARM64)). Left open: nothing noted.
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.
- Go 1.7 — Done: load combining (
12e60452e9,7294ccb54e,b81f2f106f,481fe59012), store combining (217c284995), idx1 loads/stores (af517da2f9),ec5083e49bSHLQ into loads/stores,3dd4f74e06shifts into LEAs,036d09d5bf/dc5a7682f0/9e3c68f1e032-bit operands for byte ops, drop most byte/word insns. Left open: bounds checks of combined loads separate issue (12e60452e9). - Go 1.8 — Done: bswap idioms (
10f757486e),33b71dfa1c/f357091a6dcombine extensions with loads, s390x merging (962dc4b44d,1cfb5c3fd5),deb4177cf0masks instead of branches for slicing (Slicemask better in3d5eb4a6be1.9, 1.9). Left open: nothing noted. - Go 1.9 — Done:
bd91e3569a,4477fd097f,21c71d7788,0fe58bf650,86dc86b4f9,b548eee3d9/074b73b1b2fixes,f9bec9eb42(MOVL for small constants: 1657 MOVQ replaced, binary 4KB smaller). - Go 1.10 — Done:
9c99512d18,da34ddf24b,67fdf587dc,624630b824,6b8a3c8889,ac29f4d01c,bb1fd3b5ff,a2f22a6803indexed MOVB/MOVBU/MOVH/MOVHU on ARM,782ee23884redundant zero extension removal. Left open: big-endian ppc64 merging (bb1fd3b5ff). - Go 1.11 — Done:
0f2ef0ad44,77ba071ec6,39fefa0709,ecd9e8a2fezero stores combined on arm64,34f5f8a580/aaf73c6d1eregister-indexed and shifted-register-indexed ARM64 loads/stores,4b00d3f4a2,20cf5c4962,098ca846c7,29fcd57a9f,983fca55c0. Left open: nothing noted.
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.
- Go 1.7 — Done: the items above;
6b02a19247SSE4.2 strings.Index;009c002c92AVX2 for sha. Left open: indexed forms incomplete. - Go 1.8 — Done:
8589049d5c,cc0248aea5,c53879512f,10f757486e,d251030fa6. - Go 1.9 — Done:
f9bec9eb42,9d5987d79f,0fe58bf650,a4e1a72f0a1.10 (actually 1.10),3e63cdf850. - Go 1.10 — Done:
df70982825,14cb41584d,adbfdfe377,a5868a47c6,a4e1a72f0a,ac29f4d01c,6b8a3c8889,7dd279013b. Left open: ADDconst rematerialization reverted (2cb61aa3f7). - Go 1.11 — Done: the 1.11 items above, plus
0cacc4d0e2,22115859a5,b1df8d6ffa,c284171bbb,8bf4b7e673,29e5a81e28BS{F,R}L clobber flags,b71ea0b7ddCMOVLEQF clobbers AX.
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.
- Go 1.8 — Done: initial ARM (
8cc3f4a17e) and ARM64 (d99cee79b9,ed1ad8f56c,659dd4f1d7) optimizations. Left open: assembler restrictions on offsets. - Go 1.9 — Done:
8577f81a10,257b01f8f4,4eb48a336e,fb0ccc5d0a,38fbada557. - Go 1.10 — Done: ARM fused ops and extensions (
a2f22a6803,64607dbd26,2899c3e8cb,a07176b45a,9732485851,1ec78d1dd1,78ea9a7129), ARM6407ec4385f1omit boolean zero extension,c02fc1605a. - Go 1.11 — Done: ARM64 fused ops and CSEL (
2d0172c3a7,cdd961630c,ebb77aa867,3c8b824453,f4c3072cf5,e244a7a7d3,1057624985,f524268c40,34f5f8a580,aaf73c6d1e,ecd9e8a2fe,77ba071ec6,39fefa0709),f6ce1e2aa5/78a579316bbugfixes,b08a9b7eccsoftfloat on ARM.
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.
- Go 1.7 — Done:
66b47812eb,f8fc3710fd,039d455f9d,d55a099e22,111d590f86. Left open: Duff’s device unsafe interplay with the allocator. - Go 1.8 — Done:
9d4b40f55d,1e94d79f9d,114c05962c,c69dd3f054,dede2061f3(PPC zeroing and moves). - Go 1.9 — Done:
23bd919136,a8b2e4a630,b8d327a438,fddc004537no nil check for Zero/Move on 386, AMD64 and S390X. - Go 1.10 — Done:
df70982825,c02fc1605a,3fda3765b8(PPC64 rule regression fix). - Go 1.11 — Done:
4f7b774822,094258408d,ecd9e8a2fe,8916773a3d,b46d398887. Left open: Duff’s device still used for mid-size blocks (later replaced).
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)
- tracking issue #15756 cmd/compile: parallelize compilation referenced by 21 of this thread's commits
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.
- Go 1.7 — Done: As type (
0d9258a830,e853131699,7893442011,4fc02d175b), ADATA removal (2dcbbbd193,4f753e77f1,7c18f8cdc3), object file symbol refs (3648d2d4cd),c6e11fe037newcmd/internal/syscommon architecture representation (b17b95301aArchFamily iotas) replacing the ‘8’,‘6’ constants, remove LSym fields (61b9315d37,dd2ba0c7a7,31a9e50524), Prog cache (f388b58540,45c2e38b37). Left open: assemblers use global Link state. - Go 1.8 — Done: the items listed above (
d391dc260a,6fe1febc86,246074d043,9b88fac00c,0cd8faf744,e6134702bb,4c9a372946,1bd91d4ccc,7124056f7e,e518962a27simplify Plists,e9fddf8f86darwin dynlink,d61c07ffd8). Left open: nothing noted. - Go 1.9 — Done: Follow removal (
bed8129ee6), FuncInfo (9cb2ee0ff2), concurrency-safety (see Design),7a98bdf1c2removes AUSEFIELD pseudo-op,ac7761e1a4removes Link.Plists,3394633db0eliminates AMODE,8ada52228cremoves ProgInfo tables,cce4c319d6drops AVARFOO ops,7bb5b2d33aremoves Addr.Node and Prog.Opt,d0ff9ece2b. Left open: nothing noted. - Go 1.10 — Done:
8c67f210a1RestArgs,c35f239d52splitaenum.go/vex_optabs.gofrom the x86 assembler,a5868a47c6,918b98ca70restore dropped bounds checks. Left open: nothing noted. - Go 1.11 — Done:
a76249c38dlarger initial LSym.R,c9c73978e2slice-extension idiom inLSym.Grow,02495da6b6consolidated entry stack map,9f95c9db23,62adf6fc2dASCII cleanup,6d0f757bb9arch package definition. Left open: nothing noted.
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.
- Go 1.7 — Done:
76f272d717,0b4f578266,009c002c92,8262564971,7de8cfdf9cdynlink rewrite no longer clobbers flags. Left open: AVX coverage partial. - Go 1.8 — Done:
5ce715fdfe,13c829e5f6,4e24e1d999,04ade8e428,64e152910e,b92d39ef69removes some function prologues (those with no frame whose only call was in a sort Swap style wrapper…),9741d83ceaassembles files once per package (10% offgo build -a math). - Go 1.9 — Done:
7f9832254c,92d918da03,a2da2108ee,1f85d3ad09,88f423edacprologue branch layout (wrapper case moved to end),2ebe1bdd89. - Go 1.10 — Done: RestArgs (
8c67f210a1), AVX/AVX2 (41fa265db0,c35f239d52,1e83f883c5,39de58bad7,4e19cfcdf2,49322ca9ed),a5868a47c6,0d65cd6c1c,5b043abe12restrict shift ops to 8-bit args. Left open: AVX-512 not yet. - Go 1.11 — Done: AVX-512 (
5437cde96c,67fe8b5677), legacy completeness (b80b4a23d1), cleanups and speedups (8e3dd8ab88,3db3826a57,d487488135,58b18cfda5). Left open: nothing noted.
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)
- discussion [RFC] Go assembly for ARM64 golang-dev · cited in this thread's commits
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.
- Go 1.8 — Done:
748aa84424,a866df2671,1e3c57c2cc,698bfa17a8(LR saved in leaf functions with a non-empty frame),ae9570a5b9. - Go 1.9 — Done:
579297e1e1,d039d01fe9,0bae9b083b,2b6c58f6d5,990dac2723,b5931020b7,2122fc6358. - Go 1.10 — Done: the list in Design. Left open: assembler accepts forms it should reject (fixed 1.11:
9d72e8c6861.11,e351a160051.11,9594ba4fe51.11). - Go 1.11 — Done: Design list;
3d6e4ec0a8two assembler fixes;95b4498ac0trailing NOP crash;911839c1f4;4d59b14400negative LDP/STP offsets. Left open: nothing noted.
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.
- Go 1.8 — Done: PPC64 vector and VSX (
f1973fca71,0acefdbea0), MIPS32 (5d28bc58b6,f58ce7fe79), MIPS prologue fix (b55cee1893),fedb0b3018ARM MOVW $-off(R),b6946fb120. - Go 1.9 — Done: ARM and PPC64 items above (
85ecc51c48,7d4cca07d2,09b71d563a,6910e1085b,c5ddc558ba,a041806335,14f3ca56ed,6897030fe3,dcf643f1fd),b53acd89db,c12cd31a33RLDIMI fix,555d1e36f9MOVFL fix,604e4841d6stackbarrier removed. - Go 1.10 — Done:
526f3420c2,3a165bba34, ARM VFP/ARMv6 (75cb22cb2f,074547a5ce,f727fa7939,0ed1c38017,9bf521b2b4,1ac8846984). - Go 1.11 — Done:
9a9a8c0165,6633bb2aa7,e1f8fe8dff,5b14c7b324,4c15fdb32b,99e37e98b4,e940358fc9,0a5be12f5c,1b44167d05.
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.
- Go 1.7 — Done:
7fdec6216c,d9557523c2,26849746c9fixes for defer in 386 shared libraries. - Go 1.8 — Done:
c96e94e69d,b92d39ef69,698bfa17a8,430b82009c,4c9a372946(rewindmorestack removed). - Go 1.9 — Done:
41fd8d6401,88f423edac,a143f5d646,450472989b,13c35a1b20. - Go 1.11 — Done:
99e37e98b4,75b56a0895,e940358fc9,a7f73c436d,495174302f,9b331189c1. Left open: nothing noted.
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 doc Proposal: Register-based Go calling convention Austin Clements, 2020-08-10 · linked from #40724
- proposal #40724 cmd/compile: switch to a register-based calling convention for Go functions referenced by 35 of this thread's commits
- design doc Proposal: Create an undefined internal calling convention Austin Clements, 2019-01-14 · linked from #31230
- proposal #27539 proposal: runtime: make the ABI undefined as a step toward changing it referenced by 6 of this thread's commits
- proposal #18597 proposal: cmd/compile: define register-based calling convention linked from #27539
- tracking issue #44816 cmd/compile: register ABI bringup testcase failures (tracking issue) referenced by 4 of this thread's commits
- discussion Bug in new amd64 ABI golang-dev · linked from #40724
- background Go internal ABI specification. listed in Go: A Documentary
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.
- Go 1.12 — Done:
-symabiscollection mode in cmd/asm (ba2e8a629b), compiler parsing of symabis (97e4010fd4), separation of data and function LSyms (a2e79571a9). Left open: the information is parsed but “doesn’t yet do anything” (97e4010fd4). - Go 1.13 — Done: ABI wrappers for linkname’d symbols (
b402bd4499);-allabismode removed (74d92db8d7). Left open: wrappers are aliases for the stack ABI only; no register convention exists. - Go 1.16 — Done: asm ABI selectors for runtime packages (
dd58239dd2);GOEXPERIMENT_REGABIasm macro (4d1cecdee8);-S=2dumps symbol ABI versions (982ac06f3d); the late call expansion groundwork (see next thread); cmd/go/asm get-linksharedplumbing (56b783ad94). Left open: no register assignment in the compiler yet. - Go 1.17 — Done: [dev.regabi] ABI wrappers as IR (
cb28c96be8), morestack spill support (c1370e918f), AMD64 parameter register defs and Arg ops (74cac8d479), abiutils refactor and plumbing (c41b999ad4,5a76c3d548,7a2f3273c5,d0d21b7c4c), X15/R14 reservations (401d7e5a24,5d7dc53888,397a46a10a,2e60c00f56), register args retrieved from registers (f2df1e3c34), passed to callees (4532467c18), simple register results for ints and strings (9d88a9e2bf), aggregate OpArg in registers (9f33dc3ca1), morestack handling (a2d92b5143,3778f8e07d,b6e1c33603), results loaded into registers on the open-defer return path (f5efa5a313), output parameters passed in registers spilled as autos (8ed438c077, fixed0ec2c4abba), partially-live argument spilling reworked to occur after liveness (23f8c203f0) and spills placed after OpArg{Int,Float}Reg (56af34f875,a690a5d75f), frame offsets checked against the ABI (5ff7ec98b7,97b32a6724), redundant register moves reduced in call setup by allocating inputs in a better order (6b8e3e2d06), reflect.callReflect/callMethod wrappers not clobbering arguments (3711ea0b5d), struct field padding in register-passed structs (d19eece91f), several offset/trailing-empty-field fixes (998fe70b68,98dfdc82c8,5f646f0a98,45ca9ef5c1,052da5717e,382851c1fd,14ef2d8c01,d6f6ef6358), the five-way experiment split (eaa1ddee84). Cross-checked with git log: the five sub-experiments (andregabiargs) were switched on by default for amd64 in April 2021 (9dd71ba913,fd3612e433,bbb510ccc9,88655480f3,af8a176e91; Android9c12f1b433). Left open: only amd64 (regabiSupported); ABI0 vs ABIInternal still gated by experiment machinery; all non-amd64 ports remain stack-based.
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+)
- proposal #40724 cmd/compile: switch to a register-based calling convention for Go functions referenced by 6 of this thread's commits
- design doc Proposal: Register-based Go calling convention Austin Clements, 2020-08-10 · linked from #40724
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.
- Go 1.16 — Done:
AuxCallintroduced (b4ef49e527,3c85e995ef,39da81da5e), new LE ops (35e413c537), expansion before late opt (44c0586931), OpArg expansion delayed (15f01d6ae9), regression switch (c78c79f152), correct leaf type (f8d80977b7). Left open: still guarded bygo116lateCallExpansion; results are not expanded late. - Go 1.17 — Done: Results-returning
StaticCall(e25040d162), late return expansion (92cb157cf3), integration with abi info (d0d21b7c4c),AuxCall.argsmade redundant and removed (a93849b9e2), type-preserving stores (ca3aefc4a9), recursive invalidation (a9e475a15a), OpArg scheduling (cb42e3e979),Auxmarker interfacessa.Aux(1a98ab0e2d) withir.Nodeconversions rewritten to*ir.Name(6db970e20a,dcec658f6c). Left open: nothing noted beyond the architecture ports.
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.
- Go 1.17 — Done: all of the above hashes. Left open: nothing noted.
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.
- Go 1.13 — Done: magic-variable prediction tightened (
53948127d3), for-loop verbosity (0fbf681840), stopping the shadowing of thebooltype in rules (9f56845f2c),07b4b4a1a8adds a scale field to SSA ops. Left open: generated files still huge. - Go 1.14 — Done: output reduced by 200 KiB (
870080752d), block-rewrite code reduced (c7d81bc086), minor simplifications (bdf0fe5448). Left open: commutative expansion still permutation-per-rule. - Go 1.15 — Done: ellipsis rules across all ports; commutative loops (
bd6d78ef37) with preceding correctness fixes (73269b8e75,a63ed10d44,49f8d45994); HMUL marked non-commutative on amd64 because rulegen could not yet express the asymmetric rules without infinite loops (6817210edf); bounds checks cut (a3f234c706); chunking removed (6dd11bcb35);AddArgN(7913f7dfcf); typed aux begins (28157b3292,ea7126fe14) and 386/s390x/mips/wasm/riscv64/dec ports start. Left open: arm64, ARM, amd64, s390x partially migrated. - Go 1.16 — Done: ARM, ARM64, AMD64, s390x, mips64 rule files completed;
=>-only enforcement for ARM;aacbd7c3aa. Left open: nothing noted. - Go 1.17 — Done: reserved-name check (
5437b5a24b), resultInArg0 check automation (4ebb6f5110), ValAndOff int32 (b70a2bc9c6), explicit Aux match in store-combining rule (a829114b21, which notes a TODO to require Aux/AuxInt to be always specified on the LHS), smaller no-op rewrite funcs (bd8b3fe5be). Left open: thea829114b21message flags the “unspecified aux is ignored in matching” behaviour as error-prone.
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.
- Go 1.12 — Done: RMW/loadop work above (
705f3c74e6,b75c5c5992,3bc34385fa,c6bf9a8109,5f5ea3fd4d,b40db51438,24e51bbe64,b1f656b1ce,623c772814), MULQ overflow intrinsics (a1ca4893ff),RotateLeft(5ac2476748), simplify asm generators (4545fea4fc,d17ac29158,2294e3ebd3,ccc337d8ee), correction of load-compare combination (9f291d1fc3). Left open: address mode rules are order-sensitive. - Go 1.13 — Done: a SETcc-store rule typed incorrectly is corrected (
23f3ea82b9); no other amd64-specific design work. Left open: nothing noted. - Go 1.14 — Done: FMA intrinsic via VFMADD231SD guarded by
cpu.X86.HasFMA(7a6da218b1), AND-over-shifts (4e2b84ffc5,f41451e7eb),amd64p32rules removed (b12703e052,ce49f9506a). Left open: nothing noted. - Go 1.15 — Done: addressing-modes pass (
98cb76799c), HasCPUFeature (fff7509d47), spectre (877ef86bec,fc8a6336d1), 386 conversion. Left open: 386 pass first reverted (5b897ec017). - Go 1.16 — Done: And/Or atomic intrinsic (
44dbeaf356), typed-aux completion (39a426d356). Left open: nothing noted. - Go 1.17 — Done: memmove inlining (
3b321a9d12), multi-register shifts (43d5f213e2), BT-modify removal (b211fe0058), RMW fix (359f44910f), store coalescing (bf48163e8f). Left open:b211fe0058leaves “which fallback strategy is better” as TBD.
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.
- Go 1.12 — Done: constant synthesis (
644ddaa842,86ce1cb060,c430adf136,808186203b), indexed FP and atomics (84feb4bbb7,1018a80fe8,26d62b4ca9), FRINTND/Abs/Copysign/Float bits (8149db4f64,2e5c32518c,a19a83c8ef), madd (b444215116), frame pointer (8f4fd3f34e). Left open: nothing noted. - Go 1.13 — Done: v8.1 atomics (
448089854a), bits intrinsics (5714c91b53,f8f265b9cf,dd91269b7c,159b2de442,4e2b0dda8c,192b675f17), FCMP $0 (27cce773d3), changed FP flags (6efd51c6b7),TSTWfix (2ef8abb41f),4ea7aa7cf3uses R20/R21 in Duff’s devices. Left open: nothing noted. - Go 1.14 — Done: vector LD/ST (
8403d4ea90), system registers (9c67516ed6,9ce5cad0aa), unsafe-point marking (4736088463), NOOP (3876bd67ef,01f15b62b3), no REGTMP in ZeroRange (0f992b9948). Left open: nothing noted. - Go 1.15 — Done: PCALIGN (
2f8798ed07), prologue_end stmt (a88c26eb28), typed aux started,rev16(2783249068), ARM64 compare redo of flag consts (a07e28194a), VCNT fix (e5e386938f). Left open: nothing noted. - Go 1.16 — Done: SHA512/SHA3/SIMD instructions (
93eeb819ca,3036b76df0,d7ab277eed,3089ef6bd7,dfdc3880b0,fa04d488bd,5d0b35ca98), CAS (15131caeaa), atomic And/Or/swap intrinsics (cdb19b4dba,ecc3f5112e), darwin/arm64 PIE and c-shared (234de9e1c2,f8e554021b,b85c2dd56c), call reloc fix (3f7b4d1207), unsigned compare to 0/1 (06337823ef), EON (01aad9ea93), VMOVQ fix (cd99385ff4), frame pointers made unconditional (5c2c6d3fbf),a86b6f23f0. Left open: nothing noted. - Go 1.17 — Done: conditional-select rules (
f5e6d3e879), REV16 (b182ba7fab), float slices (3ee32439b5), inlined memmove (3b321a9d12), 128-bit FP pair loads (79beddc773), extended register ops with RSP (a607408403,355c3a037e), shift-amount range checks (0636d88f6d,9726c78539), R16/R17 and R12 clobber marks for special calls (11b4aee05b,4a7effa418), encoding fixes (48b7432e3f,5daefc5363,f439a76253,c925e1546e,e2e05af6e1,a6a853f94c,27dbc4551a), small-stack NOSPLIT (d8394bfc7f), huge-frame prologue (4702dd67a7). Left open: nothing noted.
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+)
- tracking issue #27532 all: port to RISC-V referenced by 21 of this thread's commits
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.
- Go 1.14 — Done: the assembler layers and compiler as above (
a3ceb57fb4through98d2717499), linker reloc (1046a9cb1b), DWARF register map (fb8cdb7e4b). Left open: cgo unsupported (g in TP clash, later6f45b39e4d1.16); PIE not enabled. - Go 1.15 — Done: instruction generation rework, atomics, compare-and-branch, pseudo-ops (listed above). Left open: nothing noted.
- Go 1.16 — Done: g moved to X27 (
6f45b39e4d), Duff’s devices, PIE/buildmodes, extension elimination (320cc79002),8b51798304removes X27 and S11 register names, branch-pseudo fix (0433845ad1), MOV for extensions (8a013233ac). Left open: nothing noted. - Go 1.17 — Done: constant materialization (
6517844129,c2d625168f,8fd0f83552), IsNonNil (125eca0f72), Eq32/Neq32 zero-extend before subtraction (c584f42dcf), huge-frame prologue (042f4cbb6f), atomics And/Or (497feff168). Left open: nothing noted.
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.
- Go 1.12 — Done: rule improvements, indexed addressing, record-form compare avoidance, Mul/TrailingZeros/OnesCount intrinsics (see hashes above), float zero (
f776d51bf7), PCALIGN (bdba55653f), AIX 64-bit addressing (041526c6ef,4295ed9bef,503091a77c,20ac64a2dd). Left open: nothing noted. - Go 1.13 — Done: POWER9 CNTTZ (
3023d7da49), RotateLeft (f2a18b1456), Add64 (50ad09418e), XX1-form encoding fix (259de39375), mid-stack-inlining NOP (2d3474043c,b098c0f467). Left open: nothing noted. - Go 1.14 — Done: DQ-form (
6b67f7d65d), VSX moves (816ff44479), LR for indirect calls (24e549a717), unsafe points (ceca99bdeb), vmrg (8f79ca1d1a), AIX Duff (09c9bced82), ANDCC register masks (55924135ee). Left open: nothing noted. - Go 1.15 — Done: index fixes, move/zero improvements (
815509ae31), ISEL shifts (a1550d3ca3), power9 mod (56933fb838), PCALIGN (37470c0664,c79c5e1aa4,60a964ea45), shift constraints (daf39d06ee), NaN constants (78aa4af239), AND-remainder (a3ef804891). Left open: nothing noted. - Go 1.16 — Done: MADDLD, SUBFIC, combined shifts, EXTSWSLI, MULLI, bclrl hint (
e7c7ce646f,7615b20d06,967465da29,a424f6e45e,bdab5df40f,cb674b5c13), TOC symbols static (076dc2111b), stop removing NOPs (7d7bd5abc7,46ca7b5ee2). Left open: nothing noted. - Go 1.17 — Done: optab consolidation and DS-form handling (hash list above), 5-operand RLWNM (
48ddf70128). Left open: nothing noted.
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.
- Go 1.12 — Done: OnesCount, RotateLeft,
6f9b94ab66,f94de9c9fb,4f15b54780(int32 overflow in moves),abd1dde1f7(no-op), VMSLG (3f3142ad99). Left open: nothing noted. - Go 1.13 — Done: atomic ordering (
ac8dbe7747), Add64/Sub64 (2c1b5130aa), RISBG family (0f79510dc5), IPM (6966b67510), RestArgs (9c843f031d), optab param (e61985427e). Left open: spills of the carry per loop iteration noted in2c1b5130aa(“may be able to reduce”). - Go 1.14 — Done: control values, numeric masks, compare-and-branch (above), Mul64 (
98aa97806b,ac2ceba01a), And8/Or8 (b3885dbc93), sign extension removal (5c5f217b63). Left open: nothing noted. - Go 1.15 — Done: flag-producing FP ops (
b2790a2838), simplified memory-operand rules (a03618500c), immediates (e464d7d797), typed rules. Left open: nothing noted. - Go 1.16 — Done: shift/mask (
854e892ce1), addressing modes (17ae587383), strongly typed rules finished (03eb7e20e4), atomics And/Or (646531c52a). Left open: nothing noted. - Go 1.17 — Done: shift-count type fix (
a58be734ea1.16). Left open: nothing noted.
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.
- Go 1.12 — Done: the 386 and ARM items listed through
a0a7e9fc0c,3933302550,2b69ad0b3c, MIPS fix6a64efc250. Left open: nothing noted. - Go 1.13 — Done: ARM ReverseBytes16 (
fee84cc905), MIPS64 UNPREDICTABLE fix (95d4e6158b),a189467cda. Left open: nothing noted. - Go 1.14 — Done: ARM FMA/Abs/rotate, MIPS64 Duff, NaCl removals (
11d7775c9f,f6c624a22a). Left open: nothing noted. - Go 1.15 — Done: 386 addressing modes (
d84cbec890,af7eafd150,24925c7ed9), flag-constants for ARM redo (40ef1faabc), MIPS64 intrinsics, MSA. Left open: first conversion crashed randomly because intermediateptr+xmay point outside the object (explained inaf7eafd150). - Go 1.16 — Done: 387 removal (
fe2cfb74ba), softfloat (04b8a9fea5), ARM typed rules, CMN fix (694025e74f), MIPS atomic And/Or (e5ad73508e). Left open: nothing noted. - Go 1.17 — Done: ARM:
4826abb6c21.16,c4190fc34d; MIPS:afe517590c,e31e84010e; scratch mem removal (2a18e37c4e). Left open: nothing noted.
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 doc WebAssembly architecture for Go listed in Go: A Documentary
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.
- Go 1.12 — Done:
6c631ae227,52cae2763e. Left open: nothing noted. - Go 1.13 — Done: intrinsics, GOWASM options, global/PC cleanup, SP caching (
5ee1b84959,4d23cbc671,cf8cc7f63c,496b8dbbfc,203e188837,72d24a7484,2396101e05). Left open: nothing noted. - Go 1.14 — Done: shift/rotate/bounded work and extension elimination (listed above). Left open: nothing noted.
- Go 1.16 — Done:
8acbe4c0b3. Left open: nothing noted.
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)
- proposal #25819 math: add guaranteed FMA referenced by 3 of this thread's commits
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.
- Go 1.12 — Done:
a1ca4893ff,899f3a2892,dd789550a7,5ac2476748,6f9b94ab66,f94de9c9fb,9aed4cc395,23578f9d00,8149db4f64. Left open: nothing noted. - Go 1.13 — Done: arm64/ppc64x/s390x Add/Sub intrinsics, wasm,
4a4e05b0b1, divisibility (a28a942768). Left open: nothing noted. - Go 1.14 — Done: FMA (
33425ab8db,7a6da218b1,58b031949b),97592b3c14, Mul64 on s390x/mips64x,ac2ceba01a, signed divisibility (6b1d5471b9), s390x And8/Or8,ec10e6f364(atomic {Load,Store}8 lowering). Left open: nothing noted. - Go 1.15 — Done:
ab7ecea0c8, riscv64 atomics, HasCPUFeature. Left open: nothing noted. - Go 1.16 — Done: And/Or atomics. Left open: nothing noted.
- Go 1.17 — Done: riscv64 And/Or. Left open: nothing noted.
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.
- Go 1.12 — Done:
bce1f12225,13d5cd7847,3068fcfa0d,ff9481ec0d,5403b8eceb,f493e55723,a3593685cf. Left open: nothing noted. - Go 1.13 — Done:
80b6812d7b,4ae31dc8c5,8a317ebc0f,2c423f063b. Left open: nothing noted. - Go 1.14 — Done: multiple control values (
9c2e7e8bed), postorder simplification (75198b9a9c),72dc9ab191. Left open: nothing noted. - Go 1.15 — Done: exit layout, Greater/Geq removal, comparison canonicalization, scheduler uses, shortcircuit phis, flagalloc cleanup,
d7c073ecbf,806318d6ad. Left open: nothing noted. - Go 1.16 — Done:
1c0d1f4e97,b2a8317b31,91b7619310,bf833ead62,37a32a1833,6c64b6db68(no constant fold of divide by zero),a4b95cd092(comparison folding fix). Left open: nothing noted. - Go 1.17 — Done: depth-first layout (
600259b099),6ae3b70ef2,5b328c4a2f,90ec257735,b38b1b2f9a,4357f71ca7. Left open:600259b099accepts that some functions get larger or slower.
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)
- proposal #24543 runtime: non-cooperative goroutine preemption referenced by 1 of this thread's commits
- design doc Proposal: Non-cooperative goroutine preemption Austin Clements, 2019-01-18 · linked from #24543
- tracking issue #36365 runtime: clean up async preemption loose ends referenced by 5 of this thread's commits
- motivating issue #10958 runtime: tight loops should be preemptible referenced by 1 of this thread's commits
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.
- Go 1.12 — Done: only call-site clobbering experiment (
c8545722a1,360771e422). Left open: “stack maps at non-call safepoints are still imprecise” (c8545722a1). - Go 1.14 — Done: unsafe-point marking across ports;
c3f149250e,b2482e4817,61fa79885b. Left open: nothing noted. - Go 1.15 — Done:
9d812cfa5c,faafdf5115,601bc41da2,2bad2f7eba,ee7d9f1c37. Left open: flag-gated. - Go 1.16 — Done:
84d7a85089,01f99b4e95,32a84c99e1. Left open: nothing noted.
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.
- Go 1.12 — Done:
7b88b22acf,2b58ca6e3d,b3294d9491,321a3d6e85,72496e35ce,01a1eaa10c,9a7e70e34f(OpArg scheduled earlier for debugging). Left open: nothing noted. - Go 1.13 — Done:
82af9e6749,6081a9f7e6,68a98d5279. Left open: nothing noted. - Go 1.14 — Done: compiler-generated debug_lines (
ee3dded36d,9e6a84f2b1,46b7557836). Left open: nothing noted. - Go 1.15 — Done:
a88c26eb28,363cd66d60,1dcf34f2ec. Left open: nothing noted. - Go 1.16 — Done:
63fd764502,9ef65ff137,4220d67084. Left open: nothing noted. - Go 1.17 — Done: register param location work (listed above). Left open: nothing noted.
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 doc (PUBLIC) Building a better Go linker listed in Go: A Documentary
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.
- Go 1.13 — Done:
cc5eaf9346,134ef176f0,807761f334. Left open: nothing noted. - Go 1.14 — Done:
2c484c0356,53b7c18284,cd75cf4bc0,d56c8149ac,97e497b253,9cf6c65ca3,ffca64dcf3. Left open: “currently nothing will consume the new object file” (2c484c0356). - Go 1.15 — Done:
8cdb769b08,181faef82c,330f53b615,6c7d6cefd0,88d6d37b77,e08f10b8b5,f5fcc9b8e0. Left open: nothing noted. - Go 1.16 — Done: content-addressable symbols and cleanup (hashes above),
c89251204e. Left open: type descriptors are not yet dedup’d (526d99a49asays deduplication is “just an optimization, not a requirement … until we get to type descriptors”).
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.
- Go 1.12 — Done: PCALIGN on ppc64 (
bdba55653f), parse refactor (52b2220559). Left open: nothing noted. - Go 1.13 — Done: DATA validation (
f31305b71b), BSWAPW reject (720af3c8c4),cb8aefd3b0ADJSP. Left open: nothing noted. - Go 1.14 — Done:
9d032026d6,b2482e4817. Left open: nothing noted. - Go 1.15 — Done: alignment (
71d477469c,1dbcbcfca4,d92a5a80b5), spectre. Left open: nothing noted. - Go 1.16 — Done: asm refactor and ABI selectors (
dd58239dd2),8247da3662. Left open: nothing noted. - Go 1.17 — Done: FuncInfo (
4dd77bdc91), prologue simplification. Left open: nothing noted.
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)
- proposal #40724 cmd/compile: switch to a register-based calling convention for Go functions referenced by 16 of this thread's commits
- design doc Proposal: Register-based Go calling convention Austin Clements, 2020-08-10 · linked from #40724
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.
- Go 1.18 — Done: ARM64 gets parameter registers
4bb927f82e, ABI-compatible prologue registersa4b2a04bc5, morestack arg spilling4c68edd1fe, arg/result load/spill06df0ee7fa, updated CALL ops8e7abefdaa, memmove call matchinge4003463ff, and is switched on by963f33b03b;clobberdeadon ARM646b1e4430bb. AMD64 regabi becomes unconditional and theregabigexperiment is removedc93d5d1a52. Do not schedule in-register args latecf23daeda3. PPC64 gets ABI register infocceadf8527, initial spill09d3df0183, new CALL ops333f3de2a1, enabled with duffcopy support24c2ee7b65, plus prologue0edc6c4fa0. Argument stack-slot liveness for tracebacks30a82efcf4.maymorestackdebug hook (asm + compile)3839b60014. Method-wrapper tail call restored by redefining OTAILCALL to carry an OCALLc10b980220. Tail-call fixes in leaf functions on PPC64d198a36d8c, MIPS/S390X67917c3d78. Loopjmpdeferreplacement was landed and reverted in the same cycle (fd0011dca5,e3e9f0bb2d); see the defer note under Other. Left open: riscv64, loong64, s390x still on stack ABI; the bug “args not spilled into dedicated slots” is called out in30a82efcf4. - Go 1.19 — Done: user-visible riscv64 moves to register ABI: context register moved from X20 to X26
9faef5a654, duff device registers X10/X11→X24/X2529b968e766, ABI register infocaf5cd9da8, initial spill4d2da99498, CALL ops849e42f028, morestack/prologue6c17529af7, enabledd3362fc124.sys.Archgains LR and fixed frame size, replacingCtxt.FixedFrameSize8af7fac5a1,5f625de4d0.loopreschedchecksrepaired for regabi3b01a80319. Left open: loong64 and s390x still stack-based. - Go 1.22 — Done: loong64 regabi: registers changed to avoid ABI arguments
070139a130, register infoebca52eeb7, spill supportbd224fa522, CALL ops6b77d1b736, obj prologuee58c9baa9f, enablede5615ad876; duff registers movedf43581131e.obj: unspill code in prologue marked preemptible2b3c1c5937. Left open: s390x. - Go 1.26 — Done: s390x register ABI: info
73b6aa0fec, initial spill81242d034c, CALL ops24697419c5, prologue85e6080089, enabled89552911b3. [dev.simd] morestack spills the correct SIMD registerc0f031fcc3.obj: argp adjustment for wrapper frames removed6f6c6c5782. Left open: nothing noted for 386/arm/mips (they never got commits). - Go 1.27 — Done: wrappers for embedded interfaces use tail calls
e7a09d1ffb, with arch fixes for indirect tail calls383000da24, arm3259b188cee2/97d6430458, mips64a95f60871f. - Go 1.28 — Done: SVE register-argument morestack spill offsets fixed
3e62cba7ac;clobberdeadon loong645d4666bcda; separate pointer vs register widths564f7d071a.
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.
- Go 1.18 — Done: regalloc switch sorted by arch
dcee007aad; eliminate successive swapse1c294a56d; helper removal of phi args (7b554575e4,f686f6a963). Left open: nothing noted. - Go 1.19 — Done: XER made allocatable on PPC64
a4a0f9b148(FPR31 reallocated33987b408c, F0/F27-31 allocatablee84a37068b); better propagation of desired registersbdd0f0f780;merge(stack merging helper) uses applicative balanced trees to cut sets sharing (heap −95% on the pathological test)857cda4625; carry chain scheduled disjointlyc386269ed8(1.7–2.4× on PPC64 crypto/elliptic multiplication). - Go 1.20 — Done: temporary registers: ops can declare a temp so regalloc reserves one instead of a fixed register; amd64 converted
5f7abeca5a, with a fix when mask is empty9088c691da;issueSpillremovede81263c791; fix score forSelect{0,1}of flags897ad2fe90; sparsemaps separated7ddc45263c; carry chain scheduling priority readjustedd031e9e07a;OpVarKilldropped from the backend6dc7b060cd. - Go 1.21 — Done: scheduling pass improved (forward scheduling)
12befc3ce3; register args scheduled before stack argsb08d5ee5e9; values with no in-block uses scheduled later6224db9b4d; SP scheduled early40ed359182, InitMem after Args518889b35c;tightenpass enhanced for memory valuesea8f037996(revertedaa6e168480, relandede0ceba8139); heuristics for resultInArg0 overwrite decisions3c7884a21b(reverted2bf0f54bbd); “avoid registers” priority lowered4237dea5e3; drop overwritten block input requirements2c6aaaea64; anchored SP (SPanchored) so LEAQ of a variable schedules after its VARDEFf959fb3872; explicit boolean for “clobbers flags” mov marker8354f6b5bb. - Go 1.22 — Done: scheduler special case for entry block removed
505e50b1e3; loong64 dynlink no-op in regalloc08b9640333; liveness stackmap excess bits removed05d4b57c9f. - Go 1.24 — Done: regalloc drops values that are not used until after a call
36b45bca66; premature deallocation inloopreschedchecksfixeda8be834912. - Go 1.25 — Done: handle desired registers of 2-output instructions
a0029e95e5;tightenregister-pressure heuristic relaxed1e756dc5f7; nilcheck ops not moved by tightena1ddbdd3ef; “don’t preload registers if destination already scheduled”524946d247; induction-variable increments scheduled late19f05770b0; temp register reuse fixedcc16fb52e6; GOSSAFUNC output now includes liveness7472b4c324; residual GC register map code removed3b25b3c195. - Go 1.26 — Done: ops may specify clobbering of an input register (copy made before clobber, like resultInArg0)
89a0af86b8, used byensure we use allowed registersfix5a56d8848b; faster regalloc liveness (fill loop headers instead of iterating to a fixpoint; passes over CFG from loop-depth to 2; liveness time 225→150 ms on cmd/compile/internal/ssa)16705b962e; rematerializable values’ output reginfo respected10e7968849and removed from the live set across calls (arm64 .text −0.34% on preprofile)284379cdfc,9bbea0f21afixedreg values always available;4d0658bb08prefer fixed registers (zero register) for values;containsUnavoidableCallfixed5045fdd8ff; loopRotate handles nested loops592c2db868; irreducible loops no longer trustedf15cd63ec4; stackalloc liveness switched to sparse path exploration (−74 ms CPU on a cmd/compile build)4e761b9a18;reduce stack object livenessetc. - Go 1.27 — Done: regMask refactored into a struct to allow >64 registers
1901161d96(later resync2868672202); riscv64 prefers x8–x15/f8–f15 (compressed-friendly)4212586726; desired registers propagated through phis (back-edges of innermost loops are dead ends)5b106947d1; schedule increments after flags628674a0c1; likely-used values moved into registers before entering a loop97d6430458; no lift of divides out of loops9c688e3f4dand lifting ops out of loops made opt-inf93424692d;earlyOkfor opsc3a43395bc; double-drop fixesf65692ea56,54ebdf6151; cachedsdomreverse POa6c48f2ca9; reduce lock/scheduler contention in the compilercc6923e839. - Go 1.28 — Done: free “doomed” registers at loop headers
be79f93983;RegisterSetwidened tossaop.RegMask966c78ed74; HI/LO special register handling removed from regalloc for mips (mul/div results no longer homed there)af4b02c461,6db72bb92b; break false dependencies for BSF/BSRca9c560a43.
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)
- proposal #45453 all: add GOAMD64 environment variable referenced by 3 of this thread's commits
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.
- Go 1.18 — Done: user-visible BMI1 instructions at GOAMD64≥v3
04572fa29b, TZCNT060cd73ab9, MOVBE3e5cc4d6f6(reverted/relandedb0351bfd7d,fe7df4c4d0), POPCNT register break via shorter XORb99abf3cce; TESTL for cpu-feature checksd2d21d98e4; ANDL for small immediates (4476→3024 ANDQ in the Go binary, 0.03% smaller)732f6fa9d5; MOVOstoreconst with offset folding (16-byte stack zero as one MOVUPS)144e0b1f6e; folding of 32-bit pointer offsets removed46121306d3; prefetch and publicationBarrier intrinsics23f4f0db68,739328c694; Plan 9 X15 not zeroed02117775d1. Left open: more v3 instructions. - Go 1.19 — Done: LZCNT (avoids BSR zero special-case)
a92ca51507, SHLX/SHRX/SARX incl. load forms and constant-shift folding (7fbabe8d57,d6320f1a58,517781b391,66f03f79da,cb702a2a56), MOVBE index/store32de2b0d1c,ba6df85c7c, false dependency removed for TZCNT96081f8545, BSRQ with nonzero input7d3b64a2e3, v3 bit-length intrinsic fix for 16/8 bits5a103ca5e9. - Go 1.20 — Done: combine more constant stores
09932f95f5; 3-operand LEA split in the new late-lower pass1c783f7c68,{SHL,SHR,SAR}Xrules moved to late lower44563340cc; Windows TEB TLS slots on amd64/386 (da564d0006). - Go 1.21 — Done: X15 + XORL for
zerorangein ggen326df693d7; shorter movzx forLoweredHASCPUFeature1ddab59229; simplified signed division2a41dbf13c,ee522e2cda; broken LEA “optimization” removed6a97a60b4b; RDPID in the assembler05cd6cbb98; frame pointer:obj/x86uses PUSH/POP to save/restore BP (Windows toolchain −0.7%)f66581ead6, then MOV instead of LEA for FP setupadc1db23ee, with the implicit NOFRAME heuristic for frameless nosplit functions removed after every amd64 OS marked asm explicitly (d939b3717f…ea75b9458f,d20e688fcf). - Go 1.22 — Done: shorter MOVs for clears of 9–12 bytes and MOVQ+MOVL for 11-byte moves
3d4f41aea8,a65b4904fc; indexedSET*ops67983c0f78; direct memoryBTS, avoid BTS when OR works611706b171; hardware float min/max (amd64+arm64)319504ce43; PCALIGN accepted on 386/amd64f322d67ced; ANDL/TESTL if upper 32 bits zero (reverted0e02baa59a). - Go 1.23 — Done: user-visible
PCALIGNMAX align,amountdirective for fine-grained loop alignment (useful only on amd64 per measurements, none on Apple Silicon)18d0e6a14f. - Go 1.24 — Done: sync/atomic And/Or intrinsics on amd64
c18ff29295,Xchg8dc8902f4eb; swissmapmatchH2/matchEmptyintrinsified with SSE ops (0–3% faster map ops; moved to free functions because methods cannot be intrinsified)dce30a1920; floats stored as integer constantsf66db49976. - Go 1.25 — Done:
x < 128→x <= 127for imm8 (−3.15% archive/tar geomean)d524e1eccd;(x&1)==0via AND/XORe57769d5ad; shift-by-1 → ADDcd595be6d6; reuse flags rather thanTEST x x6729fbe93e;x*z+ylowers to FMA when enabled (v3)43b7e67040; multiplication strength reduction by automatic linear-combination search (amd64, arm64)12110c3f7e; PCMPEQB commutative8cb6d3b826; Plan 9 SSE/FMA special-cases removedc18ff21cc8,35cb497d6e,c9b07e8871. - Go 1.26 — Done: duff devices replaced by generated loops (see Move/Zero thread);
zerorangesimplifiedb10eb1d042; do not Zext bools before CMOV1662d55247;bits.Mul64on 32-bit machines1e5bb416d8; VEX/k-register/AVX-512 plumbing for SIMD (see SIMD hooks thread). - Go 1.27 — Done:
x86MOVQ r64,imm32shortened for positive immediates0db36238c6; 32-bit copies for 64-bit copies of 32-bit values478d86446e;float32(round64(float64(x)))optimization (amd64/arm64/wasm)7336381cd1,425a88193c,d51d4d75a6; flags→bool→flags round-trips removed9e0467b174; ADC/SBB can eat InvertFlags9f9476d09b; removal of the multi-register shift (SHRD/SHLD) rewrites because when count≡0 mod 64 they leavelorather thanlo|hia531ad45f7; miscompile fixes: SETcc/Jcc mapping38e988efb4,x+x<<63c3bfc824a5/truncated const shiftf2f369db0e; ssaConfigbranchelim arches2d33a456c61.26 (1.26). - Go 1.28 — Done:
LEAVEinstruction for Go-compiled functions (retried after revert; distpack −0.79%, code −2%; asm functions keep arithmetic restore since users may scribble on BP)56d7cfd457,ebdf81fda7,38b5f91aa5, extended to ABIInternal asm3d45c38896, breakage on ABI0 NOFRAME fixed4c06723736, excluded on plan9d6bec360b3;bits.Mul64→ MULXQ at v3 (flag-free, interleavable with ADCX/ADOX)4dfb312b5f;x*y-z/x-y*z→ fused multiply-subtract at v3fb355d8a3c,88a3e6202a;x±carry→ ADC/SBB0a6ccc557a;LOCK ADDinstead ofLOCK XADDwhen sum unused,LOCK SUB,LOCK INC/DEC(030734422d,b0197759ac,61154e8998); sub-word read-modify-write folded ({ADD,AND,OR,XOR}{B,W}constmodify)ee74f99276; X15 for 4/8-byte zero stores7ecd9096a2; float min/max branch idiom → MINSD/MAXSDf9de8c0900; VEX encodings for zero/move/spill when AVX presentc2f76b656d,5763a306d2; magic-constant 32-bit unsigned div by pre-shifted constant1b0e91bf6e; -buildmode=pie on openbsd/amd648b3ed8a068; endbr64 (1.25)04de448c6d1.25.
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)
- tracking issue #44734 cmd/asm: refactor the framework of the arm64 assembler referenced by 2 of this thread's commits
- discussion x86 asm source of truth golang-dev · linked from #44734
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.
- Go 1.18 — Done: ZR/REGTMP encoding errors fixed (
aef24d8f7d,9cbc76bdf9,8e18428e38; pseudo-register addressing and SXTB checksc92c2c9d62,e9e0d1ef70, move-constant checks41f74079a9,6656269288); constant rotates83b36ffb10, rotate merged with logical ops6f35430faa, ext+shift → SBFIZ/UBFX43b05173a2,7b69ddc171; double-negate fold315dbd10c9; shift range checks for ARM/ARM64ccfc41eee0. - Go 1.19 — Done: jump tables on ARM64
540f8c2b50(following the amd64 jump-table CL); prologue/epilogue with STP/LDPc6d9b38dd8and small-frame prologue changed to save LR+decrement SP in one instruction because glibc cannot block SIGSETXID and a signal on the old stack would clobber below SP (frame 0x1f0→0xf0)64ef16e777;stacksplitoptimized31be6285a8; ANDconst+UBFX merge8ab42a945a; TLBI, DC, TRN1/2 instructions; fix ADR/ADRP/BITCON/SYS encodings. - Go 1.20 — Done: late-lower pass makes constants materialize once (movz+movk) instead of repeatedly folded into adds
ddc7d2a80c; TST optimizations in-blockefe5929dbd; shift byv&63uses hardware masking54c7bc9cff; CMP↔︎CMN rulescf53990b18; LDP/STP memory moving0a52d80666; carry-chain scheduling enabled on arm64 (reverted oncefec83c8a7d, relanded)4c414c7673,669ec549b5; Duff’s device on darwin/arm644bcc138bc6; Muluhilo split8bf9e01473. - Go 1.21 — Done: redundant sign/zero extension omitted
4d180f71dc; address folding for globals on ARM64 except -dynlinkb675a75c3d; CMP→CMN under<,>=42f99b203d; assembler code cleanup series (8aa0175368…e7c7f33263). - Go 1.22 — Done: large constants into vector registers via rodata
0a17b2c174; fewer literal-pool uses (208fc13245,19e2e3c291); InvertFlags absorption into Noov comparisonsfb5bdb4cc9,ace1494d92(saves an instruction: CSET,CSEL instead of CSET,CSET,BIC); top 32 bits kept zero for 32-bit opsa9bedc36a5; frame pointer restore in epilogue fixedc9888bdfe2. - Go 1.23 — Done: atomic And/Or intrinsics on arm64 (using register temp
v.RegTmp, results = old value)63c1e141bc; asm tests run for cross builds0def9d5c02;FMOVQlarge offsets (1.26)0c4444e13d1.26. - Go 1.24 — Done: bitfield rule additions
7273509466,b92f3f29c1; MADD/MSUB combination more often98e719f677/fix0a0a7a5642; STP/LDP around morestack74163c895a; prologue/epilogue pattern-matched again for small frames (MOVD.W/LDPmismatch noted in the commit)6d39245514; MSR DITb5c2b1ec13;UDFfor undefined Progs3e3ce20df8;Xchg83aa71c12ea; span7 helpers557211c150. - Go 1.25 — Done: LDP/STP load/store combining variants
0b88a87879, sign-extension bug in paired 32-bit loads fixedbeac2f7d3b; BTI instruction9ce47e66e8; epilogue SP increment in one instructionf7cc61e7d7; register encoding moved intooprrr/opldrrhelpers (f90f3cfcf7,4f5863c2ce,fd0805b660); more conservative use of zero register16dbd2be39; constant-classification refactor (391dde29a3…06f82af183); OpMove used more than memmove calls11fa0de475; fold negation into add/subdfebef1c04. - Go 1.26 — Done: generated loops replace duff (below); new bounds-check strategy
394d0bee8d; NEON inLoweredMemmove/MemmoveLoop(Raspberry Pi 5: −22.5% geomean)5c9a26c7f8; CCMP generation introducesARM64ConditionalParamsaux (condition, NZCV, constant)10ac80de77; 32×32→64 multipliese1a12c781f; PAC instructions11d3d2f77d; shorter constant integer loads3b3d6b9e5d; fold boolean NOT into branchesba634ca5c7; conditional-instruction type correctiond0d0a72980; ARM64RegisterExtension moved into obj45eee553e2. [dev.simd] arm64 helpers (FMOVQ vector load/store, VMOVI16B)9076ed87501.27 (1.27). - Go 1.27 — Done: 128-bit vector ops for Move expansion (FMOVQ/FLDPQ/FSTPQ for 16–64 bytes)
ec3373e379,e0ebb4c646; TBZ/TBNZ merge shifts/ROR/SRA3d4fed3179,1f11689588,5e17860e1b; shift lowering cleanup0d54be530b; data-driven SVE assembling path79f3d38562, GP/SIMD reg support926a1bef08, register with indexce4459cf0e, register lists1aa9d9a69b, memory forms (14685c05aa,65203e06e6,356b87fa7b), special operands4398c11b51, immediates42083b1ea0, ASIMD shifts/arithmetic/misc/reduction/widening/narrowing instructions (9111d85e2f,b1c8857f95,c1352b7df1,f2e70034cc,e73621e34a,9a0be0742b), refactors that move encoding helpers into obj (f827b85425,845b7b0e49,85f838f46c,f91403c2a0); SVE put behindGOEXPERIMENT=simdd7a37379a9; fixes: missing condition in SBFX→SBFIZ943343f7fe, Rsh with 32-bit rhs miscompilef2f369db0e; RPRFM range prefetche49b53439d. - Go 1.28 — Done: SVE archsimd support in the compiler (
68f391a3c2, broadcastdfbb9da7ff, mask First/All/Nonead2488e65c,8a2ef7eb43,9bdbfc8fa5, predicate zero276175b8fe, Broadcast+Mask peepholee76a7d23eb, callerSave70c31c1f4f); spill/reloadSTR/LDRfusion intoSTP/LDPas the last step of code generation via a newssagen.ArchInfo.SSAGenFinishhook (the SSA pair pass runs before regalloc and cannot see spills; second Prog becomes ANOP)aa5fc60a63; byte compare vs 128 → TBZ/TBNZ197b975aee; redundant byte/halfword zero-extensions stripped5de5a2ec8a; CASPA/CASPAL/CASPLc122d7e6c5, LDSMAX/LDSMIN/LDUMAX/LDUMINb553a7cb8a, VSMOVb01fe21278, ID system registers601d28a5b0, large offsets in load/store pairsb3f5034b15; miscompile: noov InvertFlags at min int — the optimization was unsound since its inception (CL 233097), so asymmetric comparisons are no longer folded607cfcea17.
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)
- proposal #71105 cmd/compile: change GORISCV64=rva20u64 to include compressed instructions referenced by 7 of this thread's commits
- proposal #61476 all: add GORISCV64 environment variable linked from #71105
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.
- Go 1.18 — Done: FMA support
ea51e223c2, Mul64/MulUintptr intrinsicsefd206eb40,1951afc919, Abs/Copysignc69f5c0d76; branch-with-zero and NEG absorbed into branchesbcd146d398,8fff20ffeb; immediate-operand constant folding (e.g. the 5-instruction arithmetic right shift becomes onesrai)fe8347b61a; Prog-rewrite elimination series inobj/riscv(5ec298d7b0,9cb5716f00,ba66d62b68,8a01010c26,0b66310924,2d90df91a8,37e9c1d6fe,da790cccc5); c-archive/c-shared6458b2e8db; stack-bounds prologue made nonpreemptible59d04d104d; trampolines from large functionsbde0463da3. Left open: register ABI (next cycle). - Go 1.19 — Done: register ABI (see ABI thread); boolean comparison fix
d6e6140c98; two-operand forms94727bed02. - Go 1.20 — Done: a run of ~12 constant/extension peepholes: SLTI/SLTIU known outcomes
a7bcc94719, SLT constants→immediateba8c94b5f2, remove redundant SEQZ/SNEZ77da976419, combining ops with immediates7234c90352, mask+zero-ext0ca355318f, avoid extension after word arithmetic5760fde4df, FP compare239115c3ef,646c3eee06, negation folding4274ffd4b8, sub const3e11e61f3c, Slicemaskc011270fa5, bounded shiftda6556968f; CAS32 arg sign-extension fix (LL/SC loads sign-extend; shared with loong64/mips64)0b2ad1d815. - Go 1.21 — Done: loading large immediates with trailing zeros
481a6beba5; invalid shift amount check5454834f01; FMA negation commutativity fix3fce111535; regenerate encoding table8e8303e1d0. - Go 1.22 — Done: shift lowering for int32/uint32 (srlw/sraw/srliw/sraiw replace extend-then-shift)
561bf0457f,8fc043ccfa, correctness fix70c7fb75e9; FMA performance05f9511582,63ab68ddc5; atomics mapped per RISC-V spec (amo.aqrl; lr.aq/sc.rl) 890b96f7ab; call relocation reworkbda5e6c3d0; PCALIGN9840d38cdb; publicationBarrier intrinsicd98f74b31e. - Go 1.23 — Done: user-visible rounding-mode suffixes on FP conversions (
FCVTLD.RNE, default RTZ)3580c212c2; hardware float min/max with qNaN pre-check09ed9a6585; Zba/Zbb/Zbs in the assemblerdb423dde85, native rotations for rva22u64 (significant SHA-512 gain)3659b8756a, rotation pseudo-instructions and canRotatedaa58db486,997636760e; rva22u64 MOV improvementsa6ecdf29e3. - Go 1.24 — Done: integer min/max instructions
3d9a89b057; vector instruction encodings and registers77c53c16e9,4646556ba4; encoding information reworkeddc8ceb63a8; shift+add combine for rva22u64e126129d76; ANDN/ORN/XNOR always provided0ee5d20b1f; prologue_end DWARF stmtb45c7f1661. - Go 1.25 — Done: vector load/store, integer arithmetic, fixed-point, reductions, mask, FP and permutation instruction support, vsetvl configuration (
37e9c5eaba,535e0daefd,4c1b8ca98c,2e60916f6e,936ecc3e24,5a342266e6,1cc624fd62,c8545439b5); CSR map5a7db813a6; synthesized MIN/MAXd88f93f720; GORISCV64-gated intrinsics TrailingZeros (CTZ), Len (CLZ), OnesCount (CPOP, with Zbb check for rva20u64: +5 ns without Zbb vs up to −11 ns with), Bswap (REV8)6fb7bdc96d,b70244ff7a,4d10d4ad84,90e8b8cdae; constant constructiond37624881f; plugin build modecdc9560794;R_GOT_PCREL_ITYPE_RELOC371ee1469c; generic simplifications123141166b; fold zero register for*zeroinstructionse373771490. - Go 1.26 — Done: compressed (RVC) instructions: encodings
924fe98902, enabled for runtime/sysb9ef0633f6, assembler support with better bit-pattern encodinga15d036ce2,0f9c8fb29d, use in generated code9859b43643; FCLASS for infinity/subnormal tests320df537cc,34aef89366; float↔︎int register moves via FMVfcc036f03b; Zicond assembleref05b66d61; generated loops replace duff (below); segment/fault-only-first vector loads371c1d2fcb,6b8d507508; store-to-load forwarding rules4373754bc9; assembler docsb24aec598b; generic CSR opsdd8276657f. - Go 1.27 — Done: Zicond codegen for CondSelect, enabled only for the ConstantTimeSelect intrinsic because on predictable branches zicond is 4 instructions vs 2
ca94cf1247,3a29ebeef9; Zbc91d1a88376, Zvbb/Zvbc58efaf3859, Zihintpause081aa64e61, FENCE operands/FENCE.TSOb5b9e3cdfe; CSR pseudo opsca10097f29; avoid improper control-transfer hints2d1f571c6b; constant materialization refactor and 32-bit unsigneddfd2e8027c,0a0c9c88fd,7f7e4942f1; float32(abs|sqrt64)f3966c1ada. - Go 1.28 — Done: crypto vector suites Zvkg/Zvkned/Zvknh/Zvksed/Zvksh
0b02c60612; compressed branches, returns and known-immediate jumps7496531e00,e5ec1263ca,46f508b3aa; jump tables8e35b7dc52; atomic And/Or intrinsics78c9eae8ee; math rounding intrinsics77fb1abefe; bit testsa4485949ce,91d55590e6; SLLW for Lsh32603439a1c6; multiply-by-constant0485478d9b; single ADDI for load/store offsets and unified split point1f162b7286,689cdf4cda; MOVDnop removeddeab4989e6; rva23u64 feature macros documented4472de41fd; large move/zero fix5c1ad454f5; sign extension of unsigned values no longer elided incorrectly10a908733a; vector DWARF regsa89c2ff8a3.
loong64 backend and assembler
Span: Go 1.18 → Go 1.28 · Status at HEAD: feature-complete port with LSX/LASX assembler coverage; very active
- proposal #46229 all: port to linux/loong64 referenced by 8 of this thread's commits
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.
- Go 1.19 — Done: arch bring-up (above), SPWRITE FuncInfo
654b03167f, division-constant folding fixa2bca290e7. - Go 1.20 — Done: brachelim enabled
268f4629df; tail-call flag8a9e2d9d49; MASKEQZ/MASKNEZ1dcef7b3bd; ROTR + intrinsics7d574466a9,e8f0340fa4; Add64/Sub64 intrinsics97760ed651,90a3527427; resultInArg0 register checks removedc1c6b0ca79. - Go 1.21 — Done: c-shared
1f908bd060; save LR after SP decrement, remove branch delay slots (3ed8a1e629,b561ebab46); all unconditional jumps realized with B/BL (inheritedBEQ ZERO,ZEROfrom MIPS dropped)09f1ddb158; BEQ/BNE with 0 → beqz/bnez22f9317f20; auto-align loop heads to 16 bytes via syntheticPCALIGN $16(≤3 NOOPs)f736a9ad01, PCALIGN directive47b22b6548; MUL/DIV rewrites14015be5bb,96428e160d; PCALAU12Ib4ac4b4b42. - Go 1.22 — Done: regabi, Lowered{Move,Zero} improved
3754ca0af2, duff cleanup06f420fc19. - Go 1.23 — Done: atomic memory access instructions
504212bbd7; recheck jump offsets after loop alignmentf7f56ded01. - Go 1.24 — Done: atomics intrinsics based on AM* instructions (xchg
72a92ab5b7, xadd4f7af5d192, load/store barriers3214129a83,ac345fb7e7, Cas5432cd96fd, And/Or4b0da6b13f, Xchg8fe7d97d032) — Xchg8 via LAM_BH because LL/SC needs too many temps for an intrinsic; publicationBarriere534989d18; intrinsics: Abs/Copysigne705a2d16e, Max/Minff14e08cd3, OnesCount via VPCNTfb9b946adc, TrailingZeros/Len/Bswap/Reverse (ab55465098,d98c51809d,d6fb0ab2c7,583d750fa1), FMAe6cc9d228a, prefetch (1.25)da9c5b142c1.25; stacksplit branch inverted for static prediction (SP <= stackguard→ morestack)5923a97f43; assembler cleanup series (~12 obj/loong64 commits e.g.bd85a3b153,ee3da50617,0214749fef); LSX/LASX loads/stores/compares/PCNT751a817ccc,4ce8c0604e,2751443e92; register-indexed load/store rulesef3e1dae2f; loong64-specific memmove inlining5f88755f43;NOSPLITsmall stacks0a8dea10ce; many FP/CRC32/FMA/bit-field instructions. - Go 1.25 — Done: ~25 vector assembler commits (e.g.
53d689ce99,b0e2f185c5,b10c35945d,47fd73a51c,d65c209b4b); immediate loading optimizedb9630c56db,f24d2e175e; shifts2a772a2fe7,b143c98169; unalignedOK for memcombine09d76e59d2; negation foldingc31a5c571f; prefetch intrinsicda9c5b142c. - Go 1.26 — Done: ALSL.{W,WU,V} + ADDshiftLLV multiplication rules
4c7362e41c,bd71b94659,90b7d7aaa2; jump tableaf6999e60d; generated loops replace duff7bba745820,91e76a513b,4f7bbc62c7; LSX-basedLoweredZeroLoopc4bb9653ba; multiplicationse071617222,a552737418; 32×32→64feae743bdb; new bounds-check strategy5216fd570e; MOVVP prologue optimization revertedeb7c67fdc9→dd1d597c3a; many instruction additions (LDPTR/STPTR882335e2cb, ADDU16I.Dddce0522be, FSELee7bb8969a, VPERMI/VSHUF/VEXTRINS etc.). - Go 1.27 — Done: math.{Ceil,Trunc,Floor,RoundToEven} intrinsics
3fdac6780b; float32(abs|sqrt64)f65fe3216e; Bswapc61a48619f; unaligned equal functions045ceb7240; assembler refactored into a single opcode map and instruction file (de0d77c6f8,99d4927f69,b261436c52, merges003c0bbf16,73f72d8c99,c5f1695f79,9b07a7668c,72f89282ac); ll.acq/sc.rel/sc.q47f26133bd; FRINTf93504bfd6. - Go 1.28 — Done: unified OpCode definition and register aliases shared with the linker
e83070192e; zero-upper-bits made a declared op attribute (e8285ccd6e,1ba085ec21) plus redundant extension removal (286c7389e9,a1b734e408,a4bac74d2a); floating-point comparisons absorbed into branches384cf71367; mask comparisons69b2918247; offset+base lowering38f24c5c46; ibard7e269d1ac; preprocess/stacksplit/WORD implementation optimized (4b590865d7,9350068b23,29ceccda77,143c05549f); vector encoding fixes.
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)
- tracking issue #44549 cmd/asm,cmd/compile,cmd/link,cmd/objdump: ppc64 ISA 3.1 support referenced by 4 of this thread's commits
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.
- Go 1.18 — Done: register ABI + duff (above), long conditional branch fixup
8f397bc118, prefixed-instruction alignment46796703d7, operand classification rework (5a3d0f5a63,1e2820a6d7,aa1c6f5639); memmove inliningb35c668072; prefetch6ae3afa7e7; AtomicLoad rulesce72766a02; PowerPC rule fixes94323206ae. - Go 1.19 — Done: Add64/Sub64 lowered into SSA with XER CA elision and spill support, carry-chain scheduling disjoint
0c43878baa,3d7ca9f7c0,c386269ed8(1.7–2.4× on native crypto/elliptic multiplication); ORNc570f0eda2; BI argument of BC fix4a5711c3cf;bdnzmnemonics7b1ba972dc; publicationBarrierc9f67c289f; preferred nop86371b0360; ISEL CR bit arg2f23364340. - Go 1.20 — Done: ISA 3.1 and MMA registers
3a067b288e,85c0d26c6a,e6eaf39cfa; atomicCas without branch62125c9b79; condition-code ops (…CC) reuse to drop comparesd0b0b10b5c;lateLowerenabledf841722853with ISEL mergedc6b7c86df; Mul64uhilo rules871a3a409a; big uint32 constants in registers4e6f90fecd. - Go 1.21 — Done: prefixed MOV*/ADD for power10
72301a9863; SETBC/SETBCR rules (2.5% on ISEL-heavy loops, fewer registers)a432d89137; byte-reverse intrinsics for power10cd1fc87156; NOTOC29d5272da8; PCrel in linkerccb8db88c5; unbounded-shift reworkf98dd29910; SETB99f811ecca; xxspltidp (1.23)ebf7747dbe1.23. - Go 1.22 — Done: 34b ADD/MOVD constants
e9379a8f8b,061d77cb70; masking codegen5cdb132228,c8caad423c,773039ed5c; avoid ANDCCconst80834af206,9341046302; RLWNM/RLDICR5f872db5fc,41c71d48a1; lwsync on failed CAS so notesleep/notetsleep see wakeups655155d0a7; mask/small-constant in-register generation (36ecff0893,dcd018b5c5); KDSA/KMA/KMCTR are s390x. - Go 1.23 — Done: float min/max
dfb17c126c; ADDZEc7065bb9db; CMP against $0408739fc96,cc673d2ec5; ADDconst/ANDconst reintroduceddca577d882; RLWINM combos (7994da4cc1,0222a028f1); 32-bit constant adds6e5398bad1. - Go 1.24 — Done: decimal floating point
b03b1acfb0,7403a6a122; MulUintptr intrinsic2b0a157d68; Xchg815618840f6; carry addition rules745ec75719; shift regression fix1846dd5a31. - Go 1.26/1.27/1.28 — Done: smaller panicBounds frame
e0a1ea431c; MADDLD folding81afd3a59b; large prologue694182d77b; logical immediates for uint8/16d74de3ce79;(x+x)<<cfoldcbc2d06c9b; sync/atomic Add/And/Or made sequentially consistentf4d1304904; atomics cleanup553ac454c2-class553d..553ac454c2; signed-division ops mark flag clobbering (fix)6200ca7253; VSX vector arithmetic assemblerc9f4d3fd64,b082320566; async-preempt safepoint bug in objf0fb6be53c, preempt code builds on POWER8152b1d604b.
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.
- Go 1.18 — Done: duffcopy/duffzero initial use on ppc64x
24c2ee7b65; Plan9 duff. Left open: nothing noted. - Go 1.20 — Done: Duff’s device enabled on darwin/arm64
4bcc138bc6; fine-tuned memory moving on arm640a52d80666. - Go 1.25 — Done: nil check done before calling duff functions on arm64/amd64
dbaa2d3e65;noDuffDeviceflag removed26040b1dd7. - Go 1.26 — Done: amd64: DUFFZERO and DUFFCOPY replaced by generated loops (
eb7f515c4d,ec9e1176c3; the DUFFCOPY change was reverted4e182db5fcand relandedca66f907dd); arm64 (28aa529c99,15d6dbc05c) with runtime duff removede4291e484c; riscv64 and loong64 (879ff736d3,4dac9e093f,7bba745820,91e76a513b) with runtime duff removed (b9a4a09b0f,4f7bbc62c7);zerorangesimplified on amd64/arm64/riscv64/loong64/mips/mips64 (b10eb1d042,5e94d72158,77643dc63f,e0870a0a12,06e791c0cd,bb48272e24; mips fix0b26678db2); zero constant register reuse on mips64xbdd51e7855. - Go 1.27 — Done: vector-based Move expansion on arm64
e0ebb4c646;LoweredMoveLoop/LoweredZeroLoopsimplified on loong64c1c0af1e16,a92edd97bf.
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.
- Go 1.26 — Done: per-arch migration: arm64
394d0bee8d, amd64/386076eae436e, arm327dccd6395c, loong645216fd570e, mips649f9d7b50e8, mips328330fb48a6, riscv6495693816a5, s390x7a1679d7ae, ppc64802d056c78; old-style calls removed21ab0128b6; bounds-check slice SUB/SUBconst compare optimizationdcb479c2f9, shift-through-AND folding for slicesbd80f74bc1; bounds report fixaf03343f93; ppc64 panicBounds frame made smallere0a1ea431c. Left open: nothing noted.
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.
- Go 1.20 — Done: late lower on arm64
ddc7d2a80c, PPC64f841722853,bd6cd7db07, amd641c783f7c68,44563340cc; riscv64 late lower type fix2952fe4f16. - Go 1.21 — Done: memcombine pass
cedf5008a8; generic rules cleanup: store rules use store type60140a86b3. - Go 1.22 — Done: consecutive-load memcombine
e713d6f939. - Go 1.25 — Done: automated multiplication strength reduction
12110c3f7e; loong64 equivalente0716172221.26 (1.26); constant folding flagsaf278bfb1f. - Go 1.26 — Done: ARM64 and others’ rules for fold negation into multiplication
2cdcc4150b;branchelimsupported arches moved to Config2d33a456c6; SIMD rule file plumbingce0e803ab9. - Go 1.28 — Done: rewrite rule clean up
b5ff81cdbb, multiplication reordering54bae9bf84;x-(x&y) → x&^y1008f900f6.
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)
- motivating issue #62737 cmd/compile: merge temporaries and named variables by GC shape referenced by 4 of this thread's commits
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.
- Go 1.23 — Done: intervals
7979c8f588, merging enhancements82e929e4f8,26a1f4ae00,e01b1eb289. - Go 1.25 — Done: residual register GC map code removed
3b25b3c195; stack object alignment 1.285d29d80b6c1.28. - Go 1.27 — Done: bitvec 2 GB limit removed
f745645e58, less reserved memorybfb851c31e. - Go 1.28 — Done: mergelocals address-use analysis tightened
06bc6592c3, fixes for arm32/s390x/riscv645d5c7ff1f0and mips3a171a2aca. Left open: nothing noted.
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.
- Go 1.19 — Done: arm64
c6d9b38dd8,64ef16e777,31be6285a8;3562977b6f(mips, s390x, riscv). - Go 1.21 — Done: amd64 BP save via PUSH/POP
f66581ead6, explicit NOFRAME on every OS + removal of heuristic (d939b3717f…d20e688fcf). - Go 1.22 — Done: PCALIGN on 386/amd64
f322d67ced. - Go 1.23 — Done:
PCALIGNMAX18d0e6a14f. - Go 1.24 — Done: arm64 small-frame consistency
6d39245514; drop NOFRAME TODOa4eba85fad. - Go 1.28 — Done:
LEAVEon amd6456d7cfd457/38b5f91aa5; fixes for ABI0 NOFRAME BP expectations4c06723736.
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.
- Go 1.19 — Done: arm64
540f8c2b50. - Go 1.26 — Done: loong64
af6999e60d. - Go 1.28 — Done: riscv64
8e35b7dc52, mips64xc8337c9bb2, mipsx84ee19cf1a; common table-entry code403b3349ca.
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.
- Go 1.18 — Done: Mul64 on riscv64
efd206eb40, MulUintptr1951afc919. - Go 1.19 — Done: Add64/Sub64 in SSA for PPC64
0c43878baa. - Go 1.20 — Done: arm64 carry scheduling
669ec549b5; Add64/Sub64 on loong6497760ed651,90a3527427; Muluhilo split on ARM648bf9e01473; 386 TrailingZeros (1.21)cedfcba3e81.21. - Go 1.22 — Done: Add64/Sub64 on mips64
5f8a2fdf09,b1474672c6. - Go 1.24 — Done: PPC64 MulUintptr
2b0a157d68; 386 Ctz64 (relevant to swissmap)0140aae6d0. - Go 1.26 — Done: bits.Mul64 (Mul64uhilo, Hmul64, Hmul64u, Avg64u) on all 32-bit systems, making constant 64-bit division emit directly (i386 i/999: 7 mul→4 mul)
1e5bb416d8; wasm Avg64u/Hmul646e165b4d17. - Go 1.28 — Done: MULXQ at GOAMD64≥v3
4dfb312b5f; ADC/SBB generation0a6ccc557a.
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)
- proposal #61395 sync/atomic: add OR/AND operators for unsigned types referenced by 2 of this thread's commits
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.
- Go 1.21 — Done: MIPS64x And/Or
75add1ce0e. - Go 1.22 — Done: riscv64 atomics per spec
890b96f7ab; ppc64lwsyncon failed CAS655155d0a7. - Go 1.23 — Done: arm64 And/Or intrinsics (sync/atomic aliases)
63c1e141bc. - Go 1.24 — Done: amd64 And/Or
c18ff29295and Xchg8dc8902f4eb; PPC6415618840f6, arm643aa71c12ea, loong64 (above). - Go 1.27 — Done: ppc64 sync/atomic Add/And/Or sequentially consistent
f4d1304904. - Go 1.28 — Done: riscv64
78c9eae8ee; arm64 pair CASc122d7e6c5; ppc64 atomics cleanup553ac454c2.
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)
- proposal #42372 proposal: cmd/compile: go:wasmexport directive linked from #65199
- design doc Proposal: go:wasmexport directive Francesco Guardiani, 2020-12-17 · linked from #65199
- background go:wasmexport - implementation notes linked from #65199
- background WebAssembly architecture for Go linked from #63131
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.
- Go 1.20 — Done: bulk memory ops
50557edf10; docs on Go→wasm mapping660d4815ea. - Go 1.24 — Done: wasmexport frameless return PC fix
4ffa2aecc1, parameter types5a9aeef9d5, detection before runtime init0b632d26b9. - Go 1.25 — Done: indirect calls use 64-bit ops because >65536 functions overflowed the 16-bit shift of the PC
c876bf9346; large return address as i64d60a684c87. - Go 1.26 — Done: signext/satconv always enabled
742f92063e; unaligned memory ops53ad68de4b; Avg64u/Hmul64(u)6e165b4d17; FP→int32/64 match change reverted3bc9d9fa83. - Go 1.27 — Done: constant folding/identity/mask/sign-extension elimination rules (
b764d47e4d,def4e491be,bd5dc3a2fb,65f76a20a3,55de3efde3); vector ops in WASM asm (dev.simd)3b2c371844. - Go 1.28 — Done: float min/max lowered to wasm instructions
4494c64f5a; fold redundant extensions26844c5113; avoid widening 32-bit import results9c8f4b0ab1.
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)
- proposal #60072 cmd/go: add GOMIPS32, GOMIPS64 ISA levels (iii, r1, r2, r5, r6) referenced by 2 of this thread's commits
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.
- Go 1.18 — Done: MIPS64 extension elision fix
d93ff73ae2; s390x/mips tail call fix67917c3d78; Spectre assembler error on 386c091767d87. - Go 1.21 — Done: 386 TrailingZeros intrinsics
cedfcba3e8; s390x breakpoint fix3e77efc720; Windows 386 TEB TLS slots28f8dbd7b9; arm HAUTO addpoolfdb3dc471d. - Go 1.22 — Done: s390x KDSA/KMA/KMCTR crypto instructions
61719e18f7,ed8cbaf6ac,a40404da74,b06f59e75c. - Go 1.25 — Done: negation folding for mips64x/mipsx/s390x (
8097cf14d2,d52679006c,7589e96042); publicationBarrier on mips (94e3caeec1,3dbc775d60); s390x LCDBR4ae6ab2bdf. - Go 1.26 — Done: user-visible all remaining windows/arm code deleted
98238fd495; 386/wasm FP→int conversion change reverted6d5b13793f; s390x float min/maxcedf63616a; 386 8-bit semantics78d75b3799-class reverted; s390x MVCLEd71d8aeafd. - Go 1.27 — Done: arm single BIC for AND with negative-rotated immediates (no REGTMP clobber)
358cf41413; s390x VSTRL4aa6dad54e; mips NORconst removed514790c2b9. - Go 1.28 — Done: mips64/mips load/store simplifications
bcd794d277,e798729d8c; bounded shift on mips64x85ec0589e2; mips64le large pointer offsetsd52464487f; s390x VLER/VSTER/VLLEZab480527a3; s390x CMPUconst/SRDconst fix69fe0c724a.
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
- proposal #73787 simd/archsimd: architecture-specific SIMD intrinsics under a GOEXPERIMENT linked from #79781
- proposal #79781 simd/archsimd: support ARM64 SVE SIMD intrinsics under a GOEXPERIMENT referenced by 1 of this thread's commits
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.
- Go 1.26 — Done: k registers
11d2b28bff, register shapes (1161228bf1,6bc3505773,884f646966,1b87d52549), simdGen prog writerc81cb05e3e, AVX-512 upper regs/masked loads/stores (8db7f41674,1ac5f3533f), VPTESTf1e3651c33. - Go 1.27 — Done: X/Y/Z16–31 allocatable
e7ec26cf8f, callerSave of AVX-512/maskedc3b161cb/wasm vectorsb15400c775; mask optimizationsacc4bad854; arm64 helpers9076ed8750. - Go 1.28 — Done: SVE (arm64 thread).
SSA package split into subpackages (issue #80409)
Span: Go 1.28 → Go 1.28 · Status at HEAD: in progress
- tracking issue #80409 cmd/compile/internal/ssa: split into subpackages referenced by 5 of this thread's commits
- motivating issue #20104 cmd/compile: move arch-specific rewrite rules and ops into arch-specific packages referenced by 1 of this thread's commits
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.
- Go 1.28 — Done: the steps above; Left open: “later once we split out most of the rest of ssa we’ll rename ssacore to the ssa package” (
9b32c1225c); name stutter (op.Op386ORL) to be fixed later (ef0bd1f8df).
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.
- proposal #73787 simd/archsimd: architecture-specific SIMD intrinsics under a GOEXPERIMENT linked from #79781
- proposal #78902 simd: architecture and vector-size agnostic SIMD intrinsics under a GOEXPERIMENT linked from #79781
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.
- Go 1.26 — Done: register shapes, vec/mask SSA types and the first AMD64 SIMD lowering (
8ecbd59ebbgenerated 63k lines:simdssa.go,simdAMD64.rules,simdAMD64ops.go,simdgenericOps.go,simdintrinsics.go); helpers moved from generated code into the compiler (6c50c8b892); vectors made uncomparable (5a78e1a4a1); CPU-feature pass (d2270bccbd,ba72ee0f30); X15 low-128 zeroing (f38e968aba). Left open: spilling masks (“will depend on the architecture”), 256/512-bit zero values built on the fly, VZEROUPPER not evaluated (f38e968aba). - Go 1.27 — Done:
isSIMDplumbing reused unchanged for WASM (obj.WasmV128inssagen/abi.go) and arm64 NEON (8a1b0c8ecb); unsigned-constant fix for load folding (3f6eabdf091.26 was 1.26) ; cgocallback X15 clearing fixes by runtime team (52fd498a96, cross-area). Left open: nothing noted. - Go 1.28 — Done: SIMD-zero block placement (
d61b240b11), no-code values not implying features (fbea197d32), VEX encodings (5763a306d2), critical-edge features (0b2fd4aa9c), whole-X15 zeroing (3f1287d96a), RegMask widening (966c78ed74), GOAMD64 seeding attempted and reverted (bc769ea44b,4a1a9dfeb7). Left open: GOAMD64-derived feature seeding.
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.
- tracking issue #81819 simd: unify API generation around executable spec referenced by 1 of this thread's commits
- proposal #78902 simd: architecture and vector-size agnostic SIMD intrinsics under a GOEXPERIMENT linked from #81819
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.
- Go 1.26 — Done: first generated amd64 code (
8ecbd59ebb); generator output ordering fixes (3a4d10bfcaremoved a map iteration,35b8cf7fed,e62e377ed6); simdgen migrated into the repo (b7c8698549) with etetest.sh rewrite (8b90d48d8c); tests generated in repo (88568519b4,a24ffe3379); memory operand parsing and merge (f42c9261d3,0b323350a5,5a0446d449); SHA features parsed from XED (2b8eded4f4); feature implications (9ef1692c93). Left open: the generator depends on XED data path ($XEDDATA); the API source of truth is spread over YAML categories, hand-written files and docs. - Go 1.27 — Done: multi-architecture parametrisation and ARM64 definitions (
8a1b0c8ecb,8437d1b188,951257bd14); asmRule for patterns on asm instructions (9922fb1d24); argsMatchRule for broadcast-to-VMOVI folding (098d688071);ImmMaxto limit immediate jump-table sizes (b84139dfad); SetElem general-register width detection (f4d50e5ebf); auto-merge ofsimdgenericOps.go(d3a3061d75); zero-valued fields omitted fromsimdAMD64ops.go(6fb7a1cddd); decode non-broadcast memory operands (4136ffed69); insert_ordered_map for deterministic output (82489f7402). Left open: sort-order glitch only “attempted to pin down” (e3afca43e2). - Go 1.28 — Done: gentools consolidation, Operand type filled by XED/ARM64/SVE (
7b916597d3,1f0d9ab3af), README, GOROOT logic; “mem” naming (c5612f0ccf); CPU feature of mask ops (f507c3fc9f). Left open: transition of old inputs (categories.yaml) to the executable spec, see spec thread.
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.
- proposal #73787 simd/archsimd: architecture-specific SIMD intrinsics under a GOEXPERIMENT linked from #76473
- proposal #78979 proposal: simd/archsimd: enable AMD64 architecture-specific SIMD by default referenced by 3 of this thread's commits
- proposal #76473 simd: rename low-level SIMD package to simd/archsimd (GOEXPERIMENT) referenced by 1 of this thread's commits
- proposal #78902 simd: architecture and vector-size agnostic SIMD intrinsics under a GOEXPERIMENT linked from #78979
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.
- Go 1.26 — Done: user-visible the whole API above reached master through reverse merges starting
d4f5650cc5(2025-11-24),2b62144069(12-03),9e09812308(12-08); package renamedsimd->simd/archsimdwith package doc (144cf17d2c,1da0c29c2a); doc rewrites (05e41225f6..d64add4d60),Mask.AsIntMxN->ToIntMxNand ToMask only on integer vectors (f110ba540c,c0ba519764); Broadcast rename (8ac4477d83); tests skipped without AVX (1b291b70df). Left open: many names still to rename (see API shaping thread);DotProductQuadrupleremoved pending redesign (70c22e0ad7). - Go 1.27 — Done: user-visible op additions/fixes: VPSHUF[HL]W and VPEXTR[BW] choices (
43a85e04d3,989942d20a), VPSRL immediate peepholes (aa80d7a7e6), signedNeg()and MaskString()(923c23ea10), VAES capture (83b232b0af),CopySign->MulSign(59740699ed), SumAbsDiff result type (f6c3137899), shift semantics fixes (39b20d32a9), Load/Store renames and GetElem/SetElem bounds panics (b70242b3b9,ff05f35fb6,13a70de131). Left open: nothing noted. - Go 1.28 — Done: user-visible 8-bit interleave on amd64 (
efef5f05e5),Int*.Absreturns Uint (7e0c8984d8), OnesCount emulation for 8-bit (385f272158), Mask All/Any/None/TrailingZeros (bc10f4ab7f,145490ed8a,0291846537), Mask Xor/AndNot/Equal/Not (9f0fa36092,e6cce8b4da,2753eb3c05), exposing CLMUL feature checks (b7f9fde523), floating Min/Max made non-commutative so CSE cannot merge opposite operand orders (c7aa83bdd2: AMD64 Min/Max prefer the second operand for NaNs/equal zeros), alternating FMA lane parity fix (fe0449ba3a), signed rotates dropped (8031ffce5f: unclear sign semantics), AndNot operand order for KANDN fixed (e2889b0bf5: x.AndNot(y) on 512-bit masks returnedy &^ x, found by conformance testing). Left open:MulAddin the spec had to be split into int/float becausex*y+zonly fuses at GOAMD64>=v3 (958aac0d81, “inspired proposal #82653”).
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.
- proposal #73787 simd/archsimd: architecture-specific SIMD intrinsics under a GOEXPERIMENT linked from #78979
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.
- Go 1.26 — Done: user-visible mask types and conversions (
a034826e26,6f7a1164e7),.Masked()peepholes (ede64cf0d8,cf31b15635), merging ops (86b4fe31d9), emulations of mask/merge methods for AVX2 (94d72355f6,2080415aa2), mask load/store removal (2e71cf1a2a),ToMaskinteger-only (f110ba540c). Left open: IfElse naming, Merge emulation wrongly named at first (“under a wrong name; subsequent CLs will … rename”,94d72355f6). - Go 1.27 — Done: user-visible
IfElsesupersedesMerge(e115b2f987,6f010acf75), ARM64IfElsesemantics corrected (a2b3c73f75),Mask.ToIntNsrepairs (54719fce02), arm64 uint->mask conversions removed because “everyone has signed” (07664b7a53). Left open: nothing noted. - Go 1.28 — Done: user-visible mask queries
All/Any/None/TrailingZeros, Mask Xor/AndNot/Equal/Not (see AMD64 thread); SVE predicate masks (see SVE thread). Left open: nothing noted.
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.
- Go 1.26 — Done:
38b76bf2a3,5a0446d449,48f366d826,3ec0b25ab7,4eb5c6e07b,443b7aeddb,c4fbf3b4cf,3f6eabdf09. Left open: bounds-checked jump table “in the next CL” (38b76bf2a3). - Go 1.27 — Done:
4136ffed69,098d688071,aa80d7a7e6,b84139dfad. Left open: nothing noted.
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”).
- proposal #76473 simd: rename low-level SIMD package to simd/archsimd (GOEXPERIMENT) referenced by 1 of this thread's commits
- proposal #73787 simd/archsimd: architecture-specific SIMD intrinsics under a GOEXPERIMENT linked from #76473
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.
- Go 1.26 — Done: user-visible flag added (
4d2c71ebf9), branch default-on (2ef7106881), reverse merges:d4f5650cc5(2025-11-24),2b62144069(2025-12-03),9e09812308(2025-12-08); package moved tosimd/archsimd(144cf17d2c). Left open: experiment default off;simdname was reserved for a portable package. - Go 1.27 — Done: reverse merges
a004b4b1b0(2026-05-26),0d75fdfd4f(2026-05-29),33001a6225(2026-06-01) bring WASM, NEON, midway, GODEBUG=simd emulation switch to master; “for 1.27” API pruning (see API shaping thread). Left open: nothing noted. - Go 1.28 — Done: reverse merge
060245832f(2026-07-15);go.modmodule path forsimd/_genrepaired (db97d07dbb1.27); SVE reaches master (68f391a3c2). Left open: flag still off by default at HEAD (no commit changes this).
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.
- Go 1.27 — Done: user-visible first midway translator (
f1aa77c984), arm64 support in midway (b7b03d9e3d), emulations (40eb69725e,10231ce696,d7b3da6e25rotates,edbc93f594FP negation,cedf3d5870unsigned OnesCount), CLMUL (cea3788e05), broadcast (b7327ecacf), GODEBUG switch (a92cf0ee94), shared type file (b2b5aa4d80), endianness test (c910146a98), docs and examples (c086c0e53b,c83d55f695),StorePartreturns length (d2b991c840),HiToLorouted through Float64x2 (2458ba018e). Left open: BUG notes in doc.go: reflection calls, SIMD-dependent global var initializers, and modified names in stack traces do not work properly. - Go 1.28 — Done: user-visible architectural variation / “nclm” variant (
8dd7aa0168), GODEBUG parsing fixes (30843789bf,069f875c97), ReduceSum for floats and 8/16/32-bit ints (33c1f65079,366d1805d1,ba7fdba26b),Load*Partno longer reads past the slice end (beaf715aaa), generatedsimd_emulated.go(c1db0bc3c4), copies-test (a58ac9ec4a). Left open: SVE-sized vectors (rewriteSizes comment: “this will change for SVE and cannot just be a size-based choice”).
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.
- Go 1.27 — Done: user-visible initial Add/Sub/Mul (
8a1b0c8ecb), then the families above, test generator (61a10105e6), widening renamedLoLong->WidenLo(80ab7bc1fa). Release-trimming for 1.27: const-shift methods postponed (7de45e82f2),SetHiremoved andGetHirenamedHiToLo(20e9fe5d36),LookupOrKeepremoved (67c2e8b1fb), uint->mask conversions removed (07664b7a53), deprecatedAs...removed (9a23a318c9), Deinterleave/Transpose renamed (e50343d863), Reduce returns scalar (8d29cf23b1). Left open: multi-register TBL, const shifts needing rename. - Go 1.28 — Done: pairwise min/max, 64-bit-element Reduce, 8-bit ConcatAddPairs (
77984f0ab4,ac2f54e4aa,651ec67c3b), VCMTST (62bb6b4a80). Left open: nothing noted.
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.
- proposal #79781 simd/archsimd: support ARM64 SVE SIMD intrinsics under a GOEXPERIMENT referenced by 18 of this thread's commits
- proposal #73787 simd/archsimd: architecture-specific SIMD intrinsics under a GOEXPERIMENT linked from #79781
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.
- Go 1.28 — Done: user-visible MVP (
68f391a3c2,e5da70bf01,6fbc254146,fddf2d237d,1a8b63dad2), IfElse/Masked (4756f33d43), arithmetic/compare/bitwise families (5b7fced4a3,9de2d19c49,cc71e4955c,76b21f0229,a9aed73003,216cbea870, Sqrt/Ceil/Floor/Trunc/Round978a95f616..5bcfe1bdd1, Equal/NotEqual/GreaterEqual1dcfdf6fbf..2ba3d5f667, Max/Min/Div/MulAdd740fcaabb9,bfc1e31771,24fb3e7bc4,8921b16049), ReduceSum/Max (dbd397ac6d,387be25962), ShiftAllLeft (c72492ee6d), mask<->int (142236914f), SGEMM test with assembly baseline (2924f24765), SVE2 CPU detection (e077557cfb), RegMask widening. Left open: only a subset of SVE ops; predicate-register allocation bugs possible (RegisterSet fixed966c78ed74); vector-length handling is fixed-256-bit model.
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.
- Go 1.27 — Done: user-visible
8742584113through the above. Left open: nothing noted.
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.
- tracking issue #81819 simd: unify API generation around executable spec referenced by 11 of this thread's commits
- proposal #78902 simd: architecture and vector-size agnostic SIMD intrinsics under a GOEXPERIMENT linked from #81819
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.
- Go 1.28 — Done: initial spec (
810fe52b24), generator (aa7b8a796d), specls (9704c21b22), refgen, categories and commutativity (251583b606), shifts (f081a1ab32), docs filled from spec (ede51d4986,9450dc4b55), name filling (d9c017a1fa,021b49fbb9), host feature audit (1fe66956c2), “simd-common” done (b6680141e6),ScaleSaturatedrename (299a042625), parameter-name reconciliation (c35d7bdf94,5d8f28c94d). Left open: retirecategories.yaml, behavioural conformance beyond mathlib tests; impl-defined behaviour (“allowed wiggle room is not yet well-defined”).
API shaping and renames before stabilisation
Span: Go 1.26 → Go 1.28 · Status at HEAD: API still experimental; renames continue.
- proposal #76473 simd: rename low-level SIMD package to simd/archsimd (GOEXPERIMENT) linked from #78979
- proposal #78979 proposal: simd/archsimd: enable AMD64 architecture-specific SIMD by default referenced by 2 of this thread's commits
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.
- Go 1.26 — Done:
ead249a2e2,2c25f3e846,c2d775d401,416332dba2,0978935a99,3fdd183aef. Left open: more renames. - Go 1.27 — Done: renames above incl.
b70242b3b9,fed139a1b3,35ea327859,8583ef15c8,3e04a409d1,3d6c5301df. Left open: nothing noted. - Go 1.28 — Done:
23761e7bb2,299a042625,8031ffce5f. Left open: nothing noted.
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.
- Go 1.26 — Done:
94d72355f6,2080415aa2,be57d94c4c(Not),88568519b4. Left open: emulations named “wrong name” first. - Go 1.27 — Done:
10231ce696,40eb69725e,edbc93f594,cedf3d5870, test deduplication. Left open: nothing noted. - Go 1.28 — Done:
385f272158,ba7fdba26b,71e81366eb,23207a3342. Left open: nothing noted.
Changes in this slice that are not part of a thread are listed on the release pages: 1.26 · 1.27 · 1.28