aboutsummaryrefslogtreecommitdiff
path: root/docs/getting-started.md
blob: 6781c0f608d018eb0c586c7c45269d89b3b4edc2 (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.