Files
LithosAnanake/docs/lithosananke/hosted-acceptance-test
Robert Allan JamesandClaude Sonnet 5 d922e152aa Add riscv64-clang Makefile target; closes out 2026-07-24 punch list
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
(commit 4485c38 / 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>
2026-08-02 07:43:01 -04:00
..

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:

  1. Build completes with no errors.
  2. blk_subsys_init succeeds — no "Failed to initialize block subsystem" error. (This was broken on every architecture, including amd64, prior to edced063 — see below.)
  3. The internal POST suite runs (substantial log output — expected, not a failure by itself) and the piped 1 2 + . BYE script prints 3 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 << 16 is UB (shifting a negative value) under -Wshift-negative-value; changed to -(100LL << 16).
  • src/physics_pipelining_metrics.c — removed dead q48_mul_q48() (-Wunused-function under clang; GCC doesn't catch this by default).
  • src/word_source/editor_words.c — removed dead set_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 failed965/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.