1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
|
# hdass language reference
hdass emits NASM for x86-64; fasm and masm are planned. The compiler output itself isn't tied to an OS, but the examples and toolchain here target Linux (Linux syscall numbers, `nasm -f elf64`, `ld`). Pipeline: `lex → parse → analyze → emit`.
## A first program
```hdass
[entry: main]
const SYS_WRITE = 1
const SYS_EXIT = 60
const STDOUT = 1
data message = "type shi.\n"
proc main
{
rax = SYS_WRITE
rdi = STDOUT
rsi = message
rdx = message.len
syscall
rax = SYS_EXIT
rdi = 0
syscall
}
```
Writes `type shi.` to stdout and exits.
## Comments
```hdass
rax = 1 // line
/* block */
```
## Directives
Top-level `[key: value]`, configuring the whole program.
| Directive | Meaning |
| --- | --- |
| `[bits: 64]` / `[bits: 32]` | Target bitness. Default 64. |
| `[entry: NAME]` | Makes procedure `NAME` the entry point. |
| `[enable: NAME]` | Turns on an [extension](#extensions). |
Unknown keys, a `bits` value other than 32/64, and unknown extensions are errors.
## Constants and data
`const` names a constant integer expression — integer literals, character literals, other constants, and `+` `-` `*` `/`. Integers are decimal, `0x` hex, or `0b` binary (these forms work anywhere an integer does). `data` puts a string in `.data`; the name is its address and `.len` is its length in bytes.
```hdass
const STDOUT = 1
const MASK = 0xFF
const AREA = 8 * 6 // 48
data message = "type shi.\n" // message -> address, message.len -> 10
```
## Procedures
`proc` groups a body. Parameters name registers — `value` below is `rdi`.
```hdass
proc print_number(value: rdi)
{
rax = value
}
```
Each procedure ends with `ret`, except the entry point. `[entry: NAME]` exports `NAME` with `global` and drops its `ret`, so it must end the program itself (an exit syscall). Link with `ld -e NAME`.
## 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`.
## Statements
```hdass
rax = SYS_WRITE // mov
rcx -= 1 // += -= *= /= -> add sub imul idiv
rax = rbx * rcx // + - * / in a value; / and /= use rax:rdx (see Gotchas)
rdx = buffer + 31 // address math
loop: // label
goto loop
if rcx != 0 // == != < <= > >= ; runs the next statement only
goto loop
syscall
print_number(r12) // call; args go into the callee's parameter registers
stack buffer[32] // stack buffer; buffer is its base address
```
## Dereference (`^`)
`^reg` is the memory at the address in `reg` — NASM's `[reg]`. On the left of `=` it stores there. The store width comes from the value operand, so a sized sub-register picks the size:
```hdass
^rsi = rdx // mov [rsi], rdx (qword)
^rsi = dl // mov [rsi], dl (byte)
^rsi = eax // mov [rsi], eax (dword)
```
A leading size keyword sets the width explicitly. It down-converts a full register to the matching sub-register, and gives an immediate a width NASM would otherwise reject:
```hdass
^byte rsi = rdx // mov byte [rsi], dl (rdx -> its low byte)
^word rsi = rax // mov word [rsi], ax
^dword rsi = r12 // mov dword [rsi], r12d
^byte rsi = '0' // mov byte [rsi], '0'
^byte rsi = 10 // mov byte [rsi], 10
```
`^reg` is also a value — it loads from that address. A size keyword loads a narrower value and zero-extends it into the target:
```hdass
rax = ^rsi // mov rax, [rsi]
rbx = ^byte rsi // movzx rbx, byte [rsi]
rcx = ^dword rsi // mov ecx, [rsi] (32-bit load zero-extends)
rdx = ^rsi + 4 // load, then add 4
```
## 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.
## Extensions
### logical_registers
Uniform names for the general-purpose registers, so you don't juggle the irregular `rax`/`rbx`/`rsi`/… spellings. `r1`–`r14` map to:
| `r1` | `r2` | `r3` | `r4` | `r5` | `r6` | `r7` | `r8` | `r9` | `r10` | `r11` | `r12` | `r13` | `r14` |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| rax | rbx | rcx | rdx | rsi | rdi | r8 | r9 | r10 | r11 | r12 | r13 | r14 | r15 |
`rsp`/`rbp` and the instruction pointer keep their own names.
```hdass
r1 = 5 // mov rax, 5
r4 = r10 // mov rdx, r11
r6 += r1 // add rdi, rax
```
A `.8`/`.16`/`.32`/`.64` suffix selects the width, mapping to the sub-register:
```hdass
r1.8 // al
r1.16 // ax
r1.32 // eax
r1.64 // rax
^byte rsi = r4 // mov byte [rsi], dl (r4 -> rdx -> dl)
```
Arch `r8`–`r15` share the `rN` spelling, so with the extension on a bare `r8` is the *logical* register (which is arch `r9`). Reach arch `r8`–`r15` through logical `r7`–`r14`. Architecture names like `rax` and `rsi` still work everywhere.
## Building a program
```bash
hdass program.hdass -o program.asm
nasm -f elf64 program.asm -o program.o
ld -e main program.o -o program
./program
```
The [README](../README.md) has a Docker setup with these tools.
## Gotchas
- **Clobbering is yours.** `syscall` trashes `rcx`/`r11`; a callee trashes what it touches. Nothing is saved for you — `examples/fibonacci.hdass` keeps its counter in `r15` for this reason.
- **Widths must match.** `rax = r1.8` becomes `mov rax, al`, which won't assemble.
- **Division uses `rax:rdx`.** `/` and `/=` go through `idiv`, so they clobber `rax` and `rdx` regardless of the target, and the divisor can't be `rax`, `rdx`, or an immediate — put it in another register first.
- **The entry procedure has no `ret`** — end it with an exit syscall.
|