boot2

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

commit 3b6a3c115744b6b268f83c3a7e122ab4155e78c2
parent f10eab299b822884ec68f4c6b3b8285859515ca9
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Thu, 23 Apr 2026 21:25:10 -0700

Add P1v2 program-entry model (aarch64 _start stub calling p1_main)

P1.M1pp now unconditionally invokes the backend %p1_entry() macro, which
on aarch64 emits :_start: read argc from [sp], compute argv=sp+8, call
p1_main, sys_exit its return value. Portable sources just define
:p1_main; hello.P1 is converted accordingly.

Diffstat:
Mdocs/P1.md | 35+++++++++++++++++++++++++++++++++++
Mp1/P1-aarch64.M1pp | 15+++++++++++++++
Mp1/P1.M1pp | 11+++++++++++
Mtests/p1/hello.P1 | 35++++++++++++-----------------------
4 files changed, 73 insertions(+), 23 deletions(-)

diff --git a/docs/P1.md b/docs/P1.md @@ -458,6 +458,41 @@ naturally aligned addresses for `LD` and `ST`. Unaligned word accesses are outside the portable contract. Byte accesses have no additional alignment requirement. +## Program Entry + +P1 v2 defines a portable program-entry model so that portable source +does not need to know how the native loader delivers `argc` / `argv`. + +The target backend is responsible for emitting a per-arch `_start` +stub that: + +1. Captures `argc` and a pointer to the first argv word from the + native entry stack or registers. +2. Calls the portable label `p1_main` under the one-word direct-result + convention with: + - `a0` = `argc` + - `a1` = `argv`, a pointer to the first argv word. Subsequent slots + live at offsets `1*WORD`, `2*WORD`, ..., and the list is + terminated by a NULL word. +3. On return from `p1_main`, performs `sys_exit` using the returned + value in `a0` as the exit status. + +Portable P1 source defines `p1_main` as an ordinary P1 function and +reads `argc` / `argv` through the standard calling convention. + +At entry to `p1_main`, the native entry-stack layout has already been +consumed by the backend stub. Portable source may not assume anything +about the `sp` value inherited from `_start` except that it satisfies +the call-boundary alignment rule and that the standard frame protocol +(`ENTER` / `LEAVE`) works correctly from it. + +`p1_main` may return normally, or it may call `sys_exit` directly at +any point. + +The portable entry model does not expose `envp` or auxiliary vectors. +Targets that need them must provide target-specific extensions; those +are outside the portable P1 surface. + ## System `SYSCALL` is part of the portable ISA surface. diff --git a/p1/P1-aarch64.M1pp b/p1/P1-aarch64.M1pp @@ -496,6 +496,21 @@ %aa64_mem(ST, x8, sp, 8) %endm +%macro p1_entry() +# :_start stub emitted by the aarch64 backend per the P1v2 program-entry +# model. Captures argc from [sp] into a0, computes argv=sp+8 into a1, +# calls p1_main under the one-word direct-result convention, then issues +# a native Linux sys_exit with p1_main's return value as the exit status. +:_start +%aa64_mem(LD, a0, sp, 0) +%aa64_add_imm(a1, sp, 8) +%aa64_lit32_prefix(br) +&p1_main +%aa64_blr(br) +%aa64_movz(x8, 93) +%(0xD4000001) +%endm + %macro p1_syscall() %aa64_mov_rr(x8, a0) %aa64_mov_rr(save0, a1) diff --git a/p1/P1.M1pp b/p1/P1.M1pp @@ -226,3 +226,14 @@ %macro sys_waitid() %p1_sys_waitid() %endm + +# ---- Program entry -------------------------------------------------------- +# +# The portable P1v2 program-entry model emits a backend-specific `:_start` +# stub that captures argc/argv from the native entry state, calls the +# portable label `p1_main` under the one-word direct-result convention +# (a0=argc, a1=argv), and sys_exits p1_main's return value. The stub is +# emitted unconditionally here so portable sources only need to define +# `:p1_main` as an ordinary P1 function. + +%p1_entry() diff --git a/tests/p1/hello.P1 b/tests/p1/hello.P1 @@ -1,34 +1,23 @@ -# tests/p1/hello.P1 — P1-language hello world. +# tests/p1/hello.P1 — P1-language hello world using the portable entry model. # -# 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 backend-owned `:_start` stub is emitted automatically by P1.M1pp. It +# captures argc/argv from the native entry state, calls `p1_main` under the +# one-word direct-result convention (a0=argc, a1=argv), then sys_exits +# p1_main's return value as the process exit status. # -# 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. +# p1_main is an ordinary P1 function written to the portable ISA. It's a +# leaf — no P1 `%call` — so no %enter/%leave frame is needed. It ignores +# argc/argv, writes "Hello, World!\n" to stdout via sys_write, and returns +# 0 in a0 so the backend stub exits with status 0. -:_start +:p1_main %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() + %li(a0) %0 %0 + %ret() :msg "Hello, World!