Open-Source Wikis

/

Go

/

Components

/

Compiler

golang/go

Compiler

The Go compiler, called "gc" (Go compiler — not garbage collector), lives under src/cmd/compile/. It is the reference implementation of Go and the compiler that ships with go build.

Purpose

gc takes one Go package's source files and emits one object archive (.a) plus an export-data file. The downstream tool (the linker, or another compile invocation that imports this package) consumes those.

The compiler is a single binary, compile, invoked once per package by cmd/go. It does not build whole programs — that's the go command's job.

Directory layout

src/cmd/compile/
├── README.md                           # The canonical compiler overview
├── default.pgo                         # PGO profile used to build the toolchain itself
├── doc.go                              # Package doc / man page
├── main.go                             # Tiny entry point, calls into internal/gc
└── internal/
    ├── syntax/                         # Lexer, parser, syntax tree
    ├── types2/                         # Type checker (port of go/types)
    ├── importer/                       # Package import (reads export data)
    ├── noder/                          # Convert syntax+types2 → IR (Unified IR)
    ├── ir/                             # Compiler IR node types
    ├── types/                          # Compiler types (parallel to types2)
    ├── inline/                         # Function inlining
    ├── devirtualize/                   # Devirtualize known interface calls
    ├── escape/                         # Escape analysis
    ├── walk/                           # Order-of-eval + desugaring
    ├── ssagen/                         # IR → SSA
    ├── ssa/                            # SSA passes, rewrites, codegen
    │   └── _gen/                       # Source rules; go generate produces *.go
    ├── reflectdata/                    # Type descriptors for runtime/reflect
    ├── liveness/                       # Pointer liveness analysis
    ├── dwarfgen/                       # DWARF debug info generation
    ├── pgoir/                          # PGO data ingestion + use
    ├── inline/inlheur/                 # Heuristics for inlining
    ├── coverage/                       # Coverage instrumentation
    ├── deadlocals/                     # Dead local-variable elimination
    ├── rangefunc/                      # Lowering of range-over-func iterators
    ├── loopvar/                        # New per-iteration loop var semantics
    ├── pkginit/                        # Package init function generation
    ├── staticdata/, staticinit/        # Compile-time data and init handling
    ├── typecheck/                      # Legacy typecheck (mostly retired)
    ├── amd64/, arm/, arm64/, ppc64/, ... # Per-arch SSA codegen
    └── gc/                             # The driver: orchestrates all phases

The compilation pipeline

Roughly, one compile invocation runs these phases. Numbers and names match src/cmd/compile/README.md.

graph TD
    Source[*.go source files] --> Syntax[1. syntax: lex + parse]
    Syntax --> Types2[2. types2: type check]
    Types2 --> Noder[3. noder: build Unified IR]
    Noder --> Middle[4. middle end: escape, inline, devirt]
    Middle --> Walk[5. walk: order + desugar]
    Walk --> Ssagen[6a. ssagen: IR → SSA]
    Ssagen --> SsaGeneric[6b. ssa generic passes]
    SsaGeneric --> SsaArch[7. ssa lowering + arch passes]
    SsaArch --> Obj[obj.Prog]
    Obj --> Output[*.a object + export data]

1. Syntax (internal/syntax)

Pure-Go lexer + parser, distinct from the public go/parser. Produces a syntax-AST with position information for diagnostics.

Key types in cmd/compile/internal/syntax/nodes.go:

  • *syntax.File — one source file's AST root.
  • *syntax.FuncDecl, *syntax.TypeDecl, *syntax.VarDecl, *syntax.ConstDecl — top-level declarations.
  • *syntax.Stmt, *syntax.Expr — statement and expression interfaces.
  • *syntax.PosBase — position information shared by all nodes.

2. Type checking (internal/types2)

types2 is a near-port of the public go/types package, adapted to consume syntax.Expr instead of go/ast.Expr. It performs full type inference, including for generics. Result is a typed AST plus an *types2.Info with type/object resolution.

This is where most "before-IR" semantic analysis happens: name resolution, type checking, generic instantiation, constant folding for typed constants.

3. Noder (internal/noder)

The "noder" walks the typed syntax tree and produces the compiler's internal IR. It does this through Unified IR:

  • noder/writer.go serializes typed syntax into a binary IR stream.
  • noder/reader.go deserializes that stream into ir.Nodes.
  • The same format is also used for export data — what the compiler writes to summarize this package for downstream importers.

Unified IR is the rendezvous point between front-end (types2 + syntax) and middle/back-end (ir/ssa). It also drives generics: a generic function is exported as Unified IR, and instantiations are read back into the importing package.

4. Middle end

A handful of optimization passes operate on ir.Node:

  • internal/escape — escape analysis. Decides whether each Go value can stay on the stack or must be heap-allocated. Influences allocation strategy and write-barrier emission.
  • internal/inline and internal/inline/inlheur — function inlining. PGO data, when available, biases inlining toward hot call sites.
  • internal/devirtualize — replaces interface method calls with direct calls when the dynamic type can be inferred.
  • internal/deadlocals — eliminates locals never used.

5. Walk (internal/walk)

The walk pass does two things:

  1. Order of evaluation. Decomposes complex expressions into simpler ones, introducing temporaries where Go's evaluation rules require them. (Older codebase term: "order.")
  2. Desugaring. Replaces high-level constructs with primitives the back end understands:
    • range → conventional for loops.
    • switch → balanced binary searches or jump tables.
    • Map ops → calls into runtime.mapaccess*, runtime.mapassign*.
    • Channel ops → calls into runtime.chansend*, runtime.chanrecv*.
    • Defer/recover → calls to runtime.deferproc, runtime.deferreturn.
    • String concatenation, conversion, type assertions → runtime helpers.
    • Range over function (Go 1.23+) → state-machine transform via internal/rangefunc.

6. SSA construction and generic passes

internal/ssagen lowers IR into Static Single Assignment form. This is where the compiler crosses from "Go-shaped IR" to "compiler-shaped IR." Then internal/ssa runs a sequence of passes:

  • Generic optimization passes that don't depend on architecture: dead code elimination, common subexpression elimination, copy propagation, branch elimination, bounds check elimination, nil check removal, prove pass (range/null facts).
  • The "rewrite" engine, driven by *.rules files. internal/ssa/_gen/generic.rules holds machine-independent peepholes; per-arch files (AMD64.rules, ARM64.rules, …) hold arch-specific ones.

7. Architecture-specific lowering and codegen

Each internal/ssa/_gen/<ARCH>.rules and internal/<arch>/ssa.go together define how generic SSA is lowered into architecture-specific opcodes. The internal/ssa package then runs scheduling, register allocation (regalloc.go), stack frame layout, and pointer liveness analysis.

The output is a sequence of obj.Prog instructions per function. These are handed to cmd/internal/obj (the assembler back-end) to produce the final object code.

7a. Export data

In addition to the object file, compile writes export data: a Unified IR stream containing:

  • Public type declarations.
  • Public function signatures (and bodies, for inline candidates).
  • Generic function bodies (so importers can instantiate them).
  • Escape information for parameters of inline candidates.

The format is internally versioned. golang.org/x/tools/go/internal/... mirrors a reader for tools like gopls.

Key abstractions

Abstraction Where Purpose
syntax.File, syntax.Expr cmd/compile/internal/syntax/nodes.go Front-end AST
types2.Type, types2.Object, types2.Info cmd/compile/internal/types2/ Types and resolved identifiers
ir.Node, ir.Func, ir.Name cmd/compile/internal/ir/ Compiler IR
types.Type, types.Sym cmd/compile/internal/types/ Compiler types (parallel to types2)
ssa.Func, ssa.Block, ssa.Value cmd/compile/internal/ssa/ SSA representation
obj.Prog, obj.Addr cmd/internal/obj/ Pseudo-assembly handed to the linker/assembler
pgo.Profile cmd/compile/internal/pgoir/ Loaded PGO data

Key source files

File Purpose
src/cmd/compile/main.go Command entry; calls gc.Main
src/cmd/compile/internal/gc/main.go Top-level pipeline driver
src/cmd/compile/internal/syntax/parser.go The hand-written recursive-descent parser
src/cmd/compile/internal/types2/check.go Type-checking entry
src/cmd/compile/internal/noder/writer.go Unified IR encoder
src/cmd/compile/internal/noder/reader.go Unified IR decoder
src/cmd/compile/internal/escape/escape.go Escape analysis
src/cmd/compile/internal/inline/inl.go Inlining decisions
src/cmd/compile/internal/walk/walk.go Order + desugar entry
src/cmd/compile/internal/ssagen/ssa.go IR → SSA
src/cmd/compile/internal/ssa/compile.go SSA pass pipeline
src/cmd/compile/internal/ssa/_gen/generic.rules Architecture-independent rewrites
src/cmd/compile/internal/ssa/regalloc.go Register allocator
src/cmd/compile/internal/dwarfgen/dwarf.go DWARF emission

Useful diagnostics

The compiler exposes a lot of internal state to the user via -gcflags:

go build -gcflags=-m=2                       # inlining + escape
go build -gcflags='all=-m=1 -l'              # all packages, no inlining
go build -gcflags=-d=ssa/check_bce/debug=1   # bounds-check elimination decisions
GOSSAFUNC=funcName go build                  # write ssa.html for one function
go build -gcflags=-S                         # dump final assembly
go tool compile -d help                      # list all -d knobs
go tool compile -d ssa/help                  # list all SSA debug knobs

See Debugging for more.

Integration points

  • Linker (cmd/link) consumes object files and export data the compiler emits.
  • Runtime (runtime) is called from compiler-emitted code (channel ops, map ops, panic/recover, write barriers, GC scan helpers, etc.).
  • reflect package consumes the type metadata that internal/reflectdata emits.
  • DWARF tools (delve, addr2line) consume internal/dwarfgen output.
  • PGO uses pprof CPU profiles produced via runtime/pprof; the compiler reads them through internal/pgoir.

Entry points for modification

If you want to add an optimization, add it as an SSA pass or rewrite rule:

  • For a generic peephole, edit cmd/compile/internal/ssa/_gen/generic.rules, then run go generate ./... from _gen/.
  • For an arch-specific peephole, edit the corresponding <ARCH>.rules.
  • For a new pass, add a function in cmd/compile/internal/ssa/ and register it in compile.go.

If you're touching the front end:

  • Type-checker bugs: start in cmd/compile/internal/types2.
  • Generic semantics: same place; instantiation lives in noder.
  • Parser bugs: cmd/compile/internal/syntax.

For test layout, see Testing. Compiler regression tests usually go under test/ (run by cmd/internal/testdir).

  • src/cmd/compile/README.md — the in-tree compiler tour with phase-by-phase code references.
  • src/cmd/compile/internal/ssa/README.md — the SSA package's internal design.
  • src/cmd/compile/internal/noder/README.md — Unified IR.
  • Linker — what consumes the compiler's output.
  • Runtime — what compiler-generated code calls into.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Compiler – Go wiki | Factory