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:
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 = ¯os; macro_body_end = ¯o_body_tokens
la_a0 ¯os
- la_a1 ¯os_end
- st_a0,a1,0
+ la_a2 ¯os_end
+ st_a0,a2,0
la_a0 ¯o_body_tokens
- la_a1 ¯o_body_end
- st_a0,a1,0
+ la_a2 ¯o_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!