diff options
| author | hachem <im@hachem.wtf> | 2026-09-13 00:37:24 +0200 |
|---|---|---|
| committer | hachem <im@hachem.wtf> | 2026-09-13 00:37:24 +0200 |
| commit | 4e1edee7383af381d51b11fce45b6a15b62f10e7 (patch) | |
| tree | 790c0af3701a732d191891c489b2644dc6d45003 /docs/targets.md | |
| parent | d50af2e474281a1e88d191f783c2792824698974 (diff) | |
docs: split docs
Diffstat (limited to 'docs/targets.md')
| -rw-r--r-- | docs/targets.md | 77 |
1 files changed, 77 insertions, 0 deletions
diff --git a/docs/targets.md b/docs/targets.md new file mode 100644 index 0000000..c54636b --- /dev/null +++ b/docs/targets.md @@ -0,0 +1,77 @@ +# 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). |
