# targets hdass separates two things that assemblers usually tangle together: - the **architecture** — which instructions exist and how registers work; - the **assembler syntax** — how those instructions are written to a file. a target is a pairing of the two, chosen with `-t`: | `-t` | architecture | assembler | notes | | --- | --- | --- | --- | | `nasm` (default) | x86-64 | nasm | intel syntax, `nasm -f elf64` | | `fasm` | x86-64 | fasm | intel syntax, `fasm` (one step) | | `arm64` | aarch64 | gnu as | `aarch64-linux-gnu-as` | for x86-64, `nasm` and `fasm` emit the **same instruction bodies** and differ only in framing (file header, sections, constant and data syntax). `arm64` is a separate instruction selector: different registers, three-operand arithmetic, `ldr`/`str` memory, `cmp`+`b.cond` branches and `svc #0` syscalls. `masm` (x86-64) and a 32-bit `arm` target are planned. ## what each architecture supports the language is the same; not every construct lowers on every architecture yet. | feature | x86-64 | aarch64 | | --- | --- | --- | | moves, arithmetic (`+ - * /`), compound assignment | ✅ | ✅ | | `if` / `else` / `while`, `goto`, labels | ✅ | ✅ | | conditional select (`a if c else b`) | ✅ `cmov` | ✅ `csel` | | calls, `syscall` | ✅ | ✅ | | memory load/store (`^`), sized and signed | ✅ | partial (`ldr`/`str`) | | raw instruction statement | ✅ | ✅ | | modulo (`%`), division remainder | ✅ | ❌ not yet | | floating point (`xmm`) | ✅ | ❌ not yet | | `stack` buffers | ✅ | ❌ not yet | | bare-metal directives (`format`, `org`, `boot`, `bits 16`) | ✅ | — (x86/bios concept) | unsupported constructs emit a `; TODO` comment instead of incorrect instructions. ## the portable register model architecture-native register names (`rax` on x86-64, `x0` on aarch64) lock a program to one architecture. to write for both, enable [`logical_registers`](language.md#logical_registers): `r1`–`r14` are the general-purpose registers, mapped per target. | logical | `r1` | `r2` | `r3` | `r4` | `r5` | `r6` | `r7` | `r8` | `r9` | `r10` | … | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | x86-64 | rax | rbx | rcx | rdx | rsi | rdi | r8 | r9 | r10 | r11 | … | | aarch64 | x0 | x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 | x9 | … | aarch64 is simply `rN → x(N-1)`. the raw [instruction statement](language.md#raw-instructions) is architecture-locked too: its mnemonics are whatever you write. ## syscall abis differ even with logical registers, a *syscall* is not portable: linux uses different call numbers, argument registers and trap instructions per architecture. so a program still carries architecture-specific abi constants. | | x86-64 | aarch64 | | --- | --- | --- | | syscall number in | `rax` (logical `r1`) | `x8` (logical `r9`) | | arguments in | `rdi rsi rdx r10 r8 r9` | `x0 x1 x2 x3 x4 x5` | | trap (`syscall`) | `syscall` | `svc #0` | | `exit` number | `60` | `93` | | `write` number | `1` | `64` | c works the same way: portable source, per-platform syscalls. ## os independence the emitted instructions aren't tied to an os; only the syscall numbers and the `[entry]`/link convention are. the examples and toolchain here target linux (elf, `ld`, and `qemu-aarch64` for arm); see [getting started](getting-started.md).