# Language: lexicon, statements and expressions Layer 1, the common language, the same on every target. Verified against `go tool asm` of Go 1.27.1 and against gasm's parser, which is differentially tested against the toolchain. The authoritative sources behind this page are the assembler's lexer (`cmd/asm/internal/lex`), its parser (`cmd/asm/internal/asm/parse.go`) and the toolchain's own test data. ## Source files and targets An assembly source is a `.s` file. The Go build convention names a target-specific file with the architecture suffix, `_amd64.s`, `_arm64.s`, `_riscv64.s` or `_loong64.s`; files without a suffix are portable across targets. The same assembler program assembles every target: `go tool asm` picks the target from the `GOOS` and `GOARCH` environment variables, and gasm from the file name suffix or the `--arch` flag. ## Character set and identifiers Sources are ASCII text. An identifier is a sequence of ASCII letters, digits and underscores, digits never first, with exactly two additions: - U+00B7, the middle dot `·`, stands for the period in a symbol's package-qualified name; - U+2215, the division slash `∕`, stands for the slash in a package path. The two substitutions exist because the parser treats a real period and a real slash as punctuation. The syntax is otherwise uppercase throughout: instructions, registers and directives are written in upper case. The one inherited exception is the `g` register name on 32-bit ARM. ## Comments Two comment forms, both Go's: ```text // a line comment /* a block comment */ ``` A comment of the form `//go:build` or the legacy `+build` comment is not a plain comment: the lexer reports it to the build system as a build constraint. ## Statements The grammar of one line, from the parser: ```text {label:} WORD[.qualifier] [ arg {, arg} ] (';' | '\n') ``` - A **label** is an identifier followed by a colon. Labels are function-local: two functions in one file may reuse the same name, and a reference resolves within the function that contains it. A branch instruction names its target with a bare label operand, and the assembler resolves it PC-relative. The explicit forms `offset(PC)`, a constant counting instructions from the branch, and `name(SB)`, a cross-function static reference, appear as branch targets as well. - **WORD** is the instruction or directive name, upper case. On the ARM family the word may carry a dot qualifier selecting a condition or shift mode, such as the condition suffixes on 32-bit ARM; the amd64, arm64, riscv64 and loong64 assemblies carry no instruction qualifiers apart from their own width suffixes, which are part of the mnemonic. - **Arguments** are separated by commas, with no trailing comma. - A statement ends at a newline or at a semicolon, so several statements fit on one line separated by `;`. Blank lines are free. The first word of a line is a directive if it is one of the directive names (TEXT, DATA, GLOBL, FUNCDATA, PCDATA, PCALIGN) and an instruction otherwise. Unknown instruction names are errors; the instruction set is the set the toolchain itself defines per target, plus the common pseudo-instructions. ## Literals | Form | Examples | Notes | |---|---|---| | Integer | `0`, `42`, `0x2a`, `0o52`, `0b101010`, `1_000` | decimal, hexadecimal, octal and binary forms with Go's digit separators | | Character | `'a'`, `'\n'`, `'\x41'` | single quoted, Go escape rules | | String | `"this program can only run\n"` | double quoted, Go escape rules; accepted where an operand takes raw bytes, in practice a DATA initialiser | | Float | `1.5`, `1e9` | accepted by the lexer; only meaningful where the target's encoding takes a float operand | ## Expressions Constant expressions may appear wherever a constant is expected: in immediates after `$`, in memory offsets, in frame and data sizes. The evaluator works on unsigned 64-bit values with Go's operator precedence, and the parser states its grammar in exactly those terms: ```text expr = term { '+' term | '-' term | '|' term | '^' term } term = factor { '*' factor | '/' factor | '%' factor | '<<' factor | '>>' factor | '&' factor } factor = const | '+' factor | '-' factor | '~' factor | '(' expr ')' ``` Two consequences are worth naming, because the arithmetic surprises people who read it as C: - Shifts bind at the multiplicative level, next to `*` and `&`, while `|` and `^` bind at the additive level. `$x<<1|3` computes `(x<<1)|3`, which differs from `x*2+3` whenever `x` is odd. Plan 9 arithmetic is Go precedence applied to a byte-oriented language, not the C expression it resembles. - The evaluator is unsigned and guarded: division or modulo by zero is an error, and so is dividing a value with the high bit set; shift counts must be non-negative; and a right shift of a value with the high bit set is rejected rather than sign-extended. An address expression such as `(index*4)(base)` is evaluated at assembly time only if every name in it is a constant; a name that resolves to a symbol turns the expression into a relocation request, never into a folded constant. Named constants enter expressions through the preprocessor (`#define`, `-D`) and, in Go-embedded packages, through the generated `go_asm.h`; see PREPROCESSOR.md and RUNTIME.md. ## The common pseudo-instructions A handful of instructions exist on every target, assembled by the assembler itself rather than the encoder: `NOP`, which emits the target's no-operation encoding, and the frame-management pseudo-instructions the compiler emits (`FUNCDATA`, `PCDATA`) which DIRECTIVES.md specifies. Everything else is the target's own instruction set, and the assembler knows only the instructions the toolchain's compiler emits; a hand-written kernel wanting more lays the encoding down with `BYTE` on amd64 or waits for the extended layer. ## Case study: three lines, decomposed ```text B.EQ 1(PC) // arm64: condition qualifier on the mnemonic, // target one instruction past the branch JMP done // every target: bare label, function-local, // resolved PC-relative MOVQ $reader__size>>3, CX // amd64: expression over a go_asm.h constant ``` The first shows a qualifier and the explicit relative target form; the second the ordinary label reference; the third an expression over a generated constant. Labels are reusable between functions without conflict.