Assembly Guide

Purpose and Scope

Go's assembler is the toolchain component that accepts Go assembly source and produces object data for the linker. It is most often encountered by people maintaining low-level runtime routines, performance-sensitive package implementations, or architecture-specific code that cannot be expressed conveniently in ordinary Go. This page explains the repository-backed command entry point for that tool, how it chooses an architecture, how it prepares linker context, and how assembly files move from input text to object output. The goal is not to replace the full assembler language guide, but to connect the public model of Go assembly with the executable that implements the command.

The key concept to define first is that Go assembly is not simply native processor syntax. The public documentation describes it as a Plan 9-style, semi-abstract instruction language used by the gc toolchain. That distinction matters when reading repository code: the assembler command is not just a text-to-machine-code translator. It creates a linker-oriented object representation using architecture metadata, object writer state, and parser output. Some instruction names correspond closely to machine operations, while others are part of the toolchain's abstract instruction vocabulary and are resolved later with architecture-specific behavior.

Sources: src/cmd/asm/main.go

Relevant Source Files

  • src/cmd/asm/main.go — Defines the asm command executable: startup validation, flag parsing, architecture selection, linker context configuration, input parsing, object writing, and failure cleanup.

This page intentionally centers on src/cmd/asm/main.go because that file is the public command boundary for the assembler. The implementation details it exposes are the pieces a developer needs when asking practical questions such as: which GOARCH is being assembled for, how command-line flags affect object generation, why an output file disappears after an error, or why symbol ABI output behaves differently from normal assembly output. Deeper syntax, parser grammar, and architecture tables live behind imports from internal packages, but the command entry point shows how those subsystems are composed into the tool that users invoke.

Sources: src/cmd/asm/main.go

Command Position in the Toolchain

The assembler executable follows the same broad pattern as other commands in the Go toolchain: it is normally driven by the go command during package builds, but it can also be invoked through go tool asm for debugging or specialized build workflows. The command is package main, not a library API, so its contract is process oriented: parse flags, assemble named input files, write an object or symbol ABI output file, report diagnostics, and exit with a status code that surrounding tools can interpret. That shape is visible in the top-level main function, which performs all orchestration in a single command flow.

At startup, the command removes timestamp and file prefixes from the standard logger and sets a fixed asm: prefix. That gives diagnostics a stable, recognizable source when the assembler is invoked indirectly by a larger build. It then opens telemetry counters, validates build configuration, and reads buildcfg.GOARCH as the selected target architecture. The use of build configuration rather than a source-file directive is important: assembly behavior is tied to the target selected for the toolchain invocation, and the executable chooses its architecture before it begins reading input files.

Sources: src/cmd/asm/main.go

Execution Flow

The execution flow starts with command-line flag parsing. After flags.Parse, the command records an invocation counter and counts flag usage under an asm/flag: telemetry prefix. It then calls arch.Set with the selected GOARCH and a boolean representing shared or dynamic-link mode. If architecture selection fails, the command terminates with a fatal message naming the unrecognized architecture. This is the first major validation boundary: no lexer, parser, or object writer is created until the architecture has been recognized and the command can build a matching link architecture.

Once an architecture is selected, the command creates an obj.Link context with obj.Linknew(architecture.LinkArch). This context is the assembler's shared state for object generation. The command populates it with build and debugging options: compressed instruction output, debug assembly printing, verbose logging, dynamic link flags, shared-link flags, may-more-stack debug configuration, PC table debugging, assembler mode, package path, standard-library marker, and a dummy DWARF function count for assembler-generated data. These fields show that the assembler is tightly coupled to the linker object model, not merely to textual syntax.

The -spectre flag handling is a compact example of compatibility with other toolchain commands. The command accepts an empty value, ignores index so shared flag lists can be used with compiler and assembler invocations, and enables retpoline behavior for all or ret. Unknown settings produce a log message and exit code 2. That behavior is useful when writing build scripts: not every compiler mitigation flag has independent assembler behavior, but the assembler intentionally tolerates some values so a single -spectre list can be applied across compile and assembly phases.

Sources: src/cmd/asm/main.go

Inputs, Outputs, and Object Generation

After configuration, the command connects buffered standard output to the linker context and calls architecture.Init(ctxt). Architecture initialization happens before opening and writing the target object file, so any per-architecture state is ready when parsing begins. The output file is created with bio.Create(*flags.OutputFile), and normal object output begins by writing the object ABI header string followed by ! unless the command is running in symbol ABI mode. This early header write is why later error handling must close and remove the output file: a failed assembly may already have created a partial artifact.

The command also sets up macro definitions for enabled GOEXPERIMENT values, but only when the package path is allowed to use assembly ABI special handling. It asks objabi.LookupPkgSpecial(ctxt.Pkgpath).AllowAsmABI before appending definitions named GOEXPERIMENT_ plus each enabled experiment. This matters for runtime and other privileged assembly code: source can conditionally assemble different routines depending on experiments, while ordinary packages do not automatically receive the same ABI-sensitive macro surface. The behavior is repository-specific and is part of how experimental runtime features can be coordinated with assembly implementation.

For each positional file argument, the command creates a lexer with lex.NewLexer(f) and a parser with asm.NewParser(ctxt, architecture, lexer). It installs a diagnostic callback on the linker context that marks that a diagnostic occurred and logs the formatted message. Then it chooses one of two parse modes. In symbol ABI mode, parser.ParseSymABIs(buf) writes ABI metadata directly to the output buffer. In normal mode, the parser returns the first program counter into an obj.Plist; if parsing succeeds, obj.Flushplist(ctxt, pList, nil) transfers the parsed program list into the object context.

The command processes input files sequentially and stops at the first failed parse. If all files parse successfully and the command is not in symbol ABI mode, it numbers symbols with ctxt.NumberSyms() and writes the final object file with obj.WriteObjFile(ctxt, buf). This division is helpful when reasoning about failures: parsing and program-list flushing happen per input file, but final symbol numbering and object emission happen only after the entire input set succeeds. A build system that passes multiple assembly files to one invocation therefore receives one success or failure result for the group, not independent object outputs per file.

Sources: src/cmd/asm/main.go

Architecture-Specific Behavior

Architecture-specific behavior enters through the selected architecture value returned by arch.Set. The command does not hard-code instruction names, registers, relocation rules, or parsing tables in main.go; instead, it passes the architecture object to both the linker context and parser. That design matches the public assembler model: the syntax is shared enough to document as Go assembly, but its exact instruction selection and legal operations vary by target. When a developer investigates why a file assembles on one GOARCH and not another, the first command-level fact is that all parsing and object emission are parameterized by buildcfg.GOARCH.

Shared-library and dynamic-link settings also influence architecture selection and context flags. The boolean passed to arch.Set is true when either -shared or -dynlink is active, and the linker context receives separate Flag_dynlink, Flag_linkshared, and Flag_shared values. These are not cosmetic options. They affect how generated object data must interact with external linkage, shared objects, and dynamic-link assumptions. Assembly authors rarely set all of these directly, but they may observe their effects when building the same low-level routine for ordinary static binaries, shared libraries, or dynamically linked configurations.

Sources: src/cmd/asm/main.go

Compact Reference

ComponentSource-backed behavior
Entry pointmain() in src/cmd/asm/main.go orchestrates the command from logging setup through object emission.
Target architectureUses buildcfg.GOARCH, then calls `arch.Set(GOARCH, *flags.Shared
Link contextCreates obj.Linknew(architecture.LinkArch) and marks ctxt.IsAsm = true.
Debug and build flagsCopies assembler flags into context fields such as CompressInstructions, Debugasm, Debugvlog, Flag_dynlink, Flag_shared, Flag_maymorestack, and Debugpcln.
Spectre handlingAccepts empty value, ignores index, enables ctxt.Retpoline for all or ret, and exits for unknown settings.
Output creationUses bio.Create(*flags.OutputFile) and writes an object header unless -symabis mode is active.
Input processingFor each file argument, creates a lexer, creates a parser, and either parses symbol ABIs or parses program text into an obj.Plist.
FinalizationCalls ctxt.NumberSyms() and obj.WriteObjFile(ctxt, buf) only after successful normal parsing.
Error handlingLogs the failed file when known, closes and removes the output file, and exits with status 1.

This reference is most useful when debugging build behavior rather than when learning instruction syntax. If assembly text is rejected, the parser and architecture tables determine the detailed language rule. If object output is missing, malformed, or unexpectedly affected by flags, the command-level flow above shows when output is opened, when headers are written, when final object writing occurs, and when the file is removed. In practice, this means an assembler diagnostic can leave no artifact by design, even though the output path was opened earlier in the invocation.

Sources: src/cmd/asm/main.go

Practical Workflow and Next Steps

When writing or maintaining Go assembly, start from the public assembler guide for syntax and calling-convention concepts, then use the command model here to understand how that source enters the build. For a quick inspection workflow, compile or build a small Go program with assembly listing options, then compare that semi-abstract instruction output with architecture-specific runtime or library assembly. When invoking the assembler directly, remember that the target is inherited from the active toolchain configuration, and command flags are translated into linker-context fields before any source file is parsed.

For repository contributors, the most important implementation boundary is that cmd/asm/main.go is orchestration code. Changes to command startup, flag compatibility, output lifecycle, telemetry, or error cleanup belong near this entry point. Changes to instruction parsing, macro handling, or architecture rules usually belong in the internal packages that the entry point wires together. Read this page alongside the broader toolchain command reference and compiler architecture material if you need to understand how assembly, compilation, object writing, and linking cooperate in a complete Go build.

Sources: src/cmd/asm/main.go