commit 66f47611dd938c3f3a445b94fe4e42df02f8d175
parent ee3139b5876283352651f76efaaa65a93bdfcf10
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Wed, 6 May 2026 13:03:14 -0700
rm old docs
Diffstat:
| D | docs/PLAN.md | | | 517 | ------------------------------------------------------------------------------- |
| D | docs/SEED-AMD64-TODO.md | | | 122 | ------------------------------------------------------------------------------- |
| D | docs/SEED-VIRTIO-BLK.md | | | 359 | ------------------------------------------------------------------------------- |
| D | docs/TCC-ARM64-ASM.md | | | 314 | ------------------------------------------------------------------------------- |
4 files changed, 0 insertions(+), 1312 deletions(-)
diff --git a/docs/PLAN.md b/docs/PLAN.md
@@ -1,517 +0,0 @@
-# Cleanup & Audit Plan
-
-The mechanical goal is met: hex0-seed → … → tcc3 → musl → seed-kernel runs on
-{aarch64, amd64, riscv64} via two drivers (podman, seed). This plan covers
-the next pass: making the result auditable and uniform.
-
-## Decisions
-
-1. **Output layout**: `build/<arch>/<driver>/...`
-2. **Makefile vs bootN scripts**: replace the parallel Makefile recipes
- (`make m1pp`, `make hex2pp`, `make scheme1`, etc.) with rules that drive
- the `bootN.sh` scripts. One pipeline, one layout.
-3. **Reproducibility criterion**: every `boot{N}/` output artifact must be
- byte-identical between `DRIVER=podman` and `DRIVER=seed`, per arch.
-4. **Source preparation is a separate up-front host stage.** All flattening,
- patching, unpacking, calibration happens once into a canonical generated
- source tree. Boot stages copy/reference from it; they do no source prep
- themselves.
-5. **Path-based Makefile deps.** `make build/aarch64/seed/boot6/Image`
- walks the dependency chain — no separate `boot0`/`boot1`/… phony
- targets driving stages. Outputs are the targets.
-6. **Tests live in their own Makefile** (`tests/Makefile`), invoked from
- the top level but not commingled with the build pipeline.
-7. **Consistency over backcompat.** Patch, refactor, rename, rewrite as
- needed. tcc patches are in scope when they buy uniformity.
-8. **Phase order**: A6 → A3 → A0 → A4 → AT → AX → A2 → A5. Each phase
- lands on a clean base from the previous one. AT (tcc patches) and AX
- (tests Makefile) are largely independent of the others and can slot in
- wherever convenient.
-
-## Phases
-
-### [DONE] A6. Hoist driver/arch boilerplate
-
-**Goal.** One source of truth for arch→platform mapping, driver dispatch,
-prereq checks, and log prefixes. Stage-N scripts shrink to the parts that
-are actually stage-specific.
-
-**Touch list.**
-- New: `scripts/lib-arch.sh`. Exports `PLATFORM`, `KERNEL_NAME`,
- `KERNEL_IMAGE`, `MUSL_ARCH` from `$ARCH`. Single source.
-- `scripts/lib-pipeline.sh`, `scripts/lib-runscm.sh`: source `lib-arch.sh`
- in `_init_*`; pull `case "$DRIVER"` validation + image build / kernel
- check up here.
-- `scripts/boot{0..6}.sh`: delete duplicated `case $ARCH` and
- `case $DRIVER` blocks; delete prereq `[ -x "$BOOT(N-1)/x" ] || die`
- blocks; replace with `require_prev hex2 catm M0` style helper.
-- All scripts: standardize log prefix to `[bootN/$driver/$arch]`.
-- `scripts/boot.sh`: same.
-
-**Validation.** `scripts/boot.sh aarch64` end-to-end on both drivers.
-
----
-
-### [DONE] A3. Per-driver output trees
-
-**Goal.** `build/<arch>/<driver>/...` everywhere. Kill the
-`build/.seed-bootstrap/` shuffle in `boot.sh`. Two drivers can coexist on
-disk; nothing gets clobbered when you switch.
-
-**Touch list.**
-- `scripts/lib-pipeline.sh`, `scripts/lib-runscm.sh`: change `OUT`/`STAGE`
- derivation to insert `$DRIVER`.
-- `scripts/boot{0..6}.sh`: every `BOOT(N-1)=build/$ARCH/boot(N-1)` →
- `build/$ARCH/$DRIVER/boot(N-1)`.
-- `scripts/boot.sh`: drop the `.seed-bootstrap` stash/restore. The boot6
- kernel that the seed driver uses now lives at
- `build/<arch>/podman/boot6/{Image|kernel.elf}` and is referenced
- directly when running seed.
-- `seed-kernel/run.sh`: kernel-path arg / env still works; just point
- callers at the new location.
-- `Makefile`: every `OUT_DIR := build/$(ARCH)` → include `$(DRIVER)`.
-- `.gitignore` if it pins `build/<arch>/`.
-- Top-level `Makefile` comment block (output-layout doc).
-
-**Validation.** Both `DRIVER=podman scripts/boot.sh aarch64` and
-`DRIVER=seed scripts/boot.sh aarch64` produce coherent trees side by side.
-
----
-
-### [DONE] A0. Canonical generated source tree (host source-prep)
-
-**Goal.** All host-side source preparation happens once, up front, into a
-single canonical tree at `build/<arch>/src/`. This tree is the audit
-basis and the only thing boot stages read for source. Boot stages do no
-flattening, no unpacking, no patching, no calibration.
-
-**Layout.**
-```
-build/<arch>/src/
- bin/ # binary inputs that aren't built by a stage
- hex0-seed # (vendored seed only)
- src/ # everything textual
- vendor-seed/ # ELF.hex2, *.hex0, *.hex1, *.hex2 (vendored)
- M1pp/ # M1pp.P1
- hex2pp/ # hex2pp.P1
- P1/ # P1.M1pp, P1-<arch>.M1, P1-<arch>.M1pp,
- # P1pp.P1pp, entry-*.P1pp, elf-end.P1pp
- catm/ # catm.P1pp
- scheme1/ # scheme1.P1pp, prelude.scm
- cc/ # cc.scm, main.scm
- tcc/ # tcc.flat.c, stdarg-bridge.h,
- # tcc-0.9.26-…/{include,lib}/ tree
- libc/ # libc.flat.c (mes-libc flattened)
- musl/ # filtered musl-1.2.5 tree (overrides
- # merged, deletes applied), per-arch
- # skip-list, generated alltypes.h /
- # syscall.h
- kernel/ # seed-kernel kernel.c, arch/<arch>/*,
- # user/*
- test-fixtures/ # only the slice tests need at runtime
- # (host runner reads tests/ directly)
-```
-
-The `bin/` vs `src/` split is established here; downstream stages
-inherit it (their `$STAGE/in/{bin,src}/` is a copy-or-symlink view of
-the right slice of this tree).
-
-**What moves into A0.**
-- `scripts/stage1-flatten.sh` (tcc flatten) — runs once, output to
- `build/<arch>/src/src/tcc/`.
-- `scripts/libc-flatten.sh` (mes-libc flatten) — runs once, output to
- `build/<arch>/src/src/libc/`.
-- `scripts/musl-vendor.sh` (musl unpack + overrides + deletes) — runs
- once, output to `build/<arch>/src/src/musl/`.
-- `scripts/boot5-calibrate.sh` (musl skip-list per arch) — runs once,
- output as part of `src/musl/skip.txt`. If skip-list is committed
- upstream (`vendor/upstream/musl-1.2.5-skip-<arch>.txt`), copy it; else
- run calibration (which itself depends on boot4 — see below).
-- All the per-stage `.stage/in/` materialization moves out of
- `lib-pipeline.sh` / `lib-runscm.sh` and becomes a thin "copy from
- `build/<arch>/src/`" step.
-
-**Calibration ordering.** `boot5-calibrate.sh` needs a working tcc3, so
-the canonical src tree is built in two passes: **A0a** (everything that
-doesn't need a compiler) before boot0, **A0b** (musl filter using the
-calibration result) after boot4. Both write into `build/<arch>/src/`.
-Document the split clearly; the Makefile encodes it via deps.
-
-**Touch list.**
-- New: `scripts/prep-src.sh`. Orchestrates A0a (vendored copy, tcc flatten,
- libc flatten, musl unpack+merge).
-- New: `scripts/prep-musl.sh`. The A0b half (run/copy calibration, apply
- filter, snapshot result into `src/musl/`).
-- `scripts/boot{3,4,5,6}.sh`: delete the auto-invoke flatten/calibrate
- blocks; rely on `build/<arch>/src/` being present.
-- `scripts/lib-pipeline.sh`, `scripts/lib-runscm.sh`: add a
- `stage_input_from_src <subpath> <bin|src>` helper that pulls from the
- canonical tree.
-- `scripts/boot{0..6}.sh`: every `pipeline_input` / `runscm_input` call
- switches to the new helper. Inputs become declarative.
-- `scripts/boot{4,5,6}-gen-runscm.sh`: paths emitted into `run.scm` use
- the canonical layout.
-- Retire `WORK_SUBPATH` env in `scripts/boot-build-p1pp.sh` and similar
- ad-hoc input plumbing.
-
-**Validation.** `tree build/<arch>/src/` is reviewable in one sitting.
-Re-running any boot stage twice produces byte-identical outputs. The
-flatten/musl scripts are no longer triggered from within a boot stage.
-
----
-
-### [DONE] A4. Makefile drives `bootN.sh` with path-based deps
-
-**Goal.** Outputs are targets. `make build/aarch64/seed/boot6/Image`
-walks the chain: prep-src → boot0 → boot1 → … → boot6.
-
-**Status note.** Implemented with single-target rules anchored on per-
-stage `.stamp` files (real artifacts depend on the stamp via empty
-recipes). GNU make 4.4 does not propagate "must remake of prereq"
-through grouped explicit targets (`&:`) across multi-level chains, so
-the rule shape sketched below — combining real outputs and `.stamp`
-into one grouped target — would skip the boot6 rebuild after a deep
-source change. The single-target form keeps the stamp the recipe peg
-and makes the artifact files declarative-only, which restores correct
-propagation. The `tests/Makefile` include and the `boot-build-p1*.sh`
-retirement are deferred to AX, which migrates the test infrastructure
-that still depends on them.
-
-**Rule shape.**
-```
-build/$(ARCH)/$(DRIVER)/boot1/M1pp \
-build/$(ARCH)/$(DRIVER)/boot1/hex2pp: \
- build/$(ARCH)/$(DRIVER)/boot0/hex2 \
- build/$(ARCH)/$(DRIVER)/boot0/M0 \
- build/$(ARCH)/$(DRIVER)/boot0/catm \
- build/$(ARCH)/src/.stamp \
- scripts/boot1.sh scripts/lib-pipeline.sh scripts/lib-arch.sh
- scripts/boot1.sh $(ARCH)
-```
-
-The `.stamp` files in `build/<arch>/src/` and each `build/<arch>/<driver>/boot{N}/`
-are the make-rule pegs. Real outputs are listed as additional targets so
-single-binary builds work.
-
-**Touch list.**
-- `Makefile`: rewrite the body. Per-stage rule blocks: src-prep, boot0,
- boot1, …, boot6. Each lists its real outputs and depends on the
- previous stage's outputs plus the relevant scripts and library files.
-- `Makefile`: drop `m1pp`/`hex2pp`/`scheme1`/`cc`/`tcc-boot2`/`tcc-tcc`/
- `tcc-tcc-tcc` aliases entirely. Path is the name.
-- `Makefile`: drop dead vars (`TOOLS_CATM`); single tcc-version var.
-- `Makefile`: hoist `cloc` (~14 lines) into `scripts/count-lines.sh`.
-- `Makefile`: include `tests/Makefile` for test targets (see AX).
-- `Makefile`: top-level convenience targets accept `DRIVER` and `ARCH`,
- expand to the right path. `make all` builds everything.
-- `BOOT6_TIMEOUT` env in `boot6.sh` (parity with boot3/4/5).
-- Retire `boot-build-p1.sh` and `boot-build-p1pp.sh` if the boot-script
- path subsumes them (it should — they're container-side helpers for
- the old Makefile recipes).
-
-**Validation.** `make build/aarch64/podman/boot6/Image` from a clean
-tree builds the full chain. `make build/aarch64/podman/boot1/M1pp` only
-builds prep-src + boot0 + boot1. `touch scripts/boot4.sh && make
-build/aarch64/podman/boot6/Image` rebuilds boot4–6 only. Output bytes
-unchanged from pre-A4.
-
----
-
-### AT. tcc patches for uniformity
-
-**Goal.** Eliminate per-arch workarounds that exist only because tcc
-0.9.26 is incomplete. After AT, every arch is on the same footing in
-seed-kernel and the build pipeline.
-
-The four subitems are independent — none gates another. AT.3 was
-previously claimed to gate AT.4 (per the "once asm-support patches
-land" wording); audit found that wrong (AT.4 is a pure refactor).
-
-**In scope.**
-
-- **AT.1. amd64 `.quad` 64-bit-literal truncation.** `seed-kernel/arch/amd64/kernel.S:406–413`
- encodes the GDT as `.long lo, hi` pairs because `.quad 0x00af9a000000ffff`
- truncates. Root cause is **not** in tcc's `gen_le64` (that path is 64-bit
- clean); it's in mes-libc's `vendor/mes-libc/mes/abtol.c:29`, which
- accumulates `strtoull` into an `int`, so any 64-bit hex literal in
- `tccasm.c:asm_expr_unary` loses its high bits at parse time. Two fixes:
- - **(preferred)** one-line edit in `abtol.c` (`int i` → `long long i`).
- Repairs every `strto*` caller, not just asm.
- - **(alternative)** new patch `tccasm-asm-expr-parse-number.{before,after}`
- that inlines a 64-bit hex/oct/dec accumulator in `asm_expr_unary`,
- bypassing `strtoull`.
- Then revert `kernel.S:406–413` to plain `.quad` GDT entries. (`mmu.c`
- has no workaround — uses C-side `u64` constants — and needs no change.)
- Existing memory `project_tcc_arm64_svcul_truncation.md` describes an
- unrelated arm64 codegen-side truncation already patched in
- `scripts/simple-patches/tcc-0.9.26/arm64-svcul-no-truncate*`; track
- separately.
-
-- **AT.2. `.note.*` SHT_NOTE / PT_NOTE.** [done] tcc emitted
- `.note.*` sections as `SHT_PROGBITS` and never wrote a `PT_NOTE`
- phdr, forcing the post-link `seed-kernel/scripts/elf-pvh-note.c`
- tool to rewrite the ELF for amd64 PVH boot. Three patches landed in
- `scripts/simple-patches/tcc-0.9.26/`:
- - `note-section-sht-note.{before,after}` — in `tccelf.c`
- `find_section()`, name-prefix-check `.note*` → create as `SHT_NOTE`.
- - `pt-note-phdr.{before,after}` — in `tccelf.c:elf_output_file`,
- gate `phnum++` on "any `SHT_NOTE+SHF_ALLOC` section exists" (else
- aarch64/riscv64 phnum perturbs and breaks the A5 reproducibility
- target); compute `min(sh_offset)` / `max(sh_offset+sh_size)`
- over those sections; fill the leftover `PT_NULL` slot with one
- `PT_NOTE` after `fill_unloadable_phdr` runs.
- - `load-obj-accept-sht-note.{before,after}` — in
- `tccelf.c:tcc_load_object_file`, add `SHT_NOTE` to the
- accepted-section-type list. Strict pair with the find_section
- change: without it, .o files emitted by the patched compiler
- drop their `.note*` sections during link, then the subsequent
- `.rela.note.*` merge derefs `sm_table[].s == NULL` and segfaults
- the linker. (Found during validation: aarch64 was byte-identical,
- amd64 boot6 link crashed under QEMU until this third patch.)
- Then deleted `seed-kernel/scripts/elf-pvh-note.c`, the
- `cp seed-kernel/scripts/elf-pvh-note.c` block in
- `scripts/prep-src.sh`, the `if [ "$ARCH" = amd64 ]` block in
- `scripts/boot6.sh`, and the amd64 PVH-fixup block in
- `scripts/boot6-gen-runscm.sh`. `seed-kernel/arch/amd64/kernel.lds`
- is unchanged (consumed by clang+ld.lld, not tcc).
-
-- **AT.3. riscv64 inline-asm — audit + memory update only, no patch.**
- Audit found `riscv64-asm.c:801–819` ships only stubs for
- `subst_asm_operand` (loud error), `asm_gen_code` (empty), and
- `asm_compute_constraints` (empty) — same silent-drop class as arm64
- per memory `project_tcc_inline_asm_silent_drop.md`. But every
- register-asm callsite in the repo is already worked around: musl
- syscall/tp/atomics overrides under
- `vendor/upstream/musl-1.2.5-overrides/arch/riscv64/`, and 13 externs
- in `seed-kernel/arch/riscv64/arch.h` defined in `kernel.S`. There
- is no live miscompile to fix. A real Phase-3 inline-asm port (~400+
- LOC, parallel to the unstarted arm64 Phase 3) is well out of scope.
- AT.3 work:
- - Update `project_tcc_inline_asm_silent_drop.md` to add a riscv64
- section.
- - Document in `docs/TCC.md` that riscv64 and arm64 share the
- silent-drop bug class until Phase 3 lands.
- Three small wins that *don't* need any tcc work and can land here:
- inline `arch_mmio_ptr` (pure pointer arithmetic), `arch_system_off`
- (one MMIO write), and the `saved_user_sp` accessors in pure C — see
- AT.4 for context.
-
-- **AT.4. arch.h API uniformity (refactor, not inline-asm).** The
- previous framing — "amd64/riscv64 use externs, aarch64 inlines" —
- was wrong. aarch64 also externs every primitive (arch.h:51–60); the
- difference is one layer up: aarch64 exposes a small primitive set
- (`sysreg_read/write`, `arm64_barrier(kind)`, `cpu_pause(kind)`,
- `arm64_psci_call`) and synthesizes the `arch_*()` API as macros
- (arch.h:62–72). amd64/riscv64 spell each `arch_*` as its own
- dedicated extern. There is **zero** `__asm__`/inline-asm in any
- seed-kernel C file, so AT.4 has no tcc dependency. Refactor:
- - amd64: introduce `amd64_fence(kind)`, port-io and msr dispatcher
- helpers; collapse the per-API externs in
- `seed-kernel/arch/amd64/kernel.S:219–378` into the primitive set;
- rewrite `arch_*()` in `arch.h` as macros.
- - riscv64: introduce `riscv_csr_read/write(id)`,
- `riscv_fence(kind)` (parallel to `sysreg_read`/`arm64_barrier`);
- same macro rewrite for `arch_*()`.
- - `arch_idle_forever` and `arch_system_off` may stay as their own
- externs (don't compress into a `(kind)` dispatcher cleanly) —
- aarch64 keeps `arm64_psci_call` in the same shape.
-
-**Touch list.**
-- `vendor/mes-libc/mes/abtol.c` (AT.1 preferred fix) **or** new patch
- `scripts/simple-patches/tcc-0.9.26/tccasm-asm-expr-parse-number.*`
- (AT.1 alternative).
-- `scripts/simple-patches/tcc-0.9.26/note-section-sht-note.*`,
- `pt-note-phdr.*`, and `load-obj-accept-sht-note.*` (AT.2). The
- patch directory is `scripts/simple-patches/tcc-0.9.26/` (not
- `seed-kernel/simple-patches/`, which doesn't exist).
-- `scripts/stage1-flatten.sh`: list any newly-added patches in
- `apply_our_patch` (around line ~223); regenerate the flattened tree.
-- `seed-kernel/arch/amd64/kernel.S`: lines 406–413 (`.quad` GDT
- revert, AT.1); lines 219–378 (refactor to primitive set, AT.4).
-- `seed-kernel/arch/amd64/arch.h`: rewrite `arch_*()` as macros (AT.4).
-- `seed-kernel/arch/riscv64/{kernel.S,arch.h}`: same refactor (AT.4).
-- `seed-kernel/arch/riscv64/arch.h`: inline `arch_mmio_ptr`,
- `arch_system_off`, `arch_read_user_sp`, `arch_write_user_sp` (AT.3
- side wins).
-- `seed-kernel/scripts/elf-pvh-note.c`: delete (AT.2).
-- `scripts/prep-src.sh`: drop lines 121–123 staging elf-pvh-note.c
- (AT.2).
-- `scripts/boot6.sh`: drop lines 80–89 (AT.2).
-- `scripts/boot6-gen-runscm.sh`: drop lines 99–117 (AT.2).
-- `docs/TCC.md`: document the AT patch set; add the riscv64+arm64
- silent-drop note (AT.3).
-- Update `project_tcc_inline_asm_silent_drop.md` to cover riscv64
- (AT.3). Other `project_tcc_*` memories stay as historical record.
-
-**Validation.**
-- AT.1: `readelf -x .data build/amd64/$DRIVER/boot1/M1pp` (or any
- amd64 binary using a 64-bit hex literal) shows correct upper bits.
- amd64 boots after `.quad` revert.
-- AT.2: `readelf -S build/amd64/$DRIVER/boot6/Image` shows `.note.Xen`
- type `NOTE`; `readelf -l` shows one `PT_NOTE` covering it. `cmp`
- vs the pre-patch post-fixup ELF byte-identical at the affected
- offsets. amd64 boots through QEMU PVH `-kernel`.
-- AT.2 reproducibility: aarch64/riscv64 `readelf -l` phdr counts
- unchanged from pre-patch (proves the SHT_NOTE-presence gate works).
-- AT.3: docs+memory updated; no functional change expected.
-- AT.4: seed-kernel builds on all three arches with the new macro
- layer; amd64/riscv64 byte output unchanged (refactor only).
-- All test suites still pass. `boot6` has no amd64-conditional shell
- logic.
-
----
-
-### [DONE] AX. tests/ Makefile
-
-**Goal.** Tests are a separate concern with their own Makefile. Top-level
-build is decoupled from test dispatch.
-
-**Status note.** Landed as a single phase. Per-suite split happened via
-`SUITE=<name>` dispatch (kept to minimize churn) rather than per-target
-phony names like `test-cc`; `make test SUITE=cc` and `make test` (all)
-both resolve through `tests/Makefile`. The standalone `make -C tests`
-entry detects a missing `REPO_ROOT` and re-execs the top-level Makefile
-with goal forwarding.
-
-**Touch list (landed).**
-- `tests/Makefile`: includes the legacy test-build rules (M1pp/hex2pp/
- scheme1/cc/tcc-boot2/tcc-tcc/tcc-tcc-tcc/mes-libc) and the suite
- dispatch. Top-level `Makefile` sets `REPO_ROOT := $(CURDIR)` and
- `include tests/Makefile`.
-- `tests/lib-runner.sh`: `discover`, `report`, `show_diff`, `fail`,
- `compare_runtime`, `read_expected`. Sourced by `tests/run-suite.sh`.
-- Moved `scripts/{run-tests,boot-run-tests,boot-build-p1,boot-build-p1pp,
- boot-build-cc,seed-accept*}.sh` → `tests/{run,run-suite,build-p1,
- build-p1pp,build-cc,seed-accept}.sh`. The three seed-accept scripts
- collapsed into one with a `kernel | boot34 | boot5` mode arg.
-- `boot-build-p1{,pp}.sh` retirement was scoped to "move under tests/"
- rather than full deletion: they are still pure transformations the
- test path needs (single P1pp → ELF), and the bootN.sh chain doesn't
- expose an equivalent.
-- Test fixtures normalized to `NNN-` prefix everywhere. `cc-cg`,
- `cc-libc`, `cc-lex`, `cc-pp`, `cc-util`, `M1pp` migrated NN- → NNN-;
- `tests/P1` (was unprefixed) got sequential NNN- prefixes assigned
- alphabetically.
-- Golden-file convention unified to `.expected` (primary output) /
- `.expected-exit` (exit code). `.expected-toks` renamed → `.expected`
- in `cc-lex`/`cc-pp`; the suite contract still differentiates by
- pipeline (lex/pp pipelines emit token streams to stdout).
-- `tests/README.md` documents the per-suite contract, fixture-naming
- rule, and how to add fixtures.
-
-**Validation.** `make test` from repo root green on the default arch.
-`make -C tests <target>` delegates back to the parent. `tests/Makefile`
-is self-contained when `REPO_ROOT` is exported.
-
----
-
-### A2. Audit manifest + hashes
-
-**Goal.** A single command produces `manifest.txt` + `sha256.txt`
-covering the canonical source tree and every boot stage's outputs, with
-provenance.
-
-**Notes.**
-- The canonical source tree from A0 is the audit basis. A2 adds the
- manifest and the hashes; the bytes are already there.
-- For host-flattened tcc/libc: snapshot the flattened source (per
- decision), record the flatten script's SHA in the manifest line so
- the recipe is identified.
-- One audit per `<arch>/<driver>` pair. Compare-drivers (A5) diffs two
- manifests.
-
-**Touch list.**
-- New: `scripts/build-audit.sh`. Walks `build/<arch>/src/` and
- `build/<arch>/<driver>/boot{0..6}/`; emits
- `build/<arch>/<driver>/audit/manifest.txt` (one line per file:
- `<path> <stage> <role> <origin> <sha256>`) and
- `build/<arch>/<driver>/audit/sha256.txt` (flat hash list).
-- `Makefile`: `audit` target depending on prep-src + boot0..boot6 for
- the active driver.
-
-**Validation.** `sha256sum -c build/<arch>/<driver>/audit/sha256.txt`
-clean. Line counts of `manifest.txt` and `find` agree.
-
----
-
-### A5. `make compare-drivers` reproducibility target
-
-**Goal.** Mechanical proof: every `boot{N}/` output artifact is
-byte-identical between `DRIVER=podman` and `DRIVER=seed`.
-
-**Recipe.**
-1. `DRIVER=podman make build/$arch/podman/boot6/Image`
-2. `DRIVER=seed make build/$arch/seed/boot6/Image`
- (the seed driver consumes the podman-built kernel for its QEMU run)
-3. `diff -r build/$arch/podman/boot{0..6}/ build/$arch/seed/boot{0..6}/`
-4. `diff build/$arch/{podman,seed}/audit/manifest.txt` (provenance lines
- may differ; hashes must match for shared artifacts).
-
-**Touch list.**
-- New: `scripts/compare-drivers.sh`.
-- `Makefile`: `compare-drivers` target wrapping it.
-
-**Pre-step: determinism audit.** Find and eliminate non-determinism
-sources before declaring success. Likely suspects:
-- ELF timestamps (any `__DATE__`/`__TIME__` macros in tcc/musl/kernel)
-- cpio mtimes when packing initramfs (lib-pipeline.sh, lib-runscm.sh,
- seed-kernel build) — pin to epoch 0 or fixed value
-- File-iteration order in shell glob enumeration (boot5 musl source
- walk is the main risk; `LC_ALL=C sort` everything)
-- Any `find -printf` ordering, tar mtimes, ar mtimes
-
-**Validation.** `make compare-drivers ARCH=aarch64` exits 0. Per arch,
-report the highest stage that converges (amd64/riscv64 may be partial
-until the live blockers in `docs/SEED-{AMD64,RISCV64}-TODO.md` are
-cleared).
-
----
-
-## B. Polish (independent, do anytime)
-
-### Docs
-- `README.md`: arch matrix; DRIVER explanation; pointer to
- `scripts/boot.sh` and `make build/<arch>/<driver>/boot6/Image`;
- test-suite section.
-- `docs/SEED-AMD64-TODO.md`: prune items completed by 799eba0; keep
- amd64 boot3+ validation, fixed-point check, run-tests under seed.
-- `docs/SEED-RISCV64-TODO.md`: prune items completed by e4bfcde; the
- boot0 stage-4 user-trap blocker stays.
-- `docs/SEED-VIRTIO-BLK.md`: banner marking it as design notes, not
- pipeline state.
-- Audit `docs/OS.md`, `docs/LIBC.md`, `docs/LIBC.txt`,
- `docs/TCC-ARM64-ASM.md` for staleness vs current code.
-- `cc/cc.scm.md` → `docs/CC-IMPL.md` (or fold into `docs/CC.md`).
-- `docs/TCC.md`: rewrite around the AT patch set.
-
-### kernel.c
-- DTB / cpio / hvm_start_info magics → named constants with one-line
- comments.
-- amd64 PVH `hvm_start_info` path: inline the one-sentence ABI note
- from SEED-AMD64-TODO.
-- `sys_unlinkat` flags param: two-line guard or a comment.
-
-### bootN.sh polish (mostly folded into A6, A0, A4)
-- `boot4.sh` `TCC_BOOTSTRAP_RELAX_FIXEDPOINT`: surface in
- `boot.sh --help`.
-- Standardize argument convention: positional `$ARCH` only;
- env-vars only for tunables (`DRIVER`, `BOOT*_TIMEOUT`, etc.).
-
----
-
-## Validation gates
-
-After each phase:
-1. `make test` (all suites) green on the default arch.
-2. `scripts/boot.sh aarch64` end-to-end clean on both drivers (or the
- equivalent `make build/aarch64/<driver>/boot6/Image` after A4).
-3. Diff `build/aarch64/<driver>/boot{0..6}/` trees against the previous
- phase's outputs — phases A6, A3, A0, A4, AX must be no-ops for
- output bytes. AT changes outputs (intentionally — workarounds gone).
- A2 adds the audit tree. A5 is the proof.
-
-After all phases:
-- `make compare-drivers ARCH=aarch64` exits 0.
-- `make compare-drivers ARCH=amd64` and `ARCH=riscv64` report progress
- up to the current seed-driver completion line.
-- `tree build/<arch>/src/` is the audit basis: one tree, one read.
diff --git a/docs/SEED-AMD64-TODO.md b/docs/SEED-AMD64-TODO.md
@@ -1,122 +0,0 @@
-# amd64 seed-kernel TODO
-
-Working doc. Captures the tcc 0.9.26 limitations we worked around
-to bring up `boot6 amd64` and `DRIVER=seed ./scripts/boot.sh amd64`,
-plus what's still unvalidated. Pairs with `docs/OS.md` (kernel
-contract), `docs/TCC.md` (compiler), and `docs/SEED-RISCV64-TODO.md`
-(parallel write-up for the riscv64 path).
-
-## Goal
-
-`DRIVER=seed ./scripts/boot.sh amd64` should run the full
-boot0→boot6 chain entirely inside the tcc-built amd64 seed kernel
-(the kernel is its own build driver, podman only mints the first
-kernel image). This validates every kernel path the chain depends
-on under real workloads.
-
-## What works (May 2026)
-
-- `scripts/boot6.sh amd64` (DRIVER=podman) builds a clean PVH ELF at
- `build/amd64/boot6/kernel.elf`. Boots under
- `qemu-system-x86_64 -machine microvm -kernel ...`; runs scheme1 +
- tcc3 + the user smoke tests under `seed-kernel/run.sh ARCH=amd64`.
-- `DRIVER=seed scripts/boot6.sh amd64` self-rebuilds the kernel
- inside itself (closes the bootstrap loop).
-- `DRIVER=seed scripts/boot{0,1,2}.sh amd64` complete cleanly on the
- tcc-built kernel (boot0 ≈1 s, boot1 ≈14 s, boot2 ≈43 s under TCG).
- Outputs are `build/amd64/boot{0,1,2}/...`.
-
-## tcc 0.9.26 limitations worked around for amd64
-
-These are amd64-specific gotchas; the existing `docs/TCC.md` and
-the simple-patches in `scripts/simple-patches/tcc-0.9.26/` cover the
-shared/aarch64/riscv64 issues.
-
-1. **`.quad` literal truncation.** tcc's assembler silently
- truncates a `.quad` value to its low 32 bits when the high half
- is non-zero (`gen_le64(int64_t c)` is fed a value already lost
- through the parser path). The amd64 GDT entries
- (`0x00af9a000000ffff`, `0x00af92000000ffff`) are the natural
- trip wire — without the workaround, the bootloader's `lgdt`
- loads zero P/L bits, the long-mode `ljmp` raises #GP, and the
- kernel never prints anything.
- - **Workaround:** `seed-kernel/arch/amd64/kernel.S` encodes each
- descriptor as `.long lo, hi` so the high half is parsed as a
- fresh 32-bit literal. Comment in-place explains why.
- - **TODO:** add a `simple-patch` to fix `.quad` parsing in
- tccasm.c so the source can use the natural form. Suspected
- site is the parser path that intermediates 64-bit values
- through a 32-bit `int` before reaching `gen_le64`.
-
-2. **No PT_NOTE program header / SHT_NOTE section type.** tcc's
- linker emits exactly two PT_LOAD phdrs for static EXEs (no
- PT_NOTE), and `find_section` defaults every assembler-created
- section to SHT_PROGBITS regardless of name. QEMU's PVH
- `-kernel` path scans PT_NOTE phdrs for the Xen 18 note that
- names the 32-bit entry; without one it errors out with
- "Error loading uncompressed kernel without PVH ELF Note".
- - **Workaround:** `seed-kernel/scripts/elf-pvh-note.c` is a
- tiny tcc-built post-link tool that locates `.note.Xen` via
- the section header table, retypes the section as SHT_NOTE,
- and writes a fresh PT_NOTE phdr at `phoff + phnum*phentsize`
- (the gap between Ehdr/Phdrs and the first PT_LOAD's content
- at offset `s_align` = 0x200000 has plenty of room). Wired
- into `boot6-gen-runscm.sh` and `boot6.sh` for amd64 only.
- - **TODO:** patch tcc to (a) detect ".note.*" section names and
- create them as SHT_NOTE, and (b) bump phnum + emit PT_NOTE
- phdrs for each SHT_NOTE alloc section after `layout_sections`.
- That removes the need for the post-link tool.
-
-## amd64 kernel additions (not tcc workarounds)
-
-These are real kernel features that aarch64/riscv64 already had in
-some form and amd64 was missing. Listing them so the riscv64-style
-TODO has full context, not because they're broken.
-
-- **MSR-based `syscall` entry path.** scheme1 (and any tcc-built
- user binary that follows the SysV/Linux amd64 ABI) emits the
- `syscall` instruction, which goes through MSR_LSTAR — not the
- IDT. The previous kernel only handled `int $0x80`. Added
- `amd64_syscall_entry` in `kernel.S` and `MSR_EFER.SCE | MSR_STAR
- | MSR_LSTAR | MSR_SFMASK` setup in `mmu.c::setup_cpu_tables`.
-- **CR0.MP / CR4.OSFXSR / CR4.OSXMMEXCPT.** tcc emits xmm-spill
- prologues (`movq %xmm7, -0x20(%rbp)`) in user binaries; without
- these CR bits set, the first such instruction raises #UD/#NM.
- The gcc-built kernel + `-mno-sse` user (user/hello.c) avoided
- this; tcc has no `-mno-sse` equivalent so we enable SSE in
- long-mode init.
-- **PVH `hvm_start_info` cmdline parsing.** microvm has no DTB.
- PVH passes the `hvm_start_info` phys addr in EBX; we preserve
- it through long-mode init and pass to `kmain`. `parse_dtb` in
- `kernel.c` recognises magic 0x336ec578 and reads
- `cmdline_paddr` at +0x18 to populate `dt.bootargs`.
-- **Legacy `SYS_open(2)` alias.** amd64 stage0 `hex0-seed` issues
- `syscall 2` (Linux's legacy open) directly. Added
- `ARCH_SYS_open` in amd64 arch.h and a switch case that aliases
- to `sys_openat(AT_FDCWD, ...)`.
-
-## What's not yet validated
-
-1. **boot3 → boot6 under DRIVER=seed amd64.** boot3 (scheme1
- driving cc.scm to compile tcc.flat.c + libc.flat.c → tcc0)
- is compute-bound under TCG-emulated x86_64 on Apple Silicon
- (no hvf for amd64). The aarch64 path uses hvf and runs at
- near-native speed; amd64 is 5–20× slower. Concretely: boot3
- timed out at the previous 1800 s default. We bumped the
- timeout knob (`BOOT3_TIMEOUT`, `BOOT4_TIMEOUT`) and a 4 h
- boot3 run is in progress — see `build/amd64/.boot3-stage/
- transcript.txt` for the live log.
-2. **Fixed-point check.** Once boot3/4/5 complete, the artifacts
- (catm/scheme1/tcc0/tcc1/tcc2/tcc3/libc.a/...) should byte-match
- the DRIVER=podman amd64 outputs. Untested.
-3. **`scripts/run-tests.sh amd64`** under DRIVER=seed. Untested.
-4. **boot4's tcc fixed-point assertion (`tcc2 == tcc3`)** under
- DRIVER=seed amd64. Untested.
-
-## Cost / acceleration note
-
-The amd64 seed driver runs under `qemu-system-x86_64 -cpu max`
-with TCG (no hvf for x86_64 on Apple Silicon). A full
-`DRIVER=seed scripts/boot.sh amd64` is realistically a multi-hour
-operation. The aarch64 path is the fast/CI iterating loop; the
-amd64 seed path is mostly there for parity validation.
diff --git a/docs/SEED-VIRTIO-BLK.md b/docs/SEED-VIRTIO-BLK.md
@@ -1,359 +0,0 @@
-# Seed kernel: virtio-blk for I/O
-
-Plan to replace the cpio-initrd-in / UART-hex-out I/O shape of
-`seed-kernel/` with a pair of virtio-blk-MMIO devices: one read-only
-disk carrying the boot inputs (cpio newc, byte-identical to today's
-`-initrd`) and one read-write disk for outputs. UART stays the console.
-
-## Motivation
-
-Current shape (`seed-kernel/run.sh`, `lib-seed-runscm.sh:98-103`):
-
-- **In**: QEMU `-initrd` loads cpio newc; kernel finds it via DTB
- `/chosen/linux,initrd-{start,end}` (kernel.c:286-296), unpacks via
- `parse_cpio` (kernel.c:429-465) into the in-memory tmpfs.
-- **Out**: PL011 UART is the only egress. On exit (`dumpfs` bootarg),
- `dump_tmpfs` (kernel.c:926-939) hex-encodes every tmpfs file framed by
- `=== DUMP-BEGIN ===` / `=== FILE … ===` / `=== DUMP-END ===` sentinels.
- `seed-kernel/scripts/extract-dump.sh` reassembles host-side.
-
-This works but has real costs:
-
-1. **Output throughput.** Hex doubles size; PL011 is byte-at-a-time MMIO.
- For boot5 (full musl + libc.a + crt*.o, tens of MB), UART dump is the
- dominant wall-time cost on TCG and a non-trivial slice under HVF.
-2. **No mid-run egress.** Output exists only at exit. A crash mid-build
- loses everything in tmpfs.
-3. **Symmetry.** Inputs ride a structured device (initrd memory region);
- outputs ride a debug device (UART). Two separate framings.
-4. **Optionality.** Once the kernel can talk to virtio-blk, mounting a
- real on-disk artifact cache (across multiple runs) becomes trivial.
-
-## Non-goals
-
-- Generalised block layer, partitions, ext2/FAT, write-back cache,
- buffer cache, multi-queue, MSI-X, IRQ delivery.
-- virtio-net, virtio-9p, virtio-console, PCI transport.
-- Replacing the in-memory tmpfs semantics user code sees (`sys_openat`,
- `sys_read`, `sys_write` against `files[]` stays exactly as-is).
-
-The seed kernel keeps its single-process, polling-only,
-no-interrupts shape. virtio-blk just swaps the boot-time *transport*
-and the exit-time *dump format*.
-
-## Design
-
-Two virtio-blk-MMIO devices on QEMU virt:
-
-```
--drive file=in.img,if=none,format=raw,id=hd0,readonly=on \
--device virtio-blk-device,drive=hd0 \
--drive file=out.img,if=none,format=raw,id=hd1 \
--device virtio-blk-device,drive=hd1
-```
-
-`in.img` is the cpio newc archive (today's `initramfs.cpio`), padded to
-a 512-byte multiple. `out.img` is a pre-allocated zero file sized to an
-upper bound (≈256 MB covers boot5 worst case).
-
-Boot flow:
-
-1. `kmain` brings up MMU as today.
-2. New `virtio_blk_init()` walks DTB nodes named `virtio_mmio@*`
- (compatible `"virtio,mmio"`); for each, probe MagicValue / Version /
- DeviceID. ID 2 = block; finish device init for each, then read
- sector 0 to classify (cpio magic → blk0, otherwise → blk1). Panic
- if the count of either is not exactly 1.
-3. `parse_cpio` is fed by `blk_read_all(blk0)` instead of the initrd
- memory region. cpio bytes land in the existing kheap-backed buffer;
- `parse_cpio` is unchanged.
-4. On exit, kernel writes a serialised tmpfs to blk1 in the flat
- format described below, then PSCI off. No bootarg gating — the
- write is unconditional (a no-op if `files[]` is empty).
-5. Host reads blk1 with a small extractor (`extract-blk.sh`).
-
-UART stays the console; `uart_puts` everywhere is untouched. The
-`dumpfs` bootarg, `dump_tmpfs`, the `=== DUMP-{BEGIN,END} ===` /
-`=== FILE … ===` sentinels, and `scripts/extract-dump.sh` are all
-deleted in the same change.
-
-### On-disk layout for outputs
-
-Tiny custom format — no FS. Sector-aligned (512 B), little-endian, all
-offsets in sectors:
-
-```
-sector 0 magic "SEEDFS\0\0" (8B) | nfiles u32 | reserved u32
- followed by nfiles directory entries:
- path[96] | data_offset_sectors u32 | size_bytes u64 | _pad
- (entry size 112 B → 4 entries/sector → sector 1.. for table)
-sector N.. file data, each file padded up to 512-byte boundary
-```
-
-Reusing the existing `path[96]` and `MAX_FILES=4096` from `struct file`
-keeps the table at ≤900 KB (under 2 sectors of header + ~896 KB table).
-The host extractor walks the table and writes each file out.
-
-This is roughly "cpio-without-the-headers-per-file." Could equally
-write cpio newc back; the flat table is just smaller code in the
-kernel (no hex name length, no per-entry headers, no parse loop).
-
-## Memory / DMA
-
-virtio-mmio descriptors carry **physical** addresses. Kernel-side
-buffers must therefore have known PAs.
-
-The current MMU (kernel.c:144-213) gives us this for free in two
-regions:
-
-- **`L1[1..3]`** identity-maps VA 1..4 GB to PA 1..4 GB as Normal
- memory. Kernel image (0x40080000) and kheap (0x40xxxxxx..0x4b000000)
- live here, so any `kalloc()`'d buffer has VA == PA. `mem_cpy` etc.
- work directly.
-- **`L1[4]`** is a 1 GB Device block aliasing PA 0..1 GB at VA 4..5 GB,
- which is how we already reach UART; we'll reach the virtio-mmio
- control regs at `0x0a000000..0x0a004000` through the same alias
- (`DEVICE_ALIAS_BASE + 0x0a000000`).
-
-So DMA buffers come from `kalloc()` (Normal, identity-mapped, VA==PA)
-and the device regs from the existing high alias. No MMU changes.
-
-**Cache coherency.** virtio-mmio in QEMU is `dma-coherent` per the DTB
-(`/virtio_mmio@…/dma-coherent`); virtio-mmio v2 + the modern feature
-bits assume coherent DMA. Inner-shareable WBWA (already programmed in
-TCR/MAIR) plus DMB before NotifyQueue and DMB after reading the used
-ring is sufficient. No explicit cache maintenance ops.
-
-**Reservation.** virtio queue memory must be 4 KB-aligned. The cpio
-read buffer must be sized to the cpio length (fetched from blk0 capacity
-and trimmed at parse). Sizes of interest today:
-
-- boot5 cpio ≈ 30-80 MB. Already fits in current kheap (192 MB).
-- Output blob: bound by tmpfs total bytes. Current `kheap_end =
- 0x4b000000` allows ~176 MB heap; sufficient for boot5's output
- (≈10s of MB). Sizing is unchanged from today's cpio-in-RAM design.
-
-Conclusion: no memory layout changes required. Only one new fixed
-allocation: a small (single 4 KB page) virtqueue area per device.
-
-## virtio-blk-MMIO driver shape
-
-A polling, single-virtqueue, one-request-at-a-time driver. Spec ref:
-virtio 1.2 §4.2 (MMIO transport) and §5.2 (block device).
-
-**Layout (one struct `virtio_mmio` per device):**
-
-```c
-volatile u32 *regs; // VA in DEVICE_ALIAS_BASE+phy
-struct vring_desc desc[8]; // 8 descriptors plenty (we issue 1 at a time, 3 chained)
-struct vring_avail avail;
-struct vring_used used;
-u16 next_desc;
-u16 last_used;
-u64 capacity_sectors;
-```
-
-**Registers used (offsets from §4.2):**
-
-`MagicValue (0x000)`, `Version (0x004)`, `DeviceID (0x008)`,
-`DeviceFeatures (0x010)` / `DeviceFeaturesSel (0x014)`,
-`DriverFeatures (0x020)` / `DriverFeaturesSel (0x024)`,
-`QueueSel (0x030)`, `QueueNumMax (0x034)`, `QueueNum (0x038)`,
-`QueueReady (0x044)`, `QueueNotify (0x050)`,
-`InterruptStatus (0x060)`, `InterruptACK (0x064)`,
-`Status (0x070)`, `QueueDescLow/High (0x080/084)`,
-`QueueDriverLow/High (0x090/094)`, `QueueDeviceLow/High (0x0a0/0a4)`,
-`Config (0x100)` (block: 8-byte capacity at +0).
-
-**Init sequence (§3.1.1):**
-
-1. `MagicValue == 0x74726976` ("virt"), `Version == 2`, `DeviceID == 2`.
-2. `Status = 0` (reset), then `|= ACKNOWLEDGE`, then `|= DRIVER`.
-3. Read `DeviceFeatures` (sel 0 and 1); negotiate `VIRTIO_F_VERSION_1`
- only (bit 32). Refuse `VIRTIO_BLK_F_RO` if mismatched with intent
- (we set `readonly=on` on blk0 so the device offers RO; the driver
- doesn't need to *negotiate* RO since we just won't issue writes).
-4. `Status |= FEATURES_OK`; reread Status to confirm.
-5. `QueueSel = 0`; `QueueNumMax` (≥8 always on QEMU); `QueueNum = 8`.
-6. Allocate 4 KB-aligned 4 KB page; lay out desc[8] / avail / used per
- §2.7, write `QueueDesc{Low,High}`, `QueueDriver{Low,High}`,
- `QueueDevice{Low,High}` to PAs.
-7. `QueueReady = 1`; `Status |= DRIVER_OK`.
-8. Read `Config + 0` for capacity (sectors).
-
-**Request shape (§5.2.6):** chain of three descriptors:
-
-```
-desc[0]: read-only, points to struct virtio_blk_req_hdr {u32 type, u32 reserved, u64 sector}
-desc[1]: write-only (for read req) / read-only (for write req), points to data buffer (multi-sector OK with one descriptor per spec; in practice we use 1 desc per ≤4 MB chunk and loop)
-desc[2]: write-only, 1 byte status
-```
-
-Add head index to `avail.ring[avail.idx % qsz]`, `dmb ishst`,
-`avail.idx++`, `dmb ishst`, `regs[QueueNotify] = 0`, then poll
-`used.idx` until it advances past `last_used`. Read status byte;
-0 = OK, else fail.
-
-Chunk size: pick 1 MB per request (2048 sectors). Cpio fetch loops
-until `capacity_sectors` sectors are read or the cpio TRAILER is seen
-(we can also just read all of `capacity_sectors` since `in.img` is
-sized to the cpio).
-
-### Public API
-
-```c
-int blk_init(void); // probes DTB, finds blk0/blk1
-u64 blk_capacity(int dev); // sectors
-int blk_read (int dev, u64 sector, void *buf, u64 nsectors);
-int blk_write(int dev, u64 sector, const void *buf, u64 nsectors);
-```
-
-Used by:
-
-- `kmain`: `blk_init()`; `blk_read(0, 0, cpio_buf, blk_capacity(0))`,
- then `parse_cpio(cpio_buf, capacity*512)`.
-- `dump_tmpfs_blk()`: serialise `files[]` into the SEEDFS layout
- described above and `blk_write(1, …)`.
-
-## DTB walking
-
-`parse_dtb` (kernel.c:254-317) currently records only `chosen.initrd*`
-and the `memory@…` reg. Extend with a callback that, when entering a
-node whose name starts with `"virtio_mmio@"`, captures up to N reg
-tuples into a `dtb_info::virtio_mmio[]` array (PA + size). The MMU
-device alias already covers all of these.
-
-Subtlety: per QEMU virt, only some of the 32 virtio-mmio slots are
-populated — unpopulated slots return `MagicValue==0` / `DeviceID==0`.
-The driver init must skip those.
-
-## Build-system changes
-
-`seed-kernel/Makefile`:
-
-- Add `kernel.c` dep on a new `virtio_blk.c` (or inline in `kernel.c`
- to keep the single-TU shape — leaning toward inline; the driver is
- ≤300 lines).
-- Add `$(OUT)/in.img` rule: copy `initramfs.cpio`, pad to a
- 512-byte multiple with `truncate -s %512`.
-- Add `$(OUT)/out.img` rule: `truncate -s 256M`.
-- Update `run.sh`: drop `-initrd "$INITRD"`; add the two
- `-drive`/`-device` pairs above. `INITRD` becomes `IN_IMG`,
- `OUT_IMG` is created fresh per run.
-
-`scripts/lib-seed-runscm.sh`:
-
-- Replace `-initrd "$INITRAMFS"` with the two-disk variant.
-- Drop `dumpfs` from the `-append` line (no longer recognised).
-- Replace `"$EXTRACT" … "$TRANSCRIPT"` with
- `extract-blk.sh "$S_OUT_DIR" "$OUT_IMG"`. The DUMP-END grep guard
- is replaced by checking that `extract-blk.sh` finds the SEEDFS
- magic at sector 0; absence means the kernel didn't reach exit.
-
-`seed-kernel/scripts/extract-blk.sh`: reads sector 0 magic, walks
-the table, writes files. Output contract matches what
-`extract-dump.sh` produced (same filenames in the same dump dir),
-so `seed_runscm_export` and downstream acceptance scripts don't
-need to change.
-
-`seed-kernel/scripts/extract-dump.sh` is deleted; `tier1-gate.sh`
-and `tier2-gate.sh` switch their `EXTRACT` envvar / direct calls
-to `extract-blk.sh`.
-
-## Implementation order
-
-Single branch, single landing. Internal checkpoints in a sensible
-order; no dual paths in the tree at any commit boundary.
-
-1. **Add `virtio_blk` driver and DTB enumeration.** Extend
- `parse_dtb` to record `virtio_mmio@…` reg tuples. Add the driver
- (init, `blk_read`, `blk_write`). Not yet wired into `kmain`.
- Sanity check: a unit-test `kmain` that probes and prints
- capacity for both disks boots cleanly under a hand-built
- `run.sh` with two `-drive`/`-device` pairs.
-2. **Cut over input path.** Replace the initrd-region read in
- `kmain` with `blk_read(0, 0, cpio_buf, blk_capacity(0))`. Delete
- the `chosen.initrd-{start,end}` handling from `parse_dtb` and
- the "no initrd" panic. Update `Makefile` to produce `in.img`
- from `initramfs.cpio`.
-3. **Cut over output path.** Add `dump_tmpfs_blk` and
- `extract-blk.sh`. Delete `dump_tmpfs`, `dump_tmpfs`'s sentinels,
- the `dumpfs` bootarg parser, `g_dumpfs`, and
- `scripts/extract-dump.sh`. `dump_tmpfs_blk` runs unconditionally
- from `sys_exit_final` before PSCI off.
-4. **Acceptance.** Run `tier1-gate.sh`, `tier2-gate.sh`,
- `tests/seed-accept.sh`, `tests/seed-accept.sh boot34`,
- `tests/seed-accept.sh boot5`.
- All must produce byte-identical artifacts to the prior
- (cpio+dumpfs) tree at `HEAD~1`. Expect boot5 to surface any
- off-by-one in the directory table fastest (≈3900 tmpfs entries).
-
-## Decisions (resolved)
-
-- **Console.** PL011 stays. `uart_putc/_puts/_putd/_putx` and user
- `write(1, …)` are unchanged. Only the file dump moves to virtio.
-- **virtio version.** Pin MMIO Version == 2 (MagicValue == 0x74726976,
- Version regs read in init). Anything else: `uart_puts` a panic line
- and `wfe`. QEMU 10 (current host) and any QEMU ≥4.0 ship v2; the
- build harness has a single QEMU floor and we don't support pre-v2.
-- **Identifying blk0 vs blk1.** Slot order in the DTB does not depend
- on `-drive` attachment (verified with `dumpdtb`: all 32
- `virtio_mmio@…` nodes are present unconditionally), and QEMU's
- command-line-to-slot mapping is not contractual across versions.
- Use **content-based** identification: after enumerating all
- populated DeviceID==2 devices, read sector 0 of each and call the
- one whose first 6 bytes are `"070701"` (cpio newc magic) `blk0`;
- the other is `blk1`. If neither matches or both match, panic. This
- removes the dependency on `-drive` ordering on the qemu command
- line entirely.
-- **Output image size.** Host pre-allocates `out.img` as a 256 MB
- sparse file (`truncate -s 256M out.img`). Header at sector 0
- records total used bytes; `extract-blk.sh` reads only that many.
- No truncation of `out.img` is needed — sparse + bounded read is
- free on APFS / ext4.
-- **Initial kheap sizing.** Today `kheap_end` starts at `0x44000000`
- (64 MB) and bumps to `0x4b000000` after `parse_cpio` finishes,
- because the initrd region was reserved up to `0x4b000000`. Without
- `-initrd`, that region is free from boot, so set the initial
- `kheap_end = 0x4b000000` (176 MB). The cpio read buffer
- (`kalloc(blk_capacity(0) * 512)`) lands in this range. Boot5 cpio
- ≈ 80 MB; comfortably fits.
-- **Persistence across runs.** Out of scope. `out.img` is
- re-created (truncated to 256 MB of zeros) before each run by the
- harness; the kernel always writes a fresh header at sector 0.
-- **Per-request chunking.** 1 MB chunks (2048 sectors) per virtio
- request, single data descriptor per chunk (3-descriptor chain:
- hdr / data / status). 8-entry virtqueue, one in-flight request at
- a time, polling `used.idx`. No interrupts (`InterruptACK` written
- once per used entry to clear the device-side bit, but no IRQ
- handler — DAIF stays masked as today).
-- **Coherency.** Inner-shareable WBWA (already programmed); `dmb
- ishst` before `QueueNotify`, `dmb ish` after observing
- `used.idx` advance. No `dc civac` / `ic` ops — virtio-mmio is
- `dma-coherent` per DTB and the device DMAs into the same
- inner-shareable domain the kernel reads.
-
-## Risks (residual)
-
-- **Empty / mis-sized `in.img`.** If the harness fails to stage the
- cpio onto blk0, `parse_cpio` walks zero bytes and `find_file("init")`
- fails — exactly the same failure mode as a missing `-initrd` today
- (`kernel.c:1136-1139`). No new risk.
-- **Boot5 file count growth.** `MAX_FILES = 4096` and `path[96]`
- remain the binding limits, unchanged from today. The on-disk
- directory table is sized off these constants; bumping either
- requires a same-commit bump to `extract-blk.sh`'s parser.
-
-## Estimated effort
-
-- DTB walk extension + virtio_blk driver + integration in `kmain`:
- ~300 lines C, one work session.
-- Output serialiser + extractor: ~80 lines C + ~40 lines shell.
-- Build/run wiring + acceptance plumbing: ~50 lines shell across
- Makefile, run.sh, lib-seed-runscm.sh.
-- Stabilising acceptance against existing fixtures: a couple sessions
- to chase any byte-divergence (most likely culprit is dump ordering
- or padding, both fixable in extractor).
-
-Total: ~1-2 days of focused work, gated by byte-identical
-acceptance vs the cpio+dumpfs tree at the pre-cutover commit.
diff --git a/docs/TCC-ARM64-ASM.md b/docs/TCC-ARM64-ASM.md
@@ -1,314 +0,0 @@
-# tcc arm64 assembler — design
-
-Working doc. Adds an `arm64-asm.c` to vendored tcc 0.9.26 so the
-ARM64-target build accepts `.S` inputs and `__asm__("…")` blocks.
-Lands in two phases: a narrow first cut covering exactly what the
-repo's `.S` files need today, then incremental extension to ride
-parity with `riscv64-asm.c` and (modulo x86 quirks) `i386-asm.c`.
-
-Goal of this doc: lock the **internal shape** so phase 1 is a
-genuine subset of the final assembler — not a stub we throw away.
-
-## Why this exists
-
-`vendor/upstream/tcc-0.9.26.tar.gz` ships per-target asm:
-
-| arch | file | notes |
-|------|------|-------|
-| x86_64 / i386 | `i386-asm.c` (1720 LoC) | shared by both targets |
-| arm | `arm-asm.c` (94 LoC) | stub: directives only, every opcode → `tcc_error` |
-| riscv64 | `riscv64-asm.c` (856 LoC) | real assembler |
-| arm64 | — | absent; `CONFIG_TCC_ASM` undefined for `TCC_TARGET_ARM64` |
-
-Today the boot2 Makefile compensates for the arm64 gap by cross-asm'ing
-`tcc-cc/aarch64/start.S` and `tcc-libc/aarch64/{start,sys_stubs}.S`
-through host clang `-target aarch64-linux-gnu` (Makefile:386–410).
-This doc describes the assembler that lets us delete that.
-
-## Phase 1 — narrow scope
-
-Cover exactly the mnemonics the in-tree `.S` fixtures use, plus the
-directive surface `tccasm.c` already handles.
-
-Mnemonics required by `tcc-cc/aarch64/start.S` and
-`tcc-libc/aarch64/{start,sys_stubs}.S`:
-
-| mnemonic | forms used | encoding family |
-|----------|------------|-----------------|
-| `mov` | `mov xN, #imm` (incl. negative); `mov xN, xM` | movz/movn/movk + ORR(reg) alias |
-| `add` | `add xN, sp, #imm` | add (immediate, 64-bit) |
-| `ldr` | `ldr xN, [xM]`, `ldr xN, [sp]` | LDR (immediate, unsigned offset, 64-bit) |
-| `bl` | `bl <symbol>` | BL → `R_AARCH64_CALL26` |
-| `b` | `b .` (self-loop) | B → `R_AARCH64_JUMP26` (or in-section fixup) |
-| `ret` | `ret` (uses x30) | RET (Xn=30 default) |
-| `svc` | `svc #0`, `svc #imm16` | SVC |
-
-Registers: `x0`-`x30`, `w0`-`w30`, `sp`, `xzr`, `wzr`. (`fp`=`x29`,
-`lr`=`x30`, `ip0`=`x16`, `ip1`=`x17` are aliases — defer to phase 2.)
-
-Directives: anything `tccasm.c` already drives — `.globl`, `.text`,
-`.data`, `.byte`, `.word`, `.quad`, `.ascii`, `.asciz`, `.align`,
-`.skip`, `.section`, `.previous`, labels, `.set`. Phase 1 does
-**not** need to add code here; pulling in `tccasm.c` is automatic
-once `arm64-asm.c` defines `CONFIG_TCC_ASM`.
-
-Inline `__asm__` constraint plumbing (`subst_asm_operand`,
-`asm_compute_constraints`, `asm_gen_code`) follows `riscv64-asm.c`'s
-"defined but no-op" pattern in phase 1: `.S` files work, full
-constraint-based inline asm doesn't yet. Same posture upstream
-shipped riscv64 with.
-
-**Phase 1 acceptance:** the existing `tcc-libc` and `tcc-cc` suites
-pass on `ARCH=aarch64` with the host cross-asm path removed from
-the Makefile (start/sys_stubs assembled by tcc-boot2 itself).
-
-## File layout
-
-```
-arm64-asm.c new — opcode table, parser, encoders (~600 LoC at parity)
-arm64-tok.h new — DEF_ASM(...) for regs + mnemonics (~150 LoC at parity)
-tcctok.h +3 lines: include arm64-tok.h under TCC_TARGET_ARM64
-tcc.h +1 line: include arm64-asm.c in the per-target block
-libtcc.c +1 line: same, in the ONE_SOURCE block
-```
-
-Patches go in `scripts/simple-patches/tcc-0.9.26/` and apply via
-`stage1-flatten.sh`'s `apply_our_patch` mechanism — same shape as
-the existing arm64 patches (`arm64-stdarg-array`,
-`arm64-va-pointer-operand`, `arm64-va-arg-pointer`). New
-`arm64-asm.c` and `arm64-tok.h` ship as straight files in
-`scripts/simple-patches/tcc-0.9.26/files/` and are copied into
-`$SRC` by the flatten script before preprocessing.
-
-## Internal shape
-
-The narrow set is small enough to write linearly, but it's worth
-ten minutes more to put the real ARM64 ISA encoding model in place
-on day one so phase 2 is "add table rows," not "rewrite."
-
-### Operand model
-
-```c
-enum {
- OPT_REG, /* X/W register, 0..31 (sp/zr distinguished by use) */
- OPT_SHIFT_REG, /* Xn[, lsl/lsr/asr/ror #imm] */
- OPT_EXT_REG, /* Xn[, uxtw/sxtw/sxtx #imm] */
- OPT_IMM, /* unparsed signed/unsigned immediate */
- OPT_IMM12, /* add/sub immediate: 12-bit + optional lsl#12 */
- OPT_LOG_IMM, /* and/orr/eor logical immediate (N:imms:immr) */
- OPT_MOV_IMM, /* movz/movk/movn 16-bit + hw shift */
- OPT_MEM, /* [Xn], [Xn,#imm], [Xn,Xm{,ext}], pre/post indexed */
- OPT_LABEL, /* symbol+addend; resolves to PC-rel reloc */
- OPT_COND, /* eq/ne/lt/... (4-bit cond code) */
- OPT_SYS, /* sysreg encoding for mrs/msr — phase 3 */
-};
-
-typedef struct Operand {
- uint32_t kind; /* OPT_* */
- uint8_t is_w; /* 0=64-bit, 1=32-bit (X vs W) */
- uint8_t is_sp; /* 1 if textual form was sp (vs xzr) */
- union {
- struct { uint8_t reg; uint8_t shift_kind; uint8_t shift_amt; } r;
- struct { uint8_t base, idx, ext_kind, ext_amt;
- int32_t disp; uint8_t mode; /* off|preidx|postidx */ } m;
- ExprValue e; /* immediates and label refs */
- uint8_t cond;
- };
-} Operand;
-```
-
-The `kind` enum is the type signature instruction encoders match
-against. Phase 1 uses only `OPT_REG`, `OPT_IMM`, `OPT_MEM`
-(simple base+offset variant), and `OPT_LABEL`. Adding the rest is
-"new enum value + new parse path"; encoders not yet handling them
-just `expect("supported operand")` until they do.
-
-`is_w`, `is_sp` — AArch64-specific. `Wn` and `Xn` share register
-numbers; the encoder needs to know the size to set the `sf` bit.
-`sp` and `xzr` both encode as register 31 but are not
-interchangeable per-instruction; track which token the user wrote.
-
-### Encoder organization
-
-One static helper per ARM64 instruction format, mirroring
-`riscv64-asm.c`'s `asm_emit_i / asm_emit_r / asm_emit_u / asm_emit_s`.
-Group by encoding family in C ARM ARM (Section C4):
-
-| helper | covers |
-|-----------------------------|-----------------------------------------|
-| `emit_dp_imm_addsub` | add/sub/cmp/cmn (immediate) |
-| `emit_dp_imm_log` | and/orr/eor/tst (immediate) |
-| `emit_dp_imm_mov` | movz/movk/movn (incl. `mov` aliases) |
-| `emit_dp_imm_bitfield` | sbfm/ubfm/bfm + sxtw/uxtb/lsl-imm aliases |
-| `emit_dp_reg_addsub` | add/sub/cmp shifted-reg + extended-reg |
-| `emit_dp_reg_log` | and/orr/eor/bic shifted-reg |
-| `emit_dp_reg_shift` | lslv/lsrv/asrv/rorv + lsl-reg aliases |
-| `emit_dp_reg_mul` | madd/msub/mul/mneg/smull/umull |
-| `emit_dp_reg_csel` | csel/csinc/csinv/csneg + cset/cinc aliases |
-| `emit_ldst_imm` | ldr/str (immediate, unsigned + pre/post) |
-| `emit_ldst_reg` | ldr/str (register offset, with extend) |
-| `emit_ldst_pair` | ldp/stp (incl. pre/post indexed) |
-| `emit_ldst_pseudo_eq` | `ldr Xn, =imm64` / `=sym` — inline lowering, see below |
-| `emit_branch_imm` | b/bl + label reloc |
-| `emit_branch_cond` | b.cond + label reloc |
-| `emit_branch_cmp` | cbz/cbnz/tbz/tbnz |
-| `emit_branch_reg` | br/blr/ret |
-| `emit_system` | svc/hvc/smc/brk/hint (nop/yield/wfe/wfi) |
-
-Phase 1 implements only `emit_dp_imm_addsub`, `emit_dp_imm_mov`,
-`emit_ldst_imm`, `emit_branch_imm`, `emit_branch_reg` (just `ret`),
-and `emit_system` (just `svc`). Each helper has the full ISA-format
-encoding from day one — phase 1 just feeds it the narrow operand
-shapes.
-
-### `asm_opcode` dispatch
-
-Same shape as `riscv64-asm.c`'s tail switch: outer switch on the
-TOK groups dispatching to a per-family parser, which parses
-operands, validates `kind`s, and calls the matching `emit_*` helper.
-Adding mnemonics in phase 2 is a new `case TOK_ASM_xxx:` plus, if
-needed, a new shared parser routine.
-
-### Label & relocation interface
-
-`bl <sym>` / `b <sym>` emit zero into the instruction word and call
-`greloca(cur_text_section, sym, ind, R_AARCH64_CALL26 /* or
-JUMP26 */, 0)` — both reloc types are already handled by
-`arm64-link.c:30-46`. Local backward references (`b .`, numbered
-labels) resolve through the symbol table the same way `tccasm.c`
-already wires for other arches: `asm_new_label` defines, `asm_expr`
-resolves, the relocation collapses at link time.
-
-Symbol address loads (the `ldr Xn, =sym` pseudo, and any phase-2
-movz/movk-via-`:abs_g0:`/`:abs_g1:`/etc. modifiers) emit the same
-4-instruction `movz/movk` chain that `arm64-gen.c:431-440` uses for
-compiler-emitted loads: `R_AARCH64_MOVW_UABS_G0_NC` +
-`G1_NC` + `G2_NC` + `G3`. `adrp`+`add` is deliberately **not**
-used — `arm64-gen.c:425-430` documents that the ±4GB ADR_PREL_PG
-range fails on tcc's static layout and the MOVW chain is the
-working idiom. The relocs are exercised by every existing
-tcc-built ARM64 binary, so this is well-trodden ground.
-
-### Inline-asm constraint plumbing
-
-Phase 1 stubs:
-
-```c
-ST_FUNC void subst_asm_operand(CString *s, SValue *sv, int mod) {
- tcc_error("ARM64 inline asm operands not implemented yet");
-}
-ST_FUNC void asm_compute_constraints(...) { /* no-op */ }
-ST_FUNC void asm_gen_code(...) { /* no-op */ }
-ST_FUNC void asm_clobber(uint8_t *cr, const char *str) {
- /* parse register name, mark cr[reg]=1 — copy from riscv64-asm.c */
-}
-ST_FUNC int asm_parse_regvar(int t) { /* x0..xzr, w0..wzr → 0..31 */ }
-```
-
-This is enough for `.S` files, top-level `__asm__("…")` strings,
-and the `__asm__("name")` symbol-rename form. Constraint-driven
-register allocation (`__asm__("…" : "=r"(out) : "r"(in))`) lights
-up in phase 3 once `subst_asm_operand` + `asm_compute_constraints`
-are real — straight port from `i386-asm.c`'s template logic
-adapted to ARM64 register names; no surprises.
-
-## Phase plan
-
-**Phase 1 (this design)** — `mov`/`add`/`ldr`/`bl`/`b`/`ret`/`svc`,
-all integer-register operand kinds restricted to OP_REG/OP_IMM/OP_MEM
-(base+disp)/OP_LABEL. Acceptance: `.S` files in tcc-cc/tcc-libc
-assemble through `tcc-boot2`; Makefile drops `TCC_ASM` dance for
-ARCH=aarch64.
-
-**Phase 2 (implemented)** — broadens to riscv64-parity coverage.
-Surface added:
-- DP-imm: `add`/`sub`/`adds`/`subs`/`cmp`/`cmn`/`neg`/`negs`,
- `and`/`orr`/`eor`/`ands`/`tst` (logical-imm), `movz`/`movn`/`movk`,
- `sbfm`/`ubfm`/`bfm` + `lsl`/`lsr`/`asr`/`sxtb`/`sxth`/`sxtw`/
- `uxtb`/`uxth` immediate aliases.
-- DP-reg: shifted-reg `add`/`sub` (and set-flags variants),
- extended-reg form when one operand is `sp`, logical-reg
- `and`/`orr`/`eor`/`bic`/`orn`/`eon`/`bics`/`mvn`, variable shifts
- `lslv`/`lsrv`/`asrv`/`rorv` with `lsl`/`lsr`/`asr`/`ror` reg
- aliases, `mul`/`mneg`/`madd`/`msub`/`smull`/`umull`/`smaddl`/
- `umaddl`/`smsubl`/`umsubl`/`smulh`/`umulh`/`udiv`/`sdiv`,
- `csel`/`csinc`/`csinv`/`csneg` + `cset`/`csetm`/`cinc`/`cinv`/
- `cneg` aliases.
-- Mem: `ldr`/`str` register-offset (with optional `lsl`/extend
- shift) and pre/post-indexed forms; `ldrb`/`ldrh`/`ldrsb`/`ldrsh`/
- `ldrsw`/`strb`/`strh`; `ldp`/`stp` X- and W-forms with all index
- modes.
-- Branches: `b.cond` and `cbz`/`cbnz`/`tbz`/`tbnz` (in-section
- targets only — no `R_AARCH64_CONDBR19`/`TSTBR14` reloc handlers
- in `arm64-link.c`, so extern targets error out), `br`/`blr`,
- full `ret`.
-- Pseudo: `ldr Xn, =imm64` lowers via `arm64_movimm`; `ldr Xn,
- =sym` lowers to the 4-insn `MOVW_UABS_G{0..3}` reloc chain.
-- System: `svc`/`hvc`/`smc`/`brk`/`hlt`, `nop`/`yield`/`wfe`/`wfi`/
- `sev`/`sevl`/`hint`, `dsb`/`dmb`/`isb`.
-
-`arm64_encode_bimm64` and `arm64_movimm` from `arm64-gen.c` are
-called directly: under `ONE_SOURCE` (the bootstrap pipeline)
-both `.c` files inhabit the same TU sequentially, so the static
-helpers are visible to `arm64-asm.c` without ST_FUNC promotion.
-
-Pre-existing limitation: `mes-libc`'s `strtoull` truncates via
-`strtol`, so 64-bit hex literals (e.g. `#0xff00ff00ff00ff00`) get
-clamped at parse-time. Computed expressions (`#1<<32`) work
-around it. Out of scope to fix in this phase — surfaces in any
-asm path that takes wide immediates and is unrelated to the
-encoder logic.
-
-`tests2/73_arm64.c` does not exist upstream — the doc was
-speculative. Validation instead is the in-tree `.S` round-trip
-plus a hand-checked `build/phase2-test/test.S` fixture covering
-each new family.
-
-**Phase 3** — full inline-asm constraint surface
-(`subst_asm_operand` + `asm_compute_constraints`). Ports the
-i386-asm.c template walk; ARM64-specific bits are operand modifier
-letters (`%w0` for W-form, `%x0` for X-form) and clobber semantics.
-
-## Validation
-
-Unit-level: a new `tests/tcc-asm/` suite with one `.S` (or
-`__asm__()` C wrapper) fixture per mnemonic+operand-shape combo,
-diffing the encoded bytes against a known-good (host clang or
-upstream gas) reference. Same shape as the existing P1 suite —
-fixture in, expected hex out, byte diff.
-
-Integration-level: drop the host cross-asm out of Makefile lines
-386–410 for ARCH=aarch64 and let `tcc-boot2` build start.S /
-sys_stubs.S directly. The existing `tcc-cc` and `tcc-libc` suites
-then exercise the new assembler end-to-end, including the
-stage-2/stage-3 fixed-point check.
-
-Self-host check (phase 2+): compile the patched `tcc.flat.c` itself
-with `tcc-tcc` and confirm the `arm64-asm.c` it just compiled is
-byte-identical to the one cc.scm produced.
-
-## Resolved decisions
-
-- **No literal pool.** Phase 2 lowers `ldr Xn, =imm64` to an inline
- `movz/movk` chain (call into the same `arm64_movi`/`arm64_movimm`
- logic `arm64-gen.c:155-221` already uses) and `ldr Xn, =sym` to
- the 4-instruction MOVW_UABS chain. tcc's own codegen never emits a
- pool, so adding pool infrastructure would be net-new for one gas
- pseudo nothing in-tree uses; inline lowering matches what
- compiler-emitted code already does. Cost: `Xn` is clobbered with
- the constant rather than loaded from `.rodata`, and `ldr =0x… ;
- .word foo` won't share — neither matters for any in-tree fixture.
-
-- **Logical-immediate encoder: lift `arm64_encode_bimm64`.**
- `arm64-gen.c:106-153` already implements the full N:imms:immr
- encoder as a static function, used by gen.c itself for
- `orr-immediate`-as-`movi` (line 187) and direct logical-imm
- codegen (line 1395). Promote it to an `ST_FUNC` declared in the
- arm64 block of `tcc.h` and call it from `arm64-asm.c`. Zero new
- code, no port, no licensing question.
-
-- **`R_AARCH64_MOVW_UABS_G*` is the primary path.** `arm64-gen.c:429`
- hardcodes `avoid_adrp = 1`, meaning every symbol address load in
- every existing tcc-built ARM64 binary already goes through the
- MOVW_UABS_G{0..3}_NC chain. `relocate()` (arm64-link.c:174-189)
- implements all four. Use them; don't use `adrp`/`adr` at all.