Resolves item #3 of docs/working/archive/session-logs/2026-07-24-punch-list.md ("riscv64 hosted build isn't reachable via plain make"), the last open item from that list. Decided against options (b) chasing GCC's riscv64 nanosleep-visibility failure at its root (undiagnosed, open-ended) and (c) leaving it manual — instead wired the already-verified clang recipe (commit4485c38/e287334) into the Makefile, mirroring the existing rpi4-cross pattern. Makefile: new riscv64-clang target. CFLAGS deliberately does not reuse $(BASE_CFLAGS) (hardcodes -std=c99); clang needs -std=c11 -pthread here instead. Registered in `make help` and .PHONY. docs/lithosananke/hosted-acceptance-test/README.md: riscv64 section now points at `make riscv64-clang` instead of the long manual invocation. Updated Background section and commit list to reflect that all three punch-list items touching this doc (#1 asm fix, #2 doc command, #3 make target) are now resolved. Verified: `make riscv64-clang` produces a binary with identical results to the manual command it replaces (965 passed / 0 failed, "ALL IMPLEMENTED TESTS PASSED!", "3 Goodbye!" for the piped acceptance script). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
6.8 KiB
Hosted StarForth — 3-Architecture Acceptance Test
Date: 2026-07-24 (original), corrected 2026-08-02
Branch: master
Commits: edced063 (original block-subsystem fix), 4485c38 (doc +
3 clang-surfaced bug fixes), e287334 (asm register-reuse fix), this pass
(riscv64-clang Makefile target)
What this verifies
The hosted StarForth VM (make, not Makefile.starkernel) is a static
binary. This test confirms it starts cleanly — reaches the ok> REPL prompt
with no errors — on all three architectures the project targets:
- amd64 — native execution on the build host
- aarch64 — cross-compiled, run under QEMU user-mode emulation
- riscv64 — cross-compiled, run under QEMU user-mode emulation
This is distinct from the kernel (Makefile.starkernel) 3-arch acceptance
test documented in
../amd64-isr-fix/README.md, which boots a
full UEFI bare-metal image under system-mode QEMU. This test only exercises
the hosted Linux binary, using QEMU user-mode emulation to run a
foreign-architecture static binary directly on the x86_64 host — no kernel,
no UEFI firmware, no system emulation.
Prerequisites
Cross toolchains, QEMU user-mode emulation, and clang (needed for the riscv64 leg — see below):
sudo apt-get install -y \
gcc-aarch64-linux-gnu \
gcc-riscv64-linux-gnu \
clang-18 lld-18 \
qemu-user
qemu-user alone is enough — the binaries built below are statically
linked (-static), so the non-static qemu-<arch> emulators run them
directly with no sysroot. (The qemu-user-static package, if you see it
referenced elsewhere, provides statically-linked emulators; it's not
required just because the guest binary is static.)
Invocation note
None of the three binaries accept a -c "<script>" flag — earlier
revisions of this doc showed one, but src/cli.c/src/main.c have never
had one in this repo's history. Pipe the script via stdin with -s
(script/silent mode) instead. Startup always runs the full internal POST
test suite before the REPL takes stdin, so expect substantial test-suite
output before your script's result appears at the very end.
amd64 — native
make clean && make
echo "1 2 + . BYE" | ./build/amd64/standard/starforth -s
Expect 3 Goodbye! at the end of the output, then a clean exit. No
emulation needed — this is the build host's native architecture.
aarch64 — cross-compile + QEMU user-mode
make rpi4-cross
echo "1 2 + . BYE" | qemu-aarch64 ./build/raspi/fastest/starforth -s
make rpi4-cross cross-compiles with aarch64-linux-gnu-gcc
(ARCH=raspi, TARGET=fastest, -static); see Makefile:547-554. The
output binary is statically linked, so qemu-aarch64 can execute it
directly with no sysroot.
riscv64 — cross-compile + QEMU user-mode
make riscv64-clang
echo "1 2 + . BYE" | qemu-riscv64 ./build/riscv64/fastest/starforth -s
make riscv64-clang cross-compiles with clang-18 (ARCH=riscv64,
TARGET=fastest, -static); see Makefile:566-579. This leg must use
clang, not GCC: riscv64-linux-gnu-gcc fails to build this tree
(nanosleep visibility failure under -std=c99, unrelated to any project
change — root cause not yet chased down, see
docs/working/archive/session-logs/2026-07-24-punch-list.md item #3). The
target's CFLAGS deliberately does not reuse the Makefile's
$(BASE_CFLAGS) (which hardcodes -std=c99) — clang needs -std=c11 -pthread here instead, plus -fuse-ld=lld in LDFLAGS (folding it into
CC makes clang reject it as an unused argument during -c/compile-only
invocations under -Werror).
Pass criteria
For each architecture:
- Build completes with no errors.
blk_subsys_initsucceeds — no"Failed to initialize block subsystem"error. (This was broken on every architecture, including amd64, prior toedced063— see below.)- The internal POST suite runs (substantial log output — expected, not a
failure by itself) and the piped
1 2 + . BYEscript prints3 Goodbye!at the end.
Background: the block-subsystem bug this depends on
Before edced063, the hosted VM could not start on any architecture on
this branch. src/main.c allocated a hardcoded blk_ram[1024 * 1024]
buffer, stale relative to BLK_RAM_BLOCKS=2080 (include/block_subsystem.h)
— a deliberate redesign that moved the RAM/ramdrive boundary to physical
block 2080 (user-visible LBN 2048), per BLK_FORTH_SYS_RESERVED=32. The
kernel-side buffer in src/starkernel/vm/bootstrap/sk_vm_bootstrap.c already
derived its size correctly from the shared constant; main.c was the one
remaining hardcoded holdout. The fix:
#include "block_subsystem.h"
...
static uint8_t blk_ram[BLK_RAM_BLOCKS * BLK_FORTH_SIZE]; /* was: blk_ram[1024 * 1024] */
This is why the acceptance test above is meaningful on this branch specifically: it did not pass before this fix, on any of the three architectures.
Background: the riscv64 clang build (2026-08-02 correction)
The riscv64 command above was broken in this doc from 2026-07-24 until this
correction — docs/working/archive/session-logs/2026-07-24-punch-list.md
(item #2) recorded that the working clang invocation had been found and
fixed in the old pre-split monorepo's master branch's .claude/CLAUDE.md,
but the correction was never copied into this doc. Verifying it end-to-end
for this correction surfaced that the clang build also needs three small
source fixes from that same punch list (item #1), previously ported to the
old monorepo's master but not to this line:
src/math_portable.c—-100LL << 16is UB (shifting a negative value) under-Wshift-negative-value; changed to-(100LL << 16).src/physics_pipelining_metrics.c— removed deadq48_mul_q48()(-Wunused-functionunder clang; GCC doesn't catch this by default).src/word_source/editor_words.c— removed deadset_scr()(same reason).
The punch list's highest-priority item — a genuine SIGSEGV-causing
register-reuse hazard in include/vm_asm_opt_riscv64.h's vm_pop_asm/
vm_rpop_asm (clang's register allocator can pick the same register for a
memory operand referenced both before and after a write-back, corrupting
the result; GCC happens not to hit it) was not required just to get this
acceptance test's -O3 build to compile and pass, but has since been
fixed too (commit e287334) — and turned out not to be merely a latent
risk: it was silently corrupting 10 CASE.* control-flow tests under this
exact build (955/10 failed → 965/0 failed, ALL IMPLEMENTED TESTS PASSED!, with nothing else changed).
Item #3 of the same punch list — no make target for this leg, only the
long manual invocation — is also resolved as of this pass: make riscv64-clang wires in the same recipe verified above directly into the
Makefile.