boot2

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

commit 354384eae6863bd903d5ea4ed4511c54e11636b4
parent 925240dd23966009f0b742a6199d87f1661e79fa
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sat, 25 Apr 2026 03:03:52 -0700

Refactor scheme1.P1pp: use tag idioms, lexer helpers, %die, %alignup

The tag-idiom macros (%car, %cdr, %tagof, %hdr_type, %mkfix,
%untag_fix, %untag_sym) were defined but unused -- every call site
inlined the underlying instruction. Now the file routes every tag
operation through them so the layout is documented in one place.

Adds scheme1-local helpers and uses them at the obvious sites:
  %readbuf_byte    -- load one byte at readbuf_buf[off] (skip_ws,
                      parse_one, parse_list, parse_atom scan loop)
  %is_ws_branch    -- 4-way ASCII whitespace check (same callers)
  %symtab_entry    -- base + idx*SYMENT.SIZE (intern, sym_global,
                      sym_set_global)
  %die(msg, code)  -- print_cstr + sys_exit, replacing seven copies
                      of the same la/call/li/call/spin block

Adds a generic %alignup(rd, rs, align, scratch) to libp1pp; uses it
in p1_main's heap_next setup and alloc_hdr. Drops the dead ::spin
labels after %die-style sys_exit calls (sys_exit doesn't return).
parse_one's frame is 0 since the body never touches sp slots.

Bumps M1PP_MACRO_BODY_CAP from 0x18000 to 0x30000: the new helper
macros plus the larger amd64 backend pushed the body-token pool
past the old cap.

Notes:
- parse_atom's first-byte dispatch keeps the inline la/ld/add/lb
  sequence; routing it through %readbuf_byte caused the apply
  dispatch to read a non-PRIM value on aarch64 in a way I haven't
  fully diagnosed.
- amd64 and riscv64 still fail tests/scheme1/00-exit with "not a
  procedure" -- pre-existing on the baseline, unchanged here.

Diffstat:
MM1pp/M1pp.P1 | 2+-
MP1/P1pp.P1pp | 17+++++++++++++++++
Mscheme1/scheme1.P1pp | 204+++++++++++++++++++++++++++++++++----------------------------------------------
3 files changed, 104 insertions(+), 119 deletions(-)

diff --git a/M1pp/M1pp.P1 b/M1pp/M1pp.P1 @@ -38,7 +38,7 @@ DEFINE M1PP_MACRO_RECORD_SIZE 2801000000000000 DEFINE M1PP_MACRO_BODY_START_OFF 1801000000000000 DEFINE M1PP_MACRO_BODY_END_OFF 2001000000000000 DEFINE M1PP_MACROS_CAP 0050020000000000 -DEFINE M1PP_MACRO_BODY_CAP 0080010000000000 +DEFINE M1PP_MACRO_BODY_CAP 0000030000000000 DEFINE O_WRONLY_CREAT_TRUNC 4102000000000000 DEFINE MODE_0644 A401000000000000 DEFINE AT_FDCWD 9CFFFFFFFFFFFFFF diff --git a/P1/P1pp.P1pp b/P1/P1pp.P1pp @@ -11,6 +11,23 @@ # See docs/LIBP1PP.md for the public contract. # ========================================================================= +# Compile-time helpers +# ========================================================================= + +# %alignup -- Round `rs` up to a multiple of `align` (a power of two) and +# place the result in `rd`. `scratch` is clobbered. `align` must be a +# constant integer expression. Two instructions plus an %li for the mask: +# +# rd = rs + (align - 1) +# scratch = -align (i.e. ~(align-1) for power-of-two align) +# rd = rd & scratch +%macro alignup(rd, rs, align, scratch) +%addi(rd, rs, (- align 1)) +%li(scratch, (- 0 align)) +%and(rd, rd, scratch) +%endm + +# ========================================================================= # Control-flow macros # ========================================================================= # diff --git a/scheme1/scheme1.P1pp b/scheme1/scheme1.P1pp @@ -44,25 +44,19 @@ %endm # Layout helpers. %struct stride is 8 bytes per field. -%struct PAIR { car cdr } # .SIZE = 16 +%struct PAIR { car cdr } # .SIZE = 16 %struct SYMENT { name_ptr name_len global_val pad } # .SIZE = 32 -%struct PRIM { hdr entry_w } # .SIZE = 16 - -# BSS sizing. Offsets are bytes from :ELF_end. -DEFINE OFF_readbuf 0000000000000000 -DEFINE OFF_heap 0000010000000000 -DEFINE OFF_symtab 0000020000000000 -DEFINE READBUF_CAP 0000010000000000 -DEFINE HEAP_CAP 0000010000000000 -DEFINE SYMTAB_CAP_BYTES 0000010000000000 - -# Number of symtab slots. Used as a runtime guard in intern. Must match -# SYMTAB_CAP_BYTES / SYMENT.SIZE. +%struct PRIM { hdr entry_w } # .SIZE = 16 + +# BSS arena offsets from :ELF_end. Each arena is 64 KiB; the three are +# packed back-to-back below ELF_end + 192 KiB. p1_main's startup loop +# materializes &ELF_end + OFF_X into the matching pointer slot. The +# offsets are emitted directly in bss_init_tbl via $(). + %macro SYMTAB_CAP_SLOTS() 1024 %endm -# Same as M1PP_INPUT_CAP, exposed for %li in load_source. %macro READBUF_CAP_BYTES() 65536 %endm @@ -100,6 +94,50 @@ DEFINE SYMTAB_CAP_BYTES 0000010000000000 %endm # ========================================================================= +# Scheme1-local helpers +# ========================================================================= + +# Load the byte at readbuf_buf[off_reg] into rd. Clobbers rd. `rd` must +# be the destination register; the macro reuses it as a scratch pointer +# during the la / ld / add chain before the final lb writes the byte. +%macro readbuf_byte(rd, off_reg) +%la(rd, &readbuf_buf_ptr) +%ld(rd, rd, 0) +%add(rd, rd, off_reg) +%lb(rd, rd, 0) +%endm + +# Branch to `target` if `ch_reg` holds an ASCII whitespace byte (space, +# tab, LF, CR). `scratch` is clobbered. +%macro is_ws_branch(scratch, ch_reg, target) +%addi(scratch, ch_reg, -32) +%beqz(scratch, target) +%addi(scratch, ch_reg, -9) +%beqz(scratch, target) +%addi(scratch, ch_reg, -10) +%beqz(scratch, target) +%addi(scratch, ch_reg, -13) +%beqz(scratch, target) +%endm + +# Compute &symtab_buf + idx_reg * SYMENT.SIZE into rd. `scratch` is +# clobbered. +%macro symtab_entry(rd, idx_reg, scratch) +%la(rd, &symtab_buf_ptr) +%ld(rd, rd, 0) +%shli(scratch, idx_reg, 5) +%add(rd, rd, scratch) +%endm + +# Print msg_label, exit with `code`. Never returns. +%macro die(msg, code) +%la(a0, & ## msg) +%call(&print_cstr) +%li(a0, code) +%call(&sys_exit) +%endm + +# ========================================================================= # p1_main -- runtime spine # ========================================================================= # @@ -133,9 +171,7 @@ DEFINE SYMTAB_CAP_BYTES 0000010000000000 # every pair pointer's low 3 bits are exactly the PAIR tag. %la(t0, &heap_buf_ptr) %ld(t0, t0, 0) - %addi(t0, t0, 7) - %li(t1, -8) - %and(t0, t0, t1) + %alignup(t0, t0, 8, t1) %la(t1, &heap_next) %st(t0, t1, 0) @@ -168,9 +204,6 @@ DEFINE SYMTAB_CAP_BYTES 0000010000000000 # ========================================================================= # Source loading -- argv[1] -> readbuf, length stored in readbuf_len # ========================================================================= -# -# Frame: 8 bytes -# +0 no slots; libp1pp's read_file does open+read+close in one call. %fn(load_source, 0, { %la(t0, &readbuf_buf_ptr) @@ -185,12 +218,7 @@ DEFINE SYMTAB_CAP_BYTES 0000010000000000 %eret ::fail - %la(a0, &msg_load_fail) - %call(&print_cstr) - %li(a0, 3) - %call(&sys_exit) - ::spin - %b(&::spin) + %die(msg_load_fail, 3) }) # ========================================================================= @@ -216,9 +244,7 @@ DEFINE SYMTAB_CAP_BYTES 0000010000000000 # alloc_hdr(bytes=a0, hdr_word=a1) -> tagged heap obj (a0) # Rounds bytes up to a multiple of 8 and writes hdr_word at offset 0. :alloc_hdr - %addi(a0, a0, 7) - %li(t0, -8) - %and(a0, a0, t0) + %alignup(a0, a0, 8, t0) %la(t2, &heap_next) %ld(t0, t2, 0) %add(t1, t0, a0) @@ -253,11 +279,7 @@ DEFINE SYMTAB_CAP_BYTES 0000010000000000 %b(&::append) ::probe - # entry_ptr = symtab_buf + idx * SYMENT.SIZE - %la(t1, &symtab_buf_ptr) - %ld(t1, t1, 0) - %shli(t2, t0, 5) - %add(t1, t1, t2) + %symtab_entry(t1, t0, t2) %st(t1, sp, 24) # entry.name_len == name_len ? @@ -283,16 +305,10 @@ DEFINE SYMTAB_CAP_BYTES 0000010000000000 %ld(t0, sp, 16) %li(t1, %SYMTAB_CAP_SLOTS) %bltu(t0, t1, &::append_ok) - %la(a0, &msg_symtab_full) - %call(&print_cstr) - %li(a0, 5) - %call(&sys_exit) + %die(msg_symtab_full, 5) ::append_ok - %la(t1, &symtab_buf_ptr) - %ld(t1, t1, 0) - %shli(t2, t0, 5) - %add(t1, t1, t2) + %symtab_entry(t1, t0, t2) %ld(a0, sp, 0) %st(a0, t1, 0) %ld(a0, sp, 8) @@ -351,18 +367,8 @@ DEFINE SYMTAB_CAP_BYTES 0000010000000000 %ld(t1, t1, 0) ::loop %beq(t0, t1, &::done) - %la(a0, &readbuf_buf_ptr) - %ld(a0, a0, 0) - %add(a0, a0, t0) - %lb(a0, a0, 0) - %addi(a1, a0, -32) - %beqz(a1, &::step) - %addi(a1, a0, -9) - %beqz(a1, &::step) - %addi(a1, a0, -10) - %beqz(a1, &::step) - %addi(a1, a0, -13) - %beqz(a1, &::step) + %readbuf_byte(a0, t0) + %is_ws_branch(a1, a0, &::step) %b(&::done) ::step %addi(t0, t0, 1) @@ -373,7 +379,7 @@ DEFINE SYMTAB_CAP_BYTES 0000010000000000 %endscope # parse_one() -> tagged value in a0 -%fn(parse_one, 16, { +%fn(parse_one, 0, { %call(&skip_ws) %la(t0, &readbuf_pos) @@ -382,10 +388,7 @@ DEFINE SYMTAB_CAP_BYTES 0000010000000000 %ld(t1, t1, 0) %beq(t0, t1, &::eof) - %la(a0, &readbuf_buf_ptr) - %ld(a0, a0, 0) - %add(a0, a0, t0) - %lb(a0, a0, 0) + %readbuf_byte(a0, t0) %addi(a1, a0, -40) %beqz(a1, &::lparen) @@ -405,18 +408,10 @@ DEFINE SYMTAB_CAP_BYTES 0000010000000000 %eret ::rparen - %la(a0, &msg_unexp_rparen) - %call(&print_cstr) - %li(a0, 6) - %call(&sys_exit) + %die(msg_unexp_rparen, 6) ::eof - %la(a0, &msg_unexp_eof) - %call(&print_cstr) - %li(a0, 6) - %call(&sys_exit) - ::spin - %b(&::spin) + %die(msg_unexp_eof, 6) }) # parse_list() -> tagged list value in a0. Cursor sits past '(' on entry; @@ -438,10 +433,7 @@ DEFINE SYMTAB_CAP_BYTES 0000010000000000 %ld(t1, t1, 0) %beq(t0, t1, &::eof) - %la(a0, &readbuf_buf_ptr) - %ld(a0, a0, 0) - %add(a0, a0, t0) - %lb(a0, a0, 0) + %readbuf_byte(a0, t0) %addi(a1, a0, -41) %beqz(a1, &::close) @@ -475,12 +467,7 @@ DEFINE SYMTAB_CAP_BYTES 0000010000000000 %eret ::eof - %la(a0, &msg_unterm_list) - %call(&print_cstr) - %li(a0, 6) - %call(&sys_exit) - ::spin - %b(&::spin) + %die(msg_unterm_list, 6) }) # parse_atom() -> tagged value (fixnum or symbol) in a0. @@ -499,20 +486,9 @@ DEFINE SYMTAB_CAP_BYTES 0000010000000000 ::scan %beq(t1, t2, &::end) - %la(a0, &readbuf_buf_ptr) - %ld(a0, a0, 0) - %add(a0, a0, t1) - %lb(a0, a0, 0) + %readbuf_byte(a0, t1) - # whitespace? - %addi(a1, a0, -32) - %beqz(a1, &::end) - %addi(a1, a0, -9) - %beqz(a1, &::end) - %addi(a1, a0, -10) - %beqz(a1, &::end) - %addi(a1, a0, -13) - %beqz(a1, &::end) + %is_ws_branch(a1, a0, &::end) # paren? %addi(a1, a0, -40) %beqz(a1, &::end) @@ -545,8 +521,7 @@ DEFINE SYMTAB_CAP_BYTES 0000010000000000 %ld(t2, sp, 8) %addi(t0, t0, 1) %beq(t0, t2, &::is_sym) - %addi(a0, a0, 1) - %lb(a0, a0, 0) + %readbuf_byte(a0, t0) %addi(a1, a0, -48) %bltu(a1, a2, &::is_int) # fall through to is_sym @@ -600,7 +575,7 @@ DEFINE SYMTAB_CAP_BYTES 0000010000000000 %li(t2, 0) %sub(a0, t2, a0) ::tag - %shli(a0, a0, 3) + %mkfix(a0, a0) %ret %endscope @@ -623,7 +598,7 @@ DEFINE SYMTAB_CAP_BYTES 0000010000000000 %st(a0, sp, 0) %st(a1, sp, 8) - %andi(t0, a0, 7) + %tagof(t0, a0) %li(t1, %TAG.PAIR) %beq(t0, t1, &::pair) %li(t1, %TAG.SYM) @@ -632,29 +607,26 @@ DEFINE SYMTAB_CAP_BYTES 0000010000000000 %eret ::sym - %sari(a0, a0, 3) + %untag_sym(a0, a0) %call(&sym_global) %li(t0, %imm_val(%IMM.UNBOUND)) %beq(a0, t0, &::unbound) %eret ::unbound - %la(a0, &msg_unbound) - %call(&print_cstr) - %li(a0, 7) - %call(&sys_exit) + %die(msg_unbound, 7) ::pair # head = eval(car(expr), env) %ld(a0, sp, 0) - %ld(a0, a0, -1) + %car(a0, a0) %ld(a1, sp, 8) %call(&eval) %st(a0, sp, 16) # args = eval_args(cdr(expr), env) %ld(a0, sp, 0) - %ld(a0, a0, 7) + %cdr(a0, a0) %ld(a1, sp, 8) %call(&eval_args) @@ -675,13 +647,13 @@ DEFINE SYMTAB_CAP_BYTES 0000010000000000 %st(a1, sp, 8) # val = eval(car(args), env) - %ld(a0, a0, -1) + %car(a0, a0) %call(&eval) %st(a0, sp, 16) # rest = eval_args(cdr(args), env) %ld(a0, sp, 0) - %ld(a0, a0, 7) + %cdr(a0, a0) %ld(a1, sp, 8) %call(&eval_args) @@ -704,19 +676,16 @@ DEFINE SYMTAB_CAP_BYTES 0000010000000000 # Only HEAP-tagged values can be applicable. PRIM is the only header # type wired up here. - %andi(t0, a0, 7) + %tagof(t0, a0) %li(t1, %TAG.HEAP) %bne(t0, t1, &::not_proc) - %lb(t0, a0, -3) + %hdr_type(t0, a0) %li(t1, %HDR.PRIM) %beq(t0, t1, &::prim) ::not_proc - %la(a0, &msg_not_proc) - %call(&print_cstr) - %li(a0, 8) - %call(&sys_exit) + %die(msg_not_proc, 8) ::prim %ld(t0, a0, 5) ; t0 = entry word (offset = -3 + 8) @@ -737,11 +706,10 @@ DEFINE SYMTAB_CAP_BYTES 0000010000000000 %la(a0, &name_sys_exit) %li(a1, 8) %call(&intern) - %sari(a0, a0, 3) ; idx + %untag_sym(a0, a0) ; idx %la(a1, &prim_sys_exit) %addi(a1, a1, 3) ; tag HEAP %call(&sym_set_global) - # No stack frame needed -- pure register code with one nested call. }) # prim_sys_exit_entry(args=a0). Args is a one-element list whose car is @@ -749,8 +717,8 @@ DEFINE SYMTAB_CAP_BYTES 0000010000000000 # sys_exit (a %b, not a %call -- this is a leaf with no frame, and # sys_exit doesn't return anyway). :prim_sys_exit_entry - %ld(a0, a0, -1) ; car = fixnum - %sari(a0, a0, 3) + %car(a0, a0) ; car = fixnum + %untag_fix(a0, a0) %b(&sys_exit) # ========================================================================= @@ -782,9 +750,9 @@ $(%HDR.PRIM) &prim_sys_exit_entry %(0) # Each entry: 8-byte slot pointer (4-byte label ref + 4 bytes pad) + # 8-byte offset constant. p1_main walks this once at startup. :bss_init_tbl -&readbuf_buf_ptr %(0) OFF_readbuf -&heap_buf_ptr %(0) OFF_heap -&symtab_buf_ptr %(0) OFF_symtab +&readbuf_buf_ptr %(0) $(0) +&heap_buf_ptr %(0) $(0x10000) +&symtab_buf_ptr %(0) $(0x20000) :bss_init_tbl_end # =========================================================================