Go: A Documentary

Compiler frontend

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

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

Thread map, Go 1.0 – 1.17

Thread1.01.11.21.31.41.51.61.71.81.91.101.111.121.131.141.151.161.17Status at go1.17 (or at the end of its era slice)
Compiler frontend (Go 1.0: 2008–2012)
Lexer, grammar and statement syntax (semicolons, chan operators, conversions)●the surface syntax is the Go 1 one; the yacc grammar go.y and hand-written lexer lex.c stayed the parser until the compiler was rewritten in Go (not in this range). → continues as The syntax package (parser, scanner, tree) (1.18+)
Package system, visibility and import resolution●capitalisation-based export and -p/-D/-I import resolution as of go1; compiler flags later reworked by the build system. → continues as Package paths, -p, and build-system contract (1.18+)
Export/import data format●textual export data in the object file's __.PKGDEF section was still the format at go1 and in 1.17 had moved through the binary format and then indexed export data → continues as Unified IR: one serialization for noding, stenciling, inlining and export (1.18+)
Constants and multi-precision arithmetic●arbitrary-precision constants as at go1 (mparith); later moved to go/constant and math/big → continues as Language features as seen by the type checker (by release) (1.18+)
Typecheck/walk separation and package-level declaration order●typecheck pass (typecheck.c) as one separate pass over a whole function tree, still the design at go1.17 in Go form → continues as Old typechecker and IR node-model cleanup (1.18+)
Interfaces, type switches, assertions and comparability●interface layout Eface/Iface and comparison rules as at go1 persisted; the runtime later moved to itab caches in Go-era reflect → continues as Type sets, union terms, interfaces and constraint satisfaction (1.18+)
Embedded fields, method sets and receivers●embedding via genembedtramp-style wrapper generation as at go1 → continues as Method receiver checking (1.18+)
Closures●closure conversion by value-or-reference capture classification as at go1; the compiler later rewrote it → continues as Frontend support for new language features and spec changes (1.18+)
Escape analysis and heap allocation●first escape analysis enabled by default in esc.c → continues as the escape analysis in later threads; no 1.18 link.
Inlining●first inliner, evolved massively afterwards.
Builtins and unsafe●builtin set as at go1 → continues as Language features as seen by the type checker (by release) (1.18+)
Channels, select, maps and strings as built-in types●compiler lowering to runtime calls as at go1.
Initialization and static data●sinit.c static init as at go1 → continues as Static initialization and package init (1.18+)
Composite literals●Go 1 rules; type elision for struct/array elements came later.
Variadic functions (...)●...T as a property of func types.
Statement and flow rules●Go 1 rules.
Diagnostics, error recovery and compiler plumbing●C compiler diagnostics; messages later shared with types2.
Compiler frontend (Go 1.1–1.9)
The C-to-Go translation of the compiler (cmd/gc → cmd/internal/gc → cmd/compile/internal/gc)●●●done in 1.5; the Go gc package then became the single frontend, later split into syntax, types, ssa and so on → continues as Old typechecker and IR node-model cleanup (1.18+)
Parser: from yacc to a hand-written recursive-descent parser●●●●●●●the gc-internal parser was removed in 1.8; the syntax package parser is the only parser → continues as The syntax package (parser, scanner, tree) (1.18+)
The syntax package, position representation and parse-time checks●●still the design (syntax tree with Pos and PosBase; branch checking in the parser) → continues as The syntax package (parser, scanner, tree) (1.18+)
Binary export data and the object-file split●●●●●●●●binary export format (v1..v5 "bexport"/"bimport") plus inlined bodies; later replaced by the indexed format and then unified IR → continues as Unified IR: one serialization for noding, stenciling, inlining and export (1.18+)
Inlining●●●●●●the Node-based inliner with budget 80 survives, extended later (mid-stack, then PGO) → continues as Compile-time performance, concurrency and the self-PGO'd compiler (1.18+)
Escape analysis and stack allocation●●●●●●the 2012–2015 flow-graph analysis (esc.go) with param tags remains; rewritten in later releases → continues as Old typechecker and IR node-model cleanup (1.18+)
Closures, method values and variable capture●●●●●●●still the design (capture by value when never reassigned and not address-taken; closure call → function call) → continues as Old typechecker and IR node-model cleanup (1.18+)
Language changes and rules as implemented in the compiler●●●●●●●●●each rule is part of the language; implementation later moved to types2 → continues as Frontend support for new language features and spec changes (1.18+)
Type aliases (the => experiment and type A = B)●●type A = B is the language feature; the first implementation was reverted in 1.8 and redone on [dev.typealias] in 1.9 → continues as Generic type aliases and Alias type nodes (1.18+)
Constant arithmetic, Mpint/Mpflt and floating-point constants●●●●●big.Int/big.Float-based constants (Mpint, Mpflt) → continues as Old typechecker and IR node-model cleanup (1.18+)
Static initialization, composite literals and init●●●●●●●●sinit.go model with static data, dynamic init and oaslit; extended later with staticinit → continues as Static initialization and package init (1.18+)
Walk-level lowering and runtime-call optimisations●●●●●●●●still the walk/order model; the runtime entry points mostly persist → continues as Static initialization and package init (1.18+)
Write barriers, precise stacks and liveness (compiler side)●●●●●●●●write-barrier insertion moved into SSA later; frontend marks (pragmas, OASWB) removed in 1.9 → continues as Old typechecker and IR node-model cleanup (1.18+)
Compiler data structures: Node, Nodes, Type, and the types package●●●●●ir.Node and types.Type as separate packages came in later releases; the 1.9 types package is the start → continues as Types package internals: size, algorithm kind, TFlag, SIMD flags (1.18+)
Compiler performance, formatting and concurrency●●●backend concurrency via -c, with cmd/go setting it; later default → continues as Compile-time performance, concurrency and the self-PGO'd compiler (1.18+)
Pragmas, directives and compiler flags●●●●●●●●//go: pragma set persisted and grew → continues as Compiler debugging infrastructure: bisect/hash debugging, flags, dumps (1.18+)
Race detector and sanitizer instrumentation in the frontend●●●●racewalk pass (later instrument) → continues as Compiler debugging infrastructure: bisect/hash debugging, flags, dumps (1.18+)
Diagnostics and error messages●●●ongoing; types2 later supplies messages → continues as Error reporting: codes, messages, and sub-errors (1.18+)
Compiler frontend (Go 1.10–1.17)
dev.regabi: from *Node struct to the ir.Node interface and concrete node types●●●●●●●●done: ir.Node is an interface with ~60 concrete node types (ir.Name, ir.Func, ir.CallExpr, …) and generated DoChildren/EditChildren/Copy; Left/Right/List accessors are gone; Nodes is a plain slice → continues as Old typechecker and IR node-model cleanup (1.18+)
dev.regabi: breaking up package gc into base, ir, types, typecheck, noder, inline, staticdata, reflectdata, pkginit●done; layout of cmd/compile/internal/ as it remained → continues as Old typechecker and IR node-model cleanup (1.18+)
Indexed export format and the export-data lifecycle●●●●●●●●the indexed (iexport) format is the only compiler export format, now carrying positions with columns, type information on inlined bodies, closures and ABIs → continues as Unified IR: one serialization for noding, stenciling, inlining and export (1.18+)
Generics bring-up in the compiler (dev.typeparams): parser, -G, types2, irgen, first stenciling●generic code is parsed and type-checked only when the undocumented -G flag is given; stenciling of simple generic functions and types exists but is experimental and not enabled by default → continues as Generics via GC-shape stenciling and runtime dictionaries (1.18+) and Bringing up generics in types2 (1.18+)
types2 port of go/types: reviews, error-message parity and language-version support●types2 is used for -G builds only; the legacy typechecker is still the authority for ordinary compilation → continues as Sharing code between go/types and types2 (1.18+), Error reporting: codes, messages, and sub-errors (1.18+) and Bringing up generics in types2 (1.18+)
The syntax package: parser, scanner, source reader, positions and error recovery●●●●●●●stable parser with optional generics (AllowGenerics), a fast reader, and pragma handling delegated to the noder → continues as The syntax package (parser, scanner, tree) (1.18+)
Constants: Go 2 number literals, evconst, untyped conversion and go/constant●●●●●●●the compiler's constants are go/constant.Value; Ctype/Val/Mpint representation gone; untyped types are types.Ideal{int,rune,float,complex}; rational arithmetic enabled → continues as Frontend support for new language features and spec changes (1.18+)
Language version gating (-lang) and language changes implemented in the frontend●●●●●●●-lang enforced by both checkers (old typechecker and types2) with errors naming the required version → continues as Per-file language versions and version gating (1.18+) and Frontend support for new language features and spec changes (1.18+)
Package initialization order, static initialisation and init functions●●●●●●●spec-compliant init order (initorder.go), static maps/slices/structs emitted as data where possible, fninit earlier → continues as Static initialization and package init (1.18+)
Concurrent compilation and frontend performance●●●●●●backend compiles functions concurrently via the compile queue; frontend is serial → continues as Compile-time performance, concurrency and the self-PGO'd compiler (1.18+)
Directives and pragmas in the frontend (//go: handling)●●●●●●pragma handling is an interface between syntax and the noder with misplacement diagnostics → continues as The syntax package (parser, scanner, tree) (1.18+)
Inlining-related frontend work: export of bodies, type switches, closures, devirtualization●●●●●●inliner handles type switches, method values and (re-enabled for the new export format) closures with limits → continues as Old typechecker and IR node-model cleanup (1.18+)
The legacy typechecker: error quality, alias/recursive-type handling and type checking order●●●●●●●●still the authoritative checker outside -G; heavily incrementally improved → continues as Old typechecker and IR node-model cleanup (1.18+)
Lowering in order/walk and the shape of runtime calls emitted by the frontend●●●●●●●●the frontend lowers many constructs to specialised runtime calls; some previously frontend work moved to SSA construction → continues as Method wrappers and promoted-method sharing (1.18+) for wrappers only; otherwise no direct continuation
checkptr instrumentation and compiler debugging flags●●●●●●-d=checkptr implied by -race/-msan; Debug is a struct → continues as Compiler debugging infrastructure: bisect/hash debugging, flags, dumps (1.18+)
Types package internals and type printing●●●●●●types hosts type constructors, size computation and printing → continues as Types package internals: size, algorithm kind, TFlag, SIMD flags (1.18+)
Register-ABI plumbing in the frontend●●ABI wrappers generated per ir.Func, ABIs exported across packages → continues as Method wrappers and promoted-method sharing (1.18+)

Thread map, Go 1.18 – 1.28

Thread1.181.191.201.211.221.231.241.251.261.271.28Status at HEAD
Compiler frontend: the types2 type checker (Go 1.18–1.28)
Bringing up generics in types2 (type-parameter operands, core/structural types, tparam-as-interface)●●●●●●complete; core types no longer used by the checker (removed in 1.25), residual fixes only
Type sets, union terms, interfaces and constraint satisfaction●●●●●●●●stable; comparable satisfaction by non-type-parameter interfaces since 1.20
Instantiation, Named types, Context deduplication and lazy expansion●●●●●●●●●Named has an explicit state machine, fromRHS/underlying lifecycle enforced since 1.26
Type inference and unification●●●●●●●●●●new unifier-centred inference since 1.21; reverse inference, interface inference and untyped-constant defaulting shipped; assignment-context refinements in 1.27
Generic type aliases and Alias type nodes●●●●●Alias nodes always on (GODEBUG gotypesalias removed in 1.27); generic aliases enabled
Type declaration cycles, valid-type detection and the object path●●●●●●●validType uses a type nest; setDefType removed and replaced by "pending" checks in 1.26; finite-size check at value observation
Per-file language versions and version gating●●●●●●Info.FileVersions; version computed per file from //go:build lines
Language features as seen by the type checker (by release)●●●●●●●●●●generic methods (1.27) and string(int) restriction (1.28) are the latest; untyped composite literal inference in progress
Composite-literal type elision via target types (#12854)●●in progress; targets threaded through assignments, calls, struct literals, sends, switch, delete/append/copy and comparisons
Method receiver checking●●●●receiver handled in collectRecv (1.24 rewrite)
Error reporting: codes, messages, and sub-errors●●●●●●●●●●●compiler and go/types share messages and error codes (internal/types/errors)
Sharing code between go/types and types2●●●●●●most go/types files are generated from types2
Checker performance, memory and API ergonomics●●●●●ongoing micro-optimizations driven by gopls and compilebench
Robustness: invariants, incomplete types and importers●●●●invariant checker in tests; new types2 importer in progress
Compiler frontend: syntax, IR, unified IR and the generics implementation (Go 1.18–1.28)
Generics via GC-shape stenciling and runtime dictionaries●●●●●●●●shipped design since 1.18 (first built on the old typechecker/-G=3, reimplemented on unified IR in 1.20); shape sharing is still "underlying type, pointers collapsed", with occasional shape/reshape bug fixes through 1.27
Unified IR: one serialization for noding, stenciling, inlining and export●●●●●●●●●●●the only frontend since 1.21; internal/pkgbits + noder/{writer,reader,unified,linker}.go; UIR at version V4 (V5 method indices added in 1.28 dev), documented with a formal grammar in 1.25
Old typechecker and IR node-model cleanup●●●●●●●●●typecheck is a thin library (constant folding gone, Ntype gone, ir.CurFunc gone); types2 is the only checker; IR remains ir.Node with generated node_gen.go
RType/ITab fields and implicit conversions in IR (RTTI plumbing)●complete; every runtime-type-needing IR node carries its RType/ITab explicitly
Generic methods (type parameters on methods)●●language feature enabled (go1.27 language version); GOEXPERIMENT=genericmethods was added 2026-04-30, default flipped on, flag deleted 2026-06-12; UIR V4 plus V5 method-index fix
Static initialization and package init●●●●●●●●●staticinit with inline-aware static init (enabled in 1.21), outlined map init for deadcode, init splitting redone in 1.28 cycle
Compile-time performance, concurrency and the self-PGO'd compiler●●●●●●●●●●backend concurrency = GOMAXPROCS (normally capped at 4 by go build); compiler built with PGO from cmd/compile/default.pgo, refreshed about annually; startup heap boost 128 MiB
Compiler debugging infrastructure: bisect/hash debugging, flags, dumps●●●●●●●●-d flags with concurrency tagging, internal/bisect-based HashDebug, astdump HTML
Frontend support for new language features and spec changes●●●●●●●●●●●tracks the language; most features need a noder/typecheck/walk piece plus a types2 piece owned by the fe-types area
The syntax package (parser, scanner, tree)●●●●●●●●●stable; mostly error-message and parser-tolerance work
Package paths, -p, and build-system contract●●●-p required; symbols carry real package paths; export-data readiness signal to cmd/go since 1.28 dev
Types package internals: size, algorithm kind, TFlag, SIMD flags●●●●●types.Type computes layout, alg kind, pointer bytes and abi.TFlag once, lazily and side-effect-free
Method wrappers and promoted-method sharing●●●●●●wrappers for promoted methods shared across receiver types (1.28 cycle)
SIMD frontend: the "midway" source-to-source translator●●●prototype in the frontend, -d flag controlled
ir.StaticValue, ReassignOracle and function-variable tracking●●●batch oracle used by inliner/escape; ongoing migration of callers

Compiler frontend (Go 1.0: 2008–2012)

Reader: Claude Sonnet. Cites 863 of 935 commits in this slice; every non-noise commit is cited, plus 29 larger commits the noise filter had set aside, which a separate gap reader described. Most pre-2010 messages are one line long, so many claims rest on subject lines and diffstats. About 400 small fixes are cited as per-year hash lists.

Release digest for this slice
  • Go 1.0 — The compiler frontend went from a prototype tracking a weekly-changing spec (semicolons, chan operators, export keywords) to the Go 1 language with a separate typecheck pass, exact constants, Eface interfaces, closures, escape analysis, inlining and a stable textual export format.

Lexer, grammar and statement syntax (semicolons, chan operators, conversions)

Span: Go 1.0 → Go 1.0 · Status at go1.17: the surface syntax is the Go 1 one; the yacc grammar go.y and hand-written lexer lex.c stayed the parser until the compiler was rewritten in Go (not in this range). → continues as The syntax package (parser, scanner, tree) (1.18+)

Continues in: Parser: from yacc to a hand-written recursive-descent parser (Go 1.1 → Go 1.8)

Design. The frontend is src/cmd/gc in C: a bison/yacc grammar go.y that builds Node trees directly, a lexer lex.c, and per-architecture code generators (6g/8g/5g) that initially owned most of the semantic code. In 2008 almost every syntactic rule of the language was still moving, and the compiler followed the spec as it was rewritten week by week. Statement termination is the clearest example. The first optional-semicolon support (8200a0b088, 0006c83231, c242b53d22) was replaced by “all statements and declarations are terminated by semicolons; they may be omitted at top level and before/after a closing ) or }” (d364d28142), which doubled parts of the grammar; in June 2009 the rule was moved into the lexer, which now inserts the semicolon itself so the type and statement grammars no longer needed duplicated variants (8abcdee175, 179 insertions, 284 deletions). Follow-ups made 6g match the spec exactly: no semicolon after the package clause, one allowed after imports (aaaa1fc69b), a semicolon inserted at EOF (ec13ed1fce), and mandatory { } around else blocks (58e19aa4cb, 2011). The if condition became mandatory (52943bfe4d, 2011).

Expression and type syntax went through several iterations. Conversions and constructors were first T{} and T(expr) (0194aaf918, Ken Thompson, which also stopped treating func as a type), then composite literals briefly became T(...) with a warning for the brace form (07244f7c80, Feb 2009), and returned to T{x} with a stricter split between T(x) conversion and x.(T) assertion a month later (49cc649e59). Parentheses were allowed to disambiguate types (ebc10db3e1). Channels first had a <-/-< pair, then a new syntax (ac048ce7f4) and in September 2008 binary <- is send and unary <- is receive, with case a := <-ch: and case a = <-ch: in select (47919799b4, 304440356d). Directional channel types were enforced in May 2009 (f4d3d22a94) and chan <- chan precedence and func() func() were fixed in 2010 (7abb4b3a96), plus send precedence (0e47460915). In December 2008 *chan became chan and *map became map, with new(T) taking the pointee type: “mainly a syntactic change: the compiler representations don’t change (chan and map are now hidden pointers like string)” (dc7b2e98d2, 88b5c5f0f8, 316d377ac2). The range statement arrived in December 2008 (26b357ca5b, b79272d9a2, ae5a475e20), &^ and &^= in March 2009 (bb02e481d2), [...] closed arrays in January 2009 (b0f627a6e1), and empty var ( ) factoring in October 2008 (5f1202428b). Redundant iota rules were redone twice (d915b96100, c597845e13).

The lexer also gained several user-visible rules: control characters other than white space are rejected (2538cf747b), · is disallowed in source (9e497c3656), backslash-newline in strings is illegal (0c222d6e49), //line file:number directives were added in July 2010 (444066e56d, 19d7fc4026, with the earlier //line number file form changed), and colons were permitted in the directive file name (ecda0fa5d4).

Timeline.

Package system, visibility and import resolution

Span: Go 1.0 → Go 1.0 · Status at go1.17: capitalisation-based export and -p/-D/-I import resolution as of go1; compiler flags later reworked by the build system. → continues as Package paths, -p, and build-system contract (1.18+)

Design. Visibility changed repeatedly in 2008–2009. First came export adjectives on external var, const, type and func declarations (989676d2ba, 695f83a9e0, b59b551be7), and in November 2008 a package keyword for package-local declarations (d3f6093931, 9c7b064043). In January 2009 the export name-list statement was removed and “package local” became the default; the compiler warned when the name’s case did not match the keyword (07d344e442, 3decb42e6f, 605d0746c5). Case then became the real rule: other packages’ lower-case names are rejected in struct field selectors, method calls and P.t (06869eedf9, aec4d3194a, 61590c4c44), assignments to private fields during initialization (9da6666a8a), implicit assignment to non-exported fields in copy and append (151b2f1509) and then the opposite relaxation for method receivers and copy of structs with unexported fields (0b209b36b6, d03611f628).

Package lookup went from a flat global namespace to explicit paths. 6g autoloaded imports via #pragma lib (e90ae879d6, 12c81a0ce8, f95da9a639), later by searching a pkg directory, .6 before .a, archives with __.PKGDEF and 0-length __.SYMDEF (63a84348cc). -I DIR added include directories (062d6998ab), local-path lookup was separated from standard directories (70e232e668), the package global name space was eliminated (531e6b77c8, Jan 2010) and the runtime type data records full package paths (1cecac8134). The name main stopped being reserved (c1a695c160, daffc2d2ef). Unused imports are an error (73e52ae971) and print the path (f1550482fa). Import cycles: -p tells the compiler “if you see an import of this package, give up early”, because a cycle otherwise shows up as two different copies of the same package and type-signature mismatches (1e480cd1ad). Import-path hygiene landed in early 2012: \ diagnosed (33f3afa7af), special characters rejected (dc38756ce1), absolute paths disallowed (d0d251f858), ~ allowed on Windows and slash handling (d0a4c9bb62, 29199aa4e4). Relative imports were reworked for cmd/go with a -D prefix option: import "./foo" with -D a/b/c becomes a/b/c/foo, so a _/home/gopher/foo pseudo-hierarchy makes relative imports ordinary paths (604f375110).

Timeline.

Export/import data format

Span: Go 1.0 → Go 1.0 · Status at go1.17: textual export data in the object file’s __.PKGDEF section was still the format at go1 and in 1.17 had moved through the binary format and then indexed export data → continues as Unified IR: one serialization for noding, stenciling, inlining and export (1.18+)

Continues in: Binary export data and the object-file split (Go 1.1 → Go 1.9)

Design. The compiler writes a text description of everything a package exports into the object file, delimited by $$ markers, and the importing compiler parses it with a second yacc entry in go.y (the “import grammar”). The first version dated June 2008 (2254a8ee99, 343f5aa7b4, 4528854308 made names unique). On 2008-10-03 Russ Cox rewrote it (b8babed7c2, 1827-line delta, export.c shrank by roughly 700 lines): declarations are printed as Go-like type flag.Value interface {...} with fully qualified identifiers, the delimiter changed from (( )) to $$, and sys.go uses a leading export. Other early decisions: only explicitly exported types are exposed to the importer (fb2c66710c), type names “live at the name” so type T struct{ next *T } and type T *struct{ next T } work without forward declarations (74e2e087e2), \n$$ as the section delimiter (4958d41d3b), tag strings on struct fields and across multiple names (f27aaf4819, 1850b29da6, f2b1536328), ... (2fef4c7198), embedded types (21617250f4), and a separate section for non-exported types referenced by exported ones (aacfbb461b). Constants are exact: negative constants (c3d841f5d4), large constants (2b75556436) and multi-precision floats via a decimal-integer “p” exponent syntax (f8797daa9f); complex constants (713e3e1541).

From 2011 the format was prepared for inlining: all transitive dependencies are listed, with indirect imports marked, “purely informational” (e6f3aa6c36); string literals used as package qualifiers are prefixed with @ which removed the : before tags (b536adbfba, Luuk van Dijk); further changes, with a “minimal change to gcimporter” (40b2fe004f); tags allowed on parameters (56668283f1); nil literals exported without inferred type (feaa9ed10a), implicit type bug fixes (5c52404aca, 419c53af30, 77aaa3555d), field names in struct literals inside exported inline bodies not qualified for embedded builtin types (6ff01f01f4), and a limit on the size of exported recursive interfaces to stop memory blowup (aa63a928ea). The byte/uint8 distinction was preserved in errors and import data (862179b0f5).

Timeline.

Constants and multi-precision arithmetic

Span: Go 1.0 → Go 1.0 · Status at go1.17: arbitrary-precision constants as at go1 (mparith); later moved to go/constant and math/big → continues as Language features as seen by the type checker (by release) (1.18+)

Continues in: Constant arithmetic, Mpint/Mpflt and floating-point constants (Go 1.1 → Go 1.9)

Design. Go’s “ideal” constants were implemented by hand in mparith*.c: Mpint integer arithmetic (mp constants 9c2ade358d, fixed arithmetic 272ae659db) and multiprecision floating point Mpflt (3fa4610601, Ken Thompson, December 2008). Floats are truncated when used in float64/float32 contexts (89996e1fb1). Precision work: 29 more bits of precision (Mpscale) from floating-constant multiply with rounding code for const fix = float (95b93c28e3), and floating literal normalisation moved from word-oriented to bit-oriented for up to a full extra word of precision (f6b93ab432); denormals round correctly (cf63e34b1d); constant divide bug (ce85868a00); float zero 0p+0 (f51ca384eb). The type-rule side: ideal bools (a95ee61aea), comparisons have type bool (ed6de5af4c) and, at the end, “drop ideal bool” (126d475a43) followed by “new, less strict bool rules” (e29d3dfc49) and the return of ideal bool for a corner case (a457fa500d). Complex numbers arrived in February 2010 as a three-step plan (front-end, back-end, library): complex, complex64, complex128 types (33d6a260c1), complex constant multiply and divide (fc010adcd7), simplified typecheck that does not convert to float prematurely (4ac011a011), and constant float parts of complex constants (94df1a067c).

Shift rules changed many times: typing of (1<<x) (944ad62ecd), x << "a" and 1 << int(2) (0aa1b1508a), shifts must be unsigned (message, 67941bf644), then “new shift rules” where 1.0<<2 is okay (5ab096d030) and a shift-type bug (4389192669). Unary ^: ^ type(const) inverts “enough” bits, signed becomes illegal (b8be809c10). Constant-evaluation hygiene: constant evaluation decided by whitelist instead of blacklist (1ee83f851d), string constant comparison made unsigned (3a2d64789b), “do not constant-evaluate (interface{})("bar")” (4b13d1b9b5), original expression kept for error messages (4349effb15, 60e4a61d30, 21f1769519), constant shift overflow errors (79db6ada48), friendlier constant arithmetic errors (337547d1c9) and constant % on floats (83feedf7bf). Character constants received typing rules in December 2011 (be0ffbfd02).

Timeline.

Typecheck/walk separation and package-level declaration order

Span: Go 1.0 → Go 1.0 · Status at go1.17: typecheck pass (typecheck.c) as one separate pass over a whole function tree, still the design at go1.17 in Go form → continues as Old typechecker and IR node-model cleanup (1.18+)

Continues in: The legacy typechecker: error quality, alias/recursive-type handling and type checking order (Go 1.10 → Go 1.17)

Design. Until mid-2009 type checking was intermixed with code generation: walk.c and the arch-specific code did conversions, argument checks and constant evaluation. On 2009-07-29 typecheck was introduced “before walkexpr”, not complete but the compiler still worked (9271c6402e, plus walkexpr taking Node** and dropping indir, 056940d84b; dead code deleted b754b4348f). By 2009-08-03 “typecheck.c is now responsible for all type checking except for assignment and function argument ...” (9dc22b6d6f, 1141 insertions, 1363 deletions) and assignments, switch, if, for followed (d8c19c80dc). Select was split into typecheck + walk in its own file (f7a867e1a6); := and range processing were made lazy so declarations do not need order (54b403723b, 2609731736, old2new gone); var blocks evaluate lazily (dbe004efe4); and “forward declarations not necessary” (b648716ee9, 990 insertions, 668 deletions) deleted prototypes in favour of resolving names on demand (“excluding import data, no yacc node has type Type* anymore”, 35e5906f91). Package compilation became whole-package (66bb399fdd) and forward type declarations were deleted (a92610208e). Steps toward this: functions’ forward declarations (be16caf872, e52e9ca82e) and constants referring to later constants in a block (db508ccbff).

Remaining work was explicitly noted: “initializer cycle detection” and “nicer error for type checking cycles” (b648716ee9), later done with initialization-loop diagnostics (41861a8812, 896f0c61c8) and a typechecking loop detail message (2aafad25b4). In June 2010 “new typechecking rules” made assignment and conversion code mirror the spec, and renamed runtime functions to separate interface conversions from assertions (convT2I, assertI2T, 565b5dc076). Other stages: the real type of identifiers is tracked (49a835fc97); sort errors by line number (2372408189); typecheck the whole tree before walking, a prerequisite for escape analysis (e59aa8ea4a, Jun 2011, 177 insertions 345 deletions); builtins resolve to the built-in symbol (e77f057bf3). Type declaration cycles: type T *struct { T } as invalid embedded (6c2738eb43), recursive types (05a1eb1ab0), recursive interfaces (44898c7b76, 1734cb02e7), recursive forward references through pointer fields (b6f0632e93), and “typecheck loop” fixes (5e98505ba7).

Timeline.

Interfaces, type switches, assertions and comparability

Span: Go 1.0 → Go 1.0 · Status at go1.17: interface layout Eface/Iface and comparison rules as at go1 persisted; the runtime later moved to itab caches in Go-era reflect → continues as Type sets, union terms, interfaces and constraint satisfaction (1.18+)

Design. Interface support began as signature tables: i2s, s2i, i2i conversions (ead7a6d47a, 52861558f4), “interface on arbitrary types” with global signatures for basic types (1bf38484d1), duplicate interface signatures (0a7e4a7415), and sigt/sigi structures. On 2009-05-06 “new interface rules (code got simpler!)” (a6ba5ec535). On 2009-05-20 the representation changed: before, every interface value was {addr, Itype*} with Itype{Sigt *type; Sigi *inter; void *method[n]} acting as a vtable; now interfaces with static type interface{} use struct Eface {void *addr; Sigt *type}, which “reduces the number of Itypes that have to be computed and cached”, opens up avoiding calls, and lets interface{} values be laid out at compile time for the new reflection (2da5022bcf, plus ifaceop split into ifacecvt and ifaceop, a39bae095a). The runtime moved to reflect data structures in July 2009 (c7513eac88, 541e638f9b). Conversion rules: narrowing needs type assertions (5f4f5647ef), i.(T) requires non-nil i (23e62d169f), a heuristic complains when putting T in an interface when T has pointer methods (4eb7ceba58), conversion to interface type when implicit assignment would be okay (d436a70193), typed nil constants distinct from nil interface (a14b28a24d), unsafe.Pointer not a pointer (5c2666c18c), interface embedded in interface (b4af09ab56) and disallowed interface { x, y() } (7743ffead4), recursive interface embedding (1734cb02e7).

Type switches arrived 2009-03-06 along with removing typeof (a4a10ed856), got binary search and case nil (0f469a99a3), a rewritten switch (bf983477a2, 79fa5b65cb), and the unused-switch-variable error (aac144b120). Interface equality was first a runtime hack sys.BUG_intereq (827dcb86b7) until the compiler supported it (edb1412c50, 5933dbda3a).

Comparison and map keys were slowly formalised: map key types checked at compile time (98b34e5bbd), maps allowed as keys because maps have == (75fe1303c1), then the reverse: “remove func, map compare” (5bb54b8e9c) and “disallow map/func equality via interface comparison” (46deaa297b, Dec 2011), “new comparison rule” basing comparison compatibility on assignment compatibility (23bd214aee), invalid keys disallowed (7ca406396f), and in December 2011 == on structs and arrays: “structs or arrays that are ‘just memory’, like [2]int, continue to use the AMEM algorithm; those containing strings or interfaces use generated functions for equal and hash; the runtime helper runtime.equal(t, x, y) handles the general case; for short values (≤4 fields or elements) the elementwise comparison is inlined” (196b663075). Algorithm tables got specialised entries: word-sized copy/equal (4d903504b3, 0–6% regexp benchmark gains), slices and sizes 8/16/32/64/128 (54e9406ffb, ChanUncontended 226→94 ns) and zero-sized types (1ff1405cc7, ChanSem 127→78.6 ns). Floating-point map keys were handled in 2012 (408f0b1f74). Finally the predeclared error type was added (2a0e15d36c, Nov 2011) with a temporary GOEXPERIMENT=os.Error (47f4bf763d, “this won’t last long”), and GOEXPERIMENT=reorg to ease conversions (ae7a003cf9).

Timeline.

Embedded fields, method sets and receivers

Span: Go 1.0 → Go 1.0 · Status at go1.17: embedding via genembedtramp-style wrapper generation as at go1 → continues as Method receiver checking (1.18+)

Design. “Unnamed substructures” appeared in October 2008 (61361af9e8), then import/export of embedded types, auto-& on methods and embedded methods (21617250f4, 6fecb76eea, 7ed8fc611d, b642cf8c00). Methods on arbitrary types were stepped in: any type but only *struct tested (2119294af9, 89ac5618ac), slices as receivers (b2dfd787d7), funcs (5e2c05877d), chan/map/string once they were real types (316d377ac2), complex types (883d68f885), unnamed struct types (4267974c0b). An anonymous interface field m shows up in the struct’s method set (401a95aa83). Wrapper generation requires care with private methods in signatures (35e37bbf41), methods must immediately follow their struct (ef40d9435b), and in 2011 non-methods are kept out of xmethod (11075ed893) and copytype resets xmethod (ffa6b383f5). Method expressions on concrete types came in Dec 2009 (0d668259dc, 9e2c9bb0ca), receivers relaxed (0b209b36b6), invalid receivers explained (4684df520f, 6a401339c1), names made Go-like (1bd4b6371a), unexported methods of the same name no longer confused (987a580b9f), field-and-method same name diagnosed (7dd90621f8), ambiguous selector deduplicated (7ae1fe420e), pointer-to-pointer method call message (7df9ff5594).

Timeline.

Closures

Span: Go 1.0 → Go 1.0 · Status at go1.17: closure conversion by value-or-reference capture classification as at go1; the compiler later rewrote it → continues as Frontend support for new language features and spec changes (1.18+)

Continues in: Closures, method values and variable capture (Go 1.1 → Go 1.7)

Design. Function literals first needed parameter-declaration parsing fixes (8003849498, 814320c8b4). Closures arrived with 6g support on 2009-02-06 (0970c46863, 251 insertions) building the closure via a runtime.closure call. Function literals are named _f007·filename-style to allow multiple closures per package (416b27548e); the outnamed flag is copied (941ed00b1d). Performance work was done by Luuk van Dijk in 2011: calls to function literals called in place skip runtime.closure (9b82408f6d; BenchmarkCallClosure 499→5 ns, CallClosure1 681→160 ns), merely referencing an outer variable in a closure no longer forces heap allocation (2c4edb0eea; 135→6 ns), and closures without closure variables omit the wrapper (ba25778f3f, Jan 2012). Bugs: closure bug (e8ff9a624f), top-level closure (890bdc5339), escape analysis + inlining + closure (075eef4018), loopdepth for labels (9bf3478658).

Timeline.

Escape analysis and heap allocation

Span: Go 1.0 → Go 1.0 · Status at go1.17: first escape analysis enabled by default in esc.c → continues as the escape analysis in later threads; no 1.18 link.

Continues in: Escape analysis (esc.c → esc.go) (Go 1.0 → Go 1.9) · Escape analysis and stack allocation (Go 1.1 → Go 1.9)

Design docs & discussion

Design. Early rule: taking the address of a local moves it to the heap (391425ae55), refined to not move x because of *&x (a75e347a21). On 2011-08-24 Luuk van Dijk added a real escape analysis, switchable with -s, effects inspectable with -m, defaults old paths (847b61b554, 945 insertions). Days later it was enabled: “-s now means disable escape analysis”, leaks for struct/slice/map literals fixed, ... tracking, new(T) and slice literal rewritten into stack allocation when safe, and annotations added to reflect (db5f9da425). Supporting changes: stack-allocated slice backing arrays are zeroed (c45c0c0c1d), a temp = nod+tempname helper (9854fd2a0e), FlagNoPointers passed to runtime.new (bf9c778fe2), loopdepth analysis for labels (9bf3478658), label recursion (60d47101aa), and the typecheck-whole-tree prerequisite (e59aa8ea4a). Goto restriction work removed stack frame zeroing (7f4c5ea7d8).

Timeline.

Inlining

Span: Go 1.0 → Go 1.0 · Status at go1.17: first inliner, evolved massively afterwards.

Continues in: Inlining (inl.c → inl.go) (Go 1.0 → Go 1.9) · Inlining (Go 1.1 → Go 1.9)

Design. Inlining is exported in the object file: export format changes in October and December 2011 (b536adbfba, 40b2fe004f) carry function bodies. a62722bba4 (2011-12-14) added “cross- and intra-package inlining of single assignments or return <expression>”, with hairy cases (calls, closures, ...) excluded, disabled without -l (824 insertions). Follow-ups: empty bodies and _ arguments (7e6890a670), inlining fixes (97fd7d5f34, 836a517f69, 524fb81c41), enabled by default 2012-01-10 (4a6b07f235), unsafe.Pointer permitted (2332439b1b, 1b19134c4f, 82a6a4f39e), minor changes (fc128403dc), append inlining of dst = append(src,...) (36cec789cd). The order of evaluation was repaired by a new pass that “moves things with a fixed order out of expressions and into the statement sequence”, because pulling calls out had caused illegal reorderings, worse with inlining (ee9bfb023a, Jan 2012, plus 5efd5624cc for && and ||).

Timeline.

Builtins and unsafe

Span: Go 1.0 → Go 1.0 · Status at go1.17: builtin set as at go1 → continues as Language features as seen by the type checker (by release) (1.18+)

Design. The early sys package gave way to builtins: len takes an expression (b2d9020242) and fixed arrays (4c5a165873), print/println, printn/panicn (f21e731c71), panicln deprecated (623dfd1b07). Copy: copy without result (01c2de0c15), *[10]int to slice (9be56adf3c, removed by 82ee481dfc), copy([]byte, string) (82c6f5e3d1). append landed 2010-10-27 (d8b5d039cd), optimisation by dst = append(src, ...) inlining (36cec789cd), append([]byte, string...) (77fac21e82). Panic/recover: 01eaf780a8 (2010-03-30) enforced a single argument and 9b1507b050 implemented them; defer print etc. (a186b77b03). delete(m, x) was added while retaining m[x] = 0, false for a transition month (1d687c742d) and the old syntax removed (5fc3771b3a). close on receive-only channels disallowed (f58ed4e641). Unsafe: unsafe.Sizeof, Offsetof (8a70545b57), Alignof (651972b31f), then in 2011 returning uintptr (cf9f380499), non-call use diagnosed (924ea515cf), old unsafe functions deleted (1f2445d27b), unsafe.New (33649bd278), reflect moved into unsafe (c367d1b789). string(int)/string([]int) conversions (64c3fe05bf), []int(string), []byte(string) (3910161307), direct conversion string ↔︎ named []byte/[]rune (6e3e380923), and rune (6ed3fa6553, Oct 2011; “alias for int32”, 012798a325).

Timeline.

Channels, select, maps and strings as built-in types

Span: Go 1.0 → Go 1.0 · Status at go1.17: compiler lowering to runtime calls as at go1.

Design. Chan and map became real types after the pointer-removal (dc7b2e98d2). Select: first implemented 2008-07 (b78676a49d, cb9b1038db, ece3e57c85) with select default (79fbbe37a7), moved into its own file with typecheck + walk (f7a867e1a6), special-case code for single-op blocking and non-blocking selects (5038792837), made faster by passing a pointer to the element so Scase is fixed-size and Select, Scases and SudoGs allocated together (6b2ec06587, uncontended 1080→558 ns), and “select functions are no longer special” (22853098a9). Range over a channel was made safe for multiple goroutines (b0543ddd8a). Maps: literals (182f91ffe0), nil maps (65bde087ae) and nil channels (3770b0e60c), multivalue map index (0282cc5e74), correct typecheck of x, ok (b7c4314ecb). Strings: representation changed April 2009 (3657061550), range over strings (a91a8042b4), x[y:] for strings (aa4c638b7b), string-index code generation inline (e48c0fb562), efficient x + y + z (f47d403cb4). Large channel element sizes are rejected (d116a32727) and data types are limited to 1GB (7e7008fa5e).

Timeline.

Initialization and static data

Span: Go 1.0 → Go 1.0 · Status at go1.17: sinit.c static init as at go1 → continues as Static initialization and package init (1.18+)

Continues in: Static initialization, composite literals and init (Go 1.1 → Go 1.9)

Design. Package init order was added in July 2008 (fb3af53f4e, 90da636c15, f24f8ffae0); empty init functions are dropped (49f7494894); static initialization of slices (b91a043d02) was re-enabled (802e1a6104); recursive initialization is detected (e2613711aa) and diagnosed better (41861a8812); composite literals under init were moved from walk.c to sinit.c (0631d65dc5, 18f2e360a0); an optimisation of static initialization (33da9afc3e) with a read-only flag (d348971103, 103d756663); adjustable hash code in composite-literal typechecking to get rid of O(n^2) behaviour (b3dd22fecb, 8cb8ba14a5); init loops involving functions (896f0c61c8); stack allocation for the init function (1ea5bc7533).

Timeline.

Composite literals

Span: Go 1.0 → Go 1.0 · Status at go1.17: Go 1 rules; type elision for struct/array elements came later.

Continues in: Static initialization, composite literals and init (Go 1.1 → Go 1.9)

Design. Array literals (4539ced714), map literals (182f91ffe0), key:val extensions in array and struct literals (a016081f43), constant array keys (18840f5429), &Point{1, 2} rewrite as allocation (beee6915f8), static init of literals (above). The December 2011 commits implemented “composite literals as per Go 1” (7dc9d8c72b) and removed type elision in struct literals (5cb1c82d96); [...][...]int{{1,2,3}} was disallowed (bf899befdb), slicing array literals not allowed (7d15eda95d), duplicate key shown in error (5ab9d2befd). The October 2010 version (8ffc4ec5d0) implemented the spec of that time for element elision.

Timeline.

Variadic functions (...)

Span: Go 1.0 → Go 1.0 · Status at go1.17: ...T as a property of func types.

Design. ... was a distinct, special type at first (74427c6346, 2fef4c7198); ... was double-checked (d122bb2193), allowed in method lists (101f499fa5), and then “add ... T, rework plain ...: no longer a distinct type; now a property of func types” (68796b0270, bea730d1f9); ... vs ...T crossing was an error “at least for now” (8bef7fdc39). Further ... changes and printing (75dd8fdb34, 1d315a8abd), an interim error rejecting valid code removed (2795b13156), and ... forbidden in type conversions (5b62ba14c4).

Timeline.

Statement and flow rules

Span: Go 1.0 → Go 1.0 · Status at go1.17: Go 1 rules.

Design. Labels, break/continue/goto (33ee52727f), goto restriction that jumping over variable declarations or into blocks is an error (7f4c5ea7d8), terminating statement requirement “new return restriction” (c18d1a78a4), no implicit return in empty function body (0b2683d1ee), declared and not used (59914723df disabled initially), unused type-switch variable (aac144b120), write-only _ (5438be4541), error on redeclaration (54abac678a), nested function errors (371b77ad20).

Timeline.

Diagnostics, error recovery and compiler plumbing

Span: Go 1.0 → Go 1.0 · Status at go1.17: C compiler diagnostics; messages later shared with types2.

Continues in: Diagnostics and error messages (Go 1.1 → Go 1.9)

Design. Large numbers of “bug NNN” fixes track the test/bugs directory (193eac7917, 1cb7f85d74 “0 expected bugs”). Error-reporting infrastructure: syntax-error recovery (1efa6a0544, 417a971f9a), helpful error rules (46f89d53c6, d7c1048771), line numbers from yacc lookahead (4656686cf5, 38df5ec58d), pretty types with 6g -t to disable (387c1bc31e), expression printer %#N (4152b925f9), errors sorted by line (2372408189), duplicate errors removed (529369e452, 6865cc0e8e), original expressions preserved (8133cb3565), a fatal-bug vs error distinction (9475cafa73), graceful seg-fault exit (b49561d884), cumulative error count (3208917d54), debug-flag docs (4b6cd239c5), and -L full paths (779dfea487). Build plumbing: builtin.c.boot generated from runtime.go (d06a79e31b, 8b3b1edc04), golden copy (ce7f345cd1), generated y.tab.[ch] checked in (d53cdd1775), Plan 9 yacc (920df48fb1), clang support (60a4b5e4db, 6998ea2982), and the move of portable code from 6g into gc (531f242f5d, 8e54729b5a, d30285a6f5, bac922c6e1, 5e792b6c09). Alignment: 20cfa4a568 rolls the receiver into input parameters, outputs in their own struct.

Timeline.

Changes in this slice that are not part of a thread are listed on the release pages: 1.0

Compiler frontend (Go 1.1–1.9)

Reader: Claude Sonnet. Cites 1,297 of 1,507 commits in this slice; every non-noise commit is cited, plus 30 larger commits the noise filter had set aside, which a separate gap reader described. Read the design-heavy messages and subject lines. Some timeline items are plain hash lists.

Release digest for this slice
  • Go 1.1 — C cmd/gc maintenance: method values, new return rules and shift/const rules; first escape-analysis flow tracking and inlining with locals; first race-detector instrumentation (041fc8bf96); mostly error-message and robustness fixes.
  • Go 1.2 — Three-index slices (b4e92cee97), precise stack collection built but disabled (30ecb4cd05), large stack variables moved to heap, many inlining-export and message fixes.
  • Go 1.3 — Precise stacks enabled (ecf700b5ee/53061193f1) with temporary-lifetime shortening and compact liveness bitmaps; mparith float-range fixes; -pack, -live.
  • Go 1.4 — Write barriers emitted (fcb4cabba4), pointer-only interface data word (950ad4fa2b), for range x {}, escape-analysis soundness fix for stack copying, go:nosplit.
  • Go 1.5 — Compiler translated from C to Go (cmd/internal/gc → cmd/compile), math/big-based constants, Node slimming, capture-by-value closures and closure-call transformation, stack allocation of strings/maps/interfaces, transitive inlining, flow of params to results in escape analysis.
  • Go 1.6 — Hand-written recursive-descent parser replaces yacc, compact binary export data under -newexport, captureless-closure constants, go:noinline, -msan, mass NodeList→slice cleanup.
  • Go 1.7 — Binary export data on by default (inlined bodies, positions, -linkobj), NodeList removed in favour of Nodes, Type encapsulation and shrinking, itabs emitted by compiler, [dev.ssa] backend lands with frontend support, lexer rewrite.
  • Go 1.8 — New syntax package and noder replace the gc lexer/parser, textual export format removed, printer vs fmt.Formatter formatting rewrite, go:notinheap/yeswritebarrierrec, mapassign returns a slot pointer, first (reverted) alias implementation.
  • Go 1.9 — Type aliases implemented, syntax.Pos position redesign with columns in errors, parse-time label/goto checking, concurrent parsing and initial backend concurrency (-c), cmd/compile/internal/types split, -importcfg, export-data version 5 with inlined-body positions.

The C-to-Go translation of the compiler (cmd/gc → cmd/internal/gc → cmd/compile/internal/gc)

Span: Go 1.5 → Go 1.9 · Status at go1.17: done in 1.5; the Go gc package then became the single frontend, later split into syntax, types, ssa and so on → continues as Old typechecker and IR node-model cleanup (1.18+)

Design docs & discussion

Design. Until Go 1.4 the frontend was C in src/cmd/gc (lexer lex.c, yacc grammar go.y, typecheck.c, walk.c, esc.c, inl.c, sinit.c, …) shared by the per-arch code generators 5g/6g/8g. The Go 1.5 cycle converted it mechanically to Go. The runtime-C-removal branch work started it: fd2bc95d53 ([dev.cc]) changed cmd/gc to emit an -asmhdr header (struct field offsets and consts that cmd/dist used to scrape from the C compiler), to accept //go:cgo_xxx and //go:linkname directives, and to hard-code that interfaces contain only pointers. The translated code was committed as cmd/internal/gc (9c8c0e729d “reconvert to pick up bug fixes”, f6791da7ad for new6g etc.), still driven by a Go-ported yacc (cmd/yacc, go.y → generated y.go). Matthew Dempsky’s cmd/yacc work restored the “unexpected X, expecting Y” syntax-error style that Bison produced (e7d9f81645, 398bf9d5a0, 4f2a73011f, 82e1651a24), and a1bb3030c8 made yacc emit arrays instead of slices.

C idioms were then peeled off: Strlit/Zconv deleted (bed1f90d08), C hash tables replaced by Go maps (d0b59deb71), md5.go replaced by crypto/md5 (392434e1d0), Mpint/Mpflt re-based on a vendored math/big (e5a0d6399e, 4da157a723, 888767fcc4, 5bb89eb009; the vendored copy was refreshed repeatedly: 1e48683708, 65cd1ba682, f39cca94af, 646939c0e3, 5693bee0f1), Val.U became interface{} (13485be939, Node 424→392 bytes) and many int flags became bools (4a174ee4be, 5bf428ef80, 7885de55dc, d328756a69, 75883bae28, e7fe9f56ea). The package was then moved to cmd/compile/internal/gc with the binary renamed from 6g to compile (b3a508c03e usage names).

The second half of the effort was memory: Node dominated compiler memory, so func-only, name-only and param-only fields were split out into Node.Func, Node.Name, Node.Param (57279ba752 504→416 bytes, 76ec0ee53a, f4ab8203ba, 3ed9e4ca3c, bd8bb67357), and a long series moved fields into those substructures and deleted dead ones (ffef180f82, 66be1481df, 496ad0a286, 4fdd53680c, 71080fbb9e, 60e5f5bdff, 140ef3c59a, ac2bda1b00, 3007e953d0, 11322d45a1, 3c3019aa51, fd2154f906, bd4fff6358, da094f19f1, f1f0eda446, 3be7f28e82, 81d5810be9). cd9f417dbb documented which Node fields each Op uses.

Timeline.

Parser: from yacc to a hand-written recursive-descent parser

Span: Go 1.1 → Go 1.8 · Status at go1.17: the gc-internal parser was removed in 1.8; the syntax package parser is the only parser → continues as The syntax package (parser, scanner, tree) (1.18+)

Continues in: The syntax package: parser, scanner, source reader, positions and error recovery (Go 1.10 → Go 1.17)

Earlier history: Lexer, grammar and statement syntax (semicolons, chan operators, conversions) (Go 1.0 → Go 1.0)

Design. In Go 1.1–1.4 the grammar was go.y with Bison (y.tab.c), custom error-message tables generated by bisonerrors (made compatible with Bison 2.7 e7ef3b6daf and 3.0 4eaf91a7a7), and a hand-written lexer (lex.c). Many error-message commits in these releases tweak the yacc actions (for example d00bd1d1f4 friendlier error for C-style if (i == 0), 9146ac14ee avoids parser stack overflow on a long else if chain, c956dcdc54 rejects parenthesized .(type)).

After the Go port (see previous thread) Robert Griesemer translated the grammar into a recursive-descent parser (parser.go) in b569b87ca1: “a translation of the yacc-based parser with adjustments”, yacc actions copied for correctness, enabled by default, -oldparser to revert; measured about 18% less parse time per file and about 3% less time compiling cmd/compile. Follow-ups made syntax-error handling and recovery match the old messages (c8bc7f1abd, ac658a83c2, 0133d24c94, 64cd86798b, aad18b849b, 656a732a21, 5500d46914 for &(T{}), 1a111ea2c7 for <-x vs <-chan), and b5fe07710f deleted go.y and y.go (6025 lines removed). In 1.7 the lexer was rebuilt around bufio.Reader directly (5aa5db7593, 539aa05a64 always use getr, 8dd2ce2b98 factor out literal lexing, aa5b44aeab set nlsemi where the token is identified, 071e43a958 keywords no longer Syms, 3c0fae5306 pragmas tracked in the lexer, 8fd7c11d59, e0fa809f4c rationalize lineno vs lexlineno, a5b7a8d6dd error sorting, 49d1e30710 binary-expression parsing shared with go/parser, f5f8b38462 removes prectab).

The parallel track was the new cmd/compile/internal/syntax package (see next thread). 2ff463948c introduced noder.go which converts the syntax AST into gc Nodes (hidden behind -newparser); adda7ad295 made it the default on 2016-08-16, and 57df2f802f removed the old lexer/parser in 1.8.

Timeline.

The syntax package, position representation and parse-time checks

Span: Go 1.8 → Go 1.9 · Status at go1.17: still the design (syntax tree with Pos and PosBase; branch checking in the parser) → continues as The syntax package (parser, scanner, tree) (1.18+)

Continues in: The syntax package: parser, scanner, source reader, positions and error recovery (Go 1.10 → Go 1.17)

Design. c8683ff797 (2016-03-04, in go1.8) added cmd/compile/internal/syntax: “fast Go syntax trees”: nodes, scanner, parser and printers, building trees for the whole std lib at about 1.8M lines/s and 282 MB allocated for 1.07M lines. Its error messages were aligned with the old parser (70544c91ff). In the 1.9 cycle the representation of positions was redesigned on the [dev.inline] branch: 8d20b25779 introduced a general syntax.Pos plus PosBase (line-directive aware), 32bf2829a1 processed //line pragmas in the scanner, 24597c080b introduced cmd/internal/src.Pos, 4808fc4443 replaced src.Pos/LineHist with syntax.Pos throughout (the commit notes that compiler data structures grew, with Template compile up by about 9.5% and Compiler by about 4% in compilebench), and 33c036867f removed LineHist. The column unit was set to bytes (f3b56de4d2, 1f9f0ea32b starts columns at 1), and 5267ac2732 removed the gcCompat flag and set node positions “in a principled way”: the position of an Operation is that of the operator, so a + b + c has different positions for the two additions; this changed export data and pcline tables so it did not pass toolstash. e0329248d5 added positions for { and } (1.9% more node memory), b5f81eae17 replaced inlined statement lists with BlockStmt (+0.4%), f37ee0f33b records the column at function end, d1f5e5f482 “always construct a correct syntax tree”, and 2a5cf48f91 made error messages print line:col (byte offsets; -C flag to disable and planned for removal).

Two compile-time structural changes rode on the syntax tree. Files are parsed concurrently (ec63158d71, with b90aed020d reorganizing file parsing and e48919bcde splitting mkpackage). Label and goto checking, first a separate checkcfg.go pass over Nodes (c03e75e539, 299 lines, so that SSA generation can skip dead code), was moved into the parser as syntax/branches.go (912a638b0c, with a CheckBranches mode flag so tests that only care about syntax can switch it off; also fixed the issue 8042 “jumps over declaration” reporting), with bb70f517e9 reporting the innermost block for gotos jumping into blocks and a50962131a computing BranchStmt.Target. style cleanups aside, 912a638b0c also removes the dependency on the symbol dclstack.

Syntax error quality got a long tail of work in 1.9: 9799622f09 distinguishes ; from newline in messages, 3c22e5ca27/53c6ac5419/f823d30514/efb3cab960 improve errors for malformed if/for/switch headers and var decls in init clauses, 1693e7b6f2 character literals, d390283ff4 compiler directives must start at the beginning of a line, 5e954047bc/ee2f5fafd8/b5e1ae46ad tolerance and crash fixes, 422c7fea70 rejects declarations in a for post statement.

Timeline.

Binary export data and the object-file split

Span: Go 1.1 → Go 1.9 · Status at go1.17: binary export format (v1..v5 “bexport”/“bimport”) plus inlined bodies; later replaced by the indexed format and then unified IR → continues as Unified IR: one serialization for noding, stenciling, inlining and export (1.18+)

Continues in: Indexed export format and the export-data lifecycle (Go 1.10 → Go 1.17)

Earlier history: Export/import data format (Go 1.0 → Go 1.0)

Design docs & discussion

Design. Export data in Go 1.1–1.5 was text: a $$ section of Go-like declarations written by export.c, parsed by the ordinary lexer/parser. Inlinable bodies were exported as source-like fragments, which made inlining bugs show up as export-data bugs: 8d95245d0d, 761830f481, fc7b75f216, 78f5b616fc, 62891fb811, 20ebee2c31 and c1fc8d5296 all repair missing or corrupted body exports; constants were exported in hexadecimal (fbaf59bf1e) and later without the original expression (f09887cdce).

Go 1.6 added ae2f54a771, Robert Griesemer’s compact binary export format (bexport.go/bimport.go) under -newexport, importable in “mixed mode” with the textual format; export sizes were about 26–30% of the textual size for std packages in the commit’s table. It initially lacked inlined function bodies. Binary-escaping (e6ccfc1ad1: no $ bytes inside) and -importmap before unsafe handling (a8fc547f78) followed. In 1.7 4598382724 exported and imported all bodies without hooking them up, a9ea36afbb completed body export (three sections: platform-independent objects, objects pulled in by inlined bodies, and the bodies themselves; “completely compiler-specific and easily changeable”), compiler-specific flags moved to a compiler-specific section (b1851a3c11, 7d0d122247), position info was added (5c593a3227 “first cut”, delta-encoded line numbers and canonicalised strings, 14% size increase; e8d4ffb766 fixed the node used; 6c11e2710e delta-encoded filenames, 15–25% savings on some packages; 889c0a66fc disabled position export “for now”), and 7538b1db8e switched to binary by default on 2016-04-15, quoting ~65% smaller export data, at least 62% faster writes and 37% faster reads, with -newexport=0 as the escape hatch. feb6131b1a added -linkobj to write the object in two parts (export data for compiles, everything else for linking) and moved two bits the linker needed into the object header proper.

Go 1.8 removed the textual format (b4e9f70412, f50ced6d73, 074d6a649c), then reintroduced version-0 binary reading (c85b77c22b) and added a version byte with graceful failure on skew (5a6f973565), and exported aliases (03d81b5ed9, later revert of user visibility 8e970536df). 1.9 added linknames to export data (e3efdffacd: lets -l=4 inline functions that contain linknamed calls), position info in inlined bodies (9fd359a29a: +8–20% export size, accurate line numbers for cross-package inlining), type-alias format (49de5f0351: embedded-field name modes), interface embedding information (ee272bbf36), a cheaper position encoding with a -64 new-file marker and /-component paths, bumping the version to 5 (a6b16e0024: about 8% smaller), ad8c17b703 not exporting dead code in inlinable functions, and ff0748670c/2123a6c644 fixes for exported bodies and line-directive-relative positions.

Timeline.

Inlining

Span: Go 1.1 → Go 1.9 · Status at go1.17: the Node-based inliner with budget 80 survives, extended later (mid-stack, then PGO) → continues as Compile-time performance, concurrency and the self-PGO’d compiler (1.18+)

Continues in: Inlining: budget, mid-stack inlining, closures, and what became inlinable (Go 1.10 → Go 1.17) · Inlining-related frontend work: export of bodies, type switches, closures, devirtualization (Go 1.10 → Go 1.17)

Earlier history: Inlining (Go 1.0 → Go 1.0)

Design. Inlining (inl.c/inl.go) works on the typechecked AST: ishairy decides eligibility by a node-count budget, the callee body is deep-copied (treecopy/mkinlcall) with parameters turned into assignments to fresh locals, and the body is exported so importing packages can inline too. Go 1.1 widened it to functions with local variables (530147e870, with cb856adea9 annotating locals with unique ids so copies get distinct names, ae5e791ed2 saving the local var list before inlining, dfdfba14b9 letting temporaries from inlining be registerized), fixed a series of inlining-related miscompiles (b6ea905ed9 receive operator, a4682348c2 complex literals, 319131f295 composite literals in if, 9aef20e823 embedded builtins, 49da9a8e44 unnamed structs with embedded fields). Go 1.5 added transitive inlining (77ccb16eb1: calls that are themselves inlined away no longer block inlining of the caller, together with a larger budget so heapBits.next inlines; html/template compile time −12%). Go 1.7 allowed bodies that declare constants (fc6bcdee79) and switch (e41f527f4d, with a follow-up ac8d97b679), and Go 1.8 added -m-style explanation of why a function is not inlined (e4cae432d6) and stopped considering recursive calls (376d9665a8). Go 1.9 stopped counting statically dead if arms toward the budget (566e72d0ce, newly inlinable: bits.OnesCount, (*Float).Sign, etc.), then removed dead if code once after typechecking instead (dffe5ac9f2, with the caveat that labelled/goto code is kept), fixed names of inlined methods from other packages (83843b1610) and positions of inlined bodies via export data (9fd359a29a).

Timeline.

Escape analysis and stack allocation

Span: Go 1.1 → Go 1.9 · Status at go1.17: the 2012–2015 flow-graph analysis (esc.go) with param tags remains; rewritten in later releases → continues as Old typechecker and IR node-model cleanup (1.18+)

Continues in: Escape analysis: the esc.go fixes, the escape.go rewrite, and the move to package escape (Go 1.10 → Go 1.17)

Earlier history: Escape analysis and heap allocation (Go 1.0 → Go 1.0)

Design docs & discussion

Design. Escape analysis (esc.c, later esc.go) assigns each Node a “loop depth” and tracks whether addresses flow to the heap, to a longer-lived scope, or to results, producing per-function parameter tags used at call sites. Go 1.1 added tracking of the flow from input to output parameters (976ca1a47d step 0 synthesizes names for anonymous results, 75692424d2/507fcf37d2 the analysis, 572d984eaa, 71282131a1 fixes), applied results to closures (9fe60801ae: avoids allocating closures used as “macros”, defer args or Walk idioms; BenchmarkCallClosure1 119→7 ns), handled method values (38e9b0773d) and added a noescape marker for extern funcs (fd178d6a7e). Go 1.2 moved big stack variables to the heap (1f4d58ad5d) and fixed wrapper escape analysis (7cfa8310c7). Go 1.4 made it run always, even in -N mode (00d2f916ad) and fe3c913443 fixed an unsoundness with functions returning an indirection of a parameter, which mattered once stacks were copied (heap objects pointing at stack variables would not be updated). The fix tags such parameters as “content escapes” without recording which result; a more precise mapping was explicitly left out as a trade-off (more precise means fewer heap allocations).

Go 1.5 improved flow precision (7fbb1b36c3, David Chase): the per-function summary records outK = paramJ, outK = *paramJ, heap = paramJ, heap = *paramJ in a tunable number of bits (2 chosen), “level” split into three parts to model address-of cancelling indirection; 420 fewer heap allocation sites compiling std and a 15% reduction in allocations performed by the compiler on html/template. Dmitry Vyukov’s series then used escape results to stack-allocate more: 32-byte buffers for non-escaping []byte→string and concatenation (e6fac08146), []byte(str) conversions (9568126f35), string(rune) (4ce4d8b2c4), non-escaping make(map) with hmap and one bucket on the stack (b3be360f16, 75 std cases, NewEmptyMap 124→55.7 ns), interface backing storage (2270133981), &T{} like new(T) (2af3cbe308, 1.1), and no copy for range []byte(str) (71be013842). Go 1.5 also ran escape analysis after method-wrapper generation (5be61b18d4); Go 1.7 eliminated PPARAMREF (20803b845f), changed liveness handling of heap-escaped params with the new PAUTOHEAP pseudo-class (b6dc3e6f66), and stopped leaking dottype expressions (5c5e8d4105). Go 1.9 stopped updating outer variables after capturevars completes (61336b78c1) to make results deterministic under concurrent compilation.

Timeline.

Closures, method values and variable capture

Span: Go 1.1 → Go 1.7 · Status at go1.17: still the design (capture by value when never reassigned and not address-taken; closure call → function call) → continues as Old typechecker and IR node-model cleanup (1.18+)

Earlier history: Closures (Go 1.0 → Go 1.0)

Design. Closures are OCLOSURE nodes whose body becomes a hidden top-level function (25922c0658 introduced hidden closure functions earlier in Go 1.1). Method values x.M (d3c758d7d2, 1.1) are compiled as closures over the receiver (176 lines in closure.c). Go 1.5’s Dmitry Vyukov work changed the capture model in two steps. 0e80b2e082 “capture variables by value”: the spec says capture by reference, but if a captured variable is neither address-taken nor ever assigned after capture it is effectively const, so the closure copies it; about 80% of std captures became value captures, with zero heap promotion of PPARAM to PPARAMREF (BenchmarkCompressedZipGarbage allocs 153→126). Range variables and outputs needed special-casing (eaa872009d, 3bf005cea3). c4ee44b7b9 transforms direct closure calls (func(){...}(), go func(){}(), defer func(){}()) into plain calls with closed variables passed as arguments, so no heap context is needed (CreateGoroutinesCapture 6→2 allocs); it split the work into capturevars (decides capture mode, needed before escape analysis) and a new transformclosure pass run just before order/walk, and the call-site rewrite happens in walk of OCALLFUNC(OCLOSURE). Closure names became simpler (3a0fbfab57), called closures get captured args prepended to their params (731dcdae6d), and Go 1.6 made captureless closures constants (4f97ec0866): they can initialise globals at link time, which removed fmt.ssFree’s reachability from fmt.init and shrank hello-world by about 4%. In Go 1.7, PPARAMREF and Func.Outer were deleted (20803b845f, 93369001c7), Func.Cvars became a Nodes (52d9479e3b, 922ce58de0) and transformclosure was simplified (c7917de0bb); b64f549ba9 added -d closure and detection of leaking closures in the runtime.

Timeline.

Language changes and rules as implemented in the compiler

Span: Go 1.1 → Go 1.9 · Status at go1.17: each rule is part of the language; implementation later moved to types2 → continues as Frontend support for new language features and spec changes (1.18+)

Paths not taken: Quaternion types (never landed)

Continues in: Language version gating (-lang) and language changes implemented in the frontend (Go 1.12 → Go 1.17)

Design. The gc typechecker (typecheck.c, then typecheck.go) implemented each spec change as a mix of grammar edits, typecheck rules and walk lowering. The log shows these language changes (the user-visible bullets in the timeline):

Go 1.1 spec changes. Method values (d3c758d7d2). “New return requirements”: a function with results must end in a terminating statement, implemented in typecheck by ecab408c42 (145 new lines, removing a walk check). The revised rule for shifts in equality comparisons (465ff4cfc0), shift defaultlit in interface context (1947960a6f), untyped complex inference (861aa4698a), integer-valued idealized floats allowed as indices (88b98ff791), bool losing idealness under logical operators (5188c0b59c), rejection of selectors on blank (b65acaeab2), methods on non-locals (7c3694c4de), division by constant zero (ba05a43608), “latest rules for checking make sizes” (f02067a99a), and bounds checks of constant indices/slices in typecheck (85d60a727c, 72bf3bc176, e3977f0d3a). ced8004a00 rejects (**T).Method expressions.

Go 1.2. Three-index slices x[i:j:k] (b4e92cee97, “Design doc golang.org/s/go12slice … experimental”; 8cd6341cf8 lets it into the release), including escape/racewalk/fmt support and per-arch cgen.

Go 1.3. Zero-length arrays are not comparable (502958ffa6), duplicate-key check for maps with interface{} keys (3072df5c1d, fixed again by ec38c6f5e3), fallthrough misuse rejected (96678f9dc0), float rounding and exponent range fixes (cb50277510, a43673cf8a, 60be4a2450, 1be479df92).

Go 1.4. for range x {} (8d504c4e97), pointer constants disallowed (7f40e5e6e5), short variable declarations with duplicate symbols prohibited (55df81d37f), blank methods in interfaces disallowed (db4dad7fd7), **T method calls disallowed (93fcb92257), initialization cycles through method values detected (fe8f799ef7), bounds error for out-of-range constant string slices (870f4e190c).

Go 1.5. Logical operators on untyped operands produce untyped bool (5cc29ab95e), method selectors no longer auto-dereference a named pointer type (b59dd94f33), map index expressions allowed in for range (b581ca5956), map literals may omit the key type (73d109c509), str[i] has type byte (c735064cde), unsafe.Pointer constants may only be converted to uintptr (a3dfcf51c6), detection of bad append(f()) (3f209abb29), goto validation ignores type declarations (5726af54eb), p-notation floats rejected in source (596bb76248), and an error if only one argument to complex (cb1897a58c).

Go 1.6. Rejections and fixes to constant typing: 336c998291 slice/map/func comparison against converted nil, be20948e27 bool(true) is a constant expression, 27691fa467 !typedbool is typed (so const _ bool = !mybool(true) is no longer accepted), c488548967 labels with package names, d6a203ecab const and label names may match, 67722fea50 invalid unsafe.Sizeof([0]byte{}[0]).

Go 1.7. 49dad0f571 arbitrarily deep embedded fields (the 1.7 compiler removed the limit), 75b886ab79 rejects embedded unsafe.Pointer, 3eaa304629 ignore receiver name when finding duplicate methods, f5423a63df a dot expression cannot be a struct literal key, e48a2958d1 empty and absent struct tags are identical, c97ddf9c10/f229e46783 assignment error fixes (“cannot assign to struct field in map”).

Go 1.8. 8895a99c9f typed non-integer constants as len/cap args to make are disallowed, 7f1bc53379 only integer constant keys in array literals, 39055700b1 ignore struct tags when converting between structs (the Go 1.8 spec change), 936ae27b9c untyped array bounds representable as integers are valid, fc5df089da constant comparison fix.

Go 1.9. 660200528a no implicit dereference of pointer-to-array for real/imag, 829adf5047 real/imag of untyped constants, c9446398e8 composite-literal structs with _ fields, 422c7fea70 no declarations in for post, 5f5d882f5c bad pragma combinations earlier, 10a200e560 division by complex zero.

Type aliases are in their own thread.

Timeline.

Type aliases (the => experiment and type A = B)

Span: Go 1.8 → Go 1.9 · Status at go1.17: type A = B is the language feature; the first implementation was reverted in 1.8 and redone on [dev.typealias] in 1.9 → continues as Generic type aliases and Alias type nodes (1.18+)

Design docs & discussion

Design. The first attempt (Go 1.8 cycle) added alias declarations to the new syntax package (32db3f2756) and package-level aliases to the compiler (1b0cf430dd, “requires -newparser=1”, then c0e2318f7c simplified parsing), using the => notation (89632aa183: “type T = p.T” disallowed, must use =>; 50f66fbb66 disallows init as alias) with import/export of alias declarations (03d81b5ed9, bcc0247331 avoiding double export). On 2016-11-04 8e970536df reverted the user-visible parts (“Decision to back out current alias implementation”), leaving import/export code in place. The second attempt came after the decision to use = in the spec: 3e11940437 ([dev.typealias]) recognized the syntax but complained, 49de5f0351 defined a tentative export/import format (embedded fields get three modes: name equals type name and exported, same but unexported, @+field name when different), f011e0c6c3 made related fixes in compiler, go/types and go/importer, b2386dffa1 type-checked alias declarations (known issues listed in the message: duplicate method declarations through alias names not detected, cycle error messages, byte/rune setup), 5c160b28ba improved cycle errors, de2e5459ae declares methods after resolving the receiver type, 9ecc3ee252 avoids false-positive cycles from aliases (fixes #18640) and 5d92916770 changed Func.Shortname to *Sym.

Timeline.

Constant arithmetic, Mpint/Mpflt and floating-point constants

Span: Go 1.1 → Go 1.9 · Status at go1.17: big.Int/big.Float-based constants (Mpint, Mpflt) → continues as Old typechecker and IR node-model cleanup (1.18+)

Continues in: Constants: Go 2 number literals, evconst, untyped conversion and go/constant (Go 1.11 → Go 1.17)

Earlier history: Constants and multi-precision arithmetic (Go 1.0 → Go 1.0)

Design. Constants were represented by Val with arbitrary-precision Mpint (fixed array of limbs, 464 bits then 512) and Mpflt (hand-written multi-precision floats in mparith), plus Mpcplx. In Go 1.1–1.4 work was bug fixing: complex literals (15bc7ab957), fixing inverted comparison order in mpcmpfltc (f85ba7d50a), exponent overflow and underflow checks (cb50277510), rounding of large negative exponents toward zero (a43673cf8a), float32 conversion and printing (60be4a2450, 1be479df92), ovf in mpaddxx message (397f129daf), isgoconst crash (32dffef098). Go 1.5 replaced the limb arithmetic: Mpint on math/big.Int (888767fcc4), Mpflt on big.Float (5bb89eb009, via a vendored cmd/internal/gc/big because the real math/big had no Float yet), after which a51d5f27e8 set multi-precision to 512 bits and 1122836b5f rejected huge exponents without math/big; 85de30e72f (1.6) allowed huge right shifts and 715f63778d avoided converting huge floats to integers; 76285213b8/79a3b561b5 removed -0 special handling (“there are no -0 floating-point constants”). Go 1.7 turned Mpint/Mpflt functions into methods (d3253876f2, 0659cf6911) and added special-casing for comparisons against zero (07749aef98), and 97360096e5 replaced Ctype switches with type switches. 1.8 and 1.9 refined diagnostics: e99dafc4a8 reworked the “truncated to int” messages (“integer too large” instead of overflow wording; SetFloat returns bool), 871300308a, bcf2d74ce8 large exponents, 2e60882fc7 no duplicate error on ideal→float overflow, ff3a4ef7ec less reliance on Mpint internals, 2acd3fa8f1 fewer switch cases in evconst, b83a916f71 iface == iface constant evaluation respects !=.

Timeline.

Static initialization, composite literals and init

Span: Go 1.1 → Go 1.9 · Status at go1.17: sinit.go model with static data, dynamic init and oaslit; extended later with staticinit → continues as Static initialization and package init (1.18+)

Continues in: Package initialization order, static initialisation and init functions (Go 1.10 → Go 1.17)

Earlier history: Initialization and static data (Go 1.0 → Go 1.0) · Composite literals (Go 1.0 → Go 1.0)

Design. sinit decides, per top-level var x = expr, whether the initializer can be laid out as static data (staticassign) or needs init-time code (anylit/fixedlit/maplit). Go 1.1 skipped codegen for var _ = ... when unneeded (6592456feb) and computed initialization order for blank vars (880d869764, 1.2). e5c105033a (1.3) fixed an unsound optimisation: var y = x; var x int was treated as x = 0, wrong when x is defined in C/assembly (cgo), so propagation is disabled when x has no Go initializer. Go 1.4 stopped using static init for small structs/fields (cfafa9f4cb). Go 1.5 resolved &x.f at link time (2fc29a83ae), statically initialised function pointers (632217aae4, go tool text segment −7.4 kB), and unsafe.Pointer(&x) of a global (9f90f31c3a). Go 1.6 statically initialised captureless closures (4f97ec0866), fixed initialization logic (aaabe3d849). Go 1.7 reordered slicelit to fill the stack array before slicing it (934c359964, so no write barriers; binaries ~1% smaller), treated static composite literals as side-effect free (55ab07c224) and eliminated a copy for static literals (b024ed0d94), CSE learned that newobject is bespoke (db5338f879: issue 15235 size 10k compile 44.9 s → 7.7 s). Go 1.8 added OSLICELIT and unified arraylit/structlit (49cce1a62e, cd2d5ee048), statically initialised some interface values (dfc56a4cd3), two tables for table-driven map inserts (d98de0c3d0), size hints for map literal allocations (1faea596e4), OSTRUCTKEY (add3ff549a: Unicode allocs −13.7%) and bba1ac4fd9 (only explicit OKEY nodes). Go 1.9 fixed a missed write barrier for global = T{} (160914e33c, oaslit not for globals), unrolled small static maps with cutoff 25 (50688fcb6e: 15 min → 45 s on #19751), and for functions with more than 500 user inits builds a static array of functions and loops (d1b544c7eb).

Timeline.

Walk-level lowering and runtime-call optimisations

Span: Go 1.1 → Go 1.9 · Status at go1.17: still the walk/order model; the runtime entry points mostly persist → continues as Static initialization and package init (1.18+)

Continues in: Lowering in order/walk and the shape of runtime calls emitted by the frontend (Go 1.10 → Go 1.17)

Design. Many compiler changes in this era moved decisions from the runtime to compile time, as walk.c/order.c rewrites to cheaper runtime calls. Interface conversion: convT2I cache (18e86644a3, BenchmarkConvT2ISmall 43→15 ns), inline pointer-sized convT2I (8fd65b0e1d), iface==concrete without allocation as x, ok := i.(C); ok && x == c (77a2113925, 38 std sites, ~75% faster), calls by reference (deb554934c), non-variadic assertI2T (ccdb50931f), inline x := y.(*T) (4224d81fae), scalar x, ok := y.(T) (615a52b95b, fmt printArg 3952→2624 bytes), itabs emitted by the compiler (f00bbd5f81) so remaining convT2I get the itab directly (859b63cc09), inline convT2E/convT2I when result does not escape (6129f37367), inline convI2E (395d36a67d), specialised convT2E16/32/64/string/slice/noptr and zero-value no-alloc (504bc3ed24, 93% of calls), efaceeq/ifaceeq simplification (e67d881bc3), CONV rather than CONVNOP for interface conversions (807c80fce3), canonical empty interfaces (62947bedd2). Maps and strings: []byte key lookup without copy (f5f5a8b620), mapassign returns a pointer to the slot so m[k] = v is *mapassign(t, m, &k) = v (442de98c14, preparation for m[k] += v in one call), mapassign_fast* and mapdelete_fast* (ec091b6af2, 5d6b7fcaa1, int32/int64/string keys, ~30% faster assigns), string range (0dae9dfb08, ASCII fast path 43.7→10.3 ns), runtime string ops written in Go (61dca94e10 with go:nosplit), makeslice with int arguments (e6f9f39ce5), growslice using int (81d4072eb0) and removing growslice_n (d1341d6cf3), vararg runtime calls removed (24699fb05c, 6f6a9445c9). Loop and arithmetic: memclr idiom recognition for for i := range a { a[i] = zero } (f03c9202c4, 18 std places; 64-byte clear 50.8→4.1 ns), single-comparison integer in-range checks (6286188986) extended to switch cases (8c85e23087, “min range size 2”), samesafeexpr extensions for m[k] op= v (9d2b988e4a, 9d4623fe43), constant switch cases tracked in a map (e26499153e), explicit zero-divide panic (18559e2da7), faulting arguments computed before argument stores (cf28e5cc9d), panicwrap argument-free (2c91bb4c8a), loop rescheduling by stack bound via FORUNTIL (d71f36b5aa), unsafe builtins handled as universal builtins (3f2cb493e5).

Timeline.

Write barriers, precise stacks and liveness (compiler side)

Span: Go 1.2 → Go 1.7 · Status at go1.17: write-barrier insertion moved into SSA later; frontend marks (pragmas, OASWB) removed in 1.9 → continues as Old typechecker and IR node-model cleanup (1.18+)

Design. For the concurrent GC, the compiler had to supply precise pointer maps and, later, write barriers. The compiler-side steps were: precise stack-frame liveness (precisestack, disabled for 1.2 in 30ecb4cd05: “the zeroing required without it is too expensive”, re-enable by GOEXPERIMENT=precisestack; enabled by default in 1.3 by ecf700b5ee, reverted/relanded as 53061193f1 after a 32-bit fix, costing about 5% of all.bash time and tracked as issue 7345); this guarantees interface values are always initialised so the type word is trustworthy. Cost reduction followed in 1.3: shorter temporary lifetimes through orderexpr-allocated temporaries with VARKILL (daca06f2e3, 96d90d0981: ambiguously live temps in godoc 860 → 711 → 121 → 23), and compact liveness bitmaps (4676fae525: one bitmap length per set, duplicate bitmaps merged, bitmap sets named gclocals.<md5> and dupok so the linker dedups; bitmap bytes in godoc 1326600 → 245580, binary −9%). Liveness got 10x faster in 1.4 (7aa3031eba). Go 1.4 emitted write barriers (fcb4cabba4: *p = x becomes runtime.writebarrierptr(p,x); “the goal is to slow things down (and be correct)”, BinaryTree17 +10.7%), with 2/3/4-word specialisations (a3416cf5cd), elision for *x = T{}-style zeroing (3c40ee0fe0) and for x = x[0:y]/x = append(x, ...) (1552e62d70); the interface representation changed so that the data word holds only pointers (950ad4fa2b, 75b72b1b27 re-enabled IfacePointerOnly). In 1.5 barriers are emitted “at lower level” (0ad4f8b1f7, preparation for inlining), with the writeBarrierEnabled check inlined (653d56075d), no barrier for *tmp when tmp=&PAUTO (a7bb393628), a barrier in copy of function parameters to heap (e5ef657264), //go:nowritebarrier (557a61d270). Go 1.6 added go:nowritebarrierrec (3a765430c1, special-case allocm 402e37d4a9), 1.8 added go:yeswritebarrierrec (a9e6cebde2) and go:notinheap (77527a316b): no new(T), make([]T), append, or implicit allocation of a notinheap T, propagated to structs/arrays containing them, so stores to pointers to such types need no barrier. Go 1.7 removed PHEAP bits (b6dc3e6f66: new PAUTOHEAP pseudo-class, walkexpr rewrites references to indirections of a stack PAUTO), so liveness and the SSA back end see ordinary variables.

Timeline.

Compiler data structures: Node, Nodes, Type, and the types package

Span: Go 1.5 → Go 1.9 · Status at go1.17: ir.Node and types.Type as separate packages came in later releases; the 1.9 types package is the start → continues as Types package internals: size, algorithm kind, TFlag, SIMD flags (1.18+)

Continues in: dev.regabi: from *Node struct to the ir.Node interface and concrete node types (Go 1.10 → Go 1.17)

Design docs & discussion

Design. The Go-port compiler spent two years reshaping its main data structures, mostly with toolstash -cmp as the correctness bar. Lists: NodeList linked lists were first replaced by slices in specific places (1.6), then Ian Lance Taylor built a transitional nodeSeq interface (466c948b55, “I tried to write a program to convert *NodeList to Node, but ran into too many problem cases”) and converted package by package (ca56c590d4, 2aa2da295f, 132ebeac3f, 99b6b77e22, 55c65d4a97, 80e5b52566, 65c4b55aba, 72d90d8238, 2a68c6c27c, 6c4e90a99e, f444b8a80e, 72655afb4e removes NodeList), leaving Nodes (1d5001afef for Nbody and Func.Inl). Types: Type shed linked Down/Type tricks: TFIELD types became a separate Field type (2e9369067b, c278f9302e), Type.Fields became a Fields type (517b6131b2, 0d2e92c2ca, 1c2bdfb7d9), Func.Inldcl, In/Out/This tuples were removed (c837761b52, dbed1c6361), constructors typArray, typSlice, typChan, typPtr, typMap encapsulated fields (eb98e51563, 09c672d50a, fdf6761e01, 76e72691a0), TSLICE split from TARRAY (40f1d0ca9f), and f38f43d029 “shrink gc.Type in half” by moving etype-specific fields into auxiliary types reached via Extra (alloc −2% to −3.4%, +1% allocs). Node shape: ODOT stores its selector in Sym instead of an allocated ONAME Right (5f525ca60d: 5000 fewer newname calls, 6.6% fewer nodes), OSTRUCTKEY and implicit OKEY removal (see literals), ODCLFIELD without Name/Param (dc46b882d5) and constructing Fields directly when importing (d553c29dc1), 166b1219b8 allocating Name/Param/Func together with Node (allocs −5%), ed70f37e73 packing bool fields into bitsets (alloc −2.4 to −4.1%), then Node.Class, Typecheck, Initorder, Embedded, Walkdef, hasVal, Likely into flags (386765afdf, 502a03ffcf, e2560ace3c, af7da9a53b, d286399641, 2fb2ebc32e, 7f0757b394), Ullman→HasCall (870d079c76), Sym flags as bitset8 (a5a1fd4bc9, b2c391b70c) and Used moved to Name (fc08a19cef). The big structural split came in f68f292820 (2017-04-04): “factor out Pkg, Sym, and Type into package types”, creating cmd/compile/internal/types with a types/utils.go of hooks and two conversion functions between *gc.Node and a dummy types.Node to break cycles; follow-ups moved the dcl stack (fd44d872de, 48163968b2), typepkg (24c52ee570), unexported Nopkg/PkgMap (62a2bee7a5, 39a132cb2f), removed Sym.Link, FieldName, IterFields and Cmptmp (1368977a08, 5280dfbfad, 5b38923eba, 3158583713). Type caching: pointer/slice types are cached (78a267e379: stack slot reuse relies on Type identity), disabled in the back end (f9fb4579e1) for concurrency, with 2805d20689 replacing ptrto by typPtr. Haspointers caches were removed (944d56d763, e82c925f5e) and dowidth ran more in the front end (0b6a10ef24, 8dafdb1be1 WidthCalculated, c51559813f, b1b4f67169).

Timeline.

Compiler performance, formatting and concurrency

Span: Go 1.7 → Go 1.9 · Status at go1.17: backend concurrency via -c, with cmd/go setting it; later default → continues as Compile-time performance, concurrency and the self-PGO’d compiler (1.18+)

Continues in: Concurrent compilation and frontend performance (Go 1.10 → Go 1.17)

Design docs & discussion

Design. Compile time was tracked by compilebench (Template, Unicode, GoTypes, Compiler, MakeBash). In 1.7 ad4c55c076 replaced fmt.Sprintf with concatenation, 65b4020403 stopped creating 2 Syms and 2 Nodes per string, 135109d241 reduced slice-header allocation for :=. In 1.8 f542576b9e added compiler phase timing (-bench flag writing benchmark-format records like BenchmarkCompile:go/types:fe:parse), 6e759ad2e2 added runtime/trace support, and the fmt.go printing layer was reworked: a printer experiment (dc2a0d59a2, f7ac5da495, allocations up 10%) was abandoned in favour of fmt.Formatter/fmt.State for *Node, Op, Val, *Type, *Sym, Nodes (adcd34c732, 9e1f6a326e, 97ba3c8242, 8d0bbe2b48, a0d2010208, 266c6223df, 0ca5f269d4 removes the printer), then 37d452c3e9/0cd3ecb016 brought allocations back below the pre-change level for issue 16897 (reflect.go uses the plain tconv string method). In 1.9 723ba180b3 removed the fmtmode and fmtpkgpfx globals (they “stand in the way of making the compiler more concurrent”) by encoding the mode as named types (fmtNodeErr, fmtNodeDbg, fmtNodeTypeId), after weighing and rejecting struct wrappers (allocate on interface conversion), %*v precision, fmt.State context and a full rewrite; efc47819c0 threads a recursion depth instead of Trecur. Then 756b9ce3a5 (2017-03-19) added the initial backend concurrency: phases 5–8 (SSA, lowering, genssa, assembly) run per function in parallel using -c; order/walk proved hard to make safe, so with c>1 all functions finish phase 4 before any later phase starts, auto-generated functions (wrappers, eq/hash) after the first batch are compiled serially, and c=1 has no goroutines at all so cmd/go could “simply have cmd/go set c=1 always” if problems appear. A handful of mutexes were added; supporting changes: disabling typPtr caching in the backend (f9fb4579e1), per-function autotmp numbering (b87fcc6e06), typenamesym not touching Sym.Def and signatlist as a map sorted for reproducibility (91433eb577 reverted to 6d9c77f5dd after non-deterministic builds #19872), liveness variable indices in a map instead of Node.Opt (794d29a46f), 61336b78c1, -d compilelater, f5c878e030 randomised compile order under the race detector, 0b6a10ef24. Other perf commits: 302474c61c disable memprofile when unused, da67c23fbb flag to disable DWARF (−2.8% alloc), d62c6c3c39, f8b0231639 always optimise when building the runtime, 22f1b56dab -c flag, 8eb14e9de5 string debug flags, f9be63b9ba -d help listing keys.

Timeline.

Pragmas, directives and compiler flags

Span: Go 1.1 → Go 1.9 · Status at go1.17: //go: pragma set persisted and grew → continues as Compiler debugging infrastructure: bisect/hash debugging, flags, dumps (1.18+)

Continues in: Directives and pragmas in the frontend (//go: handling) (Go 1.10 → Go 1.17)

Design. Pragmas are recognised in the lexer (then in noder): //go:noescape for extern funcs (fd178d6a7e, 1.1), //go:nosplit and //go:linkname, //go:cgo_* (fd2bc95d53, 1.5; linkname requires import "unsafe"), //go:nowritebarrier (557a61d270), //go:systemstack (08e25fc1ba, restricted to runtime by dac87e9ed3), //go:noinline (a92543e1a1), //go:nowritebarrierrec (3a765430c1), //go:yeswritebarrierrec (a9e6cebde2), //go:notinheap (77527a316b). pragmas tracked in the lexer (3c0fae5306), handled immediately by noder (ee161e8591, 55875977eb), with bad combinations reported earlier (5f5d882f5c), and directives must start the line (d390283ff4). Flags: -pack (b022334029), -live (8bd8cede03), -d accepting comma-separated name=value lists (6c328efc4c), unknown -d flag aborts (516d9ef53b), -d disablenil (01512b3edb), -importmap (7bcc6a1615), -buildid (0f6da8923f), -memprofilerate (00d4a6b35d), new flag syntax (8a072ada84, d5ff441d9a), -msan (0c69f1303f), -linkobj (feb6131b1a), -dolinkobj (005c77dde8), -importcfg (4d6b08de70, “a generalization of and supersedes -importmap, -importsuffix, and -I”), -C, -c, -bench, -d help, -d compilelater, -trimpath documented (f2eb3de636), -A removed (832082b44e). Position-independent code for -buildmode=c-shared on arm (876b7cc227) and -buildmode=shared on linux/386 and arm (3ae4dc8a75, 78c05b8657) were small compiler pieces.

Timeline.

Race detector and sanitizer instrumentation in the frontend

Span: Go 1.1 → Go 1.7 · Status at go1.17: racewalk pass (later instrument) → continues as Compiler debugging infrastructure: bisect/hash debugging, flags, dumps (1.18+)

Continues in: checkptr instrumentation and compiler debugging flags (Go 1.12 → Go 1.17)

Design. racewalk.c walks the typechecked AST and inserts calls to the race runtime around memory accesses. It arrived in Go 1.1 (041fc8bf96, 431 lines, enabled by -b), with PC passed directly to racefuncenter (ce287933d6, encoding/json race benchmark ~1.9x faster) and &&/|| instrumented (c0b3c17184). Go 1.2 fixed tail-call wrappers (e440354c40, 269b2f2d4d) and arrays (3be794cdc2). Go 1.6 generalised racewalk to instrument for other tools (9e902f0f3a) and added -msan (0c69f1303f); Go 1.7 made insertion careful (c3b3e7b4ef) and Go 1.9 randomised compile order when race-enabled (f5c878e030).

Timeline.

Diagnostics and error messages

Span: Go 1.1 → Go 1.9 · Status at go1.17: ongoing; types2 later supplies messages → continues as Error reporting: codes, messages, and sub-errors (1.18+)

Earlier history: Diagnostics, error recovery and compiler plumbing (Go 1.0 → Go 1.0)

Design. A continuous stream of message-quality work: replacing “typechecking loop” with real diagnostics (892fa3ae6c, 6363fc5aa6), better line numbers (bf59aafddc composite literals, dd166b9437 “no new variables”, e2711cb202), removal of the word “ideal” (703bd83623-era: 703b897f78), “have/want” signatures for argument and return count errors (2d573eee8a, ec5b6406b7, 19d2061d50 suppress when types unknown), interface satisfaction messages (2254785c3e, 574e0f9a48, 1c2021ca14 suggests *T), unknown-field message (7b4545653c), import errors (d06dcd4595, 8d530f2472, 85195e2ccf, 5be1821a31, 9fda4df9a0 deterministic), and in 1.9 duplicate-error suppression (d62c6c3c39, 3218b1aa6f, f62c608abb, d55f528826, 2e60882fc7), correct redeclaration position (d79bb78a71), assignment count mismatch messages (88cf932e98, 73a44f0456, 3c7a812485), init direct invocation (cb6e0639fb).

Timeline.

Changes in this slice that are not part of a thread are listed on the release pages: 1.1 · 1.2 · 1.3 · 1.4 · 1.5 · 1.6 · 1.7 · 1.8 · 1.9

Compiler frontend (Go 1.10–1.17)

Reader: Claude Sonnet. Cites 1,071 of 1,353 commits in this slice; every non-noise commit is cited, plus 30 larger commits the noise filter had set aside, which a separate gap reader described. Read the design-heavy messages, subject lines and a few full messages, and checked the -G/AllowGenerics behaviour against the go1.17 source. No diffs were read, so the typechecker, walk, inlining and types threads are thinner than the IR, export-data and generics threads.

Release digest for this slice
  • Go 1.10 — Export-data diet (skipping unreachable/duplicate inline bodies), better syntax-error recovery and line directives groundwork, and many small typechecker/map-lowering improvements; frontend still C-to-Go-translated gc.
  • Go 1.11 — Indexed export format added and made default, syntax package made independent with column-aware line directives, concurrent compilation of all functions, method-set sorting O(N+M).
  • Go 1.12 — Binary export removed, -lang introduced, constant folding restricted to Go constants, typechecker tracing and new node dumper.
  • Go 1.13 — Go 2 number literals and signed shifts, evconst consolidation, spec-compliant package init order, multi-value f(g()) rewriting moved to typecheck, -newescape default.
  • Go 1.14 — Untyped constant conversion rewrite, -d=checkptr, overlapping interfaces gated by -lang, columns in export data, scanner error limiting.
  • Go 1.15 — Misplaced-directive diagnostics, new syntax source reader, ODDDARG removal, mid-stack inlining in recursion cycles, slicesym/gdata cleanup.
  • Go 1.16 — //go:embed, go:notinheap hardening, type-switch and method-value inlining, early devirtualization, export-data hash as fingerprint, init-cycle reporting fix.
  • Go 1.17 — The largest frontend restructure: dev.regabi (Node interface, base/ir/typecheck/noder/inline/staticdata/reflectdata/pkginit split, go/constant), dev.typeparams (syntax type parameters, types2, -G, irgen, first stenciling), unsafe.Add/Slice, and typecheck info in export data.

dev.regabi: from *Node struct to the ir.Node interface and concrete node types

Span: Go 1.10 → Go 1.17 · Status at go1.17: done: ir.Node is an interface with ~60 concrete node types (ir.Name, ir.Func, ir.CallExpr, …) and generated DoChildren/EditChildren/Copy; Left/Right/List accessors are gone; Nodes is a plain slice → continues as Old typechecker and IR node-model cleanup (1.18+)

Paths not taken: The dev.regabi Node-accessor demonstration (superseded)

Earlier history: Compiler data structures: Node, Nodes, Type, and the types package (Go 1.5 → Go 1.9)

Design. Through Go 1.16 the whole frontend used one fat gc.Node struct (Left/Right/List/Rlist/Ninit plus Name, Func, Param sub-structs, and overloaded Etype/Xoffset/Opt fields). Go 1.10–1.15 only chipped at it (stringer for ops 3231d4e4ef, Node.copy 19ee2ef950 and the copy/rawcopy/sepcopy split ce58a39fca, moving ONAME-only flags into Name 06b12e660c, removing Addable 46be01f4e0 and PDISCARD 9f4fb68152, Node.Etype overloading fix e4de522c95). In Nov–Dec 2020 the [dev.regabi] branch turned that into a typed IR in a strictly mechanical sequence, most steps produced by rf scripts (“[generated]”) and verified with toolstash -cmp / buildall.

The sequence: (1) add getters/setters for every field (d166ef6876), rewrite all callers to use them and unexport the fields (acb4d1cef1); (2) make the pointer type an interface: first INode (4d0d9c2c5c), then rename so ir.Node is the interface implemented by an unexported *node (41f3af9d04, with types.Node→types.IRNode c26aead50c); (3) peel concrete implementations off the struct, starting with a hierarchy of custom node types for type syntax (4e7685ef1a), ir.Name as the ONAME node (862f638a89), ir.Func as ODCLFUNC (c4bd0b7474), ir.PkgName (f6106d195d), statements (ffa68716a0, be3d8b40b5) and expressions (9a5a11adfa), ir.Closure/CallPartExpr (4eaef981b5, e5c6463e20), a lighter ConstExpr (a2058bac21) and BasicLit (c8610e4700) so that Name only represents declared constants; OMETHEXPR stops overloading ONAME (ee6132a698, 4f9d54e41d), ONAMEOFFSET/NameOffsetExpr replaces Name.Offset abuse in static init (c76be2a24e, ffb0cb7044), OTSLICE etc. type nodes (f0001e8867). (4) Replace the generic child accessors by reflection-free generated traversal: ir.Visit/DoChildren/EditChildren visitors (0d1b44c645, d855b30fe4, 4725c3ffd1, bb5aa2b664), Copy per node (18f2df7e81), all generated from struct definitions by ir/mknode.go (2cec6c4a8c, generalised in 499851bac8, 5cf3c87fa6); ir.Dump uses reflection and prints field names (4090af83c5, 6d783e7440 exports all fields first). (5) Remove the generic operations: ir.Nod (6f27d29be0), Node.Left etc. (14d667341f adds type assertions on every n.Op() == X branch and deletes the accessors from the interface; f9d373720e replaces the remaining uses with named fields), Nodes becomes []Node (114af2a044, 440308ffd7), SliceExpr.List, SliceHeaderExpr.LenCap, RangeStmt Key/Value, CaseStmt vs CommStmt get named fields (d1d64e4cea, d19018e8f1, 27b248b307, 2ecf52b841, a59d26603f), {Selector,CallPart,Method}Expr merge (4629f6a51d).

A parallel line of work removes identity ambiguity: one *Name used to stand for both a declaration and each use, so SameSource/Uses/DeclaredBy helpers (d9cb84c84b, 133b03e1c3) prepared ir.Ident for not-yet-resolved names (63bc23b545, 4c2d66f642, 94cfeca0a5 no more ONONAME on Name). Closures get a first-class representation: Func.ClosureVars replaces OCLOSUREREAD/ClosureEnter/ClosureType (7fd84c6e46, 9ed1577779, d9acf6f3a3, 9821838832), closures are transformed during walk (41352fd401), and Func.Nname is removed from types (c754f25241, c50c7a8c06).

Timeline.

dev.regabi: breaking up package gc into base, ir, types, typecheck, noder, inline, staticdata, reflectdata, pkginit

Span: Go 1.17 → Go 1.17 · Status at go1.17: done; layout of cmd/compile/internal/* as it remained → continues as Old typechecker and IR node-model cleanup (1.18+)

Paths not taken: The dev.regabi Node-accessor demonstration (superseded)

Design. cmd/compile/internal/gc held everything (flags, error reporting, IR, typechecker, noder, inliner, static data, reflection data, init). Splitting required a dependency order. Russ Cox’s mechanical sequence of 19-23 Dec 2020: first base for flags, Debug, Ctxt, Exit and error reporting (26b66fd60b), then ir for node definitions and the TINT8 alias removal so each name shows its package (84e2bd611f), then “collect global compilation state” into a struct ir.Pkgs-style (1a3b036b83) and a series of “separate X more cleanly” prep CLs: exportsym, noder, nowritebarrierrec, dowidth, typecheck, various (85ce6ecfe3, 4836e28ac0, 1a523c8ab0, 7c8f5356ab, 3b12c6dc08, 572f168ed2, 51ba53f5c2). Helpers then migrated to base (ead4957892), types (9ee309255a, and type size calculation dac0de3748, replacing an import-cycle hack) and ir (527a1895d6), after which packages were cut out: typecheck (b9693d7627), inline (575fd6ff0a), noder (fbc82f03b1), staticdata (4dfb5d91a8), reflectdata (de65151e50), pkginit (3d8a3cb06b). typecheck1, a 1,747-line function, is split so each case is its own function (0256ba99a8), later restructured so typechecking sits in noder to be interleaved with noding while non-typechecking passes go back to gc (0f1d2129c4, with deadcode in its own package). Printing of Type/Sym moves to types (bb4a37bd93), FmtFlag/FmtMode are untangled (70155cca81, 3904a62829), and the ir↔︎types interface is narrowed (fb17dfa43d); types get constructors so IR code stops building Type literals (77a71e0057, c10b0ad628, 5ffa275f3c “first pass at abstracting Type”, e1047302bd) and mkbuiltin.go is updated for them (63c96c2ee7, 5898025026).

Timeline.

Indexed export format and the export-data lifecycle

Span: Go 1.10 → Go 1.17 · Status at go1.17: the indexed (iexport) format is the only compiler export format, now carrying positions with columns, type information on inlined bodies, closures and ABIs → continues as Unified IR: one serialization for noding, stenciling, inlining and export (1.18+)

Earlier history: Binary export data and the object-file split (Go 1.1 → Go 1.9)

Design. Up to 1.10 the compiler wrote a sequential “binary export” (bexport.go): importing a package processed every declaration and every inline body in order, even if the importer used none of it. In large Go programs the export data was big because of transitive type declarations and inline bodies. The indexed format (ca2f85fd3f) writes an index of declaration and inline-body offsets so iimport lazily loads only what compilation touches; the commit’s benchmarks compare -iexport=false and -iexport=true across -c and -l settings. It is read lazily through Sym.Def placeholders created by the importer; dd71e3fef4 and ae2a2d12f6 consolidated how imported declarations and init functions are wired up so both importers share code.

Before iexport there was incremental work to make export cheaper: skip duplicate inline method bodies already imported (f0b3626904, which for a protobuf-heavy package cut compiler RSS from ~12GB), don’t export unreachable inline method bodies (8684534321), record InlCost in export data so the importer does not recompute (a509cae90d), and simplify exporting of the universal error type, OTYPE and ONAME nodes (13772edbbd, 577967799c, c64e793850). Export data also had to be position-faithful: exported constants and declarations got precise positions (b976859b47, 3124439b3a, a5b759aab8, 26e0e8a840, 1c439e6e37).

Subsequent evolution: package “height” (longest import chain) recorded in export data to order method sets deterministically across packages (07029254a0, 49ed4cbe85); fingerprints for the linker’s index consistency check first from a build id, then from a hash of export data (aefd99de0b, 7795523910 [dev.link]); column information added at ~4% export-size cost, though importers like go/internal/gcimporter only read it (95b8cbfee9); from 2021 export began to include enough information to avoid retypechecking inline bodies (1129a60f1c), field notes (c7d708e42e, e12b0afa54), ABIs (67d565d281) and closures (1760d736f6, 5f3dabbb79, b98ce3b606, eb433ed5a2).

Timeline.

Generics bring-up in the compiler (dev.typeparams): parser, -G, types2, irgen, first stenciling

Span: Go 1.17 → Go 1.17 · Status at go1.17: generic code is parsed and type-checked only when the undocumented -G flag is given; stenciling of simple generic functions and types exists but is experimental and not enabled by default → continues as Generics via GC-shape stenciling and runtime dictionaries (1.18+) and Bringing up generics in types2 (1.18+)

Paths not taken: Generics implementations not taken: contracts parser, go2go, early stenciling demos (superseded)

Design docs & discussion

Design. The prototype from the dev.go2go branch was ported in stages. (1) Syntax: TParamList on TypeDecl/FuncDecl and File.EOF (617b633917), parsing of type parameters behind a syntax.AllowGenerics mode (b627988b0c), and a decision (73f529845c) to represent every instantiation T[P1,P2] as an IndexExpr with a ListExpr index, dropping a CallExpr.Brackets hack; later ~T as an operator and ~/| interface elements (4bbe046aad, 4638545d85), type parameters must always be named (7909d6ec28). (2) The -G flag lets the compiler accept generics (48755e06aa, “for now … only parses and then exits”). (3) A copy of go/types adapted to the compiler’s syntax tree is checked in as cmd/compile/internal/types2 plus an importer package (ca36ba83ab), and -G runs it for type checking only (87eab74628). -G levels (6b4da14dd3): -G/-G=1 parse and typecheck; -G=2 also noding; -G=3 continue to the backend. (4) A types2-based noder, “irgen” (ef5285fbd0), builds IR guided by types2 Info (types, selections 5aff757efc) skipping constant and type expressions and syntax checking, while still calling the old typechecker for desugaring; go test std cmd nearly passes with -G=3. Over Jan-Apr 2021 the old typechecker is replaced by transform functions invoked from noder2 (0265b6475f, e7e0995cba, b587b050ca, 84162b8832, 46ffbec1d6) so that nodes whose operands have type-parameter types can have transformations delayed until instantiation (eeadfa2d38), and OXDOT is resolved in noder using Selection (12cd9cf7e0). (5) Stenciling: generic functions are instantiated on demand (dcb5e0392e), including calling other generics (0fbde54ea6), methods on type parameters (12e15d430d), conversions (fdf3496fcc), generic types with methods (20050a15fe), OFUNCINST/OTYPEINST nodes (e633f343ba), inferred type arguments (3f845b3b45, fdded79e6e, 1318fb4a32), closures (d8e33d558e, f432d3fc41), interfaces (a70eb2c9f2), channels/maps/builtins (dca9c11845, c236095638, 70d54df4f6), untyped constants (90b1ed1602) and pragma propagation (083a26c7d2). Each instantiation is a full copy of the body specialised for the concrete type arguments; a stencil name is built from targ.Type.String() (c0aa7bd760).

Timeline.

types2 port of go/types: reviews, error-message parity and language-version support

Span: Go 1.17 → Go 1.17 · Status at go1.17: types2 is used for -G builds only; the legacy typechecker is still the authority for ordinary compilation → continues as Sharing code between go/types and types2 (1.18+), Error reporting: codes, messages, and sub-errors (1.18+) and Bringing up generics in types2 (1.18+)

Design. types2 began as a port of go/types (with generics from dev.go2go) to the compiler’s syntax package: syntax.Pos replaces token.Pos, positions compare with cmpPos (later Pos.Cmp 8f1099b585, and pos.go moved into syntax 93466cc1b6), errors take a poser (6e98406ac3), and Scope.Squash was added (41ff51ae00). The working method is a file-by-file “review of X.go” CL (a dozen in Oct 2020: 38af45b4cb, 6e98406ac3, 41ff51ae00, … then Dec–Mar: f38da2cbb6 unify.go, 3b5918c757 predicates.go, 196102d046 typexpr.go, bab3461123 infer.go, 1758780181 sizes.go, c2314babb8 type.go, d6bdd1aeef typestring.go, 5a0e4fc4e7 conversions.go, c69515c9fd expr.go, 68f8e1af29 call.go, 607f99904e api.go, ddcdbb417b, 0fc595ec99, 34ef294b76, 34fb2b2ed5, 135c9f45ec, 6454b2720f, 93dcaba119, 01821137c2) making each file diff-minimal against go/types, so fixes can flow in both directions (7bedd47798, f893f35d9f, 6082c05d8b are go/types counterparts).

For types2 to replace the old checker it had to emit the compiler’s error strings, which are tested by test/*.go // ERROR comments. A Config.CompilerErrorMessages flag (87bc85a846) selects compiler-flavoured wording; a series of CLs adjust particular messages (07d32c8183, 810957b155, 6812eae2e2, 43c7b214db, edf80c4209, 036245862a, 67bf62d939, ece5935364, 6afa0ae4e5, 785a8f677f) and the error machinery gets an accumulating error_ type replacing tab-indented “secondary” errors (acd7cb5887), source-order error processing in tests (836356bdaa), unused imports in source order (217a461f56), package qualifiers disambiguated (42812a2fee). Config knobs are retired as the generics work stabilises: IgnoreBranches added (bdc4ffe9a8), InferFromConstraints and AcceptMethodTypeParams removed (7d5c54eee4, 1395432f23), “finals” removed (41245ab283), MethodSet code removed (a49e941027). API ergonomics: converter methods replaced by functions (5e4da8670b, 653386a89a, 099374b55e), predeclared any as alias for interface{} (f03f934ede), parenthesised constraints (0458d8c983), type-parameter ids from a global atomic counter (8dcc071063), Named.SetTParams/Orig (892cad7a9b), a single infer function combining function-argument and constraint inference (bce85b7011, 3f4977bd58, 190cb937f7, a6d95b4508), unsafe.Add/Slice (14056d0d00), untyped constant overflow at 512 bits then 2048-bit limit (370e9f5843, a59cb5109d, c910fd7b77) and language-version checks (1ff2fdaaf1, f37b0c6c12, ddec18cf82, a9705e157b, 962d5c997a).

Timeline.

The syntax package: parser, scanner, source reader, positions and error recovery

Span: Go 1.10 → Go 1.17 · Status at go1.17: stable parser with optional generics (AllowGenerics), a fast reader, and pragma handling delegated to the noder → continues as The syntax package (parser, scanner, tree) (1.18+)

Earlier history: The syntax package, position representation and parse-time checks (Go 1.8 → Go 1.9) · Parser: from yacc to a hand-written recursive-descent parser (Go 1.1 → Go 1.8)

Design. cmd/compile/internal/syntax is the compiler’s own parser producing a compact tree with position info. Its design points in this period: (a) independence: in 1.11 it dropped cmd/internal/src and gained its own simple Pos/PosBase so it could stand alone or replace go/parser some day (5c08b9e8bd), and removed ParseBytes (e87f2a1b70); (b) line directives: /*line file:line:col*/ and //line :line:col (ac45cb9aa0, 6fa6bde924), relative column printing from a prior directive’s column (0c884d0810), GOROOT replacement and original+absolute names recorded (27e80f7c4d, ca2a886cba), documented (e65d6a6abe); (c) error recovery: function nesting considered (68e390304e), missing closing parens (0b2cb89196), missing type (670494827c), missing function call in go/defer (1a22738749), missing expression (dca0d03b9c), if header errors (be9a1774f2, 4637699e92), assignment in value context (ffc0573b85), plus BadExpr where needed (e0cb80e4d1); (d) lexical errors: BasicLit.Bad and at most one scanner error per number literal (117400ec09, 5411953df5, cdd2c265cc), better scanner messages (bfb903f252), more than one rune unread for the new number syntax (58365b34bb); (e) performance: stringer for operators and tokens with a tokStrFast to avoid scanner regressions (c879153831), “faster and simpler source reader” rewrite of source.go from an early-2019 rewrite (4de606b55f), noder long-summation optimisation (506386fd37), noder slice preallocation (33fac9bad8); (f) pragma API: instead of a uint16 bitmask, syntax hands the noder pragma text and the noder decides placement, which enables “misplaced compiler directive” errors (768201729d).

Timeline.

Constants: Go 2 number literals, evconst, untyped conversion and go/constant

Span: Go 1.11 → Go 1.17 · Status at go1.17: the compiler’s constants are go/constant.Value; Ctype/Val/Mpint representation gone; untyped types are types.Ideal{int,rune,float,complex}; rational arithmetic enabled → continues as Frontend support for new language features and spec changes (1.18+)

Earlier history: Constant arithmetic, Mpint/Mpflt and floating-point constants (Go 1.1 → Go 1.9)

Design docs & discussion

Design. The legacy frontend represented constants as Val{U interface{}} holding *Mpint, *Mpflt, *Mpcplx or string/bool/NilVal, with a Ctype tag (CTINT, CTFLT, CTRUNE, CTNIL, …). evconst folded expressions. Work in this period (a) made it spec-correct and (b) retired it in favour of go/constant, which types2 uses, so the two typecheckers agree.

  1. Go 1.13 Go 2 number literals: one liberal scanner grammar [prefix] digits ["." digits] [exponent] ["i"] with later diagnostics for malformed ones (ceb849dd97), i accepted on all numbers (4ad5537bfa), workarounds for 0o and _ removed once the syntax package handled them (a77f85a618, fbc5acbd70); shifts by signed amounts (585c9e8412). Constant folding was consolidated into evconst (3def15cd20, a1b5cb1d04, 6fa7669fd7, 7fa195c1b9), keeping folding within OADDSTR (0a04c0430e) and keeping non-Go constants (e.g. results of unsafe/-asmhdr) as plain expressions (1602e49701, eb6c433eb3, 33ac854481), mixed float precision fixed (041d31b882). Go 1.14 rewrote convlit/defaultlit: convlit1 now follows the standard “set n.Type = nil on error” convention to eliminate follow-on errors, with context passed down (581526ce96); the single TIDEAL “untyped number” type became Ideal{int,rune,float,complex} kept in sync with Val().Ctype() for precise messages like string(1.0) (e710a1fb2e); iota inside functions’ ConstSpec (55c0ad4b62), complex truncation (52cff70100, 34314280e4).

  2. Go 1.17 dev.regabi: CTNIL→ONIL and CTRUNE removed (88a9e2f9ad, c767d73227, 4af2decf30), Ctype replaced by constant.Kind (742c05e3bc) and Val by constant.Value (7d72951229, mixed benchmark effect), non-mutating evconst (6826287c6b), overflow reporting incl. unknown values and ^/^x (259fd8adbb), okforconst (331b8b4797), workarounds for go/constant removed (171fc6f223), rational arithmetic enabled (6a4739ccc5).

Timeline.

Language version gating (-lang) and language changes implemented in the frontend

Span: Go 1.12 → Go 1.17 · Status at go1.17: -lang enforced by both checkers (old typechecker and types2) with errors naming the required version → continues as Per-file language versions and version gating (1.18+) and Frontend support for new language features and spec changes (1.18+)

Earlier history: Language changes and rules as implemented in the compiler (Go 1.1 → Go 1.9)

Design docs & discussion

Design. Go 1.12 added -lang to the compiler (2e9f0817f0) so the go command can pass the go.mod language version; first just a check that type aliases need go1.9. Corrections: correct validation (7ebe35093d) and “by default accept any language” so the bootstrap compiler isn’t limited to 1.4 (69397422c0). Features then check -lang: Go 1.13 number literals, shifts, _ separators (762953be28); 1.14 overlapping embedded interfaces (8d4b685ab5 allow, 97edf77903 refactor expandiface, 7f907b9cee require -lang=go1.14); better error text (27fc32ff01); restrictions not applied to imported packages (b7d097a4cf). Go 1.16 //go:embed needs go 1.16 in go.mod (0575e35e50). In 1.17 types2 got the same: 1ff2fdaaf1 (checks), 721488498a (pass -lang), f37b0c6c12 (alias requires go1.9), ddec18cf82 (overlap requires 1.14), a9705e157b (slice-to-array-pointer conversion needs 1.17), 962d5c997a (unsafe.Add/unsafe.Slice need 1.17 in both cmd/compile and go/types).

Language changes realised in the frontend: Go 1.13 numeric literals and signed shift counts; Go 1.14 overlapping interface embedding; Go 1.15 print/println(f()) with multi-value f (af686da46f); Go 1.16 //go:embed (8bde9b320e — the go command passes an “embed config” mapping patterns to files; the compiler lays out contents as static data of type embed.FS, string or []byte; later restrictions 54198b04db no embedding of function-local vars, ec9470162f any string or byte slice type); Go 1.17 unsafe.Add and unsafe.Slice (fadad851a3, with 4bb0847b08 making unsafe.Slice((*T)(nil), 0) return []T(nil)), slice-to-array-pointer conversion (5f1df260a9, a9705e157b), and the shadowing rule that := may redeclare dot-imported names (1.10, f22ef70254). An earlier change allowed non-constant shifts as indices (088a9ad543).

Timeline.

Package initialization order, static initialisation and init functions

Span: Go 1.10 → Go 1.17 · Status at go1.17: spec-compliant init order (initorder.go), static maps/slices/structs emitted as data where possible, fninit earlier → continues as Static initialization and package init (1.18+)

Earlier history: Static initialization, composite literals and init (Go 1.1 → Go 1.9)

Design docs & discussion

Design. The classic algorithm for ordering package-level variable initialisation did not match the spec (issue 22326). Go 1.13 rewrote it (5d0d87ae16): dependency graph over variables and functions (references via function bodies), emission in spec order, and cycle diagnostics that include referenced functions “like go/types does”. The reporting algorithm later exploded exponentially on real programs (explores all paths through call graph; 600x slower than Go 1.12, 2+ minutes for a 7,000-line program), fixed in 1.16 (6598c65646). In 1.17 the logic was reworked again by moving the init pass earlier: fninit is called earlier (f4936d09fd), 68e6fa4f68 fixes package-initialization order after the IR change, 7fde0d2b50 removes Initorder/Offset Node fields used by initorder, and pkginit becomes its own package (3d8a3cb06b).

Static init (sinit.go): 1.13 turned its functions into methods over InitSchedule (ee2836048c, d23bf3daa9), map literals are evaluated incrementally rather than by evaluating all keys then all values into temporaries (ee59c06acb) with static entries only (85387aa364), sparse slice literals initialized dynamically (6fff980cf1, 1.12), static copy fix (4a1a783dda: staticcopy of struct/array should recurse into itself, which had duplicated slice backing arrays since Go 1.2), blank struct fields not initialised (2693b42466, 0031fa80a3, 0f47c12a29), and avoiding statictmp collisions with user vars (08831b150c). Go 1.14-1.15 refactor slice symbols into slicesym and delete gdata (7096b1700d, 0a18cbc2e6, 12d1c9b863) and mark static arrays noalg (2222e0601a, 54cbb6b0c2). Go 1.12 hides compiler-generated init functions in tracebacks by treating them as wrappers, splitting variable initialisers into a separate non-autogenerated function to preserve line numbers (eafe9a186c; ~0.2% bigger go binary); 1.13 reverted a related runtime ordering change (6105e8b419). Earlier: improved static map initialisation in 1.10 (91cb9edd5e) and saved escape info for map literals (098126103e).

Timeline.

Concurrent compilation and frontend performance

Span: Go 1.10 → Go 1.17 · Status at go1.17: backend compiles functions concurrently via the compile queue; frontend is serial → continues as Compile-time performance, concurrency and the self-PGO’d compiler (1.18+)

Earlier history: Compiler performance, formatting and concurrency (Go 1.7 → Go 1.9)

Design. Concurrent backend compilation arrived in 1.9; in this range it is completed. fa3e9d27f3 (1.11) removes the serial path for autogenerated functions (“as of this CL … no functions are compiled serially”), a particular win for packages with many short generated functions (e.g. benchjuju). cc90e7a51e (1.17) always uses the compile queue. c274a7c03b limits noder goroutines (1.17). The frontend remains serial except for parsing, so heavy-lift reductions are memory and algorithmic: sorting method sets earlier so that interface satisfaction is O(N+M) instead of O(NM) (91bbe5388d), noder scope-marker optimisation (3fca7306f4), file-handle throttling with EMFILE/ENFILE limits (99414a5b1d), duplicate inline body discards (f0b3626904), type printing buffer pooling and string interning because earlier uniquified deep type names regressed memory 3-5% and CPU 1-2% (2248fc63ab, 5d8a61a43e, f77e7ed7e3), symbol formatting with bytes.Buffer (211ee9f07c), types.sconv optimisations (b003a8b1ae, 11f5df2d67, 5c59e11f5e), -smallframes diagnostic flag (037ac2bd84: stack-allocation threshold from 10M to 128k for declared variables, 64k to 16k for implicit allocations).

Timeline.

Directives and pragmas in the frontend (//go: handling)

Span: Go 1.10 → Go 1.17 · Status at go1.17: pragma handling is an interface between syntax and the noder with misplacement diagnostics → continues as The syntax package (parser, scanner, tree) (1.18+)

Earlier history: Pragmas, directives and compiler flags (Go 1.1 → Go 1.9)

Design. //go:linkname’s second argument became optional so a symbol can be exposed (e.g. ABI0 wrappers for assembly, non-static in gccgo) without renaming (dde7c770ef). //go:cgo_import_dynamic permitted anywhere (41d3d153eb), cgo directives encoded as JSON between compiler and linker (8f6ae33796). 768201729d (1.15): syntax gives the noder an interface{} pragma, and the noder reports “misplaced compiler directive” if a directive sits where it has no effect (must be on a line by itself immediately before the declaration). //go:notinheap evolved: write barrier omission for []T (3a446d8652, 1.10), alias to notinheap types allowed (4f915911e8), friendlier cgo message (37f261010f), implicit propagation to containing types (22053790fa), exported to object file (a9c75ecd3d), pointers stored indirectly in reflect/interfaces (009d714098, 091257def9). //go:nocheckptr and //go:nosplit (80a6fedea0, 9979366e07) interact with checkptr; //go:registerparams was added as a limited pragma for ABI development (861707a8c8) and pragmas propagate from generic to stenciled functions (083a26c7d2).

Timeline.

Inlining-related frontend work: export of bodies, type switches, closures, devirtualization

Span: Go 1.10 → Go 1.17 · Status at go1.17: inliner handles type switches, method values and (re-enabled for the new export format) closures with limits → continues as Old typechecker and IR node-model cleanup (1.18+)

Earlier history: Inlining (Go 1.1 → Go 1.9)

Design docs & discussion

Design. Inline bodies are the main reason export data is large. 1.10: limit exported inline bodies (8684534321, f0b3626904), and -l=2’s debugging role of eagerly typechecking unused inline bodies becomes -d typecheckinl (7092a312e5). 1.11: Inline struct separated (562a199961) and unneeded interface-method-expression wrappers omitted (80bbad015d). 1.12: autogenerated (*T).M wrappers inlined (fa913a36a2: encoding/binary −22%, bufio WriterFlush −11%), which restored perf lost between 1.10 and 1.11. 1.15: mid-stack inlining allowed in recursion cycles, tracking functions already inlined at a call site (ed7a8332c4); -d optimizer logging for inlining events (4d0ed149ff). 1.16: OCALLPART inlining (8fe372c7b3), type switches inlining with export/import (5736eb0013, using the iexport Vargen mechanism for per-case variables), early devirtualization of interface method calls (5cc43c51c9). 1.17: closures export (see export thread) with temporary disabling (8027343b63), ImportedBody flag to avoid creating new closure functions when typechecking inline bodies (06b86e9803), inlining of stenciled functions (6d2a557a4d), field tracking panic fix replacing map by Node.Opt (5b0ec1a6ac).

Timeline.

The legacy typechecker: error quality, alias/recursive-type handling and type checking order

Span: Go 1.10 → Go 1.17 · Status at go1.17: still the authoritative checker outside -G; heavily incrementally improved → continues as Old typechecker and IR node-model cleanup (1.18+)

Earlier history: Typecheck/walk separation and package-level declaration order (Go 1.0 → Go 1.0)

Design. Two threads of work: incremental message improvements and bug fixes, and attempts to make declaration processing sound. Messages: assignment mismatches (b77d9fe0ea, f7a633aa79, ae9c822f78, 60736733ec), struct literal hints (5f29a7a705, 25d22d9aba, ae37f5a397, 6d781decad), shadowed builtins (1e308fbc1a, 4a095b87d3), ambiguous selectors (3d501df441), embedded fields (42ecf39e85, 5538ecadca, 6761b1eb1b), variadic misuse (a7af474359, cd47e8944d, df73945fd2, fd4b587da3, 844e1fc6f1), “cannot assign” reasons (7ee35cb301), &&/|| operands (23573d0ea2), typekind in invalid operation (754776850a), untyped operation (785a8f677f), make args (ad0ab812f8), unexported name once (d3f6e2f300), and Go-1.18-style “terminate compilation if type-checking fails” (d159d6108c). Error-column fixes: label redefinition (0b63086f64), composite literal key position (363bcd7b4f).

Declaration ordering: 9ce87a63b9 (typecheck types and funcs before consts so consts like I(T{}) see methods) was reverted in 1.12 (70dd90c4a9); 62c52a5ee1 simplifies typecheckdef; 1.14 a71967e4c5 replaces copytype by setUnderlying, 380ef6b759 simplifies defer/resume checkwidth, b136267b87/e87fe0f1f5 handle n.Type.Nod, 97bc039c9c emits an error for broken types. Alias cycles: correct detection (48987baa09), then work-around reintroduced for cyclic alias declarations (e6305380a0, “the more general problem remains”, issue 25838), crash on recursive types/interfaces (bb2f0da23a, dc0388c565, 7f3313133e, 25159d3af9), 0e85fd7561 reports a type loop for invalid recursive types (1.16) and c216ae80c9, dadfc80bc1 (findTypeLoop), plus “typecheck set correct untyped type” etc. Typechecking cleanups of Go 1.17: do not set type for OPACK/OTYPE/OTYPESW/ONAME-in-type-context (12a405b96a, 4b8b2c5864, 51d8d351c1, c082f9fee0), fbee173545, 1d0256a989, c9f43507c6, 9e6b1fcd0a, 70b277cf2e, 80098ef00c — all needed by the types2-driven noder.

Timeline.

Lowering in order/walk and the shape of runtime calls emitted by the frontend

Span: Go 1.10 → Go 1.17 · Status at go1.17: the frontend lowers many constructs to specialised runtime calls; some previously frontend work moved to SSA construction → continues as Method wrappers and promoted-method sharing (1.18+) for wrappers only; otherwise no direct continuation

Earlier history: Walk-level lowering and runtime-call optimisations (Go 1.1 → Go 1.9)

Design. The frontend’s order/walk rewrites high-level constructs and decides the runtime call signatures. 1.10: makechan/makemap called with int arguments where possible to avoid int64 conversions on 386 (8a6e51aede, cbc4e5d9c4), small-hint map creation (fbfc2031a6: if hint <= BUCKETSIZE only hash0 initialised, and a makehmap heap helper; godoc −36kB), race calls for pointer-free types (53c8be4a8d), needwritebarrier removed from the frontend (e0111bb0f4). 1.11: select open-coded into selectsend/selectrecv/selectdefault (004260afde, with runtime 3aa53b3135, 6658219b7e), range loops no longer produce a past-the-end pointer (837ed98d63), map-clearing range idiom to a single runtime call (aee71dd70b: not for key types with NaN), len([]rune(s)) (b9a59d9f2e), convT2E/I taking integers by value (22ff9521da) then strings/slices by value (0e9f8a21f8) and shrunk call sites (5848b6c9b8), order.go refactored into methods (26708439ec), temp reuse in order (389e942745, 63e964e174, 240a30da1b), makeslice callers (020a18c545). 1.13: f(g()) multi-value rewrite moved to typecheck (d96b7fbf98, redone as c0cfe9687f after crypto/ecdsa -l=4 and net/http/pprof -N regressions) so the rest of the compiler no longer handles multi-value calls outside OAS2FUNC; callnew ported to SSA conversion with a new op (23b476a3c8); inline checknil (1e58bb1491); better write-barrier removal for new objects (ca36af215f). 1.15: make+copy fused to makeslicecopy (6ed4661807), staticuint64s replaces staticbytes (19ed0d993c), pass only ptr+len to runtime calls (b6feb03b24), isSmallMakeSlice checks cap only (7b30a2d268). 1.16: unified reflect/string/slice copy (790fa1c546). 1.17: selectnbrecv returning two values (4048491234, 86deb459de), assert{E,I}2I calling conventions (e52149822b), interface conversion function selection made ABI-insensitive (cf148f3d46, 49e933fc57), racefuncentry via getcallerpc and gorecover via getcallersp (a22bd3dc73, fb03be9d55), multi-valued a, b = f() through temporaries when types differ (3249b645c9, 326ea438bb), HasCall replaced by walk.mayCall (78e5aabcdb), closures transformed in walk (41352fd401), earlier deadcode before addrtaken (6ddbc75efd).

Timeline.

checkptr instrumentation and compiler debugging flags

Span: Go 1.12 → Go 1.17 · Status at go1.17: -d=checkptr implied by -race/-msan; Debug is a struct → continues as Compiler debugging infrastructure: bisect/hash debugging, flags, dumps (1.18+)

Earlier history: Race detector and sanitizer instrumentation in the frontend (Go 1.1 → Go 1.7)

Design docs & discussion

Design. -d=checkptr (80a6fedea0, 1.14) inserts runtime checks that (1) conversions of unsafe.Pointer to *T are aligned for T, and (2) pointer arithmetic results point into the same Go heap object as one of the unsafe.Pointer operands. Disabled in runtime and by //go:nocheckptr; follow-ups recognise (*[Big]T)(ptr)[:n:m] (7b58581a23), disable it for cgo_unsafe_args and nosplit functions (8c6876e9a4, 9979366e07), enable under -race/-msan (dded58760d; 87a3ac5f53 lets explicit -d=checkptr=0 win), enable on Windows (656f27ebf8), fix &^ handling (e94544cf01). Debug flags: Debug becomes a struct instead of a 256-entry ASCII array (3bac5faa4a), -d=dumpptrs (c31540364c), -d=typecheckinl (7092a312e5), open-coded defer toggle (e617b2b0dd), -clobberdead resurrected (8628bf9a97, “TODO: make it GOEXPERIMENT?”), typecheck tracing (e25823edce), -W printing fixes, GOSSAFUNC cached (e34f660a52), obsolete flags removed (3e428363c4 -s, c68e3bcb03 -f, 5185744962 safe mode, eliminate -abiwraplimit feb844f1ea), GOEXPERIMENT check fix (bd983a6d2e).

Timeline.

Types package internals and type printing

Span: Go 1.10 → Go 1.17 · Status at go1.17: types hosts type constructors, size computation and printing → continues as Types package internals: size, algorithm kind, TFlag, SIMD flags (1.18+)

Design. 1.10-1.12: stringer for EType (6e9960ea63), HasHeapPointer recursive (316e3036a7), Type.NumResults (2dd1f87d38), TPTR merge (62e5215a2a), types.Identical extracted from gc (2d58fbac2f), Pkg/SetPkg (03f546eb60), Type.Val→Elem (2083b5d673), Funarg field removed (7500b29993), SubstAny copies all fields (9e619739fd: previously f and g with identical signatures shared a generated Result type so instantiating with different-size types corrupted offsets), SoleComponent (9892ccff23), documented internal fields (3377b4673d). 1.14: type name uniquing for really deep types (2248fc63ab) and recursive types printed correctly (5d8a61a43e); TDDDFIELD removed (a2cf16d42c). 1.12: nesting-depth limit for type descriptors increased (99e4ddd053). 1.16: TRESULTS variable-length SSA type (f554eb7bc3). 1.17: size calculation moved in (dac0de3748), constructors (77a71e0057, c10b0ad628), IsScalar (7e17b46c58), RParams (519f223aa2), allMethods (a4b8241d97), gcSizes.Sizeof zero-size struct (5cd8a34495), types.Sym function flag from 1.12 (16e6cd9a4d).

Timeline.

Register-ABI plumbing in the frontend

Span: Go 1.12 → Go 1.17 · Status at go1.17: ABI wrappers generated per ir.Func, ABIs exported across packages → continues as Method wrappers and promoted-method sharing (1.18+)

Design docs & discussion

Design. 1.12: types.Syms for functions are flagged so obj.LSyms get the right ABI (16e6cd9a4d). 1.17: dev.regabi (the name came from the branch though the ABI work lives in another area) landed: cabffc199d internal ABI spec in the tree, 6f5e79f470 memory layout, ABI analysis utilities (89f38323fa), abiutils used for all receiver/in/out frame offsets (77973863c3), ABI info plumbed to ssagen (7a2f3273c5), function values reference ABIInternal (1e8fff0f7b), funcsyms tracked by ir.Name (33b4ffc357), ABI-wrapper restructuring storing ABIs on ir.Func rather than global maps with export across packages (67d565d281), ir.Name.Func set in more cases (0d1423583b), R14/X15 reservations documented (0ad46889a1, fcf8a6640b, 478277f812, 80ddc17ae1).

Timeline.

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

Compiler frontend: the types2 type checker (Go 1.18–1.28)

Reader: Claude Sonnet. Cites 929 of 1,108 commits (all non-noise ones). Mechanism descriptions come from commit messages, not diffs. Some timeline bullets of the generics bring-up thread are thin.

Bringing up generics in types2 (type-parameter operands, core/structural types, tparam-as-interface)

Span: Go 1.18 → Go 1.28 · Status at HEAD: complete; core types no longer used by the checker (removed in 1.25), residual fixes only

Paths not taken: Generics implementations not taken: contracts parser, go2go, early stenciling demos (superseded)

Earlier history: Generics bring-up in the compiler (dev.typeparams): parser, -G, types2, irgen, first stenciling (Go 1.17 → Go 1.17) · types2 port of go/types: reviews, error-message parity and language-version support (Go 1.17 → Go 1.17)

Design docs & discussion

Design. types2 is the compiler’s copy of go/types (same code, different AST: cmd/compile/internal/syntax). The 1.18 cycle is mostly the merge of the dev.typeparams branch, about 400 commits, most of them tiny. A *TypeParam is a type whose bound is a constraint interface. After the experiment 39bc666430 / c09d854f09 / 787708a6ff, the underlying type of a type parameter is its constraint interface (the internal tparamIsIface switch was first added disabled, then turned on, then the flag and the dead code were removed). Because of this, assignability had to special-case type parameters (56dcf97656, c88e3ff648).

Operations on operands of type-parameter type are checked over the whole type set rather than one underlying type. The first helper was optype(), later under/singleUnder; d611f09200 and its follow-ups replaced the asT converters with under. bbc059572d added singleType/structure, which became structuralType (8a3be15077), which became coreType/coreString (2bf5ae0c28). Builtins, range, slicing, composite literals, conversions, channel ops, delete, close, make, copy and append then accept type sets with a single core type: 2d205ebb96, c526cf8c1e, 12e4404a0a, 16318df4a2, ff33d3dc3a, 6511922a14, 5f0ea40c67, 95f8e64fc0, 93285c89d1, 0811108670, 39bbf08e71, a304273d74, 7e69c5decf, 4cc6a91907, cfcd71790f ([]byte|string slicing). underIs/typeset.is (3f1b0ce6bb) iterate type terms for the rest.

The last two points concern the end state. In Go 1.25 the checker stopped using core types: 7194caf11b, 6adf08f747, 4b1ac7bbfe, 26ba61dfad, 05354fc3b4, 7e81bcf39f, e5d3ece35d, and the replacement helper commonUnder (8b7e376e71, with an optional condition callback and an error cause that is only allocated on failure). commonUnder is a type-set-wide “all underlying types are identical” test. 1.24 had already moved underIs/coreType/coreString onto typeset iterators (1e5a72e980, 681751a1fe); 1.26 made typeset itself return an iterator (6b32c613ca).

Timeline.

Type sets, union terms, interfaces and constraint satisfaction

Span: Go 1.18 → Go 1.28 · Status at HEAD: stable; comparable satisfaction by non-type-parameter interfaces since 1.20

Earlier history: Interfaces, type switches, assertions and comparability (Go 1.0 → Go 1.0)

Design docs & discussion

Design. In 1.18 the representation of constraints changed three times within a few weeks of the dev.typeparams merge. First came embedded “interface elements” (5770d7a637). Then the Sum type was replaced by Union (589e32dbdf). Then 4b5fdb0b7a introduced the _TypeSet abstraction, computed per interface by computeTypeSet (fac21803ce); bba460499c stopped exporting it. A type set is a method set plus a termlist. term (2fa8f00915), termlist (e5fe769be1, bb5608dd5d) and ff0c0dbca6 (unions as terms) implement ~T and union intersection with explicit tilde handling. The bottom type was replaced by the empty union (848b58e473). 9bd1817e41 limits term-list lengths, 4cda05d41a externalizes union type sets and 1afa432ab9 records union types in Info.Types. comparable became a bit on the type set instead of a ==() method (c751e2e6ba). Embedded interfaces of the form ~T where T is defined or an interface are rejected (10d6b36ca3). The old type-list syntax was removed from the parser and checker (f05c67f182, 925ea2dfc4, 440b63bd6f).

Checker.implements (e08aae2ee4, 2e30c4b4bb, 25b4b862f2) is the one constraint-satisfaction predicate, used for both interface implementation and instantiation verification (155dc0e541, 8f0578ef39, eda3de0f79). Interface identity considers the full type set (dd8bdf4a1f). missingMethod was shared with go/types (d5bd3f9a6c, 7c9885def5) and simplified over the 1.18 and 1.21 cycles.

Timeline.

Instantiation, Named types, Context deduplication and lazy expansion

Span: Go 1.18 → Go 1.27 · Status at HEAD: Named has an explicit state machine, fromRHS/underlying lifecycle enforced since 1.26

Design docs & discussion

Design. A *Named is either a declared type or an instance of one. Instances carry an inst record (origin, type arguments, a Context) and are expanded lazily: the underlying type and methods are substituted via subst only when needed. 1.18 started with an instance helper type and InstantiateLazy, then merged instance and Named (473e493d18, 166b691b65, 3100f54f20) to avoid a sanitization pass. The public API settled as Instantiate(ctxt, orig, targs, validate) returning an error (fc27eb50ff, 69d8fbec7a, 22e9265467, c7e354d9d1), with an opaque Environment renamed Context (bd97763577, a1f6208e56, 99d5d8ab6b). Context dedups instances by a type hash (30e9bfbcef, 437362ccec, 489f58779c, 633d8c120b). Hash collisions are resolved with Identical (a52e4b9c7e, 313cae3861) and signatures are deduplicated as well (a17a21c190). TypeHash was exported, then unexported (9fc28892cb, 8b66b3d49f).

Methods of instances are instantiated lazily, and from b66bc0a9d5 each method is independently lazy through a methodList guarded by per-method sync.Once, because “method signatures can introduce a significant number of edges into the type graph”. Later versions replaced Named.once with a monotonic state field and Named.mu (846f971daa, 1323b0e8f0, 02e69cfa96). Named type identity stopped being canonical, so method-set computation looks up types by Identical rather than pointer (f088f4962e), and Var/Func objects got Origin() (1170771074). An invariant keeps instances created during expansion in the same Context as the expanding type, so infinite expansion chains terminate on a previously seen instance (47e34ca533, ensure that named types never expand infinitely).

Timeline.

Type inference and unification

Span: Go 1.18 → Go 1.27 · Status at HEAD: new unifier-centred inference since 1.21; reverse inference, interface inference and untyped-constant defaulting shipped; assignment-context refinements in 1.27

Design. Function type inference infers missing type arguments from (1) typed function arguments, (2) constraints, (3) untyped constants’ default types. In 1.18 it had two entangled phases (function argument inference, “FTI”, then constraint type inference, “CTI”) around a unifier with per-parameter slots. Pitfalls were handled with local fixes: masks against infinite recursion (435718edd9, partly reverted by 5780be401f), renaming of type parameters when the call is self-recursive (93b5309f0a; made unconditional by e738a2f19b, because the heuristic for self-recursion was wrong when function literals clobbered the environment), and unification of core types for unbound type parameters (f03ab0e014, which has a kill switch enableCoreTypeUnification).

In 1.21 Griesemer rewrote it as infer2 (683b2c4600), made the only implementation by 93f5335be9 and 969c3ba839 (“use new type inference algorithm exclusively”), and deleted infer1 (d81ae7cfc7, 997dbcf6ba). The new algorithm uses one unifier holding a type parameter => type map; it is fed all facts, order-independently (8fd6cc8bb5 removes argument sorting): (1) unify every typed argument with its parameter; (2) unify each type parameter with its constraint’s core type (or, with 726d898c92 in 1.24, also its methods) iterating until fixpoint; (3) only as a last resort apply the default type of untyped constants; (4) substitute inferred types until no type parameters remain. Unification now has modes: assign for parameter passing (inexact at top level) and exact for component types (ee402e9882 removed the need for the recursion depth limit introduced for #59740). The unifier also detects no progress (4cba0bd74a), handles pointer identity (35a4d1b3bc), and uses a map instead of a type-parameter list (4b7f7eef3f).

Timeline.

Generic type aliases and Alias type nodes

Span: Go 1.22 → Go 1.27 · Status at HEAD: Alias nodes always on (GODEBUG gotypesalias removed in 1.27); generic aliases enabled

Earlier history: Type aliases (the => experiment and type A = B) (Go 1.8 → Go 1.9)

Design docs & discussion

Design. type A = T used to bind A’s object directly to T. *Alias (an explicit node whose actual points to the RHS, resolved through Unalias(t)) makes error messages mention A, helps with cycles and is the only place type parameters of aliases could live. Every type switch must Unalias first, so a long tail of commits adds missing Unalias calls (816b6031fe, 4b778470f8, ca17bda856, 5ab8f90745, 8d464e4ae3, 13e3068b9c, 3ea2be1e47, 0d478d8e07, ccb6077d11). Cycles are resolved in three phases (a555ac6332 reverted to three-phase alias resolution after d186dde818). Generic aliases (1a6498e1cb, f294ddeb29, bf279b71e2) share type-parameter plumbing with Named via genericType (3c8f925606).

Timeline.

Type declaration cycles, valid-type detection and the object path

Span: Go 1.18 → Go 1.27 · Status at HEAD: validType uses a type nest; setDefType removed and replaced by “pending” checks in 1.26; finite-size check at value observation

Design. Invalid recursive types (type T struct{ f T }, or generic types that expand infinitely, type T[P any] struct{ f *T[T[P]] }) are detected by validType (0328b4f4ca moved it to validtype.go). The 1.18 algorithm with a global typeInfo map was wrong; 07eca49055 replaced it by the type nest (the stack of embedding types), compares with Identical, tracks []*Named paths and caches valid types in Checker.valids (#52698). ad99d8840e kept infinite expansion from looping by working on origin types (it knowingly misses some invalid generic types, tracked as #48962). 83b2b47b5d fixes nest-slice overwrites (#66323). Declaration order and cycle detection moved from an object colour (object.color) to Checker.objPathIdx (ddd8558e61) in 1.26. 3531ac23d4 and ff2fd6327e replaced setDefType (which set T.fromRHS as soon as the top-level shape was known) by a check that no value of type T is used before its RHS is complete (#76383, #76384). 1274d58dac adds the finite-size check at value observation (delayed to the end of type checking unless used in unsafe.Sizeof), ffb50fb716/72c83bcc80 put it behind Named.mu as varSize, a977717393 extends it to map values.

Timeline.

Per-file language versions and version gating

Span: Go 1.21 → Go 1.24 · Status at HEAD: Info.FileVersions; version computed per file from //go:build lines

Earlier history: Language version gating (-lang) and language changes implemented in the frontend (Go 1.12 → Go 1.17)

Design docs & discussion

Design. Before 1.21 the checker had one global Config.GoVersion. 804d786a30 makes both type checkers track a version per file: a file’s //go:build go1.N can lower its semantics below the module’s go 1.M only when M >= 21 (for compatibility with toolchains that never applied downgrades). Version checks go through Checker.allowVersion(at, v) and versionErrorf; errors for the same feature appear once per declaration. e59eaee2e3 adds Info.FileVersions and 56c91c0502 replaces the local version parser by go/version. 1fc45e9a2b makes types2.Info.FileVersions map to version strings.

Timeline.

Language features as seen by the type checker (by release)

Span: Go 1.18 → Go 1.28 · Status at HEAD: generic methods (1.27) and string(int) restriction (1.28) are the latest; untyped composite literal inference in progress

Earlier history: Constants and multi-precision arithmetic (Go 1.0 → Go 1.0) · Builtins and unsafe (Go 1.0 → Go 1.0)

Design. Each language change lands in types2 and go/types together with new test cases in the shared internal/types/testdata/{check,spec,fixedbugs} and an operand-mode or builtin change. Many were gated through version.go feature tables.

Timeline.

Composite-literal type elision via target types (#12854)

Span: Go 1.27 → Go 1.28 · Status at HEAD: in progress; targets threaded through assignments, calls, struct literals, sends, switch, delete/append/copy and comparisons

Design docs & discussion

Design. To type untyped composite literals ({1, 2} with the type inferred from context), the expression-checking functions get a target that holds the assignment-context type. Before this, only function-typed contexts passed a target T (reverse inference) plus an exprWithHint/U argument. 17810c616f threads a composite-literal target through Checker.expr and relatives, 54539e7c9e reorders channel sends so the element type is known first, 39cc89aa11 adds the “check for composite literal type target” in literals.go. 8af6b7616d makes target hold the assignment-context type and removes the extra U; ef3608b90e uses it for hints (hint flag), fba9e4b2d0 deletes the remaining hint parameters; 48e387d5e0 adds tests that execute every newTarget/newHint call.

Timeline.

Method receiver checking

Span: Go 1.18 → Go 1.27 · Status at HEAD: receiver handled in collectRecv (1.24 rewrite)

Earlier history: Embedded fields, method sets and receivers (Go 1.0 → Go 1.0)

Design docs & discussion

Design. ea82219d1c (1.24) rewrites receiver checking: Checker.collectRecv analyses the receiver parameter manually; collectParams only collects names and variables and does not declare them; funcType becomes a linear sequence; the receiver and parameters are declared after type parameters so no temporary scope or Scope.Squash (removed) and no recvTParamMap is needed. unpackRecv strips only one *. Since parenthesized and pointer receiver expressions were no longer recorded in types, 86bec1ec19 records them (generic receivers), and 385e963e70 records receiver type parameters as Uses.

Timeline.

Error reporting: codes, messages, and sub-errors

Span: Go 1.18 → Go 1.28 · Status at HEAD: compiler and go/types share messages and error codes (internal/types/errors)

Earlier history: Diagnostics and error messages (Go 1.1 → Go 1.9) · types2 port of go/types: reviews, error-message parity and language-version support (Go 1.17 → Go 1.17)

Design docs & discussion

Design. Until 1.19 types2 printed gc-style messages when compilerErrorMessages was set and go/types printed its own. Go 1.20 removed this duality. f4a587ba6e gives every types2 error site an error code, 6688efd5df moves to the shared internal/types/errors package, 388fbf287c uses a zero code for “unset”, ad3ea0134f records the code in the Error struct, 5612215d32 removes compilerErrorMessages. Message wording was reconciled in about 20 commits: 2da95e0ec8 (“undefined: x”), 605148c0fb, 3b5188ed2c, 435652b468, dbe56ff6c7, 545adcfe89 (“generic” not “parameterized”), 22f4471599. 1.23’s 96e9838f39, 5e3c4016a4 separate formatting from error handling and call the handler for each sub-error. 1.25 added a typeError cause type (232dfd226b, 5af3658eaa) and rewrote messages for range, calls, send/receive, copy, make, slices and qualified identifiers (d45d502fbb, 549a88fa53, 584e631023, a4fcfaa167, 2d097e363a, a588c6fba6, ae4c13afc5, 5eaeb7b455).

Timeline.

Sharing code between go/types and types2

Span: Go 1.20 → Go 1.28 · Status at HEAD: most go/types files are generated from types2

Earlier history: types2 port of go/types: reviews, error-message parity and language-version support (Go 1.17 → Go 1.17)

Design docs & discussion

Design. types2 is the primary source; go/types files carry a // Code generated by "go test -run=Generate -write=all"; DO NOT EDIT. header. Testdata moved first: the three directories of .go2 / .src files (check, examples, fixedbugs, spec) were consolidated into one shared internal/types/testdata (bbaf0a514e, 9ad772033f, 17211c355f, 9649a4119c, 57ade1fd0b), after which tests are loaded by both checkers. Then production code: 1.21 factored platform-specific helpers into util.go (248950f192) so more files could be generated (9251822631 under.go, f52b8fa83f infer.go, d45df06663 object_test.go, 7da4675dad hilbert_test.go); 1.23 added generation of builtins, conversions, typestring, assignments, operand, mono, initorder, typeset (81609c454b, cc276a768f, 6c979d2522, 55bb3d1c10, f278f756bd, ff8a2c0ad9, 629a6f5ae0, 691f5b8e74); 1.24 factored literals and recording (0fb35efe5b, a17356cd23, 0fc3b127a0, 31e692be43, 1b6bb779ef), 1.25 range (8a7742e78c), and 1.28 union.go, sizeof_test, typestring_test, typeset_test and more (5040c0ac0c, 19533bd74e, a2306b638e, 16bdff37a8).

Timeline.

Checker performance, memory and API ergonomics

Span: Go 1.18 → Go 1.28 · Status at HEAD: ongoing micro-optimizations driven by gopls and compilebench

Design docs & discussion

Design. A set of independent optimizations: fewer delayed actions (054710ce46, 1.18), 6b609110fd sorts to reduce the complexity of initOrder, bf0bc4122f avoids re-evaluating the call context string, c641d48afd uses lazyregexp for the version regexp (init −48% time, −29% bytes, GoTypes). ddd8bc1b07 (1.20, see Named thread). b2110bcdde/0592ce5f69 use strings.Builder. 9189921e47 (1.25) externalizes the used flag of Var and PkgName into two maps on the Checker: these were in the Objects, which raised memory per type-checked package and caused a data race (#71817) when objects were reused by CheckExpr/Eval. 8002a0da7c (1.28) adds Scope.Objects iterator and caches Scope.Names in an atomic pointer; 6446eeb686 records the Id in each MethodSet entry (NewMethodSet −27%, Lookup −69% to −73% for unexported names, 0 allocs). d6dc9d3ace makes Selection more compact, 522ebc8370 fixes a data race in Unalias on alias instances. 5fa4aac0ce/14a3ffc3d2 (1.22) add gcsizes.go so the compiler uses types2.Sizes and not its own struct-layout code (fixes #60431 and others).

Timeline.

Robustness: invariants, incomplete types and importers

Span: Go 1.18 → Go 1.28 · Status at HEAD: invariant checker in tests; new types2 importer in progress

Design. b98d22c17f (1.28) adds checkInvariants to TestCheck and TestStdlib. It walks scopes, Info.Defs/Uses/Implicits/Types and asserts no nil Type(), *Func have *Signature, no nil components, accessors never panic, and Typ[Invalid] is only published if an error was reported (TestStdlib +~1 s total). de3b79e425 enforces it: Alias.Underlying, Rhs, TypeParam.Constraint panic on incomplete types, and the post-check cleanup normalizes incomplete types to Typ[Invalid], reporting an internal error if none had been recorded, so gopls and objectpath don’t hit nil pointers. 201f360765 invalidates operands after constant overflow so recording into unsafe.Pointer assertions can’t trigger. For importers: eff66248ea added package height (1.18, removed by 398d46d538), 2175e2f573 lazy resolution, 183e201251 (1.27) sets underlying for imported types, 8a856e477e (1.28) a new importer for types2 tests that builds with go build and reads the “u” export format, with ac059878bc pointing the tests at it.

Timeline.

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

Compiler frontend: syntax, IR, unified IR and the generics implementation (Go 1.18–1.28)

Readers: Claude Sonnet, plus a Sonnet gap pass. Cites 1,326 of 1,551 commits (all non-noise ones). The gap pass grouped the 1.18 stenciler burst and later small commits by subject line, without reading each message.

Generics via GC-shape stenciling and runtime dictionaries

Span: Go 1.18 → Go 1.28 · Status at HEAD: shipped design since 1.18 (first built on the old typechecker/-G=3, reimplemented on unified IR in 1.20); shape sharing is still “underlying type, pointers collapsed”, with occasional shape/reshape bug fixes through 1.27

Paths not taken: Generics implementations not taken: contracts parser, go2go, early stenciling demos (superseded)

Earlier history: Generics bring-up in the compiler (dev.typeparams): parser, -G, types2, irgen, first stenciling (Go 1.17 → Go 1.17)

Design docs & discussion

Design. A generic function F[T] is compiled once per GC shape, not once per type argument. A shape type (package go.shape, types.IsShape/HasShape flag) is the type argument’s underlying type; all pointer types collapse to a single go.shape.*uint8 shape. Shape types are named with a go.shape. prefix (b730a26729, 137543bb93), integer signedness is kept distinct because shift semantics differ (8767b87ab5), and since 1.22 the symbol uses a hash of the underlying type’s link string when that string would be absurdly long (8c1349baf7). The shaped instantiation takes a hidden first .dict parameter (7b876def6c added the dictionary argument, before the receiver). The dictionary is a read-only global [N]uintptr holding everything the shaped body cannot know statically: *runtime._type for every type derived from a type argument, sub-dictionary pointers for callees (ddb09af1b8, f503740ccf, 6dec18cc75), itabs for conversions to non-empty interfaces (02c0172500, ac3ba97907), and method-expression closures for calls through bounds (7fc38802e1, f326964824, fc72b7705d). F[int](...) becomes F[go.shape.int](&.dict.F[int], ...); the non-shaped F[T] is a wrapper.

Conversions of shaped values to interfaces, .(T) assertions and type switches use ir.DynamicType/ODYNAMICDOTTYPE (f4f503e0a3, ca3c6985cd, 0e2f1abf5b) so the type comes from the dictionary instead of the (shape) static type. Dictionary format is computed on the instantiated function after transforms rather than on the generic one (59a9a035ff), because the set of CONVIFACE nodes depends on which type arguments are interfaces. Instantiated functions are DUPOK so the linker drops duplicates (7160e32529), and dictionary/method-set survival under linker pruning needed explicit handling (2fe4b14795, 301f6c8019). DWARF carries dictionary entries (72bb8185b5), and ir.Name.DictIndex tells Delve where a derived-type local’s rtype sits in the dictionary (07cf24bdfe).

A non-obvious cost: the interface conversion the compiler inserts to call a method through a type-parameter bound would heap-allocate the data word for non-pointer-shaped types; e550c30545 removed that allocation (later reverted in 3f69cc7d77 when it proved wrong and replaced by method-expression closures stored in the dictionary).

Timeline.

Unified IR: one serialization for noding, stenciling, inlining and export

Span: Go 1.18 → Go 1.28 · Status at HEAD: the only frontend since 1.21; internal/pkgbits + noder/{writer,reader,unified,linker}.go; UIR at version V4 (V5 method indices added in 1.28 dev), documented with a formal grammar in 1.25

Paths not taken: Generics implementations not taken: contracts parser, go2go, early stenciling demos (superseded)

Earlier history: Export/import data format (Go 1.0 → Go 1.0) · Binary export data and the object-file split (Go 1.1 → Go 1.9) · Indexed export format and the export-data lifecycle (Go 1.10 → Go 1.17)

Design docs & discussion

Design. Before unified IR, a function body was copied four times by independent implementations: noder (syntax -> IR), stenciler (generic IR -> instantiated IR), inliner (backup copy + inline copy with renaming) and importer/exporter. 79cd1687e6 replaces these with a pipeline syntax -> types2 -> writer -> bitstream -> reader -> IR: the writer (noder/writer.go) walks the syntax tree plus types2.Info and emits a byte-oriented, section-based (strings, positions, packages, names, types, objects, bodies, …) export-like encoding; the reader (noder/reader.go) rebuilds IR from it. The same reader is used to create the current package’s functions, to read imported inlinable bodies on demand, and to read other packages’ export data for types2 (importer/ureader.go), so the “export format” and the compiler IR are the same thing. Lazy loading of types and objects, re-export of inlinable bodies without parsing them into IR, “sync” markers for debugging, and a quirks mode that matched old output byte-for-byte (859d903b06, later removed) were part of the first CL. The author estimated +6 kLOC / -14 kLOC once old code was gone, and claimed it supported inlining of more code (local types, type switches, closures) than the old inliner “without requiring any new code”.

Timeline.

Old typechecker and IR node-model cleanup

Span: Go 1.18 → Go 1.28 · Status at HEAD: typecheck is a thin library (constant folding gone, Ntype gone, ir.CurFunc gone); types2 is the only checker; IR remains ir.Node with generated node_gen.go

Earlier history: Typecheck/walk separation and package-level declaration order (Go 1.0 → Go 1.0) · The C-to-Go translation of the compiler (cmd/gc → cmd/internal/gc → cmd/compile/internal/gc) (Go 1.5 → Go 1.9) · Escape analysis and stack allocation (Go 1.1 → Go 1.9) · Closures, method values and variable capture (Go 1.1 → Go 1.7) · Constant arithmetic, Mpint/Mpflt and floating-point constants (Go 1.1 → Go 1.9) · Write barriers, precise stacks and liveness (compiler side) (Go 1.2 → Go 1.7) · dev.regabi: from *Node struct to the ir.Node interface and concrete node types (Go 1.10 → Go 1.17) · dev.regabi: breaking up package gc into base, ir, types, typecheck, noder, inline, staticdata, reflectdata, pkginit (Go 1.17 → Go 1.17) · Inlining-related frontend work: export of bodies, type switches, closures, devirtualization (Go 1.10 → Go 1.17) · The legacy typechecker: error quality, alias/recursive-type handling and type checking order (Go 1.10 → Go 1.17)

Design docs & discussion

Design. Pre-1.18 typecheck both resolved declarations and checked expressions; ir had syntactic type nodes (Ntype, OTFUNC, OTARRAY, …). With types2 the compiler now receives fully typed trees, so the node model was progressively reduced to what the middle end needs: nodes are typed at construction, type nodes become *types.Type, declarations/constants/ODCL* pseudo-ops vanish. Mechanisms: ir/mknode.go generates Copy/doChildren/editChildren (5f5c018ca4, bd56cb90a7), “hidden” fields (RType, ITab) are visited only through DoChildrenWithHidden/EditChildrenWithHidden (f773bef9ab, 7b867b9bb7); ir.miniExpr/miniNode shrank (a7489b8648, 7a33f793e8).

Timeline.

RType/ITab fields and implicit conversions in IR (RTTI plumbing)

Span: Go 1.19 → Go 1.20 · Status at HEAD: complete; every runtime-type-needing IR node carries its RType/ITab explicitly

Design. Under shape-based stenciling, walk/ssagen cannot call reflectdata.TypePtr(n.Type()) for derived types because the node’s type is a shape. Unified IR therefore records, per IR node that will become a runtime call needing *runtime._type or *runtime.itab, explicit RType/ITab expressions (read from the dictionary for derived types). Conversions that the language performs implicitly (assignment, call arguments, returns, composite literal elements, map keys, comparisons, multi-value spills) are made explicit OCONVIFACE nodes by the writer so the right itab/rtype expression can be attached.

Timeline.

Generic methods (type parameters on methods)

Span: Go 1.27 → Go 1.28 · Status at HEAD: language feature enabled (go1.27 language version); GOEXPERIMENT=genericmethods was added 2026-04-30, default flipped on, flag deleted 2026-06-12; UIR V4 plus V5 method-index fix

Design docs & discussion

Design. Methods with their own type parameters are supported in the unified IR pipeline by (1) writing generic methods separately from ordinary methods in the type’s encoding, (2) encoding method-level type-parameter dictionaries with dict.receivers (receiver type args) followed by method type args, with parameter indices offset (b9c77f8c00, 5ecf12eedf), (3) omitting wrapper functions for generic methods because their signatures are generic and the downstream wrapper machinery assumes otherwise, which costs a statically computed wrapper (dictionary pointer computed dynamically) (142f237601), (4) name-mangling instantiations (9cd372147e, 714a94dd31 puts type args in parentheses), qualifying identifiers (836b0984ee) and method expressions (cafe443251, fdb1ba1325). Interface satisfaction by a generic method is unsupported: 8ddf0031cf disallows a nointerface method to satisfy a constraint.

Timeline.

Static initialization and package init

Span: Go 1.18 → Go 1.28 · Status at HEAD: staticinit with inline-aware static init (enabled in 1.21), outlined map init for deadcode, init splitting redone in 1.28 cycle

Earlier history: Initialization and static data (Go 1.0 → Go 1.0) · Static initialization, composite literals and init (Go 1.1 → Go 1.9) · Walk-level lowering and runtime-call optimisations (Go 1.1 → Go 1.9) · Package initialization order, static initialisation and init functions (Go 1.10 → Go 1.17)

Design. staticinit.Schedule turns var x = <expr> into linker data where possible. Extensions over time: staticcopy of var b = a is now invalidated once an initializer that may modify a package variable has been seen (afa3f8e104, with -d=staticcopy diagnostics); static initialization of inlined calls (b1678e508b handles simple inlined calls, bed970b3ff integer conversions, 249e51e5d9, a141c58c85 string concatenation) so var myErr error = &myError{"msg"} stays static even if wrapped in an inlinable function; large global map literals are extracted into a separate map.init.N function called through a weak relocation, so the linker can dead-code-eliminate unreferenced big maps (b46e44a399, 269bdcd568 moves the switch to -d=wrapglobalmapctl).

Timeline.

Compile-time performance, concurrency and the self-PGO’d compiler

Span: Go 1.19 → Go 1.28 · Status at HEAD: backend concurrency = GOMAXPROCS (normally capped at 4 by go build); compiler built with PGO from cmd/compile/default.pgo, refreshed about annually; startup heap boost 128 MiB

Earlier history: Inlining (Go 1.1 → Go 1.9) · Compiler performance, formatting and concurrency (Go 1.7 → Go 1.9) · Concurrent compilation and frontend performance (Go 1.10 → Go 1.17)

Design docs & discussion

Design. Concurrency work lives in gc/compile.go: functions are compiled in an ordered work list by up to nWorkers goroutines; ordering is by cost (largest first) so long compiles start early. The GC is tuned for a short-lived process.

Timeline.

Compiler debugging infrastructure: bisect/hash debugging, flags, dumps

Span: Go 1.18 → Go 1.28 · Status at HEAD: -d flags with concurrency tagging, internal/bisect-based HashDebug, astdump HTML

Earlier history: Pragmas, directives and compiler flags (Go 1.1 → Go 1.9) · Race detector and sanitizer instrumentation in the frontend (Go 1.1 → Go 1.7) · checkptr instrumentation and compiler debugging flags (Go 1.12 → Go 1.17)

Design. base.HashDebug (GOSSAHASH renovated) lets any optimization be bisected to a function or file:line (including inlining stack). Hash patterns are generic across compiler flags and, via internal/bisect, GODEBUGs.

Timeline.

Frontend support for new language features and spec changes

Span: Go 1.18 → Go 1.28 · Status at HEAD: tracks the language; most features need a noder/typecheck/walk piece plus a types2 piece owned by the fe-types area

Paths not taken: Error-handling syntax prototypes: try and ? (never landed)

Earlier history: Closures (Go 1.0 → Go 1.0) · Language changes and rules as implemented in the compiler (Go 1.1 → Go 1.9) · Constants: Go 2 number literals, evconst, untyped conversion and go/constant (Go 1.11 → Go 1.17) · Language version gating (-lang) and language changes implemented in the frontend (Go 1.12 → Go 1.17)

Design. Each feature typically has: types2 acceptance (other area), writer/reader support in unified IR, and a lowering. A common pattern is “land compiler support behind GOEXPERIMENT first, flip with the language version”.

Timeline.

The syntax package (parser, scanner, tree)

Span: Go 1.18 → Go 1.28 · Status at HEAD: stable; mostly error-message and parser-tolerance work

Earlier history: Lexer, grammar and statement syntax (semicolons, chan operators, conversions) (Go 1.0 → Go 1.0) · Parser: from yacc to a hand-written recursive-descent parser (Go 1.1 → Go 1.8) · The syntax package, position representation and parse-time checks (Go 1.8 → Go 1.9) · The syntax package: parser, scanner, source reader, positions and error recovery (Go 1.10 → Go 1.17) · Directives and pragmas in the frontend (//go: handling) (Go 1.10 → Go 1.17)

Design. cmd/compile/internal/syntax is the compiler’s own parser; it differs from go/parser in retaining positions via PosBase, using LineDirective semantics, and producing Nodes directly consumed by noder and types2.

Timeline.

Package paths, -p, and build-system contract

Span: Go 1.19 → Go 1.28 · Status at HEAD: -p required; symbols carry real package paths; export-data readiness signal to cmd/go since 1.28 dev

Earlier history: Package system, visibility and import resolution (Go 1.0 → Go 1.0)

Design docs & discussion

Design. The compiler used to write "" as the local package path and let the linker substitute it; every "" assumption in compiler and tools was a source of bugs. a987aaf5f7 makes -p mandatory (go command already passed it; Bazel/Gazelle do too); ab8d7dd75e initialises LocalPkg.Path to it.

Timeline.

Types package internals: size, algorithm kind, TFlag, SIMD flags

Span: Go 1.18 → Go 1.28 · Status at HEAD: types.Type computes layout, alg kind, pointer bytes and abi.TFlag once, lazily and side-effect-free

Earlier history: Compiler data structures: Node, Nodes, Type, and the types package (Go 1.5 → Go 1.9) · Types package internals and type printing (Go 1.10 → Go 1.17)

Design. types.CalcSize historically computed only size/alignment; callers recomputed eq/hash algorithm, ptr bytes and register counts on demand, which is unsafe once SSA runs in parallel. Work moved these into CalcSize or cached fields.

Timeline.

Method wrappers and promoted-method sharing

Span: Go 1.18 → Go 1.28 · Status at HEAD: wrappers for promoted methods shared across receiver types (1.28 cycle)

Earlier history: Lowering in order/walk and the shape of runtime calls emitted by the frontend (Go 1.10 → Go 1.17) · Register-ABI plumbing in the frontend (Go 1.12 → Go 1.17)

Design. For embedded fields, method tables, itabs, reflect metadata and generic dictionaries need a stable entry point, historically one wrapper per concrete receiver type. Unified IR (1.18) took over generation (f4198f85d5, d44ed5d144); f534300f98 shares wrappers when the promoted method’s receiver (e.g. *X embedded at offset 0) is identical across A, B, C, qualifying shared receiver types (61b3e65bd2).

Timeline.

SIMD frontend: the “midway” source-to-source translator

Span: Go 1.26 → Go 1.28 · Status at HEAD: prototype in the frontend, -d flag controlled

Design. internal/midway (analysis, deepcopy, rewrite, check) is a source-to-source translator ported into the front-end that rewrites “portable SIMD” code into per-architecture calls by re-running type checking after the rewrite (f1aa77c984: “tiny-highway v0”, 12.6 kLOC incl. emulations and tests).

Timeline.

ir.StaticValue, ReassignOracle and function-variable tracking

Span: Go 1.22 → Go 1.27 · Status at HEAD: batch oracle used by inliner/escape; ongoing migration of callers

Design. ir.StaticValue/ir.Reassigned find the unique definition of a local by walking the whole function per query. ReassignOracle (8be8bfeaa2) collects the information once per function (“reduced cost in compile time”).

Timeline.

(none: all substantive bodies of work are covered by the existing threads)

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