aboutsummaryrefslogtreecommitdiff
path: root/docs/history/baremetal-measurements.md
blob: ef1ecd81994f7f37e8529b687526e2875dbf5a0d (plain) (blame)
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
# earlier bare-metal measurements

these measurements describe the builds recorded at the time; they are not current size guarantees.

## bare metal

`baremetal/run.sh` builds a freestanding image and boots it on QEMU's `virt` board, headless, with the UART on stdio. there is no kernel and no libc. startup sets the stack, zeroes bss, and calls `bare_main`, which calls `micro_run`. two targets, picked because a cross gcc and a system emulator both install from the distro packages:

```
baremetal/run.sh ready riscv          # exits 0 when the tools exist
baremetal/run.sh riscv run examples/hello.zc
baremetal/run.sh riscv io examples/echo.zc    # stdin, then 0x04
baremetal/run.sh riscv compile examples/fact.zc
baremetal/run.sh aarch64 run tests/gc.zc
baremetal/run.sh aarch64 compile tests/wide.zc
```

`run` is the 36864-word image from `genmap --preset run`: `rt.zc`, `src/run.zc` and the program, compiled by `zc0` into the ELF. nothing is read. `io` is that same image with the UART open: the script's stdin is the program's input and a `0x04` is appended, which `eof()` reports and `getc()` does not return. `compile` is the default 1,048,576-word genome. the script writes the source, a backtick, and a `0x04`. the backtick ends the unit, the same rule as the hosted compiler, and the `0x04` is what the probe after that backtick is waiting on. `test.sh` runs these, on each target whose tools are present, and skips the one that isn't. a missing `timeout(1)`, cross gcc or `qemu-system-*` is a skip, not a failure.

the guest is the same two word maps as the hosted builds. what changes per target is where RAM, the UART and the power-off device sit.

RISC-V, `qemu-system-riscv64 -machine virt -bios none -cpu rv64 -m 64`. machine mode, no OpenSBI. RAM starts at `0x80000000`; the linker uses 48MB of it. the UART is a 16550 at `0x10000000`, polled through the line-status register. exit is the SiFive test device at `0x100000`: `0x5555` makes QEMU exit 0, and `(status << 16) | 0x3333` exits with that status (`0x13333` is status 1, `0x33333` is status 3). the clock is `rdtime`. `mstatus.FS` is set so a compiler that emits a float op does not trap.

AArch64, `qemu-system-aarch64 -machine virt -cpu cortex-a57 -m 64`. RAM starts at `0x40000000`, again 48MB in the linker and 64MB given to QEMU. the UART is a PL011 at `0x09000000` (flag register at `0x18`). this QEMU 8.2 starts the ELF at EL1, and `-bios none` is not passed: that build looks for a file named `none` and refuses. `-kernel` of the ELF is enough, and the CPU begins at the entry point. if it had come up at EL2 the startup drops to EL1h before touching the stack. SIMD is turned on in `CPACR_EL1`, because `-O2` copies the genome with `q` registers and a disabled FPU takes an exception at the vector base plus `0x200` (that is what the first boot did, before there was any UART output). exit is semihosting `SYS_EXIT` (`hlt #0xf000`, `x0 = 0x18`, `x1` pointing at `{0x20026, status}`), and the command line passes `-semihosting-config enable=on,target=native`. PSCI `SYSTEM_OFF` cannot carry a status, which is why it is not the exit path. the clock is `cntvct_el0`. the UART line is the thing to read.

both images are linked with `-ffreestanding -nostdlib -static`. the cross compilers are `riscv64-linux-gnu-gcc` (`-march=rv64gc -mabi=lp64d -mcmodel=medany -mno-relax`) and `aarch64-linux-gnu-gcc` (`-march=armv8-a`). QEMU is `-nographic -monitor none -serial stdio`, so the bytes on stdout are the bytes the guest wrote. `run` is killed after 120 seconds, `compile` after 90. `tests/gc.zc` on the run-only image needs about 30 seconds; the others finish in about a second.

image sizes, from `size` and the ELF file length. bss is the machine plus the enhancement tables and a 64KB stack. the AArch64 file is bigger than its text and data because the load segment is aligned to 64KB.

| | ELF file | text+data | bss |
|---|---|---|---|
| RISC-V, run, hello | 87704 | 81741 | 410664 |
| RISC-V, run, fact | 88504 | 82541 | 410664 |
| AArch64, run, hello | 149704 | 82248 | 410656 |
| RISC-V, resident compiler | 741624 | 735565 | 8537128 |
| AArch64, resident compiler | 803616 | 736040 | 8537136 |

the compiler file does not change with the program fed over the UART. most of its text is the default genome, 103655 words, 829240 bytes. the run-only bss is the 294912-byte machine; the compiler bss is the 8MB one. both fit in the 64MB QEMU gives `virt`.

what booted, and matched the hosted interpreter on the same source: `hello`, `fact`, `wide` and `gc` as run-only images, and `hello`, `fact` and `wide` compiled by the resident compiler over the UART, on both boards. `gc` printed `1000 1 900 1 199900`.

a Cortex-M was not ported. the word is 64 bits and the run-only bss is already about 400KB, before the genome in rodata. the boards `qemu-system-arm` actually models at that size (the Stellaris `lm3s6965` has 64KB) cannot hold it, and a 32-bit core would be emulating every cell in software.

limits, on top of the ones in the smaller-machines section:

- the syscall gate is the same one as the hosted core. on these boards, open/close/read/write are a RAM file store (4 files, 128 bytes, 31-character names), `getenv("BOARD")` is `riscv` or `aarch64`, time is the hardware counter rather than unix seconds, exit takes a status, random is a counter seeded from that clock, and memory info returns `MW`, the heap bounds and the byte size. the stub port still returns -1 for every op. a file is not the host's filesystem, and the counter is not wall time, so `tests/sys.zc` stays a hosted test.
- the microcore still has no o3 fast path. the bytecode runs.
- diagnostics (`zy: data stack overflow` and the rest) go out the same UART as program output.
- the 16550 is left at QEMU's reset settings. writing the line-control or FIFO register after the source has started arriving drops the first byte, and `fn` becomes `n`, which the compiler reports as `n: isn't a type`.
- end of input is `0x04`. `micro_eof` and `micro_getc` wait until a byte is actually in the UART, so an empty FIFO is a pause and not the end, and a stream longer than the 16-byte 16550 FIFO still reaches its `0x04`. that byte is not delivered. the compiler still ends a unit on a backtick; `compile` sends `0x04` after it so the probe that follows the backtick returns. `examples/echo.zc` fed 36 letters under `io` prints those letters on both boards.
- the 192512-word compiler is not what these boards boot. `virt` has room for the default genome, and that is the one the `compile` mode uses. 294912 bytes still cannot hold a compiler.
- no native calls (the c abi needs `dlsym`), and a character is still one word.

the notes in `history/23-baremetal` have the boot log and the places the first images hung.