diff options
Diffstat (limited to 'docs/history/23-baremetal/notes.md')
| -rw-r--r-- | docs/history/23-baremetal/notes.md | 85 |
1 files changed, 85 insertions, 0 deletions
diff --git a/docs/history/23-baremetal/notes.md b/docs/history/23-baremetal/notes.md new file mode 100644 index 0000000..eed69fe --- /dev/null +++ b/docs/history/23-baremetal/notes.md @@ -0,0 +1,85 @@ +# 23 — bare metal + +the microcore already compiled with `-ffreestanding` and `port_stub.c`, and it already fit in 256KB when the program was baked in. nothing had executed that image. the stub's `uart_putc` was empty. this stage boots it on QEMU's `virt` board, with no OS, and the program's own output on the emulated UART. + +## which machines + +the host is x86_64. what was installable: + +- `gcc-riscv64-linux-gnu` and `gcc-aarch64-linux-gnu` (gcc 13) +- `qemu-system-misc` (8.2.2), which provides `qemu-system-riscv64` +- `qemu-system-arm` (8.2.2), which provides `qemu-system-aarch64` + +`qemu-aarch64`, the linux-user emulator the jit section of `test.sh` looks for, is still not installed. a system emulator does not satisfy that check, and the jit stayed skipped. + +two boards booted. + +**RISC-V `virt`.** `qemu-system-riscv64 -machine virt -bios none -cpu rv64 -m 64 -nographic -monitor none -serial stdio -kernel bare.elf`. `-bios none` means no OpenSBI, so the core comes up in machine mode at the ELF entry. RAM is at `0x80000000`. the linker script claims 48MB of it. the UART is a 16550 at `0x10000000` (byte registers: holding/receive at 0, interrupt enable at 1, FIFO control at 2, line control at 3, line status at 5; receive-ready is bit 0, transmit-empty is bit 5). power-off is the SiFive test device at `0x100000`. writing `0x5555` makes QEMU exit 0. writing `0x13333` is `FINISHER_FAIL` with status 1. + +**AArch64 `virt`.** `qemu-system-aarch64 -machine virt -cpu cortex-a57 -m 64 -nographic -monitor none -serial stdio -kernel bare.elf`. RAM is at `0x40000000`, same 48MB in the linker, 64MB given to QEMU. the UART is a PL011 at `0x09000000`, 32-bit registers, the flag register at offset `0x18` (receive FIFO empty is bit 4, transmit FIFO full is bit 5). power-off is PSCI `SYSTEM_OFF`: `hvc #0` with `x0 = 0x84000008`. that call does not take a status, so a guest that returns an error from `micro_run` still looks like a clean exit. the UART transcript is the result that matters. + +this QEMU starts that ELF at EL1. the startup still checks `CurrentEL` and, at EL2, sets `HCR_EL2.RW`, `SPSR_EL2 = 0x3c5`, and `eret`s to EL1h. that path was not taken on the boots below. `-bios none` is not on the command line. on this 8.2 build it is a filename, and QEMU exits with `Could not find ROM image 'none'`. `-kernel` of an ELF is loaded and entered directly. + +**Cortex-M was not tried.** the word is `int64_t` and the run-only bss is about 400KB before the genome in rodata. `qemu-system-arm`'s small boards (Stellaris `lm3s6965`, 64KB of SRAM) cannot hold it, and a 32-bit core would do every cell in software. `virt` has the RAM and a 64-bit CPU, so that is the port. + +## the image + +`baremetal/start_*.S` sets a 64KB stack, zeroes bss eight bytes at a time, and calls `bare_main`. `baremetal/port.c` is the five `micro_port.h` hooks. syscalls return -1. `baremetal/run.sh` is the build and the QEMU command, with `timeout` so a hang does not stick (120 seconds for `run`, 90 for `compile`). + +two modes. + +`run` asks `genmap` for the 32768-word map, compiles `rt.zc` plus `src/run.zc` plus the program with `zc0`, and links that `genome.h` into the ELF with `-DBARE_INPUT=0`. `micro_eof` is true and `micro_getc` returns -1. the program is already in the image. + +`compile` copies the default `genome.h` and `memmap.h` (1,048,576 words, the self-hosted compiler) and links with `-DBARE_INPUT=1`. the script pipes the source and a trailing backtick into the UART. `virt` has 64MB, so the 8MB machine fits and there was no reason to boot the 172032-word compiler instead. the copies sit next to `micro.c` because `#include "genome.h"` searches the including file's directory first. + +the cross compile is `-ffreestanding -fno-pic -nostdlib -static -O2`. RISC-V adds `-march=rv64gc -mabi=lp64d -mcmodel=medany -mno-relax`. AArch64 adds `-march=armv8-a`. `--build-id=none` keeps the linker from warning about a discarded note. + +## what the first boots did + +RISC-V hello printed `hello, world!` on the first try and QEMU exited 0. the 16550 needed no setup for that, and it must stay that way. an early port wrote the line-control and FIFO-enable registers. QEMU has already delivered the first source bytes by then, and enabling the FIFO dropped the `f` of `fn`. the compiler then said `zc: line 1: n: isn't a type`. `uart_init` on RISC-V is empty. + +AArch64 hello produced nothing and sat until `timeout`. the instruction trace showed `_start`, the bss clear, `bare_main`, then `micro_run`, then a jump to guest address `0x200`, which is the synchronous-exception vector when `VBAR_EL1` is 0. the faulting instruction was `ldr q0`, the `-O2` copy of the genome. `CPACR_EL1.FPEN` is 0 out of reset, so SIMD traps. startup now sets `FPEN` to 3 (and clears `CPTR_EL2.TFP` on the EL2 path) and issues an `isb`. RISC-V sets `mstatus.FS` for the same reason; that core had not emitted a float op yet, but the abi is `lp64d`. + +after those two fixes, both boards printed the same bytes as the hosted interpreter. + +run-only, no input: + +``` +hello, world! +1101110101111100000000 +3628800 +1099511627776 1 1 1 -1 +1000 1 900 1 199900 +``` + +those are `examples/hello.zc`, `examples/fact.zc`, `tests/wide.zc` and `tests/gc.zc`. `gc` took 28 seconds on RISC-V and 32 on AArch64. the second `churn(200000)` is a lot of interpreted allocation, and a 20 second timeout cut it off after `1000 1 900`. + +the resident compiler, source plus a backtick on the UART, for hello, fact and wide, printed the same three results (fact's two lines, wide's one). the compiler image itself does not depend on which file is piped in. + +## sizes + +`size` on the ELF, text and data added together. bss is the word array, the enhancement tables and the 64KB stack. + +| image | ELF bytes | 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 compiler | 741624 | 735565 | 8537128 | +| AArch64 compiler | 803616 | 736040 | 8537136 | + +the AArch64 file is larger than its text and data because the load segment's alignment is 64KB. the compiler's text is mostly the default genome, 91158 words, 729264 bytes. run-only bss is the 256KB machine. compiler bss is the 8MB one. + +## input, and what was left out + +`micro_eof` is not allowed to block. the compiler calls it after every `getc`, including the backtick that ends the unit. if eof waited for another byte, a source that ends in a backtick would hang. an empty FIFO therefore means "a byte may still arrive", not "the file is over". the one real end-of-file byte is `0x04`, and only when it is already sitting in the FIFO: it is consumed and not returned to the program. a program that loops on `eof()`, the way `examples/echo.zc` does, needs that byte queued behind the payload. the 16550 FIFO holds 16 bytes, so a longer stream can make the peek see `0x04` too early or not at all. echo-until-eof is not in the bare-metal checks. the backtick protocol does not need it. + +other limits, unchanged from the hosted microcore except where the board forces them: + +- every syscall returns -1 +- install kind 2 (an o3 region) succeeds and is ignored; the bytecode runs +- diagnostics share the UART with program output +- a run-only program has to be the `zc0` subset +- 256KB still cannot hold the compiler +- not a C ABI, and a character is still one word +- AArch64 power-off cannot report a guest failure status |
