boot2

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

commit 5bb6a0ab4ea4e61516278f007bc52516f074d154
parent 5c810d428f71b2b544e9fa4eb524eae88c838895
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Fri, 24 Apr 2026 10:23:09 -0700

Port m1pp/pokem to p1_main and emit :_start from p1_gen.py

The P1v2 program-entry model (docs/P1.md §Program Entry) has a
backend-owned :_start stub call p1_main with a0=argc, a1=argv. The
m1pp-driven path already did this via p1/P1-aarch64.M1pp's p1_entry
macro; the raw-M1 path going through build/p1v2/aarch64/p1_aarch64.M1
still had each program define its own :_start and read argv by
leaning on the portable sp+16 bias.

Emit the aarch64 :_start stub from p1/p1_gen.py (via a new start_stub
field on ArchDef), and convert m1pp.M1, pokem.M1, and the 00-hello
smoke fixture to define :p1_main instead.

Diffstat:
Mm1pp/m1pp.M1 | 58+++++++++++++++++++++++++++++++++-------------------------
Mp1/aarch64.py | 24++++++++++++++++++++++++
Mp1/common.py | 2+-
Mp1/p1_gen.py | 6++++++
Mpokem/pokem.M1 | 43++++++++++++++++++++++---------------------
Mtests/m1pp/00-hello.M1 | 9++++-----
6 files changed, 90 insertions(+), 52 deletions(-)

diff --git a/m1pp/m1pp.M1 b/m1pp/m1pp.M1 @@ -3,9 +3,10 @@ ## Runtime shape: m1pp input.M1 output.M1 ## ## Pipeline: -## _start argv/argc from kernel SP; openat+read into input_buf; -## call lex_source, then process_tokens; openat+write -## output_buf to argv[2]; exit. +## p1_main argc/argv from the backend :_start stub; stash argv[1..2] +## into input_path / output_path; openat+read into +## input_buf; call lex_source, then process_tokens; +## openat+write output_buf to output_path; return 0. ## lex_source input_buf -> source_tokens[] (via append_text + ## push_source_token). ## process_tokens Stream-driven loop. Pushes source_tokens as the initial @@ -20,8 +21,9 @@ ## without emitting output. ## ## P1v2 ABI: a0..a3 arg/return, t0..t2 caller-saved temps, s0..s3 callee-saved -## (unused here). Non-leaf functions use enter_0 / eret. _start has no frame; -## the kernel-supplied SP carries argv/argc directly. +## (unused here). Non-leaf functions use enter_0 / eret. Entry is the portable +## p1_main (a0=argc, a1=argv); the backend-owned :_start stub captures +## argc/argv from the native entry state and sys_exits p1_main's return value. ## --- Constants & sizing ------------------------------------------------------ @@ -112,35 +114,40 @@ DEFINE EXPR_INVALID 1200000000000000 ## --- Runtime shell: argv, read input, call pipeline, write output, exit ------ -:_start +:p1_main + enter_0 + # a0 = argc, a1 = argv (pointer to argv[0]). # if (argc < 3) usage - ld_a0,sp,neg16 - li_a1 %3 %0 + li_a2 %3 %0 la_br &err_usage - blt_a0,a1 + blt_a0,a2 - # output_path = argv[2] - ld_t0,sp,8 - la_a0 &output_path - st_t0,a0,0 + # Stash argv[1] and argv[2] into memory before anything clobbers a1. + ld_t0,a1,8 + la_a2 &input_path + st_t0,a2,0 + ld_t0,a1,16 + la_a2 &output_path + st_t0,a2,0 # source_end = &source_tokens (running tail pointer) la_a0 &source_tokens - la_a1 &source_end - st_a0,a1,0 + la_a2 &source_end + st_a0,a2,0 # macros_end = &macros; macro_body_end = &macro_body_tokens la_a0 &macros - la_a1 &macros_end - st_a0,a1,0 + la_a2 &macros_end + st_a0,a2,0 la_a0 &macro_body_tokens - la_a1 &macro_body_end - st_a0,a1,0 + la_a2 &macro_body_end + st_a0,a2,0 - # input_fd = openat(AT_FDCWD, argv[1], O_RDONLY, 0) + # input_fd = openat(AT_FDCWD, input_path, O_RDONLY, 0) li_a0 sys_openat li_a1 AT_FDCWD - ld_a2,sp,0 + la_a2 &input_path + ld_a2,a2,0 li_a3 %0 %0 li_t0 %0 %0 syscall @@ -250,10 +257,9 @@ DEFINE EXPR_INVALID 1200000000000000 b :write_done - # exit(0) - li_a0 sys_exit - li_a1 %0 %0 - syscall + # return 0 (backend :_start stub sys_exits with a0) + li_a0 %0 %0 + eret ## --- Helpers: text arena + token array + equality ---------------------------- ## append_text appends bytes to text_buf (used for synthesized token text, @@ -5442,6 +5448,8 @@ ZERO8 ZERO8 :output_need_space ZERO8 +:input_path +ZERO8 :output_path ZERO8 :text_used diff --git a/p1/aarch64.py b/p1/aarch64.py @@ -359,6 +359,29 @@ def encode_nullary(_arch, row): raise ValueError(f'unknown nullary kind: {row.kind}') +def aa_start_stub(): + # Backend-owned :_start stub per docs/P1.md §Program Entry. Captures + # argc from [sp] and argv pointer from sp+8, calls p1_main under the + # one-word direct-result convention (a0=argc, a1=argv), then issues a + # native Linux sys_exit with p1_main's return value. Mirrors the + # m1pp-path stub in p1/P1-aarch64.M1pp (`%p1_entry`). + # + # Raw hex outside `DEFINE` bodies must be single-quoted so bootstrap + # M0 treats it as a literal byte run rather than a token. + def q(hex_bytes): + return f"'{hex_bytes}'" + return [ + ':_start', + q(aa_mem('LD', 'a0', 'sp', 0)), + q(aa_add_imm('a1', 'sp', 8, sub=False)), + q(aa_lit32_prefix('br')), + '&p1_main', + q(aa_blr('br')), + q(aa_movz('x8', 93)), + q(le32(0xD4000001)), + ] + + ENCODERS = { Li: encode_li, La: encode_la, @@ -385,5 +408,6 @@ register_arch( stack_align=16, syscall_numbers=SYSCALL_NUMBERS, encoders=ENCODERS, + start_stub=aa_start_stub, ) ) diff --git a/p1/common.py b/p1/common.py @@ -3,7 +3,7 @@ from collections import namedtuple ArchDef = namedtuple( 'ArchDef', - 'name word_bytes stack_align syscall_numbers encoders', + 'name word_bytes stack_align syscall_numbers encoders start_stub', ) Banner = namedtuple('Banner', 'text') diff --git a/p1/p1_gen.py b/p1/p1_gen.py @@ -190,6 +190,12 @@ def emit(arch_name): seen.add(name) out.append(f'DEFINE {name} {encode_row(arch, row)}') out.append('') + out.append('## ---- Program Entry') + out.append('## Backend-owned :_start stub per docs/P1.md §Program Entry.') + out.append('## Calls p1_main under the one-word direct-result convention') + out.append("## (a0=argc, a1=argv) and sys_exits its return value.") + out.extend(arch.start_stub()) + out.append('') return '\n'.join(out) diff --git a/pokem/pokem.M1 b/pokem/pokem.M1 @@ -21,20 +21,20 @@ ## defensive code for auditability. ## ## Pipeline: -## _start stash argv; decode offset (parse_u64) and +## p1_main stash argv; decode offset (parse_u64) and ## payload (parse_hex_bytes into patch_buf); ## openat O_RDWR; advance via read into scratch_buf ## until offset bytes consumed; write patch_buf; -## close; exit(0). +## close; return 0 (stub sys_exits with it). ## parse_u64 leaf. Decimal ASCII -> u64 in a0. ## parse_hex_bytes leaf. ASCII hex -> patch_buf; returns byte count ## in a0. Uses a branchless nibble decode that is ## correct for [0-9A-Fa-f] and garbage otherwise. ## ## P1 ABI: a0..a3 arg/return, t0..t2 caller-saved temps, s0..s3 -## callee-saved (unused here). _start has no frame; the kernel-supplied -## SP carries argv/argc directly via portable-offset 0/8/16 -## (argv[1..3]). +## callee-saved (unused here). Entry is the portable p1_main +## (a0=argc, a1=argv). The backend-owned :_start stub captures argc/argv +## from the native entry state and sys_exits p1_main's return value. ## --- Constants & sizing ------------------------------------------------------ @@ -57,20 +57,22 @@ DEFINE ZERO8 '0000000000000000' ## --- Runtime shell: argv, open, advance, write, exit ------------------------- -:_start - # Stash argv[1..3] pointers so downstream code doesn't need sp,off. - # file_path = argv[1] (native sp+16 == portable sp+0) - ld_t0,sp,0 - la_a1 &file_path - st_t0,a1,0 +:p1_main + enter_0 + # a0 = argc, a1 = argv (pointer to argv[0]). Stash argv[1..3] into + # memory before anything clobbers a1. + # file_path = argv[1] + ld_t0,a1,8 + la_a2 &file_path + st_t0,a2,0 # offset_str = argv[2] - ld_t0,sp,8 - la_a1 &offset_str - st_t0,a1,0 + ld_t0,a1,16 + la_a2 &offset_str + st_t0,a2,0 # hex_str = argv[3] - ld_t0,sp,16 - la_a1 &hex_str - st_t0,a1,0 + ld_t0,a1,24 + la_a2 &hex_str + st_t0,a2,0 # offset = parse_u64(offset_str) la_a0 &offset_str @@ -153,15 +155,14 @@ DEFINE ZERO8 '0000000000000000' b :write_done - # close(fd); exit(0) + # close(fd); return 0 (backend :_start stub sys_exits with a0) la_a0 &file_fd ld_a1,a0,0 li_a0 sys_close syscall - li_a0 sys_exit - li_a1 %0 %0 - syscall + li_a0 %0 %0 + eret ## --- parse_u64(a0=str) -> a0=value -------------------------------------------- ## Leaf. Decimal ASCII -> u64. Empty string returns 0. Non-digit bytes diff --git a/tests/m1pp/00-hello.M1 b/tests/m1pp/00-hello.M1 @@ -9,7 +9,7 @@ ## a0 = syscall number on entry, return value on exit ## a1, a2, a3, t0, s0, s1 = syscall arguments 0..5 -:_start +:p1_main ## write(fd=1, buf=&msg, count=14) li_a0 sys_write li_a1 %1 %0 @@ -17,10 +17,9 @@ li_a3 %14 %0 syscall - ## exit(0) - li_a0 sys_exit - li_a1 %0 %0 - syscall + ## return 0 (backend :_start stub sys_exits with a0) + li_a0 %0 %0 + ret :msg "Hello, World!