rg-asm

"I'll have what she's having!"

rg-asm is a universal assembler for Atari ST / TT / Falcon development.

It supports a wide range of assembler dialects, and can create executables, linker objects or binary blobs and has support for all the 680x0 series CPUs (including coldfire),

But don't we already have assemblers for the atari? Why another one?

The impetus for this came from my many years writing assembly code across a range of different assemblers.

I started writing assembly "back in the day"(tm), and travelled the world of atari assemblers, bringing my code with me. From days in the dessert of the original devpac, through the blockbuster sequel (devpac 2) and the much maligned godfather 3-esque finale, through flirtations with turbo assembler, to bridging the boundary to C with assemblers for lattice c, pure assembler and vasm. With each migration, code had to be changed and modified, new bugs emerged. And once you migrated, you had to do the same process if you wanted to go back.

Wouldn't it be nice if there was an assembler that worked with all these dialects? No more migrations?

I have also recently been trawling through ggn's excellent corpus of atari code at https://github.com/ggnkua/Atari_ST_Sources, an important historical artifact for the scene.

But although the code is there, actually getting this to compile requires a plethora of different tools, or modifications to this historical set of code.

Wouldn't it be nice if there was a single assembler that could assemble all this historical code?

There was also a set of conversations I had with Havok and others at the recent Sommarhack demo party about the cultural importance of the demoscene. Some European countries are not starting to recognise the demoscene as a being a part of their national cultural heritage. We have an unique artform that is worth preserving, and many of the original pieces of code that make the games and demos we love is no longer useable.

In the games industry, when we complete a game, we make a "closing kit" where we bundle the code up with all the tools needed to build it, so if some in the future needs to port the title to a new platform or remaster it, they have a fully working starting point. We don't have that for atari productions.

So here we are, in the 2k26, with a new assembler for atari code; a universal assembler, that assembles all your (and other folks) old code, no matter what hairy old assembler it was originally written in.

And of course, being an rg prod, it has a whole range of features, including a bunch of metrics that show you cycle costs (either per line or per function), and is aware of instruction pairing.

It can even optimise and re-order your code for you in pairing efficient ways!

This is the first in a new series of productivity tools for atari development from rg. Keep your eyes on those blue, blue skies.


Contents

Part 1 — Command-line tool interface Example usage · Output files · Options at a glance · Options in detail · Size optimisations · Instruction reordering · Dialects · Errors & exit status

Part 2 — Assembler syntax Comments · Labels · Numbers & expressions · Sections · Data & storage · Alignment · Symbols & equates · org · Macros · Conditionals · Repeats · Includes · Linkage · Directive reference

Part 3 — JSON schema Global --json mode · Envelope · The schema

Also: Greetings · Final notes · License

Documentation


Part 1 — Command-line tool interface

Example usage

rg-asm [OPTIONS] <inputs>...

<inputs> are .S / .ASM / .I source files and/or .o object files, in any combination. By default, rg-asm assembles and links them into a GEMDOS executable; any .o input is only meaningful to the linker, so its presence implies an executable.

# Assemble and link into a GEMDOS executable — the default. -> MAIN.PRG
rg-asm MAIN.S UTIL.S

# Objects instead, to link with C later — one .o per source
rg-asm --obj MAIN.S UTIL.S

# A raw flat image: no header, no symbols, no relocations (boot sectors, blobs)
rg-asm --raw BOOT.S                       # -> BOOT.BIN

# Link pre-built objects into a named executable
rg-asm MAIN.o UTIL.o -o GAME.TOS

# Assemble and link in one step, choosing the output name
rg-asm MAIN.S UTIL.o -o GAME.TOS

# Realistic build: CPU, defines, include path, external symbol sidecar
rg-asm -m 68000 -D DEBUG=1 -I GODLIB CODE/MAIN.S CODE/GFX.S \
  -o BIN/DEBUG/GAME.TOS --sym=extern

# 68030 Falcon build
rg-asm -m 68030 -I GODLIB CODE/MAIN.S -o GAME.TOS

# ColdFire Firebee build
rg-asm --cf-firebee game.s -o GAME.TOS

# Strict assembly of new code, one warning category promoted to an error
rg-asm new.s --warn-level=3 -Werror=undefined-equate -o NEW.TOS

# GenPC flat image with OVR reloc table and trailing-zero strip
rg-asm --dialect=genpc --raw --reloc --strip-bss BOOT.S

CLI syntax:


Output files

For header layouts, object-format magics, and relocation details, see the dedicated output formats reference.

ExtensionContent
.oELF32 relocatable object (assemble-only output; not executable).
.TOS / .PRGAtari GEMDOS executable: header + text + data + BSS + optional symbol table. Big-endian; GEMDOS-style (bitfield-encoded) relocations.
.BINRaw flat image (--raw): no header, symbols, or relocations.
.symPlain-text symbol sidecar, written only with --sym=extern.

Source files are read as UTF-8, falling back to ISO-8859-1 (Latin-1) for the accented characters common in Atari ST source comments.


Options at a glance

GroupOptions
Input / output<inputs>... · -o <FILE> / --output=<FILE> · -I / --include=<DIR> · -D / --define=<NAME[=VALUE]>
Output kind--prg (default) · --obj[=KIND] · --raw · --reloc · --strip-bss · --no-emit
Target-m <CPU> / --cpu=<CPU> · --fpu=<FPU> · --cf-isa=<REV> · --cf-firebee · --cf-*
Dialect--dialect=<D> · --strict-dialect · --allmp · --c-precedence
Symbols & linkage--sym=<FMT> · --strip · --sym-locals · --sym-abs=<STYLE> · --sym-order=<ORDER> · --pic · --relax · --allow-undefined · --long-addresses · --error-on-undefined · --sozobon-long-names · --rgcc-tag · --prg-flags=<FLAGS>
Vendor CLI--errors=<FILE> · --header=<FILE> · --equ-file=<FILE>
Optimisation-O/--opt-size (default — patterns) · --no-opt-size · --size-opt=TOKEN (repeatable) · --opt-reorder · --opt-forward-branches / --no-opt-forward-branches · --opt-relax-passes=N
Listing / cycles--listing / --listing=<FILE> · --listing-cpus=<LIST> · --cycles / --cycles=<FILE> · --functions · --json · -q/--quiet
Diagnostics--warn-level=<0..3> · -W<spec> / --warn=<spec> · -w / --no-warnings · --list-warnings
Presentation--color=<WHEN> · --ascii · --banner=<STYLE>
Info-h/--help · -V/--version · --dump-tokens

Options in detail

Input / output

OptionMeaning
<inputs>...Source (.S/.ASM/.I) and/or object (.o) files, any mix. Assembled in order, then linked.
-o <FILE> / --output=<FILE>Output path, honoured verbatim. Defaults derive from the input: <input>.PRG / .o / .BIN per output kind. With --obj/--raw and multiple inputs, -o is rejected — omit it for per-file names.
-I <DIR>Add an include search path (repeatable). Applies to INCLUDE / INCBIN.
-D <NAME[=VALUE]>Define an equate on the command line (repeatable). -DDEBUG sets DEBUG=1; VALUE must be an integer. Visible to IFD/IFND and as a symbol everywhere.

Output kind

This determines what is emitted from the assembler:

FlagOutputDefault nameAddresses resolved by
--prg (default)GEMDOS executable<input>.PRGGEMDOS PEXEC (relocation table)
--obj[=KIND]linkable object, one per source<input>.oThe linker
--rawraw flat image — no header, symbols or relocations<input>.BINUnresolved (should be PC relative or ORG absolute)

| --reloc | With --raw, append a GenPC/OVR flat relocation table after the image (as68 -r) | (same as --raw) | sc68 loader / host tools | | --strip-bss | With --raw, strip trailing zero bytes from the flat image (sc68 -t / --strip-bss) | (same as --raw) | Smaller boot blobs |

The --obj=KIND object format selects which linker the object targets:

KINDObject formatConsumed by
elf (default)ELF32 big-endian m68kvlink / vbcc / gcc-binutils
gstGST linkable object (classic Devpac/HiSoft .O)Devpac / HiSoft linkers
purecPure C object (Atari group 1)Pure C / Lattice C / AHCC linkers (pc alias)
aoutBSD/MiNT a.out relocatableMiNT / a.out toolchains
driDRI / Alcyon object ($601A + word-parallel relocs)Alcyon, RMAC family, GenST
lout (mwc)Mark Williams C l.outMWC as
sekaAndelos / K-Seka linkableK-Seka
hunk (amiga)Lattice Amiga hunk objectLattice Amiga tools

As raw has no relocation table, you should either make it PC-relative or use org.
org sets an absolute origin for --raw and is ignored by other output types.

.PRG/.TOS/.APP/.GTP/.ACC are ultimately the same — the extension only tells the GEM desktop how to launch it. The default extension for executables is .PRG

Target CPU / FPU

-m <CPU> / --cpu=<CPU> selects the 680x0 target instruction set (default 68000). Mutually exclusive with the ColdFire selectors (--cf-isa / --cf-firebee).

<CPU>Adds over the previous model
68000Baseline instruction set (the plain ST).
68010VBR, loop mode, MOVEC/MOVES, MOVE from CCR.
68020Full 32-bit: bit-field ops, MULx.L/DIVx.L, CAS/CAS2, scaled-index and memory-indirect addressing.
68030On-chip MMU (PMOVE/PTEST); the integer set matches the 68020.
68040Integrated FPU and MMU; MOVE16 burst copy.
68060Superscalar 68040-class core; same user integer/FPU set (some rare ops trap to emulation).

--fpu=<FPU> attaches an external FPU coprocessor to a CPU without an on-chip one (68000–68030). The 68040/68060 have an integrated FPU selected automatically from --cpu, so --fpu is ignored with a 040/060 target.

<FPU>Meaning
none (default)No FPU; floating-point instructions are rejected.
68881Original 68881 coprocessor — the full 6888x floating-point instruction set.
68882Pin-compatible faster part; identical instruction set to the 68881.

ColdFire

ColdFire has two configuration axes; the ISA and feature modules.
Selecting any --cf-isa (or --cf-firebee) makes the build a ColdFire target — it replaces --cpu.

Selecting the target — either of these makes the build a ColdFire target:

OptionMeaning
--cf-isa=<a|aplus|b|c>Select a ColdFire target at this integer ISA revision (see the sub-table). Its presence is the ColdFire selector.
--cf-firebee"Firebee" V4e preset (MCF547x/MCF548x): --cf-isa=b + divide + EMAC + USP + FPU. --cf-isa/--cf-* layer on top.

The ISA revision is a cumulative ladder — each level is a superset of the one above it:

<REV>CoreHardware divideAdds over the previous revision
aISA_AoffBaseline: a subset of the 68000 plus selected 68020 enhancements.
aplusISA_A+onA handful of instructions, including TAS.B.
bISA_BonRe-introduces CMP.B/.W, CMPA.W and the 32-bit Bcc.L; adds MOV3Q/BYTEREV/MVS/MVZ/FF1/SATS.
cISA_ConThe full integer superset (V1 core): BITREV/INTOUCH/STLDSR.

Hardware divide defaults on for aplus/b/c and off for a. MOVE to/from USP is gated by the separate USP module (below), not by the ISA revision.

Feature modules — each has an enable and a disable, so a preset can be trimmed (e.g. --cf-firebee --cf-no-fpu is the Firebee bundle without the FPU). If both are given, off wins.

OptionModule
--cf-div / --cf-no-divHardware-divide unit (DIVU/DIVS/REMU/REMS).
--cf-mac / --cf-no-macMAC unit (single accumulator).
--cf-emac / --cf-no-emacEMAC unit (four accumulators).
--cf-usp / --cf-no-uspSeparate user stack pointer (MOVE to/from USP).
--cf-fpu / --cf-no-fpuOn-chip FPU.

Dialect

--dialect=<D> selects the source syntax variant. It has its own section: see Dialects.

When you pass an explicit --dialect=<name> (not the implicit default vasm), rg-asm merges encode/link preset knobs for that dialect (Alcyon long addresses, Devpac1 strip, RMAC sym discovery, …). Output kind stays --prg unless you also pass --obj, --raw, or --no-emit — dialect alone does not silently retarget to objects or flat binaries. Regression/oracle harnesses pass the full vendor argv (--raw, --obj=dri, …) explicitly; see CLI presets.

OptionMeaning
--strict-dialectReject dialect-specific superset features (TurboAss macros, …). With --dialect=gas or mri, also promote unknown-mnemonic, directive-dropped, and parser-heuristic to hard errors (LLVM/rustc ingest safety).
--allmpMot -allmp: enable \a..\z as macro arguments 10..35 (Devpac already on).
--c-precedenceWith --dialect=rmac, use C operator precedence (-4 / --c-precedence).

Symbols & linkage

OptionMeaning
--sym=<dri|gst|extern>Symbol-table format for the .TOS (see the sub-table). Default gst.
--stripStrip the symbol table from the output .TOS.
--sym-localsInclude local (non-exported) labels and equates in the symbol table — helps recover local names when disassembling. Off by default. Under --dialect=rmac/madmac, DRI uses bare .local names, omits linker __*, types equates as 0xC000 and .abs labels as 0x8000, and emits symbols in discovery order unless overridden below.
--sym-abs=<vlink|rmac>Absolute/equate DRI type style: vlink0x8200 (DEFINED|TEXT), rmac0xC000 (DEFINED|EQUATED). Default follows --dialect (rmac/madmacrmac).
--sym-order=<alpha|discovery>Symbol-table entry order. Default follows --dialect (rmac/madmacdiscovery).
--picEnforce PC-relative mode; error on any absolute relocation.
--relaxGAS -mrelax: emit relocations for same-section PC-relative branches instead of resolving them at assembly time, so the linker can fix them during size-changing relaxation. Only meaningful for relocatable object output.
--allow-undefinedDo not error on undefined symbols at link time.
--long-addressesAS68 -l: emit absolute address constants as longwords (disables abs.l→abs.w size opts).
--error-on-undefinedvasm -x: treat undefined symbol references as hard errors at assembly time.
--sozobon-long-namesSozobon jas -8: do not truncate DRI symbol names to 8 characters.
--rgcc-tagStamp the RGCC toolchain-provenance trampoline at the start of TEXT (off by default).
--prg-flags=<FLAGS>GEMDOS header flags (comma-separated): fastload, fastram, fastalloc, private, global, super, readable.
<FMT>Symbol tableNotes
driDRI/TOS format embedded in the executableUnderstood by most Atari debuggers; symbol names are truncated to 8 characters.
gst (default)GST extended format embedded in the executableLonger names; required by some profilers.
externNone embedded — a plain-text .sym sidecar is writtennm-style listing next to the executable; keeps the .TOS itself table-free.

Vendor CLI passthrough

OptionMeaning
--errors=<FILE>Write diagnostics to FILE (rmac -e, Josy -E).
--header=<FILE>Metacomco HDR=: prepend this file to each assembly source before parse.
--equ-file=<FILE>Metacomco EQU=: load equates from FILE (name=value or name equ value lines).

Optimisation

There is one size master switch, one speed knob, and two branch-sizing knobs:

OptionDefaultMeaning
-O, --opt-sizeonEnable the dialect's size-opt preset — see size optimisations.
--no-opt-sizeDisable all size transforms (instruction selection, peeps, span sizing) for layout-stable output.
--opt-reorderoffReorder instructions for speed, not size — see reordering.
--opt-forward-branchesdialectAllow auto-shortening of forward unsized branches (vasm-class default on; RMAC/MADMAC default off).
--no-opt-forward-branchesOnly auto-shorten backward unsized branches (host rmac +O2 behaviour).
--opt-relax-passes=N50Cap size-relaxation re-passes after the first pass-1 layout.
--size-opt=TOKENRMAC-style per-bit toggles applied after the master switch (--size-opt=+O4, --size-opt=~Oall, …). Repeatable / comma-separated. Unknown tokens are a hard error — see size optimisations.

Given --opt-size/--no-opt-size both on one command line, the later wins. Same for --opt-forward-branches / --no-opt-forward-branches.

In source, Devpac OPT O+ / O- and RMAC .opt "+Oall" / "~Oall" flip the whole preset; RMAC .opt "+On" / "~On" (n = 0…9) toggles one transform. A line-leading ! (RMAC/MADMAC) suppresses every size opt for that instruction only.

On the command line, --size-opt=TOKEN applies the same RMAC-style toggles after the master switch (-O / --no-opt-size). Valid tokens (case-insensitive): O+, O-, +Oall, ~Oall, +O0+O9, ~O0~O9. Numbers outside 0…9 that RMAC ignores on 56001 / PC-relative opts (+O10, +O11, +O30, …) are accepted as no-ops. Any other string is rejected with a non-zero exit (fail closed).

Size optimisations

Your source stays as written; only the emitted bytes change. The default (vasm) preset only enables transforms that are faster or neutral on every 680x0, including JMP/JSRBRA/BSR (vasm -phxass). RMAC/MADMAC enable LEA d(An),An → ADDQ (RMAC +O4, slower on 68030+) but keep JSR/JMP as written — matching host rmac, where +O2 only shortens already-written branches. Explicit size suffixes (.s, .w, .l) always win over the flag.

Cycle counts match the --cpu target (default 68000), as reported by --listing.

Shorter encodings — a smaller form of the same instruction (gated by the master switch / per-bit flags; --no-opt-size keeps the long form):

Original Optimised Cycles Bytes
before after before after
move.l #42,d0MOVEQ #42,D012462
add.l #4,d1ADDQ.L #4,D114862
sub.w #3,d2SUBQ.W #3,D28442
clr.l d2MOVEQ #0,D26422
adda.l #200,a0LEA (200,A0),A014864

MOVEQ applies for N ∈ −128..=127 and .L only; ADDQ/SUBQ for N ∈ 1..=8; the LEA form when n fits signed 16-bit. Note clr.l d2 is the one row that saves no bytes — it is a pure cycle win.

Instruction rewrites — a different, shorter instruction with identical effect and identical condition codes. Applied under -O:

Original Optimised Cycles Bytes
before after before after
cmp.w #0,d0TST.W D08442
cmpi.l #0,d1TST.L D114462
or.l #0,d2TST.L D214462
move.l #0,a0SUBA.L A0,A012862

EOR/EORI #0,Dn folds to TST as well, on the same terms. The rewrites hold because each original computes its destination unchanged and sets N/Z from it with V=C=0 — exactly TST's effect — while MOVE #0,An and SUBA.L An,An both leave An = 0 without touching the CCR. Only literal #0 immediates fire, so a symbol that happens to evaluate to zero is left alone.

Span-dependent sizing — the forms whose smallest encoding depends on how far the target is. The assembler picks the shortest form that still reaches, growing across passes until the section stabilises (--opt-relax-passes=N caps the loop). With --opt-forward-branches (vasm default) unsized forward branches may shrink; with --no-opt-forward-branches (RMAC/MADMAC default) only backward unsized branches auto-shorten. --no-opt-size forces the largest encoding of each:

Original Optimised Cycles Bytes
before after before after
bra done (near)BRA.S101042
jmp handler (near)BRA.S121062
move.w $ffff8800,d0(xxx).W161264

The unoptimised forms here are BRA.W, JMP (xxx).L (which also carries a relocation) and (xxx).L. The jmp/jsr row is the separate JmpToBranch transform (on in the vasm preset, off under --dialect=rmac); RMAC +O2 only covers the bra/bsr/bcc row.

Cross-section, external, out-of-range, register-indirect, and forced-long operands stay at their largest encoding. Symbol references never take the abs.w shortcut.

Instruction reordering

On the 68000/010, some adjacent instruction pairs execute in fewer cycles than the sum of their parts (instruction pairing — the second instruction's fetch overlaps the first's execution). With --opt-reorder, rg-asm permutes a straight-line run of independent instructions so that pairable ones land next to each other — the same instructions in a faster order, a free speed-up. It targets the 68000/010 pairing model. The pass is opt-in and off by default because reordering changes instruction order and therefore the emitted bytes.

It only ever reorders when doing so is provably safe and strictly faster:

Scope and eligibility. Scheduling is confined to a basic block — any label or control-flow instruction (BRA/Bcc/BSR/JMP/JSR/RTS/DBcc/…) ends the run. Within a run, only instructions with a reorderable shape participate: register, immediate, absolute, and register-indirect operands. Excluded (they end or sit out the run): PC-relative operands, MOVEM register lists, control registers (SR/CCR), FPU registers, and 68020 memory-indirect modes. To keep a permutation layout-neutral, a run holds only same-size instructions, and any instruction that emits a relocation (a symbol reference like #label or (label).l) stays put and ends the run.

Worked example. A shift pairs with a following MOVE/ADD/SUB/logic when both instructions cost 4n+2 cycles — here LSL.L #1,Dn (10) and ADD.L (An),Dn (14). As written, the two shifts sit together, so only one pair forms (-4 = 4 cycles saved off the base cost):

                       ; cyc  pair
    lsl.l   #1,d0      ;  10   —
    lsl.l   #1,d1      ;  10   —
    add.l   (a0),d2    ;  14  -4   ← lsl→add pairs
    add.l   (a1),d3    ;  14   —
    rts                ;  16   —      total: 60

The four are independent (distinct registers, distinct memory), so --opt-reorder interleaves them — each shift now precedes an add, forming two pairs (8 cycles saved):

                       ; cyc  pair
    lsl.l   #1,d0      ;  10   —
    add.l   (a0),d2    ;  14  -4   ← lsl→add pairs
    lsl.l   #1,d1      ;  10   —
    add.l   (a1),d3    ;  14  -4   ← lsl→add pairs
    rts                ;  16   —      total: 56

Same five instructions, same result in every register — 60 → 56 cycles purely from order. (Cycle counts are the 68000 figures from --listing; cyc is the un-paired base cost and the pair column shows the reduction each pairing applies, so a paired instruction's effective cost is cyc + pair — e.g. 14 -4 = 10.)

--opt-reorder is ignored under --pic (a same-section reference can resolve to a relocation-free PC-relative displacement that the run's safety check can't detect as position-dependent) and when a cycle listing is requested with --listing (listing rows are captured at emit time, before reordering).

Listing & cycle analysis

OptionMeaning
--listing / --listing=<FILE>Write a cycle-annotated listing. --listing → stdout; --listing=<FILE> → file. On an interactive terminal --listing renders as a boxed, coloured, per-function table (see below); a file, a pipe, --json, -q, or --color=never write the plain text form — columns address · cyc · pair · opcodes · source, where cyc is the primary CPU's un-paired base cost and pair the pairing reduction (-4/). Without --functions, the plain form ends with a per-function cycle summary; with --functions, that footer is omitted.
--no-emitAssemble (and link, for --prg) without writing a .o / .PRG / .BIN. Listings, --cycles, --functions, and the summary still run. Conflicts with -o / --output.
--listing-cpus=<LIST>Comma-separated CPUs to cost against, e.g. 68000,68030. The first is the primary (drives the per-line columns); all appear in the summary. Defaults to the --cpu target.
--cycles / --cycles=<FILE>Write a deterministic function → cycles per CPU snapshot (sorted, tab-separated). --cycles → stdout; --cycles=<FILE> → file. Commit as a CI cycle-regression baseline. Usable without --listing.
--functionsAdd a per-function bytes/cycles table to the post-assembly summary (interactive stderr only; for --prg the functions are folded from every source). With --listing, suppresses the listing's per-function cycle footer to avoid duplication.
--jsonEmit one pretty-printed JSON document on stdout for every run (success or failure). See Global --json mode.
-q, --quietSuppress the default summary (terminal auto-detect). Also suppresses the startup masthead unless --banner overrides.

Presentation

OptionMeaning
--color=<auto|always|never>When to colourise interactive stderr output (honours NO_COLOR; default auto).
--asciiDraw the summary table with ASCII characters instead of Unicode box-drawing glyphs.
--banner=<none|line|logo>Startup masthead. Omitted → auto: logo on a colour terminal, one-line banner otherwise. Suppressed by -q / --json.

Cycles come from the built-in 680x0 timing tables; instructions produced by macro expansion are shown as the macro-call line and are not individually cycle-costed (their bytes still count toward sizes).

rg-asm --obj -o game.o --listing=game.lst --listing-cpus=68000,68030 game.s
rg-asm --obj -o game.o --listing --listing-cpus=68000,68030 game.s   # listing on stdout
rg-asm --json --obj -o game.o --listing-cpus=68000,68030 game.s | jq '.summaries[0].total_cycles'

On an interactive terminal, --listing on stdout renders as a boxed table: one cycle column per CPU, a spanning per-function band with size and a static cycle sum, and the source line normalized (tabs collapsed). Expensive instructions are tinted (yellow at ≥40 cycles, red at ≥100) so the cost sinks stand out. The pair column reports 68000 bus-pairing — the prefetch/execution overlap, an analysis unique to rg-asm — as a green ↓N on every line where a saving fires:

┌──────────┬───────┬───────┬──────┬─────────────┐
│ addr     │ bytes │ 68000 │ pair │ asm         │
├──────────┴───────┴───────┴──────┴─────────────┤
│ Loop                             6 B · 28 cyc │
├──────────┬───────┬───────┬──────┬─────────────┤
│ 00000000 │ B2 80 │     6 │    · │ cmp.l d0,d1 │
│ 00000002 │ 64 FC │     6 │   ↓4 │ bcc.s Loop  │
│ 00000004 │ 4E 75 │    16 │    · │ rts         │
└──────────┴───────┴───────┴──────┴─────────────┘

The per-function cycle figure is a static sum of the listed instructions; it does not count loop iterations or branch frequency, so it is not a per-frame runtime cost.

Every global label — code or data — becomes a band (data bands show size only, since cycles are meaningless for data). A switch into a data or bss section draws a full-width ── DATA ── banner; the first text section, sitting directly under the header, gets none.

The plain form (file, pipe, --json, -q, --color=never) is byte-stable and carries no escape codes or box glyphs. --json --listing adds cpus and per-line cycles_per_cpu fields.

Diagnostics & warnings

rg-asm tags every compatibility leniency with a warning category whose policy (ignore / warn / error) is configurable GCC-style. Warnings appear on success as well as failure. Flags apply in order: --warn-level-w → each -W left-to-right.

CLI flags

OptionMeaning
--warn-level=<0..3>Base verbosity before any -W flags (default 1). See warning levels.
-wSuppress all warnings (a later -W re-enables categories one at a time).
--list-warningsPrint every category name and exit.

Warning levels

symbol-redef, syntax-error, operand-error, expr-error, immediate-range, pic-reloc, file-io, macro-error, and layout-error are always hard errors at every level — they are not affected by --warn-level. Everything else is levelled by the table below.

The four levels form a monotonic ladder — each step raises the severity of exactly one tier, so no two settings behave alike:

LevelNamePolicyTypical use
0silentignore allQuiet batch runs
1regularwarn on the wrong-output tierDefault for hand assembly
2verbosewarn on all categoriesToolchain / build logs
3stricterror on the wrong-output tier; still warn on compatibilityCI correctness gate

Level 3 is a correctness gate, not a zero-tolerance one: anything that means the emitted bytes are wrong is fatal, while dialect-compatibility quirks (whose object is still what the author intended) stay warnings. To make everything fatal — the nuclear option — add -Werror, which promotes every visible warning (so --warn-level=2 -Werror and --warn-level=3 -Werror both error on all categories).

Category tiers

Per-category policy at each level (before any -W overrides). Rows are grouped by tier; see warning levels for the level summary.

The dividing line is whether the emitted object differs from what the source asked for:

CategoryL0L1L2L3L3 -Werror
undefined-equate, unknown-mnemonic, ea-mode, cpu-availabilitywarnwarnerrorerror
expr-fallback, equr-unresolved, directive-droppedwarnwarnerrorerror
parser-heuristic, directive-ignored, nonstandard-size, implicit-externwarnwarnerror
symbol-redeferrorerrorerrorerrorerror
syntax-error, operand-error, expr-error, immediate-range, pic-reloc, file-io, macro-error, layout-errorerrorerrorerrorerrorerror

= ignored (no diagnostic). Internal encoder faults (branch relaxation, synthetic encode, diagnostic-cap suppression) use a non-listable internal category and are always errors regardless of warn-level.

-W specs

Repeatable; each spec adjusts one category (or all of them). <cat> is any name from categories or rg-asm --list-warnings.

SpecEffectExample
-W<cat>Emit category <cat> as a warning-Wimplicit-extern
-Wno-<cat>Ignore category <cat>-Wno-cpu-availability
-WerrorPromote every visible warning to an error (ignored categories stay ignored)-Werror
-Werror=<cat>Promote one category to an error-Werror=symbol-redef

Example: -w -Wcpu-availability -Werror=symbol-redef silences everything, turns CPU availability back on as a warning, and makes redefinition a hard error.

Warning categories

Category (-W…)Meaning
cpu-availabilityInstruction or addressing mode requires a higher CPU
directive-droppedDirective dropped with layout/data effect (ignored ORG, INCBIN in BSS, failed .assert, …)
directive-ignoredDialect/compatibility directive note (GenST era gate, FAIL warn)
ea-modeEffective-address mode not allowed for this operand
equr-unresolvedOperand fell back because an EQUR-aliased register was unresolved
expr-errorHard expression-evaluation failure (hard error at every warn-level)
expr-fallbackSub-expression could not be evaluated and fell back silently
file-ioInclude or incbin file I/O failure (hard error at every warn-level)
immediate-rangeImmediate operand out of range (hard error at every warn-level)
layout-errorSection layout, ORG, or reservation fault (hard error at every warn-level)
implicit-externSymbol not declared in this file, taken as external (a typo looks like this)
macro-errorMacro / REPT / IRP expansion fault (hard error at every warn-level)
nonstandard-sizeNon-standard or missing operation size (hard error when emitted as error_kind)
operand-errorInstruction arity or operand shape fault (hard error at every warn-level)
parser-heuristicParser compatibility heuristic (col-1 label, ::, trailing-comment recovery, …)
pic-relocPIC-mode relocation not allowed (hard error at every warn-level)
symbol-redefSymbol or equate redefined (hard error at every warn-level)
syntax-errorLexer or parser syntax fault (hard error at every warn-level)
undefined-equateEquate could not be resolved (often a missing include)
unknown-mnemonicUnknown mnemonic, macro, or directive

Link-time undefined symbols are separate — see --allow-undefined; they are not controlled by -W or --warn-level.

Information

OptionMeaning
-h, --helpFull help (-h short summary, --help long).
-V, --versionPrint the version and exit. With --json, emit the schema-1 envelope ("version" is the package version) instead of the plain name ver line.
--dump-tokensDump the token stream to stderr per input (debugging).

Dialects

--dialect=<D> selects the source syntax variant, applied per file — default vasm; auto heuristically detects each file (see Auto-detection). Twenty-three assembly dialects ship as stable --dialect=<name> flags today, plus auto. Avena is the remaining public-tour assembler without a CLI flag yet; the as68 card on dialects.html uses the same genpc preset as Ziggy/OVR GenPC. The AST and encoder are shared across them. Exclusions: Falcon DSP56001, 6502, Apollo 68080.

Chronological tour (same order and dates as the public page): dialects.html.

--dialectAssemblerAuthor / companyEraStatusDistinguishing syntax
alcyonDR AS68 / AlcyonDigital Research1985availableno macros; --obj=dri for DRI objects
sekaK-Seka / A-SEKAKuma Computers Ltd.1986availablecolon-required labels
gstasmGST-ASMGST Holdings Ltd.1986availabledistinct from --obj=gst; BSS era warning
metacomcoMetacomco Macro AssemblerMetacomco Ltd.1986availableST SDK Motorola syntax
madmacAtari MADMACLandon Dyer; Atari Corp.~1986available?N macros, ^^defined, .iif, name:: exports; .align n = n bytes
devpac1HiSoft GenST v1 / Devpac ST v1HiSoft Systems1986availableC precedence; warns on GenST2+ directives
assemproAssemProData Becker / Abacus; Peter Schultz1986available\LOCAL backslash locals between globals
mwcMark Williams C asMark Williams Company1986available/ comments; .prvi sections
aztecAztec C assemblerManx Software Systems1987–88availablecolumn-1 CSEG/DSEG sections
devpac2HiSoft GenST2 / Devpac ST v2HiSoft Systems1988availableC precedence; warns on GenST3-only directives
turboasmTurboAss / Omikron.AssemblerMarkus Fritze & Sören Hellwig©1989 / ~1990available@@ locals, named macro params, BLKB/BLKW/BLKL
gfaasmGFA AssemblerGFA Systemtechnik (+ Ostrowski)1988availablepath/include; colon labels
sozobonSozobon jasSozobon, Limited (Johann Ruegg et al.)~1989available@ octal radix in dc; Alcyon-compatible backend
latticeLattice C ASM (HiSoft LC5)HiSoft Systems~1990availableCSECT name,type; no implicit insn align after byte data; @func entry symbols
purecPure C PASMApplication Systems Heidelberg; AHCC (Henk Robbers)~1989–91 →availablecolon-required labels, .GLOBL/.EXTERN; fastcall registers
genpcGenPC / as68Overlanders (Ziggy Stardust; Ben)1991 / 1999availablebrace {/} macros; Devpac-style text/end; --raw + optional --reloc/--strip-bss
devpac3HiSoft GenST3 / Devpac 3HiSoft Systems1992availableC precedence; full GenST3 surface
devpacalias → devpac3HiSoft Systems1992availablesame as devpac3
brainstormBrainstorm AssembleBrainstorm (FR)1993–95availablePARGS Pascal-order stack params (not CARGS)
josyJosyVolker Hemsen1995availableSozobon jas fork; Line-A needs # immediate; preset --cpu=68020 --fpu=68881
Avena AssemblerAvena (Jet / Fried)1996soonPART/ENDPART folding markup (Falcon editor); no --dialect flag yet
as68 (Overlanders PC cross)Overlanders (Ben / Ziggy)1999tour card only — use --dialect=genpc (same syntax family)
gasGNU as (m68k)FSF / GNU~1998+available| comments, 0x/0b literals, %d0 register prefix, .byte/.globl/.macro
mriGAS MRI / Microtec modeMicrotec Research; FSF/GNU~1980s → GAS --mriavailable* comments, Motorola ops, C precedence
vasmvasm Motorola syntaxFrank Wille; Volker Barthelmann~2002–availabledefault$/% literals, */; comments, .local labels, \1/\@ macros
rmacrmac (modern MadMac)Landon Dyer (MADMAC); SubQMod; Shamus Young + GGN2008 / 2011–available.68000 CPU dirs, LTR precedence, RMAC .opt strings; .phrase/.dphrase/.qphrase; .long = align-4 (use dc.l for data)
autoavailableheuristic per-file detection (best-effort)

Era matches dialects.html card dates (~ = approximate). Status available = stable --dialect=<name>; soon = public tour only (Avena). Tour-only rows without a flag are marked .

See Dialect preset merge for how explicit --dialect interacts with --prg / --obj / --raw.

Key cross-dialect differences

Abbreviations: dp1/dp2/dp3 = devpac1/devpac2/devpac3. ✓ = native or enabled for that dialect; — = not native; · = partial / alias spellings.

This matrix covers the original sixteen-dialect tour subset (vasm through mri); newer dialects (assempro, mwc, gfaasm, sozobon, brainstorm, josy, genpc) are listed in the table above — see each dialect's oracle doc under docs/TECH/ for feature notes.

Featurevasmdp1dp2dp3latticepurecturbogasmadmacrmacalcyonmetacomcogstasmsekaaztecmri
* line comment
| line comment
$FF / %bin
0xFF / 0b
Label colon optional
.name local1:/.L
@@name local
\1 macro arg
Named macro params·
MACRO/ENDM.macro
XDEF/XREF·.globl
TEXT/DATA/BSS.text
DC.B/W/L.byte
vasm expr precedence
GenST era warnings
?N/?0 MADMAC macros
^^defined / .iif
name:: export
.68000 CPU directive
LTR expr precedence
CSECT
CSEG/DSEG
@ octal (@777)
%d0 register prefix
Implicit insn word-align

Notes: purec · = also .GLOBL/.EXTERN. turbo · = named macro params are an rg-asm ingestion superset (Fritze's Turbo-Ass had no macros). metacomco, gstasm, and mri share the Motorola baseline above; their dialect flags mainly gate diagnostics (metacomco comma+space DC trap, gstasm BSS-era warning, mri Microtec MRI compatibility). lattice skips implicit word-alignment before instructions after odd-length data.

Several features are mutually exclusive and cannot be auto-detected reliably — prefer an explicit --dialect for production builds:

Auto-detection

--dialect=auto inspects each file's content (a few fast string searches, once per file) and picks a dialect:

Signal in sourceDialect
0x/0X/0b/0B literals, %d/%a/%sp/%pc prefixes, leading /* or |, .macro/.text/.datagas
.68000/.68020/…, .68881, RMAC .opt "+O stringsrmac
^^defined, ^^referenced, .iif (no RMAC markers above)madmac
col-1 CSEG / DSEGaztec
@@ local labelsturboasm
CSECT directivelattice
none of the abovevasm

These dialects have no reliable positive marker in the auto detector — name them explicitly for production builds:

devpac1/devpac2/devpac3 (alias devpac), purec, alcyon, metacomco, gstasm, seka, mri, assempro, gfaasm, mwc, sozobon, brainstorm, josy, genpc.

Known false positives: 0x or %d inside a string/comment triggers gas; a | used as bitwise-OR (rare — Motorola code uses ! or the OR mnemonic). devpac3 cannot be distinguished from devpac2/vasm by content alone.


Errors & exit status

Diagnostics go to stderr with file, line, and column; assembly errors report the location in the original (pre-include) file, link errors the object and section:

main.s:42:8: error: undefined symbol 'gfx_init'

Exit code is 0 on success, 1 on any error (assembly/link failure, missing input, or a warning promoted via --warn-level=3 / -Werror).


Part 2 — Assembler syntax

This section documents the default vasm Motorola dialect. Other dialects differ mainly in comment/label/literal spelling (see Dialects); the directives below are shared. A line is:

[label]  [mnemonic|directive  [operands]]  [comment]

Fields are whitespace-separated; a label may begin in any column.

Comments

* Full-line comment — only when * is the first non-space character
; Comment, anywhere on the line
    move.w d0,d1        ; trailing comment

Mid-line * is multiplication or the current-PC symbol, not a comment. { and } at column 1 or inside a trailing * comment (demo-source box-drawing) are skipped to end-of-line.

Labels

GlobalLabel:            ; normal label; colon required unless followed by EQU/MACRO
GlobalLabel  EQU 4      ; colon optional before EQU / MACRO / SET / = / EQUR
.local:                 ; local label — scoped to the preceding global label
.loop\@:                ; macro-invocation-unique local (the \@ suffix)
@fastcall_label         ; @-prefixed label (VBCC fastcall ABI); no colon required

A local label (.name) is private to the most recent non-local label, so short names like .loop / .done can repeat freely between routines.

Numbers & expressions

$FF00       ; hexadecimal
%10101010   ; binary
1234        ; decimal
'A'         ; character literal — packs up to 4 bytes ('AB' -> $4142)
1.5e3       ; floating-point (DC.S / DC.D / DC.X only)
    dc.l  label+4         ; + -            addition / subtraction
    dc.l  $FF & %10       ; & | ^ ~        bitwise and / or / xor / not
    dc.l  a << 2          ; << >>          shifts
    dc.l  a * 4 / 2       ; * /            multiply / divide
    dc.l  >symbol         ; >              high word of value
    dc.l  <symbol         ; <              low byte of value
    dc.l  * + 2           ; *              current PC

Precedence, low → high: | < ^ < & < <</>> < +/- < *//.

Sections (text / data / bss)

    TEXT                        ; code section (.text)
    DATA                        ; initialised data (.data)
    BSS                         ; zero-initialised / reserved (.bss)
    SECTION name,"ax"           ; named section with flags

Three standard sections plus arbitrary named sections. Code and initialised data carry bytes into the file; BSS only reserves space (DS/DCB there emit no bytes).

Data & storage

    DC.B 1,2,3,"string"         ; define bytes (strings allowed)
    DC.W $FFFF,label            ; define words
    DC.L label,label+4          ; define longwords
    DC.S 1.5 / DC.D / DC.X      ; single / double / extended float

    DS.B 16                     ; reserve 16 bytes (zero-filled)
    DS.W 4                      ; reserve 4 words
    DS.L 0                      ; reserve nothing — used only to align (see below)
    DCB.W 8,$FFFF               ; 8 copies of the value $FFFF (count,value)
    BLKB / BLKW / BLKL 8        ; DS aliases (Turbo Asm spelling)

DC defines initialised data; DS reserves (zeroed) storage; DCB/BLK* fill a block with a repeated value.

Alignment

    EVEN                        ; align to a 2-byte boundary
    ALIGN 2                     ; align to 2^2 = 4 bytes (arg is a power of two)
    CNOP 0,4                    ; advance to (offset) past the next (align) boundary

Unaligned word/long access bus-errors on the 68000, so keep code and word/long data EVEN. CNOP offset,align aligns to align bytes then adds offset.

Symbols & equates

FOO     EQU  $8000              ; immutable constant
BAR     =    FOO+4              ; = is a synonym for EQU
COUNT   SET  0                  ; reassignable — may be redefined later
COUNT   SET  COUNT+1
scratch EQUR a3                 ; register alias — 'scratch' now means a3

; Structure / offset layout (RS counter and OFFSET share the idea)
        RSRESET                 ; reset the RS counter to 0
x_pos   RS.W 1                  ; x_pos = 0, counter += 2
y_pos   RS.W 1                  ; y_pos = 2, counter += 2
size    RS.B 0                  ; size  = 4 (current counter, reserve nothing)

        OFFSET 0                ; struct-layout mode: labels take offsets, emit nothing
fld_a   DS.L 1                  ; fld_a = 0
fld_b   DS.W 1                  ; fld_b = 4
        TEXT                    ; leave offset mode by switching section

EQU/= bind a name once; SET may be reassigned; EQUR aliases a register; RS/RSSET/RSRESET and OFFSET build struct field offsets without emitting bytes.

Note: write OFFSET 0, not a bare OFFSET — a bare directive inherits the stale position from a prior include.

org

    ORG $8000                   ; set an absolute origin

ORG is only meaningful for --raw output, where the emitted bytes are the bytes that run and no relocation exists — there it sets the absolute origin. For --prg and --obj, addresses are resolved later (by the loader or linker), so ORG is ignored. See Output kind.

Macros

memset  MACRO                   ; name before the MACRO keyword
        ; \1..\9 = positional args; \0 = all args joined by ", "; \@ = unique suffix
        move.l  \1,a0
        move.w  \2,d0
.loop\@:
        move.b  \3,(a0)+
        dbra    d0,.loop\@
        ENDM

        memset  buffer,#255,#0   ; invocation: comma-separated args

\1\9 expand to the call arguments, \0 to all of them, \@ to a per-call unique suffix (use it on local labels so repeated calls don't collide). MEXIT leaves a macro early; ENDM closes the definition.

Under --dialect=genpc, macros use brace bodies instead of ENDM:

testmac macro
{
        nop
}
        testmac

\@ still supplies a per-call unique suffix inside the brace body.

Conditional assembly

    IFD    SYM        ; assembled if SYM is defined
    IFND   SYM        ; ... if not defined
    IFEQ   expr       ; ... if expr == 0     (also IFNE, IFGT, IFGE, IFLT, IFLE)
    IFC   "a","b"     ; ... if strings equal  (also IFNC)
    ELSEIF expr
    ELSE
    ENDIF             ; ENDC is an accepted alias

Defines from -D and any EQU/SET symbol are visible to the conditionals.

Repeat blocks

    REPT 4            ; assemble the body 4 times
    nop
    ENDR

    IRP  reg,d0,d1,d2 ; once per item, with 'reg' substituted
    clr.l reg
    ENDR

    IRPC ch,ABC       ; once per character of the string
    dc.b 'ch'
    ENDR

Includes

    INCLUDE "defs.i"            ; textually include another source file
    INCBIN  "sprite.raw"        ; embed a binary file's bytes verbatim

Both search the -I paths. INCLUDE pulls in source (parsed in the current dialect); INCBIN copies raw bytes into the current section.

Linkage

    XDEF  sym [,sym…]           ; export symbol(s) to the linker  (GLOBAL/PUBLIC alias)
    XREF  sym [,sym…]           ; import external symbol(s)       (EXTERN alias)
    COMM  sym, size             ; BSS common: reserve + export    (GAS .comm)
    LCOMM sym, size             ; BSS common, keep local          (GAS .lcomm)

XDEF makes a label visible to other objects; XREF declares one defined elsewhere. GLOBAL, GLOBL, and PUBLIC are accepted spellings of XDEF; EXTERN of XREF.

COMM / LCOMM (GAS dialects only — --dialect=gas, or auto-detected) reserve size bytes in BSS for a named symbol. COMM also exports it; LCOMM keeps it local. Neither switches the current section: the assembler resumes where it was.

Directive reference

DirectivePurpose
TEXT DATA BSS SECTIONSelect the output section.
ORGAbsolute origin (raw output only).
DC.B/.W/.L/.S/.D/.XDefine initialised data.
DS.B/.W/.L, BLKB/BLKW/BLKLReserve (zeroed) storage.
DCB.B/.W/.LFill a block with a repeated value.
EVEN ALIGN CNOPAlignment.
EQU = SET EQURDefine constants / register aliases.
RS RSSET RSRESET SO FO OFFSETStruct- and frame-offset layout (emit no bytes).
MACRO ENDM MEXITMacro definition and early exit.
REPT IRP IRPC ENDRRepeat blocks.
IF* ELSE ELSEIF ENDIF/ENDCConditional assembly.
INCLUDE INCBINPull in source / binary.
XDEF XREF GLOBAL GLOBL PUBLIC EXTERN COMM LCOMMSymbol linkage.
OPT LIST NOLISTAssembler options / listing control.
FAIL PRINTForce an error / print a message during assembly.
IDNT OUTPUT ENDModule name / output name / end of source.

Part 3 — JSON schema

Global --json mode

--json emits one schema-versioned JSON document on stdout for every run — assembly success, link failure, --list-warnings, or empty inputs. No banner or user tables on stderr in this mode; diagnostics, summaries, and file metadata are fields in the envelope.

With --json, stdout carries exactly one JSON value per run (pretty-printed). Failures still emit a JSON document ("status": "error") on stdout and use exit code 1. stderr is silent for normal reporting in this mode.

File outputs (-o, --listing=<file>, --cycles=<file>, .sym sidecars) are still written to disk; paths and byte sizes appear under "outputs".

OutputUser mode--json
Post-assembly summarystderr"summaries" / "linked_summary"
Cycle listing (--listing)stdout (text)"listings" (structured lines)
Cycle snapshot (--cycles)stdout (text)"cycle_snapshots" (text)
--list-warningsstdout (table)"warnings_catalog" in envelope
Startup bannerstderrsuppressed
Success lines (wrote …)stderr"outputs"
Warnings / errorsstderr"diagnostics"
RequestUser mode--json
-V / --versionrg-asm X.Y.Z on stdoutenvelope "status": "ok" — version in top-level "version"
Listing to stdout (--listing).lst text on stdout"listings" — structured lines[] per source
Snapshot to stdout (--cycles)tab-separated text on stdout"cycle_snapshots"{ source, text }
Listing to file (--listing=game.lst)file on diskfile on disk + "outputs" entry
StreamUser mode (default)--json
stdoutclean, or listing/snapshot text from bare --listing / --cyclesexactly one JSON value
stderrbanner, summaries, warnings, status linessilent for normal reporting
rg-asm --json --obj -o game.o game.s | jq .
rg-asm --json --list-warnings | jq '.warnings_catalog[].name'
rg-asm --json --obj -o game.o game.s | jq '.summaries[0].total_cycles'
rg-asm --json --listing --obj -o game.o game.s | jq '.listings[0].lines[] | select(.offset != null)'

Envelope (schema 1)

Top-level object. Always present: schema (1), tool ("rg-asm"), version, status ("ok" | "error"), command, outputs (array), summaries (array), diagnostics (array). Optional fields are omitted when empty.

FieldWhen presentContents
error"status": "error"{ "code", "message" } — top-level failure
linked_summary--prg link succeededLinked executable summary (same shape as summaries[])
warnings_catalog--list-warnings[ { "name", "description" }, … ]
tokens--dump-tokensPer-source lexer token dumps
listings--listingPer-source structured listing (see below)
cycle_snapshots--cyclesPer-source snapshot text (tab-separated, same as file format)

Per-source summaries live in the top-level summaries array (same object shape as linked_summary).

command

Echo of the effective CLI invocation:

KeyTypeMeaning
cpustringTarget CPU, e.g. "68000"
fpustringFPU class, e.g. "none"
dialectstringe.g. "vasm"
optimizeboolSize optimisation enabled (-O)
inputsstring[]Input paths
outputstring | null-o path
output_kindstringe.g. "prg", "obj:elf", "obj:gst", "raw"
listingstring | null--listing=<FILE> path; null when --listing writes to stdout or listing not requested
cyclesstring | null--cycles path
functionsbool--functions (user per-function table only; JSON always includes functions[])

outputs[]

Each file written during the run:

{ "path": "game.PRG", "kind": "executable", "bytes": 576 }

kind is one of executable, object, listing, cycles, sym, raw, file.

diagnostics[]

{
  "severity": "warning",
  "kind": "cpu-availability",
  "file": "game.s",
  "line": 2,
  "col": 18,
  "message": "…"
}

kind, file, line, and col are omitted when not applicable.

listings[] (--listing)

Structured cycle listing — not the user .lst text. One entry per assembled source:

{
  "source": "game.s",
  "primary_cpu": "68000",
  "cpus": ["68000", "68030"],
  "lines": [
    {
      "offset": 0,
      "opcodes": "20 3C 12 34 56 78",
      "cycles": 12,
      "cycles_per_cpu": [12, 12],
      "pairing": 0,
      "line": "    move.l  #$12345678,d0"
    },
    {
      "opcodes": "",
      "line": "start:"
    }
  ]
}

Each lines[] entry:

FieldTypeMeaning
offsetnumber | omittedSection-relative address (decimal); omitted on label-only context lines
opcodesstringSpace-separated hex byte pairs (JSON has no hex number type)
cyclesnumber | omittedEffective cycle cost on primary_cpu; omitted on non-instruction rows
cycles_per_cpuarray | omittedEffective cycle cost per entry in cpus (same order); omitted on non-instruction rows and when only one CPU was requested (then cycles carries it)
pairingnumber | omittedBus-pairing saving (0 or 4) on primary_cpu; omitted on non-instruction rows
linestringOriginal source line (trimmed trailing whitespace)

The top-level cpus array names every CPU requested with --listing-cpus, in order; it is the column key for cycles_per_cpu.

--listing=<file> still writes the user .lst text to disk; bare --listing with --json populates listings.

cycle_snapshots[] (--cycles)

{ "source": "game.s", "text": "# rg-asm cycle snapshot\n…" }

Plain text in text (same format as --cycles=<file>). --cycles=<file> still writes the file.

tokens[] (--dump-tokens)

{
  "source": "game.s",
  "dialect": "Vasm",
  "entries": [ { "index": 0, "kind": "Ident", "text": "start" }, … ]
}

The schema

A JSON Schema (draft 2020-12) for the --json envelope ("schema": 1) — the machine-readable form of the Global --json mode prose above. Empty optional fields are omitted from the output.

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "rg-asm --json output (schema 1)",
  "type": "object",
  "required": ["schema", "tool", "version", "status", "command", "outputs", "summaries", "diagnostics"],
  "additionalProperties": false,
  "properties": {
    "schema": { "const": 1 },
    "tool": { "const": "rg-asm" },
    "version": { "type": "string" },
    "status": { "enum": ["ok", "error"] },
    "error": { "$ref": "#/$defs/error" },
    "command": { "$ref": "#/$defs/command" },
    "outputs": { "type": "array", "items": { "$ref": "#/$defs/output" } },
    "summaries": { "type": "array", "items": { "$ref": "#/$defs/summary" } },
    "linked_summary": { "$ref": "#/$defs/summary" },
    "diagnostics": { "type": "array", "items": { "$ref": "#/$defs/diagnostic" } },
    "warnings_catalog": { "type": "array", "items": { "$ref": "#/$defs/warning" } },
    "tokens": { "type": "array", "items": { "$ref": "#/$defs/sourceTokens" } },
    "listings": { "type": "array", "items": { "$ref": "#/$defs/sourceListing" } },
    "cycle_snapshots": { "type": "array", "items": { "$ref": "#/$defs/sourceText" } }
  },
  "$defs": {
    "error": {
      "type": "object",
      "required": ["code", "message"],
      "additionalProperties": false,
      "properties": {
        "code": { "type": "string" },
        "message": { "type": "string" }
      }
    },
    "command": {
      "type": "object",
      "required": ["cpu", "fpu", "dialect", "optimize", "inputs", "output", "output_kind", "listing", "cycles", "functions"],
      "additionalProperties": false,
      "properties": {
        "cpu": { "type": "string" },
        "fpu": { "type": "string" },
        "dialect": { "type": "string" },
        "optimize": { "type": "boolean" },
        "inputs": { "type": "array", "items": { "type": "string" } },
        "output": { "type": ["string", "null"] },
        "output_kind": { "type": "string" },
        "listing": { "type": ["string", "null"] },
        "cycles": { "type": ["string", "null"] },
        "functions": { "type": "boolean" }
      }
    },
    "output": {
      "type": "object",
      "required": ["path", "kind", "bytes"],
      "additionalProperties": false,
      "properties": {
        "path": { "type": "string" },
        "kind": { "enum": ["executable", "object", "listing", "cycles", "sym", "raw", "file"] },
        "bytes": { "type": "integer", "minimum": 0 }
      }
    },
    "summary": {
      "type": "object",
      "required": ["source", "sizes", "cpus", "total_cycles", "functions"],
      "additionalProperties": false,
      "properties": {
        "source": { "type": "string" },
        "sizes": {
          "type": "object",
          "required": ["text", "data", "bss", "total"],
          "additionalProperties": false,
          "properties": {
            "text": { "type": "integer", "minimum": 0 },
            "data": { "type": "integer", "minimum": 0 },
            "bss": { "type": "integer", "minimum": 0 },
            "total": { "type": "integer", "minimum": 0 },
            "sym": { "type": "integer", "minimum": 0 },
            "reloc": { "type": "integer", "minimum": 0 },
            "binary": { "type": "integer", "minimum": 0 }
          }
        },
        "cpus": { "type": "array", "items": { "type": "string" } },
        "total_cycles": { "type": "array", "items": { "type": "integer", "minimum": 0 } },
        "functions": { "type": "array", "items": { "$ref": "#/$defs/function" } }
      }
    },
    "function": {
      "type": "object",
      "required": ["name", "bytes", "cycles"],
      "additionalProperties": false,
      "properties": {
        "name": { "type": "string" },
        "bytes": { "type": "integer", "minimum": 0 },
        "cycles": { "type": "array", "items": { "type": "integer", "minimum": 0 } }
      }
    },
    "diagnostic": {
      "type": "object",
      "required": ["severity", "message"],
      "additionalProperties": false,
      "properties": {
        "severity": { "type": "string" },
        "kind": { "type": "string" },
        "file": { "type": "string" },
        "line": { "type": "integer", "minimum": 0 },
        "col": { "type": "integer", "minimum": 0 },
        "message": { "type": "string" }
      }
    },
    "warning": {
      "type": "object",
      "required": ["name", "description"],
      "additionalProperties": false,
      "properties": {
        "name": { "type": "string" },
        "description": { "type": "string" }
      }
    },
    "sourceText": {
      "type": "object",
      "required": ["source", "text"],
      "additionalProperties": false,
      "properties": {
        "source": { "type": "string" },
        "text": { "type": "string" }
      }
    },
    "sourceListing": {
      "type": "object",
      "required": ["source", "primary_cpu", "lines"],
      "additionalProperties": false,
      "properties": {
        "source": { "type": "string" },
        "primary_cpu": { "type": "string" },
        "lines": { "type": "array", "items": { "$ref": "#/$defs/listingLine" } }
      }
    },
    "listingLine": {
      "type": "object",
      "required": ["opcodes", "line"],
      "additionalProperties": false,
      "properties": {
        "offset": { "type": "integer", "minimum": 0 },
        "opcodes": { "type": "string" },
        "cycles": { "type": "integer", "minimum": 0 },
        "pairing": { "type": "integer", "minimum": 0 },
        "line": { "type": "string" }
      }
    },
    "sourceTokens": {
      "type": "object",
      "required": ["source", "dialect", "entries"],
      "additionalProperties": false,
      "properties": {
        "source": { "type": "string" },
        "dialect": { "type": "string" },
        "entries": { "type": "array", "items": { "$ref": "#/$defs/token" } }
      }
    },
    "token": {
      "type": "object",
      "required": ["index", "kind", "text"],
      "additionalProperties": false,
      "properties": {
        "index": { "type": "integer", "minimum": 0 },
        "kind": { "type": "string" },
        "text": { "type": "string" }
      }
    }
  }
}

Greetings

No comprehensive technical manual would be complete without a list of greetings.

So big shout outs to the folks still keeping the atari scene ticking over in the 2k26

 Aggression · Avena · Cerebral Vortex · Cream · Defence Force · Dekadence · DHS
    Dune · Effect · Ephidrena · Evolution · Extream · HMD · Holocaust · KÜA    
Lamers · LineOut · LoUD · Marquee Design · MEC · MPS · MSB · New Beat · Newline
   NoExtra · Omega · OVR · Oxygene · Paradox · PHF · Sector One · smfx · SYNC  
                                   TPT · XiA                                   

Final Notes

This is an early release of rg-asm, so of course there are likely to be bugs and issues.

Please reach out and report any that you find or share any suggestions you have for improvements.


License

rg-asm is dual-licensed under the MIT License or the Apache License 2.0, at your option.

The full license texts ship with this release as LICENSE-MIT and LICENSE-APACHE. You may use, copy, modify, and redistribute rg-asm under either license's terms. Contributions are accepted under the same dual license unless stated otherwise.

Copyright (c) 1994–2026 Reservoir Gods

♥ made with love for the scene ♥