boot2

Playing with the boostrap
git clone https://git.ryansepassi.com/git/boot2.git
Log | Files | Refs

commit a52dc7712af059af01946616204fd6aebefc7652
parent adbf3758d169db6ab5e662c4b1e436fcdfd9da06
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Thu, 23 Apr 2026 16:27:51 -0700

tests/p1: hello.P1 + harness exercising the full P1 -> ELF pipeline

First end-to-end test that drives a P1-language program through every
layer of the stack:

  cat p1/P1-aarch64.M1pp p1/P1.M1pp tests/p1/hello.P1
    -> m1pp (M1 build)   -> .M1   macro / builtin substitution
    -> m1pp/build.sh     -> aarch64 ELF  lint, M0, hex2-0
  podman run binary -> diff stdout vs .expected

hello.P1 is the smallest useful program: write(stdout, "Hello, World!\n")
then exit(0), spelled with the P1 frontend's macros (%li, %la, %syscall,
%sys_write, %sys_exit). Proves that:

  - the cat-three-files convention works (define-before-use holds across
    arch backend, portable frontend, and user program)
  - m1pp accepts inputs above its previous 8 KB cap (combined source is
    ~15 KB), and that its emit_hex_value output round-trips through M0
    (the recent quoted-hex change makes this work)
  - the full toolchain produces a runnable ELF that the kernel actually
    invokes

Filenames starting with `_` are skipped (parked), matching m1pp/test.sh.

Diffstat:
Atests/p1/hello.P1 | 37+++++++++++++++++++++++++++++++++++++
Atests/p1/hello.expected | 1+
Atests/p1/test.sh | 114+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 152 insertions(+), 0 deletions(-)

diff --git a/tests/p1/hello.P1 b/tests/p1/hello.P1 @@ -0,0 +1,37 @@ +# tests/p1/hello.P1 — P1-language hello world. +# +# Build pipeline (driven by tests/p1/test.sh): +# cat p1/P1-aarch64.M1pp p1/P1.M1pp tests/p1/hello.P1 +# -> m1pp expander -> .M1 (macro and builtin substitution) +# -> m1pp/build.sh -> aarch64 ELF binary (lint, M0, hex2-0) +# +# The binary writes "Hello, World!\n" (14 bytes) to stdout and exits 0. +# +# Notes on the syntax: +# %li(rd) — emits an "ldr xN, [pc,#8]; b +12" prefix; the next 8 source +# bytes are the literal pool slot. We supply them as either +# a sized syscall-number macro (%sys_write -> $(64) = 8B hex) +# or as a `%lo %hi` decimal pair (each %N is 4 LE bytes). +# %la(rd) — emits an "ldr wN, [pc,#8]; b +8" prefix; the next 4 source +# bytes are the literal pool slot. `&msg` resolves to a +# 4-byte label address at M0 link time. +# %syscall — wraps the P1v2 syscall ABI (number in a0, args in +# a1..a3,t0,s0,s1) into the Linux/aarch64 register layout +# and issues SVC #0. + +:_start + %li(a0) %sys_write() + %li(a1) %1 %0 + %la(a2) &msg + %li(a3) %14 %0 + %syscall() + + %li(a0) %sys_exit() + %li(a1) %0 %0 + %syscall() + +:msg +"Hello, World! +" + +:ELF_end diff --git a/tests/p1/hello.expected b/tests/p1/hello.expected @@ -0,0 +1 @@ +Hello, World! diff --git a/tests/p1/test.sh b/tests/p1/test.sh @@ -0,0 +1,114 @@ +#!/bin/sh +## tests/p1/test.sh — run the P1-language test suite. +## +## A P1 fixture is `<name>.P1`. The runner concatenates the P1 frontend +## (p1/P1-aarch64.M1pp + p1/P1.M1pp) with the fixture, expands the result +## with the M1 build of m1pp, then hands the resulting .M1 source to +## m1pp/build.sh which lints / preprocesses / M0-assembles / ELF-links it +## into an aarch64 binary. The binary is executed inside the standard +## distroless-busybox container; its stdout is diffed against +## `<name>.expected`. +## +## Filenames starting with `_` are skipped (parked). +## +## Usage: tests/p1/test.sh [fixture-name ...] +## No args: every non-`_` fixture under tests/p1/. + +set -eu + +REPO=$(cd "$(dirname "$0")/../.." && pwd) +PLATFORM=linux/arm64 +IMAGE=localhost/distroless-busybox:latest + +EXPANDER_BIN=build/m1pp/m1pp +EXPANDER_BUILT=0 + +cd "$REPO" + +build_expander() { + if [ "$EXPANDER_BUILT" = 0 ]; then + sh m1pp/build.sh m1pp/m1pp.M1 "$EXPANDER_BIN" >/dev/null 2>&1 || { + echo "FATAL: failed to build m1pp/m1pp.M1" >&2 + sh m1pp/build.sh m1pp/m1pp.M1 "$EXPANDER_BIN" 2>&1 | sed 's/^/ /' >&2 + exit 1 + } + EXPANDER_BUILT=1 + fi +} + +if [ "$#" -gt 0 ]; then + NAMES="$*" +else + NAMES=$(ls tests/p1/ 2>/dev/null \ + | sed -n 's/^\([^_][^.]*\)\.P1$/\1/p' \ + | sort -u) +fi + +if [ -z "$NAMES" ]; then + echo "no fixtures to run" >&2 + exit 1 +fi + +pass=0 +fail=0 +for name in $NAMES; do + fixture=tests/p1/$name.P1 + expected=tests/p1/$name.expected + + if [ ! -e "$fixture" ]; then + echo " SKIP $name (no .P1)" + continue + fi + if [ ! -e "$expected" ]; then + echo " SKIP $name (no .expected)" + continue + fi + + build_expander + + work=build/p1-tests/$name + mkdir -p "$work" + combined=$work/combined.M1pp + expanded=$work/$name.M1 + binary=$work/$name + + cat p1/P1-aarch64.M1pp p1/P1.M1pp "$fixture" > "$combined" + + if ! podman run --rm --pull=never --platform "$PLATFORM" \ + -v "$REPO":/work -w /work "$IMAGE" \ + "./$EXPANDER_BIN" "$combined" "$expanded" >/dev/null 2>&1; then + echo " FAIL $name (m1pp expansion failed)" + podman run --rm --pull=never --platform "$PLATFORM" \ + -v "$REPO":/work -w /work "$IMAGE" \ + "./$EXPANDER_BIN" "$combined" "$expanded" 2>&1 | sed 's/^/ /' + fail=$((fail + 1)) + continue + fi + + if ! sh m1pp/build.sh "$expanded" "$binary" >/dev/null 2>&1; then + echo " FAIL $name (m1pp/build.sh failed)" + sh m1pp/build.sh "$expanded" "$binary" 2>&1 | sed 's/^/ /' + fail=$((fail + 1)) + continue + fi + + actual=$(podman run --rm --pull=never --platform "$PLATFORM" \ + -v "$REPO":/work -w /work "$IMAGE" \ + "./$binary" 2>&1 || true) + expected_content=$(cat "$expected") + + if [ "$actual" = "$expected_content" ]; then + echo " PASS $name" + pass=$((pass + 1)) + else + echo " FAIL $name" + echo " --- expected ---" + printf '%s\n' "$expected_content" | sed 's/^/ /' + echo " --- actual ---" + printf '%s\n' "$actual" | sed 's/^/ /' + fail=$((fail + 1)) + fi +done + +echo "$pass passed, $fail failed" +[ "$fail" -eq 0 ]