Assembly Language
XQVM programs are written in a simple assembly language and stored in .xqasm
files. The assembler (the xqasm crate, invoked via xquad asm) parses
the source, resolves labels, and emits compact bytecode.
Overview
- Line-oriented format: one instruction per line.
- Comments start with
;and run to end of line. - Mnemonics are case-insensitive (
PUSH,push,Pushall work). - Labels use numeric
.Nsyntax (.0,.1,.42). - Registers use
r<digits>syntax (r0,r255). - Integer literals may be signed decimal or
0x-prefixed hexadecimal.
Quick Example
; Compute 10 + 32 = 42
PUSH 10
PUSH 32
ADD
HALT
Assembly Syntax
This page defines the complete syntax of the XQVM assembly language, derived
from the canonical PEG grammar in xqasm/src/grammar.pest.
Line Structure
Each source line has the form:
[label_def:] [INSTRUCTION [operands...]] [; comment]
All three parts are optional. Blank lines and comment-only lines are valid.
Examples
; blank line (valid)
; this is a comment ; comment-only line
PUSH 42 ; instruction only
.0: NOP ; label + instruction
.1: ; label only (anchors a jump target)
LOAD r0 ; register operand
JUMP .0 ; label reference operand
Comments
Comments begin with ; and extend to the end of the line. They can appear
on their own or after an instruction:
; full-line comment
PUSH 10 ; inline comment
Mnemonics
Instruction mnemonics are case-insensitive ASCII identifiers. All of these are equivalent:
PUSH 42
push 42
Push 42
The assembler recognises 85 of the 93 XQVM mnemonics directly. The eight
PUSH1–PUSH8 opcodes are not typeable: PUSH is a special mnemonic that
accepts an integer operand and selects the smallest of those encodings for
you, and writing one of them by hand is rejected as an unknown mnemonic.
PUSHC is an alias for PUSH. JUMP and JUMPI are the same kind of
sugar, but layered on top of opcodes that are themselves among the 85:
JUMP1, JUMPI1, JUMP2, JUMPI2 are part of the 93 and can be written
directly, though doing so interacts badly with the unused-label check – see
Control Flow. JUMP and JUMPI
are two further mnemonics, not among the 93, that pick the narrowest width
for you the same way PUSH picks a PUSHn width. See Stack
Manipulation for the width rules.
Operands
Three operand types exist:
Registers
r0, r1, r2, ..., r255
A lowercase r followed by 1–3 decimal digits. Valid range: r0–r255.
Integer Literals
42 ; positive decimal
-99 ; negative decimal
+7 ; explicit positive
0xFF ; hexadecimal (0x prefix)
0x0 ; hex zero
Integers are signed i64 values. Decimal and hexadecimal (0x prefix) formats
are supported. An optional + or - sign may precede the digits.
Label References
.0, .1, .42, .255
A dot followed by one or more decimal digits. Label references are used as
operands for the JUMP/JUMPI mnemonics (and their explicit-width forms
JUMP1/JUMPI1/JUMP2/JUMPI2).
Labels
Labels are defined either with the .N: shorthand or the explicit TARGET .N
directive:
.0: NOP ; shorthand: define label .0 at this position
.1: ; label on its own line (useful for readability)
TARGET .2 ; explicit form: identical to ".2:"
HALT
Both forms compile to the same bytecode: placing a label emits an inline
TARGET opcode at the current position and records that position under the
label’s assigned id. .0: and TARGET .0 are interchangeable spellings for
the same operation; pick whichever reads better in context. Defining the same
label with both forms is a DuplicateLabel error, the same as defining .N:
twice.
A bare TARGET (no operand) emits a raw Target opcode without binding any
label. That’s useful only for hand-built bytecode where you do not need a
corresponding jump destination; user-facing programs should use the labelled
forms.
Labels must be defined before or after they are referenced – both forward and
backward references are resolved by the assembler. Every label used as a
JUMP/JUMPI target must be defined somewhere in the program.
The .N digits are assembler-only syntax: they let the assembler pair a
JUMP/JUMPI reference with the .N: (or TARGET .N) that defines it, and
they are never emitted into the bytecode. What reaches the instruction
stream is only a sequence of bare TARGET opcodes. See Bytecode
Format for how a decoder
assigns those opcodes their sequential ids and resolves a jump operand
against them.
Whitespace
Spaces and tabs between tokens are ignored. Lines are separated by \n or
\r\n. Indentation is purely cosmetic and has no semantic meaning. A common
convention is to indent loop bodies:
PUSH 0
PUSH 10
RANGE
LVAL r0
LOAD r0
PUSH 2
MUL
POP
NEXT
Error Reporting
The assembler uses miette for rich terminal diagnostics. Errors include the source file name, line/column numbers, and a snippet highlighting the problematic token:
Error: xqasm::unknown_mnemonic
× unknown mnemonic `BADOP`
╭─[bad.xqasm:2:1]
1 │ PUSH 1
2 │ BADOP r0
· ──┬──
· ╰── unknown mnemonic
3 │ HALT
╰────
xquad asm shows the same diagnostics from the
command line.