Go: A Documentary

Data structures and type metadata

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

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

Thread map, Go 1.0 – 1.17

Thread1.01.11.21.31.41.51.61.71.81.91.101.111.121.131.141.151.161.17Status at go1.17 (or at the end of its era slice)
Runtime data structures and type metadata (Go 1.0–1.17)
Hash map core: from the first hashmap.c to the 8-entry bucket design●●●●●●●●●●●●●●●●●still the design at go1.17 (array of 8-slot buckets, tophash bytes, chained overflow, incremental growth) → continues as Swiss-table maps (GOEXPERIMENT=swissmap → only implementation) (1.24+) and Classic map maintenance and maps package helpers (pre-swiss) (1.18+)
Map growth, iteration order, overflow buckets and GC interaction●●●●●●●●●●still the design at go1.17 → continues as Classic map maintenance and maps package helpers (pre-swiss) (1.18+)
Map fast paths and compiler/runtime specialization●●●●●●●●still the design at go1.17 → continues as Classic map maintenance and maps package helpers (pre-swiss) (1.18+)
Hash functions and algorithm tables: memhash, AES hash, fallback hash●●●●●●●●●●●●still the design at go1.17 (AES hash where available, wyhash-based fallback; per-map seeds; hash functions generated per key type) → continues as Map hashing and hash functions (memhash, aeshash, fallback hash) (1.23+)
Compiler-generated equality and hash functions●●●●●●●●●●still the design at go1.17 → continues as Compiler-generated equality/hash functions and type descriptor emission (reflectdata) (1.18+)
Runtime type descriptors: layout, shrinking and name encoding●●●●●●●●●●●●●●●still the design at go1.17 (rtype with tflag, nameOff/typeOff/textOff, varint names, uncommon type following the base) → continues as internal/abi type layout unification and type metadata (rtype → abi.Type) (1.21+)
Interface values, itabs and type assertions●●●●●●●●●●●●●●●●still the design at go1.17 (compiler-emitted itabs, runtime itab hash table) → continues as Interface conversion: itabs, type switch and assertion caches (1.19+)
reflect redesign: from string parsing to Type/Value structs●●●●●still the design at go1.17 for the Type/Value API (opaque Value {typ, ptr, flag}); implementation tuned in 1.18+ → continues as reflect implementation: performance, allocation and escape behaviour (1.18+)
reflect dynamic types: ArrayOf, ChanOf, MapOf, SliceOf, FuncOf, StructOf●●●●●●●●still the design at go1.17 (synthesized rtype structures with GC info built in reflect) → continues as reflect implementation: performance, allocation and escape behaviour (1.18+)
reflect.Call, MakeFunc, method values and argument-frame GC precision●●●●●●●●●●●●replaced by the register-ABI implementation in 1.17 (see register ABI thread) → continues as Register ABI completion for reflect, memmove/memclr and bytealg across architectures (1.18+)
Register ABI support in reflect and runtime assembly (Go 1.17)●reflect assumes the register ABI only under GOEXPERIMENT=regabiargs and uses global register counts; not yet default → continues as Register ABI completion for reflect, memmove/memclr and bytealg across architectures (1.18+)
reflect performance, caches and API additions●●●●●●●●●●●●●still the design at go1.17 → continues as reflect implementation: performance, allocation and escape behaviour (1.18+)
memmove, memclr, duff routines and pointer-write atomicity●●●●●●●●●●●●●●●still the design at go1.17 → continues as memmove / memclr / memequal / internal/bytealg assembly optimisation (1.18+)
internal/bytealg and string/byte comparison primitives●●●●●●●●●●●●●still the design at go1.17 → continues as memmove / memclr / memequal / internal/bytealg assembly optimisation (1.18+)
Slices and append: growslice, makeslice and string/slice conversions●●●●●●●●●●●●●still the design at go1.17 (2x below 1024 old cap threshold, 1.25x above) → continues as Slice growth, string building and allocation-free bytes/strings helpers (1.18+)
Strings: concatenation, conversion and printing helpers●●●●●●●●●●●●still the design at go1.17 → continues as Slice growth, string building and allocation-free bytes/strings helpers (1.18+)
Soft-float, 64-bit division and complex arithmetic helpers●●●●●●●●●●●still the design at go1.17 (Go implementations in softfloat64.go, vlop, complex.go) → no continuation noted
C-to-Go conversion of runtime data-structure code●●●completed in Go 1.5; the runtime is Go with assembly stubs → no continuation noted
internal/reflectlite and checkptr●●●●●still the design at go1.17

Thread map, Go 1.18 – 1.28

Thread1.181.191.201.211.221.231.241.251.261.271.28Status at HEAD
Runtime: maps, built-in data structures, hashing, randomness and type metadata (Go 1.18–1.28)
Swiss-table maps (GOEXPERIMENT=swissmap → only implementation)●●●●●only map implementation since the experiment was deleted in 1.26 (2ae059ccaf); GOEXPERIMENT=mapsplitgroup (KKKKVVVV layout) still an opt-in experiment; micro-tuning ongoing in 1.27/1.28
Map hashing and hash functions (memhash, aeshash, fallback hash)●●●●hashing code for maps lives in internal/runtime/maps (moved from runtime), hybrid wyhash/AES keyed by MinAeshashSize, interface hash folds the dynamic type
Compiler-generated equality/hash functions and type descriptor emission (reflectdata)●●●●●●●●●●one equality/hash function per "signature"; large types call subroutines; alg and noalg descriptors share one symbol
internal/abi type layout unification and type metadata (rtype → abi.Type)●●●●●●abi.Type is the single type-descriptor definition used by compiler, linker, runtime, reflect and reflectlite; direct/indirect interface flag lives in TFlag
Interface conversion: itabs, type switch and assertion caches●●●per-call-site caches in front of interface type switches/assertions; itabs generated via rttype
reflect implementation: performance, allocation and escape behaviour●●●●●●●●●●●reflect.Value is stack-allocatable, many accessors inlineable, method Values eagerly materialised
Register ABI completion for reflect, memmove/memclr and bytealg across architectures●●●register ABI unconditional on all ports; non-register fallback paths deleted per port
memmove / memclr / memequal / internal/bytealg assembly optimisation●●●●●●●●●●per-architecture hand tuning continuing; arm64 SIMD memmove landed then reverted in 1.28 cycle
Slice growth, string building and allocation-free bytes/strings helpers●●●●●●smoothed growth formula in nextslicecap, bytealg.MakeNoZero used by bytes/strings
Runtime randomness: fastrand, chacha8rand and math/rand/v2●●●●●internal/chacha8rand generator with SIMD block functions on amd64, arm64, loong64, riscv64
Concurrent hash-trie map (internal/sync.HashTrieMap)●●stable internal API used by unique (and later sync.Map)
iter.Pull and range-over-func iterator types●●●stable; documentation churn
Classic map maintenance and maps package helpers (pre-swiss)●●●●old implementation deleted in 1.26

Runtime data structures and type metadata (Go 1.0–1.17)

Reader: Claude Sonnet. Cites 1,028 of 1,319 commits in this slice; every non-noise commit is cited, plus 13 larger commits the noise filter had set aside, which a separate gap reader described. Written in one pass from the design-heavy messages and subject lines; the long tail is cited in grouped “Other” items.

Release digest for this slice
  • Go 1.0 — Four years of foundations: hashmap.c, the C iface code, the 2009 reflect rewrite from string parsing to compiler-laid-out type structs, the 2011 Type/Value redesign and opaque Value, per-map hash seeds and randomized iteration.
  • Go 1.1 — The 8-slot bucket hashmap with incremental growth and fast-path routines, AES hash, reflect.MakeFunc/Select/Convert/*Of and method values, the removal of run-time code generation, ARM softfloat conditional execution and faster ARM division.
  • Go 1.2 — Hash lookup shortcuts, hashmap internals get typed GC info instead of bespoke GC code, itabs from persistentalloc, faster x86 memmove, Slice3, and ARM softfloat/preemption fixes.
  • Go 1.3 — Map accessors take key/value by reference, evacuated bit moved into tophash, intra-bucket iteration offset, precise GC reflect.Value (4 words), smarter slice growth, SSE memclr.
  • Go 1.4 — The C-to-Go conversion of maps, slices, hashing, interfaces and printing; the 2-bit GC type info; direct-interface policy moved into the compiler; random start bucket for iteration; 3-word Value; reflect call-frame cache.
  • Go 1.5 — Pointer-free maps unscanned, overflow pointer at bucket end, new fallback hash, 1-bit pointer bitmaps in type data, FuncOf/ArrayOf, duff routine generation and assembly cmpstring.
  • Go 1.6 — Atomic pointer writes in memmove/memclr, aeshash DoS hardening, removal of type.zero, concurrent-map-misuse detection.
  • Go 1.7 — Descriptor shrinking (nameOff/typeOff, static name encoding, tflag, embedded rtype fields), StructOf, compiler itabs and pointer-shaped convT2I, growslice/makeslice division avoidance.
  • Go 1.8 — Same-size map growth to bound overflow buckets, duplicate-itab handling across shared libraries, typemap/typelinks work, reflect.Swapper, decoderune.
  • Go 1.9 — sync.Map-backed reflect caches, hmap.extra and overflow preallocation, MakeMapWithSize, the compiler’s dxxx-to-LSym refactoring and deterministic signature ordering, C99 complex division.
  • Go 1.10 — A large series of map micro-optimizations (evacuate refactor, fast-path copies, specialized memhash), the new open-addressed itab table with compile-time function pointers, MIPS soft-float.
  • Go 1.11 — internal/bytealg created, arm64 aeshash/memmove/Compare/Index, map fast routines in type-specific files, old ARM softfloat deleted, method-set sorting in the compiler.
  • Go 1.12 — emptyOne/emptyRest early exit in map scans, overflow-checked multiplication in make/growslice/makemap, growslice bulk barrier, reflect return-value scanning fix, MapRange.
  • Go 1.13 — internal/reflectlite, reflect.IsZero, ifaceeq direct fast path, kindNoPointers removed, StructOf GC fixes, arm bytealg.
  • Go 1.14 — Hash functions generated only for map-key types with run-time interface hashing, -d=checkptr support, riscv64 bytealg/reflect, atomic memmove on arm64.
  • Go 1.15 — Cheap-checks-first generated equality algorithms, small-integer interface conversions without allocation, typehash equal to compiler hashes, quiet NaNs in softfloat.
  • Go 1.16 — Map seed rotation on clear, itab and GC-map content-addressable symbols, append growth keyed by capacity, Zero buffer, exported/non-exported interface method split, notinheap pointers indirect.
  • Go 1.17 — Register ABI support in reflect and runtime assembly (internal/abi, makeFuncStub/methodValueCall), wyhash 32-bit fallback, varint type names, VisibleFields, slice-to-array-pointer conversion, CanConvert.

Hash map core: from the first hashmap.c to the 8-entry bucket design

Span: Go 1.0 → Go 1.17 · Status at go1.17: still the design at go1.17 (array of 8-slot buckets, tophash bytes, chained overflow, incremental growth) → continues as Swiss-table maps (GOEXPERIMENT=swissmap → only implementation) (1.24+) and Classic map maintenance and maps package helpers (pre-swiss) (1.18+)

Paths not taken: Hash-map alternatives before Swiss tables (superseded)

Design docs & discussion

Design. The first map implementation is src/runtime/hashmap.c (2008, “mike’s map code”, bc0b4f0d2a; an earlier map.c was removed by that commit). In 2008 keys of pointer type were allowed by treating them like ints (5adbacb8e7). The table was a chained hash table whose pieces were freed on resize in 2010 (21518ea2af); large values were supported by b198c8612e; the spec rule “a missing key yields the zero value” was implemented in the runtime in 2010 (e781303e27) and descriptive panics for writes to nil maps followed (f2852ba618). Range over a nil map and deleting from a nil map were made legal (86145611b0, 28a50c7f51).

The structure that shipped as the long-lived design is Keith Randall’s rewrite (00224a356a, Go 1.1): the hashtable is “an array of 8-entry buckets with chained overflow. Each bucket has 8 extra hash bits per key to provide quick lookup within a bucket. Table is grown incrementally.” The commit reported Go time dropping from 0.51s to 0.34s and added hashmap_fast.c (specialized lookups, see the fast-path thread) plus compiler changes in cmd/gc/walk.c. The Hmap header shrank to a 48-byte allocation in Go 1.2 (4042b77776). Several first-bucket and lookup shortcuts followed: lazy allocation of the first bucket array (5b3ff61be6, NewEmptyMap 196→107 ns, 2→1 allocs), avoiding string hashing for small single-bucket maps by comparing lengths first (45b54ee7fb, a696ae56db), and faster range over an empty map (7b9df09261).

In Go 1.3 the runtime/compiler interface changed so that key and value are passed to map accessors by reference rather than by value (3278dc158e), part of removing vararg C calls for exact stack scanning; a shared zero-memory area, allocated by the linker for all non-reflect map types, is returned on a miss. Go 1.4 converted the whole implementation from C to Go (0c6b55e76b, “a bit slower, but not painfully so”, with the hash/eq stubs and nosplit still on the wishlist) and moved constants from the map header into the map type (668a55a882). Go 1.5 moved the overflow pointer to the end of the bucket so that pointers to zero-sized keys/values cannot point past the bucket into the next object (fbc56cf050; the nacl/amd64p32 padding fix is c5dff7282e), skipped equal(k,k) for reflexive key types (df1739c77d), and stopped scanning maps whose keys and values contain no pointers by keeping all overflow buckets alive through a separate array (85e7bee19f). In Go 1.6 the zero-value pointer in the type descriptor stopped being used: a miss now returns a runtime-owned pointer (38519e69d0) and the zero field was removed (af78482d6b); before that, reflect-created map types had their zeros allocated on demand and shared (7e26a2d9a8). Go 1.11 removed the hmap field from map types (4ebc67d334) and Go 1.12 introduced emptyOne/emptyRest slot states (4b05c01f9c). Go 1.9–1.10 added hmap.extra, covered in the growth thread.

Timeline.

Map growth, iteration order, overflow buckets and GC interaction

Span: Go 1.0 → Go 1.12 · Status at go1.17: still the design at go1.17 → continues as Classic map maintenance and maps package helpers (pre-swiss) (1.18+)

Design. Growth in the 8-slot design is incremental: when the load factor is exceeded (initially computed with floating point, later with overLoadFactor in integer math: 733567a186, 64-bit calculation b666f2860b, load-factor bug fix dbe3522c7f), hashGrow allocates a bucket array twice as large and each insert or delete evacuates the old bucket it touches plus one more from an advancing mark. Reads in Go 1.1 first tried to do grow work too (07b6add0ca) but this broke freebsd-386 and was reverted (7f0ee023ba); the final rule is that only writes evacuate (0e7144a875). The “evacuated” state was originally stored in the low bit of the overflow pointer; because the GC wants correctly typed, aligned pointers it was moved into the tophash entries (9aae6c1a8b).

Iteration order has been deliberately randomized since 2011 (e40d6e066a). In Go 1.3 the randomization changed from “start at a random bucket” to “start at the first bucket but walk each bucket at a random offset” (3be4d95731), which made single-bucket maps (<= 8 entries) iterate in one of only 8 orders; Go 1.4 returned to a random starting bucket for larger maps, bringing the iterate-and-delete pattern from O(n^2) to O(n log n) (55c458e05f, MapPop10000 270 ms to 6 ms) and kept intra-bucket randomness always (251daf8650).

Overflow handling got three mechanisms. First, Go 1.8 bounded overflow-bucket growth (9980b70cb4): after repeated fill-and-delete (issue 16070) overflow buckets were never reclaimed. Instead of repacking (which disturbs iterators) the runtime reuses the existing growth path with a same-size grow (“sameSizeGrow”) once enough overflow buckets have accumulated; the counter is 16 bits and incremented stochastically for large maps so the header does not grow. Second, Go 1.9 turned the pointer-free-map overflow slice into a general hmap.extra (a41b1d5052), added newoverflow and preallocated overflow buckets based on a simulation of inserting the midpoint element count for each bucket-array size (94e44a9c8e). Third, evacuation was made more consistent: the evacuation mark skips buckets already evacuated (064e44f218), and in Go 1.10 the evacuate routine was unified for X/Y destinations (77a9cb9b4c, removing a data-dependent branch said to be mispredicted about half the time), split into advanceEvacuationMark (e0b34e7be7), made an evacDst top-level type (776c33ee5e) and given specialized evacuate_fast32/64/str copies (4cf7f909c6, 3c182a12f7, db9f5917fc, 0480bfe57f, 8adaf68570) that drop handling of non-reflexive and indirect keys. Pointer-free bucket memory is no longer cleared on grow (3f972df4a7) and delete clears only pointer-containing memory (0c7fd56951). hmap.extra.overflow was split into overflow and oldoverflow fields (1d3ad6733e).

GC interaction passed through three stages: bespoke hashmap GC iterator code (Go 1.1, 1e01fba2fc), compiler/reflect-generated type information for the bucket type with that code deleted (Go 1.2, fb376021be), and in Go 1.5 skipping scan when neither key nor value has pointers (85e7bee19f). Reflect’s MapOf had to build matching bucket types (3660b532ac, 5bc1cef869).

Timeline.

Map fast paths and compiler/runtime specialization

Span: Go 1.1 → Go 1.17 · Status at go1.17: still the design at go1.17 → continues as Classic map maintenance and maps package helpers (pre-swiss) (1.18+)

Design. hashmap_fast.c appeared with the new hashmap (00224a356a) to give int32/int64/string keys dedicated access routines without calling the generic hash/eq through the type; the compiler (walk.c) selects them. The Go 1.4 port kept them as mapaccess1_fast32/64/str and friends (0c6b55e76b). The big optimization session is Go 1.10: strength-reduced key pointer arithmetic using add (dad5d76e8f, MapStringKeysEight_* ~-40%; 61043d4671, 29e6bdc69c, 83ae9b07b8), checking length before tophash in string maps, preferring direct key comparison over tophash when comparison is cheap (ec0e2edd3b), refactoring insertion-slot tracking so b.tophash does not escape (8585f9fdb1), storing new keys in mapassign_fast32/64 with direct assignment or writebarrierptr instead of typedmemmove after an earlier last-minute 1.9 fix (25bbb695c8), and only clearing the key’s pointer in mapdelete_faststr (dc10aeeff8). memhash32/memhash64 were specialized (2a56b023af, 176cd48e57 for big endian). A walking helper removed a nil check in the inner loop (0b0cc4154e, 77871cc664, c50a9718a6). Go 1.11 split the fast routines into map_fast32.go, map_fast64.go, map_faststr.go (d58593d8aa). Compiler-side specializations include avoiding string allocation for m[string(b)] and its struct/array-literal generalization (a0f57c3fd0), and make(map) call forms with int hints (cbc4e5d9c4).

Timeline.

Hash functions and algorithm tables: memhash, AES hash, fallback hash

Span: Go 1.0 → Go 1.17 · Status at go1.17: still the design at go1.17 (AES hash where available, wyhash-based fallback; per-map seeds; hash functions generated per key type) → continues as Map hashing and hash functions (memhash, aeshash, fallback hash) (1.23+)

Design docs & discussion

Design. Early runtime hashing was an alg table per type: Go 1.0 replaced a uint8 algorithm id with a table pointer (b9ccd077dc), with hash and equal functions taking a result pointer so compiler-generated code could be called from C. A float64 hash bug was fixed twice in 2012 (022aac7883, facee93a86) and Go 1.1 improved continuity of the hash computation (51fe5444fa) and added the AES-NI hash, first for compound objects (6b57329268). Go 1.4 converted hashing to Go calling convention with real closures (a2a9768414, 7aa4e5ac5f for equality), made non-empty interface hashing use something other than return 0 (f098a29630) and hashed floats on bytes rather than words so maps of floats get linear performance (5fbcdb2661); aeshash was disabled at compile time on NaCl (18ab35f7ba).

The fallback hash (used on arm, power and x86 without AES) was replaced in Go 1.5 with “a mixture of ideas from cityhash and xxhash” (cda0ea1c0e: ~no slower on small objects and up to ~50% faster on large ones on ARM), seeded with startup randomness (ce5cb037d1), and the size argument was removed from hash and equal algorithms (d5e4c4061b; sizes needed by generic code come through a closure that points at an assembly stub, binary 0.07% smaller). Go 1.6 made aeshash DoS-resistant by scrambling the seed before xoring with input and using table-dependent seeds for all stripes of strings > 16 bytes, costing 4 aesenc instead of 3 (91059de095, small hashes +5–10%). Go 1.10 specialized memhash32/64 (2a56b023af), Go 1.11 implemented aeshash on arm64 (73e0c302b2, Hash64 196→73 ns), and Go 1.17 switched the 32-bit fallback to wyhash (78f9015236, Hash64 121→102 ns).

Hash-function generation changed in Go 1.14: instead of generating hash functions for every type “just in case”, only map key types get them, and interface{} keys are hashed at run time by type dispatch (36f30ba289). The hash function moved from the key type to the map type, leaving a single equal in the descriptor (cmd/go dropped from 504 to 10 generated hash functions, -1.0% size, interface-key maps ~20% slower). Go 1.15 added a pointer fast path for interface hashing (afd691c579, MapInterfacePtr -33%) and made runtime typehash match compiler-generated hashes exactly because a reflect-made map assigned to a named map type could otherwise change hash function (2b8e60d464).

Seeding and DoS: per-map seeds (85aeeadaec), seed always incorporated first (63bee953a2), and in Go 1.16 the seed is rotated when a map is cleared via the for k := range m { delete(m,k) } idiom or by clearing (93810ac1f4, 1a119edd8b).

Timeline.

Compiler-generated equality and hash functions

Span: Go 1.0 → Go 1.17 · Status at go1.17: still the design at go1.17 → continues as Compiler-generated equality/hash functions and type descriptor emission (reflectdata) (1.18+)

Design. From Go 1.0’s alg table pointer (b9ccd077dc), struct and array types get compiler-synthesized eq and hash functions (cmd/gc/subr.c/alg.go, later reflectdata/alg.go). Go 1.7 simplified their generation (199cc194ac) and made alg prologue/epilogue match (e0032df81e), removed Type.Down uses (d3794f88e9), and stopped generating algs for map buckets (a6abc1cd70); alg generation after dowidth was fixed in 1.4 (8d47b08255). Go 1.9 sped up small-array comparison (3bdc2f3abf). Go 1.11 ensured alg functions are generated when the compiler calls them (c801232525).

Go 1.15 is the main optimization pass, by Josh Bleecher Snyder: order checks cheapest-first. Runtime calls (memequal, efaceeq) go last (79648bde2d); string equality is split into a cheap length half and a late contents half (daae72e88e, 5029c3671d); the same for interfaces (type pointer first, f4e13b83aa, 1cc7be89a9); arrays of interfaces and strings use two loops (7eab9506c9, 1c4e9b2eda, about 17% larger code) and small arrays unroll the first loop (c9591d73ef); an ordering bug was fixed (8c8045fd38); AlgKind became a stringer (ab3bd2c15f). Go 1.16 introduced a single not-equal label to avoid block recombination (c95bd2e6a9), and Go 1.17 reorganized the array hash loop (617383377f) and guaranteed no unaligned loads on strict-alignment architectures (873401df5b). Go 1.14 limited hash generation to map keys (36f30ba289) and Go 1.13 stopped marking the argument array as noalg (343b7fa626).

Timeline.

Runtime type descriptors: layout, shrinking and name encoding

Span: Go 1.0 → Go 1.17 · Status at go1.17: still the design at go1.17 (rtype with tflag, nameOff/typeOff/textOff, varint names, uncommon type following the base) → continues as internal/abi type layout unification and type metadata (rtype → abi.Type) (1.21+)

Design docs & discussion

Design. In 2008–2009 reflection parsed type-signature strings at run time (a cache avoided repeated parsing, 842e1a9aa7; tags and PtrTo needed special support 12a3435869). In July 2009 the compiler began laying out the data structures ahead of time and the reflect structures were unified with the interface runtime structures (4793400bd1, “no more parsing of strings”; a7b4e9f03e, 58c4142e74); the old parsing runtime Type was deleted in 2012 (6a75ece01c, 121a16fa95). Further rtype changes before Go 1.1: pointer word in CommonType (8d36a78440), PtrTo support from gc/ld (44fd7573aa), type-kind constants moved to a separate file (ede6718cd7), an unused gc field (65e61d5770, Go 1.1 “unused for now”). In Go 1.1 reflect.ArrayOf/ChanOf/MapOf/SliceOf required finding existing types in the binary, so the linker emitted a list of such types and the interface{} header was dropped from the runtime type representation, a net -156 kB in godoc (1120982590).

Between Go 1.3 and 1.5 the GC information evolved: the 2-bit “sparse” compiler format (cd17a717f9, Go 1.4, -25% memory, -3–7% binary size), reflect: generate unrolled GC bitmask (3cf9acccae), kindNoPointers/KindDirectIface flags (1806a5732b), and 1-bit pointer bitmaps in type descriptors and GC programs (6d8a147bef, Go 1.5: “1 bit per word, down from 2”). ptrdata size was added (98a9d36837), kindNoPointers was removed in Go 1.13 (db16de9203).

Go 1.7 is the descriptor-shrinking release, driven by David Crawshaw (issue 6853) and measured as binary-size cuts: the string header embedded in rtype (a858931200, cmd/go -1%), uncommonType.name removed (0231f5420f, -0.5%), ptrToThis removed (30f93f0994, -0.2%) then brought back for reflect.New speed (e369490fb7, 36.3→29.5 ns), the *uncommonType pointer replaced by a flag with the contents following the rtype (a24b3ed753, godoc -180 KB), slices removed from funcType (8df733bd22, godoc -2.4%; limits of 65535 inputs, 32767 outputs, FuncOf 128), the second method type computed at run time with reflect.FuncOf (f2772a4935, jujud -5.6 MB), and a new static name encoding (24ce64d1a9): first byte bit field (exported, tag follows, pkgPath follows), two-byte big-endian length, string data, optional tag, optional import-path pointer; cmd/go -164 KB. Method tables, names, pkgPath, rtype string and uncommonType were converted to offsets: typeOff (7d469179e6), nameOff in method tables (95df0c6ab9), pkgPath names (f120936dff), rtype string (1492e7db05), uncommonType (c165988360), with go.string.* free of pointers (4d920410d2) and unexported method type info removed (9f38796270, 44d3f89e99). tflag-based Name() fast paths (797dc58457, ed9362f769, e75c899a10) recovered JSON-decode regressions; StructOf clears tflag (4b64c53c03), and many-field structs needed an extra 8 bytes for every type with methods (af0fc83985).

Multi-module resolution (buildmode=shared/plugin): typeOff is relative to firstmoduledata.types; for other modules the runtime keeps a typemap that maps duplicates to the canonical rtype so each type has one *rtype, and reflect-created types use the offset as a global ID in a runtime reflect offset map (7d469179e6: linux/amd64 PIE jujud -3.5 MB, 4.2%). Go 1.8 made typelinks init go forwards (bee4206764), made typemaps visible to the GC (f4c7a12c2c), resolved offsets using the source module (34c480afa8) and wrote type symbols referenced in ptabs (8eb9fdaa01, 5923df1af9). Go 1.14 fixed textOff with multiple text sections (0ae9389609), Go 1.16 used a sentinel offset -1 for unreachable methods (0ab72ed020), Go 1.17 made names longer than 65535 possible with a varint length (287025925f).

Timeline.

Interface values, itabs and type assertions

Span: Go 1.0 → Go 1.17 · Status at go1.17: still the design at go1.17 (compiler-emitted itabs, runtime itab hash table) → continues as Interface conversion: itabs, type switch and assertion caches (1.19+)

Paths not taken: GOEXPERIMENT=largeiface (never landed)

Design docs & discussion

Design. src/runtime/iface.c (2008) implemented interface conversion, with new interface error messages (6f07ec721a), interface equality (e2e8aa0ee1) and zero-interface handling (900e076d21); interfaces holding interfaces were disallowed in 2009 (4b8c13dc20). An Itab caches the mapping from (interface, concrete type) to method pointers; in Go 1.2 itabs moved from mallocgc to persistentalloc (b36f2db12a). The Itab struct moved to runtime.h (5c1422afab).

Go 1.4 decided the “direct interface” policy in the compiler: isdirectiface in cmd/gc/subr.c is the only place deciding which types are stored in the data word, and the policy propagates through reflect metadata as KindDirectIface (1806a5732b). It was done so the representation could change for the concurrent GC (no ambiguity whether the data word is pointer or scalar); the follow-up made interfaces always hold pointers (33e910296e in reflect, [dev.cc]). The iface code was converted to Go (483cb61921, b53b47f5ac). Go 1.5 made _, ok := i.(T) inlinable (25e793d7ea, assertion benchmarks -80%) and fixed uninitialized memory in assertE2I2 (c4092ac398) and interface comparison (05a3b1fce5). Go 1.6 made iface/eface handling type-safe by taking iface/eface parameters (84afa1be76) and instrumented convT2E/I for race (9172a1b573).

Go 1.7 shifted work to the compiler: itab symbols are emitted by the compiler and convT2I is a two-word copy for pointer-shaped types (7427f2c4bd), with the runtime inserting compiler itabs into its hash table at init (7043d2bb5e), and convT2{I,E} no longer handling direct interfaces (998c8e034c); getcallerpc fixes (db44223fde, 260b7daf0a); wrong-panic fix (09834d1c08). Go 1.8 had to cope with shared-library duplicates: go.itab.* symbols were first module-local (8b07ec20f7, issue 17594: the same itab added twice created a list cycle), then made global again with additab called once per itab (1ec64e9b63, issue 18252), with a mark-local-earlier compiler tweak (4e584c5203) and statically initialized interface values (dfc56a4cd3). Go 1.10 set itab function pointers at compile time (89d74f5416, shorter startup) and replaced the linked list with a growable open-addressed hash table with quadratic probing and 75% max load; the read path takes two atomic loads (3d1699ea78, BenchmarkItabAll 228 ms to 4.3 ms with fcf445dce2), removing bad and link fields (98d0634b7a, 04d6f982ae), initializing itab.hash always (fcf445dce2), and not publishing a table before growth finishes (d30f99647a). Go 1.13 sped up ifaceeq for direct interfaces (694cd005c3, EfaceCmpDiff -29%) and set itab.fun[0] only on success (8d86ef2216). Go 1.15 avoided allocation when converting small integers to interfaces via staticuint64s (9828c43288; compiler -S allocs -14%). Go 1.16 made itab symbols content-addressable (cecb7a1cf3, requiring global uniqueness since pointers are compared).

Timeline.

reflect redesign: from string parsing to Type/Value structs

Span: Go 1.0 → Go 1.4 · Status at go1.17: still the design at go1.17 for the Type/Value API (opaque Value {typ, ptr, flag}); implementation tuned in 1.18+ → continues as reflect implementation: performance, allocation and escape behaviour (1.18+)

Paths not taken: A two-word reflect.Value (never landed)

Design. reflect in 2008 parsed type strings, with separate value types per kind (IntValue, PtrValue, …). The July 2009 rewrite replaced string parsing with compiler-laid-out structs (4793400bd1, a7b4e9f03e, 58c4142e74) and added chan/map/method reflection (764b6ec1aa, 5ddaf9a098, 12ebbe7463). 2010 added Kind (45bdf0367e) and Type.Bits. On 2011-03-03 PtrTo/Value.Addr made it possible to take addresses to call pointer-receiver methods (e46acb091f, old Addr renamed UnsafeAddr). In April 2011 reflect moved to Type as an interface implemented for all kinds and Value as a struct (fb175cf77e, many methods renamed: Elem to Index/MapIndex, Get to Int/Float…, v != nil to v.IsValid()); the motivation was an allocation-free Value, with the first CL a wrapper over the old API. Subsequent steps: inline methods (7b6ee1a5d4), Set semantic change so that assignable values always refer to other memory (“cannot Set result of NewValue anymore”, gob test mallocs 6/6 → 3/5, 40fccbce6b), assignability matching Go plus Implements, AssignableTo, CallSlice (e1ee3b5db6), and opaque Value struct in Nov 2011 (a479a45548, JSON benchmarks about 1.8x faster). Struct tags became key:“value” convention with StructTag.Get (25733a94fd). Other 1.0 API: MethodByName, Value.Bytes/SetBytes, Value.NumMethod, Zero, PtrTo, FieldByName with embedded structs and FieldByIndex.

Value layout continued to change under GC pressure. Go 1.3 rewrote Value to separate pointer from non-pointer information, making it 4 words, “needed for precise gc and copying stacks” (cbc565a801); reflect select/chan ops stopped using interface words (873aaa59b7, one allocation per receive case). Go 1.4 shortened it back to 3 words, scalar no longer needed because interfaces always hold pointers (a1616d4a32).

Timeline.

reflect dynamic types: ArrayOf, ChanOf, MapOf, SliceOf, FuncOf, StructOf

Span: Go 1.1 → Go 1.14 · Status at go1.17: still the design at go1.17 (synthesized rtype structures with GC info built in reflect) → continues as reflect implementation: performance, allocation and escape behaviour (1.18+)

Design. To create []T, chan T, map[K]V at run time reflect first looks for an existing instance: the linker emits a list of such types (1120982590, “typelinks”), and only if absent synthesizes a new descriptor. Go 1.1’s ArrayOf was unexported pending garbage-collection support and a correct algorithm (equality and copy), tracked as issue 4375; GC information was added for ChanOf, MapOf, PtrTo, SliceOf (3660b532ac). PtrTo of an unnamed type with embedded fields was fixed in Go 1.5 (7feb424928) and the pointer lookup consults existing type data by string before synthesizing, required for dynamic linking (2f0828ef7c). Go 1.5 added FuncOf (e1c1fa2919, d36cc02795 for PtrTo(FuncOf)) and exported ArrayOf (918fdae348, e00e65638a) and ChanOf direction (48d63035ce).

Go 1.7’s StructOf (633ab7426a) creates new struct types from StructFields in the form structTypeWithMethods to accommodate the GC; embedded interfaces with unexported methods and non-ASCII names were initially unsupported (+0.04% bin/go, reflect +6.2%). The restriction on UTF-8 names was lifted (9d36cac99c), methods that cannot work panic (87d5f6b9f6, Go 1.9), invalid field names rejected (538b3a5f37), embedded method cases fixed (83092a40ac, 0e4a0b93d2), GC programs for StructOf fixed in Go 1.13 (05b3db24c1, 9dc3b8b722, b7b7b4d6d4), unexported fields allowed in Go 1.14 (2d1c033259) and PkgPath recorded (ba66797392). Other: ArrayOf with zero-size element (9bced47706), SliceOf/ArrayOf string fixes (bddfc337f9, a4864094f0), ChanOf string parenthesization (407bf0ca67).

Timeline.

reflect.Call, MakeFunc, method values and argument-frame GC precision

Span: Go 1.1 → Go 1.17 · Status at go1.17: replaced by the register-ABI implementation in 1.17 (see register ABI thread) → continues as Register ABI completion for reflect, memmove/memclr and bytealg across architectures (1.18+)

Design. reflect.MakeFunc (ba4625c66f, 1.1) first generated code at run time through makeFuncStub, an assembly trampoline per architecture; “Step 3 of go11func” (b1b67a36ac) stopped the run-time code generation. Method values (3be703665e) use methodValueCall with a methodValue context struct. Frames: reflect.call assembled the arguments into a callXX frame; in Go 1.3 callXX routines no longer call the stubs directly but inline their behavior (85138da832), the argument frame gets precise GC info (2af7a26f1e) and in Go 1.4 call frames are cached with sync.Pool (67f8a81316, 634→338 ns, 1→0 allocs). The runtime needs to find the argument maps of these stubs during traceback (f0d44dbeaf, d5bd797ee5). Go 1.12 fixed a subtle bug (issue 27695): results in the frame must not be scanned at the start of a call (uninitialized) but must be scanned at the end; a boolean slot in the makeFuncStub/methodValueCall frame tells the runtime, and the bitmap always covers args plus results (ef50373983). Related: write-barrier fixes for method funcs (e35a41261b), callReflect result copy without write barrier (7e987b7b33), and Go 1.15 zeroing stack slots before barrier writes (3dec253783).

Timeline.

Register ABI support in reflect and runtime assembly (Go 1.17)

Span: Go 1.17 → Go 1.17 · Status at go1.17: reflect assumes the register ABI only under GOEXPERIMENT=regabiargs and uses global register counts; not yet default → continues as Register ABI completion for reflect, memmove/memclr and bytealg across architectures (1.18+)

Design docs & discussion

Design. internal/abi was created in the [dev.regabi] branch to hold ABI constants shared by runtime and reflect (baa6c75dce). reflect implements the register-assignment algorithm itself (28c5fed557 adds makeFuncStub/methodValueCall support): stack-assigned arguments follow the spec and compiler; zero-sized types need their alignment counted in stack offsets (a2f7067233) and the two short-circuits for zero-sized types were removed because an array of non-zero length of zero-sized elements must be stack-assigned (34b87b4a1a); spill space calculation fixed (cda8ee095e), counts undone on register assignment failure (51cd074c59), pointer register results kept alive in callMethod (ef36e4fd0e), stack-to-register translation in callMethod (9d46ee5ac4), ctxt preserved across moveMakeFuncArgPtrs (4fb74e0555), methodValueCall frame size on mips64 (41cf18eda7), and callReflect/callMethod are called as ABI0 (d446cb7cff).

reflect uses global register counts rather than the internal/abi constants so those constants can be non-zero (enabling the runtime’s spiller/unspiller) while reflect still works in ABI0, “turning two things we need to flip into one” (c5a1c2276e, 8cfbf34dd9). 27d306281c makes finalizers, reflect calls and Windows syscall callbacks assume the register ABI when GOEXPERIMENT=regabiargs is set. For the runtime and bytealg assembly, 849dba07a5 ports performance-critical functions (memmove, memclr, memequal, cmpstring-like) to read register arguments directly so builtin calls from compiled ABIInternal code avoid ABI wrappers (wrappers cost on many benchmarks, e.g. BiogoKrishna -4.6%); 8f4c5068e0 does the same for internal/bytealg; compile-time memequal_varlen is referenced as ABIInternal (411860251e) and method wrapper tail calls are disabled under RegabiArgs (45e87cd3ec).

Timeline.

reflect performance, caches and API additions

Span: Go 1.4 → Go 1.17 · Status at go1.17: still the design at go1.17 → continues as reflect implementation: performance, allocation and escape behaviour (1.18+)

Design. After the Go 1.4 Value shrink, the focus was cost on hot paths (JSON decoding, protobuf). Caches moved from RWMutex maps to sync.Map in Go 1.9, giving large multicore speedups (Call-48 133→12 ns, CallArgCopy +13x, 33b92cd6ce). Name() was optimized using tflag (797dc58457, ed9362f769, e75c899a10), NumMethod()==0 avoids a lock (3c6ed76da2, CodeDecoder -8%), directlyAssignable checks tflagNamed rather than calling Name (8057c0887f, 32.7→6.6 ns), flag.mustBe* methods were made inlinable (788e038e5d), reflect.Zero uses a shared zero buffer under 1 KB and Set uses memclr when the source is known zero (8925290cf7), IsVariadic cached in Call (6cadfe2fee, ~-9%), Swapper added in Go 1.8 (ca04091f5b), Value.MapRange/MapIter in Go 1.12 (ede5958385), IsZero in Go 1.13 (c40bffd905), StructField.IsExported, Method.IsExported (b83d073e9e), VisibleFields (009bfeae86), slice→array-pointer Convert that makes ConvertibleTo true although Convert may panic (760d3b2a16), CanConvert (48c88f1b1b), and struct tags with multiple keys (5d12434eee), all Go 1.17.

Timeline.

memmove, memclr, duff routines and pointer-write atomicity

Span: Go 1.0 → Go 1.17 · Status at go1.17: still the design at go1.17 → continues as memmove / memclr / memequal / internal/bytealg assembly optimisation (1.18+)

Design. Go 1.1 removed a memset/memclr shim (3a50bc1a24) and fixed amd64 memmove overflow (1b8f51c917). Go 1.2’s x86 memmove (6021449236): REP instructions have a high startup cost, so small sizes are handled with straight-line code; REP MOVSx is used for large sizes with a ~1 KB cutover; up to 128/256 bytes uses SSE loads of all data before any stores, which makes copy direction irrelevant. Go 1.3 replaced REP STOSQ in memclr with explicit SSE writes (da7cf0ba5d, Memclr5 22→5 ns). Go 1.4 widened MOV-SSE copies to 257–2048 bytes (a712e20a1d) and removed a pointless JMP-to-RET (bc9e160443); memclr memclrBytes to Go (68f91aea57). Go 1.5: auto-generated duff routines (ad3600945a, mkduff.go), faster duffzero mixing STOSQ and MOVQ/ADDQ blocks (7e0c11c32f), MOVUPS zeroing (2421c6e3df), duffcopy with movups (5cf281a9b7). Go 1.6 / Go 1.14 added the rule that memmove/memclr must write pointer-sized units atomically because the concurrent GC may observe partial pointers (issue 12552, 4b7d5f0b94, ppc64 and arm64 word copy in 2c911143fd, 168a51b3a1, amd64p32 4-byte writes 9f6df6c940, arm64 ffbc02761a in 1.14, documented in 9b5bd30716, memclrNoHeapPointers atomicity documented in 1.16 by 68058edc39). Go 1.7: MOVSB for unaligned moves (6a33f7765f, UnalignedDst4096 -41%) but not when CPUID lacks fast REP MOVSB (4b209dbf0b); ppc64 memmove speedups (c4807d4cc7). Go 1.8: ppc64x memclr (3107c91e2d), 4-byte move special case to avoid store-forwarding stall (a96e117a58, Issue18740 -36%). Go 1.10–1.12: arm64 memmove/memclr SIMD, ppc64x VSX with dcbz (d259815ccb, 213a75171d, aa9bcea390); jump tables rejected (f864d89ef7). Go 1.15: ppc64 short moves (cbd421f75b), MIPS64x memclr with MSA (918d4d46cd), duffcopy dummy for ppc64 (7939c43748). Slice-specific uses are covered in the slice thread.

Timeline.

internal/bytealg and string/byte comparison primitives

Span: Go 1.2 → Go 1.17 · Status at go1.17: still the design at go1.17 → continues as memmove / memclr / memequal / internal/bytealg assembly optimisation (1.18+)

Design. Faster equality and compare started in the runtime: b3946dc119 (Go 1.2: fast Compare for byte arrays and strings), 3d5daa2319 (Go 1.1, amd64/386 string and byte equality), 77f3e189d2 (identical-pointer fast path). Go 1.4 compared only up to min(len) (5ea131f442); Go 1.5 added runtime.cmpstring and bytes.Compare in assembly on arm (e2543ef62c) and arm64 (ee349b5d77), then ppc64 in Go 1.6 (32d3b96e8b) and a short-circuit when the slices are identical (805e56ef47). In Go 1.11 the assembly was consolidated in a new internal/bytealg package: IndexByte (403ab0f221), equality (1dfa380e3d), Count (45964e4f9c), Compare (f6332bb84a), short-string Index (ee58eccc56), wasm (b382fe28a7), and it dropped the dependency on runtime·support_avx2 (f8b28e28f8), with explicit argmaps (be9c994609), linkname prototypes (ef7ce57ac2) and later inlineable trampolines instead of linkname (ad4a58e315, Go 1.12). Per-architecture work: arm64 Compare (bfa8b6f8ff, d31ee64186, +750% for big), Index (d4e936cfd6, f8ef6ed24a), Equal (de28555c0b); arm word-wise Compare/Equal and shared IndexByte, Count (029a5af6a1, a734601bdf, 14a58d65e3, 3496ff1d19, 3cb1e9d98a); ppc64x Count/IndexByte/memequal (a0fad982b1, 23f7554194, 6994731ec2); mips64x Compare (78baea836d, a1b1ba7daf after a revert for a little-endian bug, 4f597abe77); s390x SIMD Count (b6245cef3c); riscv64 (0c703b37df, 19f6422e00); wasm (d0f10a6e68, 4a7cd9d9df); Rabin-Karp moved to bytealg (18a6fd44bb) and faster IndexAny (3af92acb9f); Index asm for ppc64x (8009a81f7a, 54af9fd9e6 for power9) and CBZ (99d6e3eec2); IndexString misuse fix (11cdbab9d4). In Go 1.17 the functions were ported to ABIInternal (8f4c5068e0).

Timeline.

Slices and append: growslice, makeslice and string/slice conversions

Span: Go 1.0 → Go 1.17 · Status at go1.17: still the design at go1.17 (2x below 1024 old cap threshold, 1.25x above) → continues as Slice growth, string building and allocation-free bytes/strings helpers (1.18+)

Design. append and copy began as C runtime helpers: a special single-byte case in copy (20c1ec263a, 38→16 ns), make([]T,0) without allocation (94c2536e3f), a type pointer passed to makeslice for GC (864c757a1c), and fix for zero-width element growth (782464aea5). Go 1.1 special-cased small byte appends (8cfed59941, Append1Byte 484→199 ns). Go 1.3 made growth account for the size class (bace9523ee, AppendGrowString -25%), checked for cap overflow (9121e7e4df), fixed a 32-bit overflow in stringtoslicerune (e1a91c5b89) and stopped setting m->locks around allocation so an append-only program triggers GC (8afa086ce6). Go 1.4 converted slice operations to Go (cc9ec52d73). Go 1.5 used memmove for append (497970f421), added missing write barriers for the copy (a73c1cef07), fixed a nil ptr with non-zero length (0d6a0d6c3f) and merged slice/sliceStruct (ab4df700b8).

Division avoidance dominated 1.7–1.12: growslice avoiding divisions (43ed65f869, GrowSliceBytes -29%; 6b0688f742), maxSliceCap table (a4dd6ea152, MakeSlice -32%), direct mallocgc calls (0150f15a92), power-of-two shifts (e53cc7ae93), sys.PtrSize use (37dd7cd040), and finally multiplication with overflow check in makeslice (05166bf54d), makemap and growslice (3b091bf6cc, Go 1.12), replacing the table. Simplified capacity check against memory (365594ad59) and overflow protection (371a5b494a), infinite-loop fix (f3e0d14313). append clears only uncopied memory (c1e267cc73, 1.8) and, in Go 1.12, uses bulkBarrierPreWrite plus memmove instead of per-element typedmemmove (96dcc4457b, AppendGrowString 123→73 ms) and skips the write barrier on the newly allocated slice (4363c98f62). The growth rule used the old length until Go 1.16 changed it to old capacity so append(s,0,0) and append(append(s,0),0) agree (2333c6299f). Compiler: avoid a spill in the append fast path (6b33b0e98e); race instrumentation (3f7f030c59, 089e482b3d). reflect.Append landed in 2010 (8b64cd9c5e).

Timeline.

Strings: concatenation, conversion and printing helpers

Span: Go 1.0 → Go 1.17 · Status at go1.17: still the design at go1.17 → continues as Slice growth, string building and allocation-free bytes/strings helpers (1.18+)

Design. Early string runtime (string.c) added NUL on allocation for low-level getenv (1a0a6f9d50), later removed again: strings are not zero-terminated and “no code must rely on zero termination” (9fa9613e0b, 1.3, reduces allocs by 1% on json). maxstring tracking caused a data race (f9f21aa1fb) and was removed in Go 1.8 (252093f120). Go 1.1 avoided allocation for "" + x + "" (8744d35dd3, fixed by 71643b2fdb). Go 1.5 stopped copying []byte for string concatenation and comparison when the string is not retained (205ae07cd3, 69cd91a598). Conversions: slicebytetostring optimized with inlined rawstringtmp and memmove (23be728950, -27%), single-byte strings use a static table (9372e3f5ef, bda42a7a78 staticuint64s), intstring uses static bytes (2f2e8f9c81), stack buffers not exposed (4d02b12417), decoderune for range over strings speeds non-ASCII decoding by up to 11% (d295174030, Go 1.8), minor spill elimination (880c967ccd). concatstring{2..5} made consistent with compiler use (35a8bbc9ea). Printing: print(float) (ac5a887d5d, ce0f1dee6f), -Inf (69879f0414), signed zero (f574726f16), printing numeric panic values (0d33dc3105, 972df38445), and the print implementation moved to Go (721c8735df, 597b266eaf, ef64d9ffcc).

Timeline.

Soft-float, 64-bit division and complex arithmetic helpers

Span: Go 1.1 → Go 1.15 · Status at go1.17: still the design at go1.17 (Go implementations in softfloat64.go, vlop, complex.go) → no continuation noted

Design. ARM (GOARM=5) floating point is emulated by an interpreter in the runtime: _sfloat runs after each instruction sequence and decodes FP instructions from the code stream. Go 1.1 added conditional-execution support (fb3a1b6821); Go 1.2 disabled preemption during software FP routines because the registers are kept per-M (e03dd07981), recorded _sfloat frame size and no arguments (8166b2da19, 6a13897bc6) and fixed arm preemption (cba880e04a, f4f2cf16b0); sqrt copy for softfloat (ee7bb07a53, Go 1.3). The interpreter was converted from C to Go in the dev.cc work (c81d248eca, 2ceca80e3f, Go 1.5), with nil-pointer handling (4b3906fec3), a fix for an SQRT register corruption (102436e800), add r11, pc, r11 (40af15f28e) and MININT conversion fix in the compiler (6db98a3c51). The old ARM softfloat code was deleted in Go 1.11 (f83e421268, -1065 lines) after the compiler gained new softfloat support for GOARM=5. MIPS softfloat routines were added for GOMIPS=softfloat (ac987df87c, Go 1.10), and Go 1.15 made soft-float results use quiet NaNs (d8bf079751).

Integer helpers: Go 1.1 prepared 64-bit int in the runtime with intgo typedefs (0b08c9483f), made ARM div/mod ~3.7x faster (3e3fa7b5f1, 281→75 ns), and Go 1.2 fixed uint64 division on 386 (70138a2108) and ARM div-by-zero tracebacks (b2794a1c2e). Go 1.4 implemented 64-bit division in Go (d11bb3b177) and converted float↔︎int64 conversions (32b3a48ea8, 0eaea6010a). Go 1.9 restored ARM div/mod stubs (79d05e75ca). Complex division: inlined float helpers (0157c72d13, 1.1; ARM -70%, 386 mixed), converted to Go (c403af83a6), made C99-compatible with new isNaN/isFinite/isInf/abs/copysign helpers (16200c7333, Go 1.9), and the NaN constant changed to match gcc’s for VFPv2 (6e9506a7b4).

Timeline.

C-to-Go conversion of runtime data-structure code

Span: Go 1.4 → Go 1.5 · Status at go1.17: completed in Go 1.5; the runtime is Go with assembly stubs → no continuation noted

Design docs & discussion

Design. Go 1.4 and Go 1.5 removed C from package runtime, file by file, as part of the dev.cc plan. In this area: maps (0c6b55e76b), slices (cc9ec52d73), hash and equality (a2a9768414, 7aa4e5ac5f), interfaces (483cb61921, b53b47f5ac), print (721c8735df, 597b266eaf), 64-bit division and float conversion (d11bb3b177, 32b3a48ea8, 0eaea6010a), complex division (c403af83a6). Earlier, goc2c was used as much as possible (67c83db60d, 1.3) to avoid architecture-dependent argument layouts and avoid maintaining stack-argument pragmas. The mechanical dev.cc commits converted header files (fee9e47559) and basic library routines (0c3c2c1724). Calling-convention helpers were added: throwgo (ec5d7ba95c), funcPC (0e07f1c99f), reflect trampolines moved into the thunk file (6baf173b67).

Timeline.

internal/reflectlite and checkptr

Span: Go 1.13 → Go 1.17 · Status at go1.17: still the design at go1.17

Design docs & discussion

Design. internal/reflectlite is a small copy of the reflect subset needed by errors (assignability and setting for As), added to avoid importing full reflect (9650726e79, 2735 lines), with context then dropping its reflect and fmt dependencies (88adc33827, 724a86fced) and sort using it too (39a51a4b0d). Go 1.14 introduced pointer-conversion checks under -d=checkptr: unsafe conversions from smaller to larger types are detected (dc72a2f95f), reflect and testing/quick were fixed to pass it (5eec0a91ea, 5a5854c2d1, 9989221ac1), messages improved (daeb5efb20, 1f231d74f6 in Go 1.15), failures use throw instead of panic (56d6b87972), only check up to ptrdata (08dd4ad7e3, Go 1.15), no alignment error for pointer-free base types (f4ddc00345), and Go 1.17 does not crash on nil pointers (b39e0f461c).

Timeline.

Changes in this slice that are not part of a thread are listed on the release pages: 1.0 · 1.1 · 1.2 · 1.3 · 1.4 · 1.5 · 1.6 · 1.7 · 1.8 · 1.9 · 1.10 · 1.11 · 1.12 · 1.13 · 1.14 · 1.15 · 1.16 · 1.17

Runtime: maps, built-in data structures, hashing, randomness and type metadata (Go 1.18–1.28)

Reader: Claude Sonnet. Cites 502 of 711 commits; 78 small fixes are uncited. Read commit messages only (long ones truncated); no diffs were read.

Swiss-table maps (GOEXPERIMENT=swissmap → only implementation)

Span: Go 1.24 → Go 1.28 · Status at HEAD: only map implementation since the experiment was deleted in 1.26 (2ae059ccaf); GOEXPERIMENT=mapsplitgroup (KKKKVVVV layout) still an opt-in experiment; micro-tuning ongoing in 1.27/1.28

Paths not taken: Hash-map alternatives before Swiss tables (superseded)

Earlier history: Hash map core: from the first hashmap.c to the 8-entry bucket design (Go 1.0 → Go 1.17)

Design docs & discussion

Design. The classic bucket-chained hmap (8-slot buckets, overflow chains, incremental evacuation) was replaced by a Swiss table in the new package internal/runtime/maps, modelled on Abseil and on github.com/cockroachdb/swiss (0733682e5f). A map is a directory of tables (extendible hashing, d94b7a1876: “splits a swisstable map into many swisstables. This keeps grow operations small”, so there is no incremental evacuation any more; a table is rehashed/split in one go and its size is capped). Each table is an array of groups: a 64-bit control word (one byte per slot: H2 = 7 hash bits, or empty / deleted-tombstone) plus 8 slots. Lookup probes group by group (probeSeq), computes matchH2 on the control word to get candidate slots, compares keys, and stops at the first group containing an empty slot. Deletion writes a tombstone unless the group has an empty slot. Small maps (<=8 entries) skip directory and table: the map points directly at one group (77e3d8cf13) and lookups scan the group linearly (808da68c1c), later with SIMD/SWAR match instead of a byte scan (bfc209518e, ~1.7x faster for unpredictable keys, from ~25ns to ~14ns). The ABI (internal/abi/map_swiss.go) and compiler side (cmd/compile/internal/reflectdata/map_swiss.go) were forked from the old map from day one (057b703407, 4f7dc282c4) so both could coexist behind the experiment; large keys/elems are stored indirectly using the same heuristics as the old map (b5fec2cf54), concurrent-write detection (fatal("concurrent map writes")) reuses the old design (2220fd3636). Hot-path work was done by the compiler too: on amd64 matchH2/matchEmpty are intrinsified with SSE (dce30a1920; still 8-slot groups to avoid changing memory layout; 0-3% faster), and the specialised fast32/fast64/faststr variants (f782e16162) were later devirtualised for hashing (252a8adbc0). Everything else in the thread is operational polish: tombstone reuse on insert (efa43c57b1), clear erases tombstones (4e63ae46e0), pruning tombstones before growing (05ed8a00e0), proper capacity-hint handling (3b424cfa9d: no allocation at hint 0; initial capacity sized to avoid growth on average).

Timeline.

Map hashing and hash functions (memhash, aeshash, fallback hash)

Span: Go 1.23 → Go 1.28 · Status at HEAD: hashing code for maps lives in internal/runtime/maps (moved from runtime), hybrid wyhash/AES keyed by MinAeshashSize, interface hash folds the dynamic type

Earlier history: Hash functions and algorithm tables: memhash, AES hash, fallback hash (Go 1.0 → Go 1.17)

Design. Map hashing is memhash{32,64}, strhash, and memhash over arbitrary memory, selected between AES-NI/ARMv8-crypto implementations and a portable multiply-mix fallback (hash64.go/hash32.go), seeded per process from hashkey/aeskeysched. Over this period the work was (1) closing weaknesses in the fallback, (2) moving ownership of the hash code into internal/runtime/maps so it can be inlined/intrinsified, (3) tuning when AES is worth using.

Timeline.

Compiler-generated equality/hash functions and type descriptor emission (reflectdata)

Span: Go 1.18 → Go 1.28 · Status at HEAD: one equality/hash function per “signature”; large types call subroutines; alg and noalg descriptors share one symbol

Earlier history: Compiler-generated equality and hash functions (Go 1.0 → Go 1.17)

Design. cmd/compile/internal/reflectdata writes runtime._type descriptors, itabs, uncommon-type method tables, and the ==/hash functions referenced from them (alg.go). Struct comparison logic was unified into cmd/compile/internal/compare (3c29aca436) so walk’s == lowering and generated eq functions share the “regular memory” analysis, and array equality loops are unrolled (1a955bcdb7: -18.6% geomean). In 1.21 equal/hash generation was moved to the walk phase and factored out (ea3054aecc, 864b54e2c3, 4636c57fe1). The 1.27 redesign (30dff416e4): instead of one equality function per type, generate one per signature, a compact summary of the comparisons needed. struct{i int32; j uint32; s string; e error} has signature M8SI (8 bytes regular memory, string, non-empty interface), so many types share one function. Generated equality functions in the go binary dropped from 634 to 286, binary ~1% smaller. Hash generation was moved onto the same signatures (0da8979210), with parsing simplifications (59ed013621, 1bd5dbfc41). In 1.28 large types call their eq/hash as a subroutine instead of inlining the body (16516099d7), and a type and its noalg variant (the descriptor without algorithms, used for hidden pointer/array types) now share one symbol; the ordinary descriptor wins and a noalg descriptor is “upgraded” in place (CL 829124 per 8fc7e76411), which needed follow-up fixes: restoring TFlagRegularMemory and not folding TFlag loads from noalg types (8fc7e76411), emission order alg before noalg (925ef8c02c), keeping the typelink on upgrade (f3e4f788ed, via setAlgAttrs). Compile time work: writeType reuses the already-looked-up type linksym (7ea76879d2, -5% wall on typescript-go/ast). SSA now folds loads of abi.Type.{Size_,PtrBytes,Kind_,Hash} and PtrType.Elem from statically known descriptors (5b218461f9; counts when compiling std/cmd: 75 Kind_, 50 PtrType.Elem, 14 Hash).

Timeline.

internal/abi type layout unification and type metadata (rtype → abi.Type)

Span: Go 1.21 → Go 1.27 · Status at HEAD: abi.Type is the single type-descriptor definition used by compiler, linker, runtime, reflect and reflectlite; direct/indirect interface flag lives in TFlag

Earlier history: Runtime type descriptors: layout, shrinking and name encoding (Go 1.0 → Go 1.17)

Design. Before 1.21 the layout of type descriptors was duplicated four times (compiler, runtime._type, reflect.rtype, internal/reflectlite) and had to be kept in sync by hand. The effort, driven by David Chase, collapsed this into internal/abi: constants for map layout (e22bd2348c), then the common abi.Type plus Method, Imethod, UncommonType, ArrayType, ChanType, FuncType, StructType (bdc6ae579a refactors “(basic) type struct into one definition” touching 46 files; d9a61be73f, a851511947, 5deeca38c2, acba24ff8c, 639957eb66, b768a1fbb5). Then runtime _type is redefined as abi.Type with an rtype wrapper for methods (a2838ec5f2, 2e93fe0a9f), reflect replaces nearly all *rtype with *abi.Type (6cf7602b71), internal/reflectlite is commoned up (3ee12d5702). A reflect change making rtype itself (not *rtype) implement Type was reverted (6d2309b576). The reorganisation also added bootstrap-with-gccgo support (6691f438c3). Later refinements: itabs use abi.ITab in reflect and drop a blank field (f2db96cd6c, cdd0ddaf45), abi.EmptyInterface, TypeOf, NoEscape (4742c52e10), abi.Kind* used throughout (daaf1f2220), go122 flag removed (3902e9ef4e), arch-family-based constants (f69703d389), bounds check constants (741a19ab41).

Timeline.

Interface conversion: itabs, type switch and assertion caches

Span: Go 1.19 → Go 1.28 · Status at HEAD: per-call-site caches in front of interface type switches/assertions; itabs generated via rttype

Paths not taken: GOEXPERIMENT=largeiface (never landed)

Earlier history: Interface values, itabs and type assertions (Go 1.0 → Go 1.17)

Design docs & discussion

Design. Type switches over interface cases and assertions to interface types called into the runtime (typeAssert, interfaceSwitch) each time. In 1.22 the compiler began emitting a per-site cache, “a hash table of a sample of all the concrete types that have been switched on at that source location”, recording the matched case number and the resulting itab for each concrete input type (39263f34a3); the same cache fronts assertions (cbcf8efa5f). Caches stay small: only two in all.bash exceed 100 entries (issue29264.go 101, issue29312.go 254, both at fmt.(*pp).handleMethods). Runtime itab handling: unnecessary nil checking removed (e7d582b55d), itab not re-initialised during missing-function lookup (2589a89468), unsafe.Slice in itabInit (9777ceceec), and no write barrier for code pointers in itabInit because on wasm a PC (function index << 16) can look like a heap pointer, crashing GC (9d2ed75ac0, itabInit marked //go:nowritebarrier). Plugin itabs no longer added twice (2d04c9d8b6).

Timeline.

reflect implementation: performance, allocation and escape behaviour

Span: Go 1.18 → Go 1.28 · Status at HEAD: reflect.Value is stack-allocatable, many accessors inlineable, method Values eagerly materialised

Earlier history: reflect redesign: from string parsing to Type/Value structs (Go 1.0 → Go 1.4) · reflect dynamic types: ArrayOf, ChanOf, MapOf, SliceOf, FuncOf, StructOf (Go 1.1 → Go 1.14) · reflect performance, caches and API additions (Go 1.4 → Go 1.17)

Design. The recurring theme is making reflect cheaper for encoding/json, text/template, etc. Strategies: reuse compiler/runtime specialisations (string-key map access via mapaccess_faststr, 23832ba2e2), avoid allocation (DeepEqual fast paths for simple kinds, a0c409cbc8: Tailscale HostInfoEqual 25.9µs→14.4µs, allocs 548→90), make hot methods inlineable (67d6be139c: Bool, String, Bytes for []byte, Len/Cap for slices; ~10-20% slower for string/map Len, substantially faster for slices; e948c3394e, c5edd5f616, 67d6be139c in 1.19), stop Value escaping (0ac72f8b96, be4fe08b57, 3e19dc2b23, 23ca330095, d573a8a8e8), and use equality functions in IsZero (1ab6b790be, then array/struct zero-bit-pattern shortcuts 12051f7d95 -69.56% geomean, ada5c2edb4, and a pointer comparison against the shared zeroVal that the compiler now uses for zero composites in interfaces, a8e0641d5b, -51.7% geomean). MapIter was reworked repeatedly: embedded hiter allocation (1b2d794ca3), Reset, SetIterKey/SetIterValue (54cdef1f10, 8dab959a8e), avoiding stack copies of hiter seen as duffcopy in profiles (89c527007f), then dropping mapiterkey/mapiterelem to speed Next (7ba074fe43), at the cost of a possible heap escape of the map bucket in test/escape_reflect.go.

Timeline.

Register ABI completion for reflect, memmove/memclr and bytealg across architectures

Span: Go 1.18 → Go 1.27 · Status at HEAD: register ABI unconditional on all ports; non-register fallback paths deleted per port

Earlier history: reflect.Call, MakeFunc, method values and argument-frame GC precision (Go 1.1 → Go 1.17) · Register ABI support in reflect and runtime assembly (Go 1.17) (Go 1.17 → Go 1.17)

Design docs & discussion

Design. The register-based internal ABI landed on amd64 in 1.17; this thread is the per-architecture rollout in the data-structure primitives: reflect.Call/MakeFunc/callMethod register spill/unspill, internal/abi IntArgRegs/FloatArgRegs constants, and ABIInternal ports of memmove, memclrNoHeapPointers, memequal, cmpbody, indexbyte, Index, Count. For each architecture the pattern was: add support (experiment-gated), always enable, then delete the fallback.

Timeline.

memmove / memclr / memequal / internal/bytealg assembly optimisation

Span: Go 1.18 → Go 1.28 · Status at HEAD: per-architecture hand tuning continuing; arm64 SIMD memmove landed then reverted in 1.28 cycle

Earlier history: memmove, memclr, duff routines and pointer-write atomicity (Go 1.0 → Go 1.17) · internal/bytealg and string/byte comparison primitives (Go 1.2 → Go 1.17)

Design. runtime.memmove, memclrNoHeapPointers, memequal, and internal/bytealg (Compare, Equal, Index*, Count*, IndexByte*) are hand-written assembly per GOARCH. Work follows microarchitecture features: alignment (PCALIGN loops), wider vectors (AVX2 double-lane Count, NEON, LSX, RVV), ERMS REP STOSB for memclr, Power9/10 instructions, and fast paths such as memequal returning immediately for equal pointers.

Timeline.

Slice growth, string building and allocation-free bytes/strings helpers

Span: Go 1.18 → Go 1.23 · Status at HEAD: smoothed growth formula in nextslicecap, bytealg.MakeNoZero used by bytes/strings

Earlier history: Slices and append: growslice, makeslice and string/slice conversions (Go 1.0 → Go 1.17) · Strings: concatenation, conversion and printing helpers (Go 1.0 → Go 1.17)

Design docs & discussion

Design. growslice used 2x growth below 1024 elements and 1.25x above, which was discontinuous and non-monotonic. 2dda92ff6f (1.18) replaces it with a smooth formula starting after 256 elements: starting cap 256→2.0x, 512→1.63, 1024→1.44, 2048→1.35, 4096→1.30 (actual growth is larger due to size-class rounding), and growth is now monotonic in initial capacity; 256 chosen to match the total reallocation count for building very large slices. nextslicecap (1d84b02b22) factors the computation out so specialised growslice variants share it, after a micro-tuning of the loop (c56f463412). For strings, bytealg.MakeNoZero allocates a []byte without zeroing, used where the caller overwrites every byte (bytes.Join/Repeat/ToUpper/ToLower, strings.Builder.Grow, transitively strings.Join/Map/Repeat/Replace…) (132fae93b7). Builder.Grow later rounds to size classes (968b71bce4).

Timeline.

Runtime randomness: fastrand, chacha8rand and math/rand/v2

Span: Go 1.19 → Go 1.26 · Status at HEAD: internal/chacha8rand generator with SIMD block functions on amd64, arm64, loong64, riscv64

Design docs & discussion

Design. Runtime randomness (rand, cheaprand) is built on a ChaCha8 stream generator in internal/chacha8rand (also backing math/rand/v2.ChaCha8): buffered output blocks, generic Go block function plus assembly with 128-bit vector math. In 1.22 math/rand/v2.ChaCha8 was added “at about the same speed as PCG (25% slower on amd64, 2% faster on arm64)” (6382893890), rolled back for big-endian and riscv64 failures (82fc03f9c9) and replayed with endianness fixes (d92434935f); runtime.fastrand was kept temporarily because of third-party linknames (dca2ef2361: to be removed in 1.24). Map iterators start at a random offset using fastrand64 (01b8f5e882).

Timeline.

Concurrent hash-trie map (internal/sync.HashTrieMap)

Span: Go 1.24 → Go 1.26 · Status at HEAD: stable internal API used by unique (and later sync.Map)

Design. HashTrieMap[K,V] is a lock-free-read concurrent hash trie: nodes are indirect (children indexed by successive hash bits) or entries (key/value with overflow chain). It was moved from internal/concurrent to internal/sync “in anticipation of using HashTrieMap from the sync package” (4804d0daca). It was generalised: keys compared with normal comparison (36b5b053be), relaxed value constraints (008904aba6), zero value ready to use (e8c5e6d635), and gained the full sync.Map operation set: CompareAndSwap, Swap, Store, LoadAndDelete, Delete, Clear, Range (85fa418fd5, 28bac5640c, 872031dc10, 700c7b95ae, 083045f99d, e5c4c79cc4, 5c7b7c7d60). An optimisation of CompareAndSwap/Swap (06e6b8efa4) was reverted (c112c0af13) after issue 70970 (regression test added 3f002abb60). unique.Make uses TypeFor (755c18ecdf).

Timeline.

iter.Pull and range-over-func iterator types

Span: Go 1.22 → Go 1.28 · Status at HEAD: stable; documentation churn

Design docs & discussion

Design. iter.Pull/Pull2 convert push iterators into pull iterators using coroutines (coroswitch) between goroutines. Hardening: next/yield called out of sequence would switch to the wrong goroutine with garbage values or crash the runtime; one bool now enforces strict alternation (ff743ce862, panics instead). Panics (1471978bac) and runtime.Goexit (9d2aeae72d) from the underlying iterator now propagate to the caller of next. Memory footprint reduced by grouping captured state in one struct (ba7b8ca336, one heap allocation instead of several; trade-off: unrelated state retained until stop).

Timeline.

Classic map maintenance and maps package helpers (pre-swiss)

Span: Go 1.18 → Go 1.24 · Status at HEAD: old implementation deleted in 1.26

Earlier history: Hash map core: from the first hashmap.c to the 8-entry bucket design (Go 1.0 → Go 1.17) · Map growth, iteration order, overflow buckets and GC interaction (Go 1.0 → Go 1.12) · Map fast paths and compiler/runtime specialization (Go 1.1 → Go 1.17)

Design. Fixes and tweaks to runtime/map.go and compiler lowering before and during the swiss transition: map constants centralised in internal/abi (“makes it much easier to change the size of map buckets”, e22bd2348c), a regression where the load factor became 6 instead of 6.5 (25e48765a4), clear marks bucket slots empty so in-flight iterators terminate (231f290e51; second iterator-after-clear fix 1f8fa4941f), maps.Clone/Keys/Values speed ups in runtime (b5a7f2eef7, f283cba396, c6fd0c22dc), maps.Clone mid-grow bug (b303fb4855), aliasing (16d3040a84), bucket sizing (1b4cf43e42), empty map with interface key (d32b4798f8), real type size for key/value iteration (cc47df017d), “user” vs “system” throws so concurrent map misuse is fatal (29bbca5c2c).

Timeline.

Changes in this slice that are not part of a thread are listed on the release pages: 1.18 · 1.19 · 1.20 · 1.22 · 1.23 · 1.24 · 1.27