Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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, Push all work).
  • Labels use numeric .N syntax (.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 PUSH1PUSH8 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: r0r255.

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.