Open-Source Wikis

/

Go

/

Components

/

Assembler

golang/go

Assembler

cmd/asm (in src/cmd/asm/) turns Go's pseudo-assembly (.s files) into object code. It is invoked by the go command once per assembly source file in a package, just like cmd/compile is invoked once per Go source file.

Purpose

Go has its own assembly dialect — derived from the Plan 9 toolchain — that abstracts over the underlying machine. The same source (asm_<arch>.s style) is processed by cmd/asm to produce architecture-specific object code. The dialect is deliberately uniform across architectures: same comment style, same directives, same syntax for labels.

This pseudo-assembly is what's in src/runtime/asm_amd64.s, src/runtime/sys_linux_arm64.s, and so on. Outside the runtime, very little Go uses assembly directly — it's almost always for hot performance paths in crypto/..., math/..., runtime/..., and a few other places.

Directory layout

src/cmd/asm/
├── doc.go                  # User-facing docs, mirrors 'go tool asm -help'
├── main.go                 # Entry; calls into internal/asm
└── internal/
    ├── asm/                # The driver + parser; uses cmd/internal/obj for the back end
    │   ├── asm.go          # Operand parsing
    │   ├── operand.go      # Operand normalization
    │   ├── parse.go        # Tokenizer + parser
    │   └── testdata/       # Per-arch encoding tests
    ├── arch/               # Per-arch instruction set helpers
    ├── flags/              # Asm command-line flags
    └── lex/                # Tiny lexer (also handles cpp-style includes)

The actual encoding tables live one level up, in src/cmd/internal/obj/<arch>/. Those packages are shared with the linker and compiler.

How it fits in the build

graph LR
    Go[Go source: foo.go] --> Compile[cmd/compile]
    S[Assembly: foo_amd64.s] --> Asm[cmd/asm]
    Compile --> Obj[obj.Prog stream]
    Asm --> Obj
    Obj --> Goobj[*.o object via cmd/internal/goobj]
    Goobj --> Archive[Package archive .a]
    Archive --> Link[cmd/link]

When a package contains both .go and .s files, cmd/go invokes cmd/compile for the Go files and cmd/asm for the assembly files. Both produce obj.Prog streams, which the linker eventually consumes.

Pseudo-assembly basics

The dialect uses uppercase mnemonics, AT&T-style operand order (source then destination on most arches), and special pseudo-registers:

Pseudo-register Meaning
SP The Go stack pointer (logical), distinct from hardware SP
FP Frame pointer for accessing parameters
SB The "static base" pseudo-register for global symbols
PC Logical PC, used in branches

Symbols are referenced as name+offset(SB). Local frame variables as name+offset(SP). Frame size is declared in the function header:

TEXT runtime·hello(SB), NOSPLIT, $32-16
    // 32 bytes of local frame, 16 bytes of args+results
    MOVQ x+0(FP), AX
    RET

The middle dot (·) in runtime·hello is the package-qualified name — Plan 9 convention.

Annotations:

  • NOSPLIT — no stack-grow prologue (corresponds to //go:nosplit in Go).
  • WRAPPER — this is a wrapper function; runtime walks through it for tracebacks.
  • NEEDCTXT — function needs the closure context.
  • TLSBSS — symbol is thread-local (Linux/etc).

Documented in doc/asm.html (in-tree).

Per-arch handling

Each architecture has:

  • src/cmd/asm/internal/arch/<arch>.go — registers, mnemonics, special tokens.
  • src/cmd/internal/obj/<arch>/ — the encoder. Many of these are partially generated:
    • anames.go — instruction names.
    • asm.go / asm5.go / asm7.go — encoder logic.
    • inst.go, inst_gen.go — instruction tables.
    • Per-arch *_gen.go files driven by _gen/ subdirectories.

Adding a new instruction usually means updating the per-arch table in cmd/internal/obj/<arch>/ and possibly its _gen/ source.

Key abstractions

Abstraction Where Purpose
obj.Prog cmd/internal/obj/link.go One assembly instruction
obj.Addr same One operand
obj.LSym same A symbol (function, data)
arch.Arch cmd/asm/internal/arch/ Per-arch metadata
lex.Tokenizer cmd/asm/internal/lex/ Source tokens
asm.Parser cmd/asm/internal/asm/parse.go Recursive-descent parser

Key source files

File Purpose
src/cmd/asm/main.go Driver: parse flags, invoke parser per file
src/cmd/asm/internal/asm/parse.go The parser
src/cmd/asm/internal/asm/asm.go Operand handling
src/cmd/internal/obj/link.go obj.Prog, obj.Addr, obj.LSym
src/cmd/internal/obj/<arch>/asm.go Per-arch encoder
src/cmd/asm/internal/asm/testdata/ Encoding tests, including SVE/AVX
doc/asm.html Pseudo-assembly user reference

Diagnostics

  • go tool asm -S — print assembly back after parsing (canonical form).
  • go tool asm -dynlink — emit instructions for dynamic linking.
  • go build -gcflags=-S — compiler-emitted assembly.
  • go tool objdump <binary> <symbol> — disassemble a binary.

Integration points

  • The compiler (compiler) emits the same obj.Prog stream that cmd/asm does, then both feed cmd/internal/obj for object emission.
  • The linker (linker) is the consumer.
  • Runtime assembly is the largest user of the assembler — every src/runtime/*.s file passes through cmd/asm.
  • go vet runs an asm-aware check (frame-size and arg-offset declarations) over .s files.

Entry points for modification

  • Adding a new instruction: per-arch table in cmd/internal/obj/<arch>/ and possibly the generator under _gen/.
  • ARM64 SVE additions and similar new vector ISAs are often the kind of change you'll see here; the testdata files like arm64sveerror.s and arm64sveenc.s cover error-path and encoding-correctness cases.
  • doc/asm.html — the canonical pseudo-assembly reference.
  • Compiler — produces the same obj.Prog stream.
  • Linker — consumes the assembler's output.
  • Runtime — biggest consumer of hand-written assembly.

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

Assembler – Go wiki | Factory