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/language.md | |
| parent | d50af2e474281a1e88d191f783c2792824698974 (diff) | |
docs: split docs
Diffstat (limited to 'docs/language.md')
| -rw-r--r-- | docs/language.md | 44 |
1 files changed, 28 insertions, 16 deletions
diff --git a/docs/language.md b/docs/language.md index ecdbf19..96f534b 100644 --- a/docs/language.md +++ b/docs/language.md @@ -1,16 +1,8 @@ # hdass language reference -hdass has two independent axes: the **architecture** (which instructions and registers) and the **assembler syntax** (how they are written). A target is a pairing: +This is the language reference. hdass transpiles to x86-64 (NASM or fasm, `-t nasm`/`-t fasm`) and AArch64 (`-t arm64`); for which target supports what, the portable [`logical_registers`](#logical_registers) model, and the per-architecture syscall ABIs, see [Targets](targets.md). New here? Start with [Getting started](getting-started.md). Pipeline: `lex → parse → analyze → emit`. -| `-t` | architecture | assembler | -| --- | --- | --- | -| `nasm` (default) | x86-64 | NASM | -| `fasm` | x86-64 | fasm | -| `arm64` | AArch64 | GNU as | - -For x86-64 the two syntaxes emit the same Intel instruction bodies and differ only in framing (headers, sections, constants, data). `arm64` is a separate instruction selector — different registers, three-operand arithmetic, `ldr`/`str`, `cmp`+`b.cond`, `svc #0` — and is early: assignments, arithmetic (`+ - * /`), control flow, calls, `syscall`, and the raw instruction statement work; floats, stack buffers, and division-remainder do not yet. masm is planned. Pipeline: `lex → parse → analyze → emit`. - -The portable way to write for more than one architecture is the [`logical_registers`](#extensions) extension: `r1..r14` are the general-purpose registers, mapped per target (x86-64 `r1 = rax`; AArch64 `r1 = x0`, i.e. `rN → x(N-1)`). Architecture-native register names (`rax`, `x0`) and the raw instruction statement are, by definition, locked to one architecture. Syscall ABIs also differ per architecture — Linux exit is `60` in `rax` on x86-64 but `93` in `x8` (logical `r9`) on AArch64 — so programs still carry arch-specific ABI constants even when the language is portable. The output isn't tied to an OS, but the examples and toolchain here target Linux (ELF, `ld` / `qemu-aarch64`). +The examples below use x86-64 register names. The output isn't tied to an OS, but the examples and toolchain here target Linux (ELF, `ld`). ## A first program @@ -48,15 +40,21 @@ rax = 1 // line ## Directives -Top-level `[key: value]`, configuring the whole program. +Top-level `[key: value]` (or bare `[key]`), configuring the whole program. | Directive | Meaning | | --- | --- | -| `[bits: 64]` / `[bits: 32]` | Target bitness. Default 64. | +| `[bits: 64]` / `[bits: 32]` / `[bits: 16]` | Target bitness. Default 64. | | `[entry: NAME]` | Makes procedure `NAME` the entry point. | | `[enable: NAME]` | Turns on an [extension](#extensions). | +| `[format: bin]` / `[format: elf]` | Output a flat binary instead of an ELF object. Default elf. | +| `[org: 0x7C00]` | Set the load address of a flat binary. | +| `[boot]` | Pad a flat binary to 510 bytes and append the `0xAA55` boot signature. | -Unknown keys, a `bits` value other than 32/64, and unknown extensions are errors. +The last three build a raw binary instead of a linked ELF, enough for an x86 +boot sector. In `bin` format there are no sections or exported symbols, and code +comes first so execution starts at the origin. Unknown keys, a bad `bits` value, +and unknown extensions are errors. ## Constants and data @@ -117,7 +115,7 @@ Each procedure ends with `ret`, except the entry point. `[entry: NAME]` exports ## Registers -Written by their architecture names — `rax`–`rdi`, `rbp`, `rsp`, `r8`–`r15` — and their sub-registers (`al`, `ax`, `eax`, `dl`, …), which imply a store's size. The [`logical_registers`](#extensions) extension adds `r1`–`r14`. +Written by their architecture names — `rax`–`rdi`, `rbp`, `rsp`, `r8`–`r15` — and their sub-registers (`al`, `ax`, `eax`, `dl`, …), which imply a store's size. The [`logical_registers`](#logical_registers) extension adds `r1`–`r14`. The segment registers (`cs ds es fs gs ss`) and control registers (`cr0 cr2 cr3 cr4`) are also recognised, for systems code that sets up segments or switches CPU modes. ## Statements @@ -214,6 +212,21 @@ rbx = ^signed word rsi // movsx rbx, word [rsi] rcx = ^signed dword rsi // movsxd rcx, dword [rsi] ``` +## Raw instructions + +Any statement that isn't an assignment, label, call or keyword is emitted as a bare instruction: a mnemonic and its operands, written in hdass's own operand syntax. This is the escape hatch for everything outside the assignment and control-flow model: `int`, `hlt`, `cli`/`sti`, `lgdt`, port I/O, the mode switch. Operands are the usual registers, immediates, constants and `^memory`, and share the mnemonic's line. + +```hdass +cli +int 0x10 +out 0x64, al +lgdt ^gdt // lgdt [gdt] +cr0 = eax // an ordinary move; segment/control regs work with `=` too +hlt +``` + +Mnemonics pass straight through, so a typo is reported by the assembler. Operands render through the same path as everywhere else, so an instruction is as portable as the rest of the language, though the mnemonics themselves are architecture-specific. + ## Expressions Assignment values and `if` operands: registers, integers, chars (`'0'`), constants, data names, member access (`data.len`), and `+` `-` `*` `/` `%`. Operators are left-associative and each right-hand operand must be a single term, so `a * b + c` works but `a + b * c` (a nested right operand) doesn't yet. @@ -299,8 +312,7 @@ fasm program.asm program.o ld -e main program.o -o program ``` -The [README](../README.md) has a Docker setup with these tools. +For `-t arm64` and the full toolchain (including the AArch64 cross-assembler and qemu), see [Getting started](getting-started.md); the [README](../README.md) has the Docker setup. ## Some stinkies Clobbering is your responsibility: `syscall` trashes `rcx` and `r11`, while a callee can trash any registers it touches, so nothing is saved automatically. `examples/fibonacci.hdass`, for example, keeps its counter in `r15` for this reason. Register widths must also match, meaning something like `rax = r1.8` would become `mov rax, al`, which will not assemble. Division clobbers extra registers: `/` `%` and their `=` forms use `idiv` through `rax:rdx`, so both are overwritten regardless of the destination. The divisor can be anything — a register, a constant, or an immediate — but an immediate or an `rax`/`rdx` divisor is first copied into `r11`, so those also clobber `r11`. Labels and procedures become plain assembler symbols, so avoid names the target assembler reserves: `loop`, for instance, is an instruction mnemonic that fasm rejects as a label (nasm allows it). Finally, the entry procedure has no `ret`; it should end with an exit syscall. - |
