aboutsummaryrefslogtreecommitdiff
path: root/docs/getting-started.md
blob: 6f9061867e93a70674ade1014bfc42bee90171d0 (plain)
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
# Getting started

## Build the compiler

hdass builds with [Meson](https://mesonbuild.com/):

```bash
meson setup build
meson compile -C build
```

That produces `build/hdass`. It has two options you need: `-o <file>` writes the
output (default stdout), and `-t <target>` picks the assembler and architecture
(`nasm` by default, `fasm`, or `arm64`). `build/hdass --help` lists the rest.

hdass only *transpiles*: it emits assembly text, which you assemble and link
yourself. The examples target Linux, so you need an x86-64 (and
for AArch64, an aarch64) Linux toolchain. The bundled
[Docker image](../README.md#building) has `nasm`, `fasm`, an aarch64
cross-assembler and `qemu`; the commands below run inside it:

```bash
docker compose up -d
docker compose exec hdass bash
```

## A first program (x86-64)

Put this in `hello.hdass`:

```hdass
[entry: main]

const SYS_WRITE = 1
const SYS_EXIT = 60
const STDOUT = 1

data message = "Hello, hdass!\n"

proc main
{
    rax = SYS_WRITE     // write(STDOUT, message, message.len)
    rdi = STDOUT
    rsi = message
    rdx = message.len
    syscall

    rax = SYS_EXIT      // exit(0)
    rdi = 0
    syscall
}
```

Transpile, assemble, link and run:

```bash
./build/hdass hello.hdass -o hello.asm
nasm -f elf64 hello.asm -o hello.o
ld -e main hello.o -o hello
./hello
```

It prints `Hello, hdass!`. `[entry: main]` exports `main` and drops its `ret`, so
the procedure ends the program itself with the exit syscall; `ld -e main` uses it
as the start symbol. Swap `-t fasm` and `fasm hello.asm hello.o` to use fasm
instead — the machine code is the same.

## Running on AArch64

The same source model runs on ARM, but the syscall ABI differs (numbers and
argument registers), so this program is AArch64-specific. Written with the
[`logical_registers`](targets.md#the-portable-register-model) extension so the
registers read the same on both architectures:

```hdass
[entry: main]
[enable: logical_registers]

const SYS_EXIT = 93     // AArch64 Linux exit

proc main
{
    r1 = 42             // x0 = exit code
    r9 = SYS_EXIT       // x8 = syscall number
    syscall             // svc #0
}
```

Build it for AArch64 and run it under qemu:

```bash
./build/hdass -t arm64 exit.hdass -o exit.s
aarch64-linux-gnu-as exit.s -o exit.o
aarch64-linux-gnu-ld -e main exit.o -o exit
qemu-aarch64 ./exit          # exits 42
```

`r1` maps to `x0` and `r9` to `x8`, `syscall` becomes `svc #0`. See
[Targets](targets.md) for the register mapping and the ABI tables.

## Next

- The whole language: [language reference](language.md).
- What runs where and why programs still carry per-architecture ABI details:
  [targets](targets.md).
- More programs to read: [examples/](../examples/) and
  [examples/arm64/](../examples/arm64/), each runnable with the steps above.