boot2

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

commit d19a402ecc3df0f912aecf4ec2a8d400f26c5f05
parent 4eba962ec4bb1f6874f10d0ebd5f62371b1a1b7a
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Fri, 24 Apr 2026 08:09:38 -0700

Expand post with missing m1pp/P1 features and P1 source examples

Adds bullets for %struct/%enum, local labels, and %str in the m1pp
section; a program-entry paragraph in the P1 section; and a new
'Three programs' section walking through argc_exit, hello, and double
with their source inlined.

Diffstat:
Mpost.md | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 80 insertions(+), 0 deletions(-)

diff --git a/post.md b/post.md @@ -154,6 +154,15 @@ Features: - **Conditional expansion**: `%select(cond, then, else)` evaluates `cond` as an integer and emits exactly one of the two branches. Non-zero is true. - **Token concat**: `a ## b` pastes two tokens into one identifier. +- **Struct / enum shorthands**: `%struct NAME { f1 f2 }` synthesizes + `%NAME.f1` → 0, `%NAME.f2` → 8, `%NAME.SIZE` → 16. `%enum` does the same + with stride 1 and a `COUNT` terminator. Saves hand-counting offsets for + records and small tag sets. +- **Local labels**: inside a macro body, `:@loop` and `&@loop` pick up a + fresh per-expansion suffix, so a macro can define its own labels without + colliding with itself at a second call site. +- **Stringify**: `%str(foo)` turns a word token into the string literal + `"foo"`. Comments (`#` and `;`) and M0 `DEFINE`s pass through untouched. That's it — no `%ifdef`, no string manipulation, no floating point. Enough to encode @@ -250,12 +259,83 @@ Calling convention: passes a writable result buffer in `a0`, real args slide over by one, callee returns that same pointer in `a0`. +Program entry: portable source writes `:p1_main` as an ordinary P1 function +with `argc` in `a0` and `argv` in `a1`, and returns an exit status in `a0`. +The backend emits a small per-arch `:_start` stub that captures the native +entry state, calls `p1_main`, and hands its return value to `sys_exit`. The +portable side never sees the raw entry stack. + P1 ships as a pair of files you catm before any P1 source: - `P1A.M1pp` — architecture-specific backend implementing the portable interface. - `P1pp.M1pp` — the portable interface that P1 programs program against. +## Three programs + +The shortest useful P1 program is a bare return. `p1_main` gets `argc` in +`a0` on entry, `a0` is also the one-word return register, and the backend +stub hands the return value to `sys_exit` — so this returns `argc` as the +exit status: + +``` +:p1_main + %ret() + +:ELF_end +``` + +Hello world is a single `sys_write` from a leaf function: + +``` +:p1_main + %li(a0) %sys_write() + %li(a1) %1 %0 + %la(a2) &msg + %li(a3) %14 %0 + %syscall() + %li(a0) %0 %0 + %ret() + +:msg +"Hello, World! +" + +:ELF_end +``` + +A few things to notice. `%li(a0)` and `%la(a2)` don't take the immediate +as a macro argument — the backend emits a load-from-literal-pool prefix +and the bytes that follow in source become the literal. Here +`%sys_write()` expands to the 8-byte Linux syscall number for write +(the aarch64 backend's choice); `&msg` is a 4-byte label reference +(addresses fit in 32 bits in the stage0 image layout). The two `%N %0` +pairs are M0 4-byte decimal immediates padded to 8 bytes. + +A function call, with a helper that doubles its argument: + +``` +:double + %shli(a0, a0, 1) + %ret() + +:p1_main + %enter(0) + %la_br() &double + %call() + %leave() + %ret() + +:ELF_end +``` + +`LA_BR` loads the hidden branch-target register; the next control-flow +op consumes it. `double` is a leaf and needs no frame. `p1_main` is not +— it calls `double`, so it opens a standard frame with `%enter(0)` to +preserve the hidden return-address state across the call, and closes it +with `%leave()` before returning. Run with `./double a b c` and the exit +status is `8` (argc=4, doubled). + ## What it cost Concrete sizes for what's landed today: