boot2

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

commit 1345a2a22f182156e9c40e5eb4c3cb63cd61ab35
parent 354384eae6863bd903d5ea4ed4511c54e11636b4
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sat, 25 Apr 2026 06:13:26 -0700

Heap-allocate PRIM objects so the HEAP tag bits land correctly

The static :prim_sys_exit literal in the data section landed wherever
hex2 placed it -- offset depended on cumulative size of the preceding
code, so the label's address was 4- or 2-byte aligned in some builds
even though it needs to be 8-aligned for `&prim + 3` to tag as HEAP.
That made apply receive values like 0x6025c7 (tag bits 7) or 0x601d4d
(tag bits 5) and bail to ::not_proc.

aarch64 happened to align in the original layout, riscv64 and amd64
didn't. Adding any instrumentation to apply shifted the binary size
and flipped which arch was lucky -- that's how the layout-sensitivity
showed up.

Fix: register_primitives now allocates a 16-byte PRIM via alloc_hdr,
which the bump allocator keeps 8-aligned, then writes
&prim_sys_exit_entry into the entry slot and binds the symbol's
global to the resulting HEAP-tagged pointer. The static
:prim_sys_exit data is gone.

  aarch64: PASS
  amd64:   PASS
  riscv64: PASS

Diffstat:
Mscheme1/scheme1.P1pp | 36+++++++++++++++++++++++++-----------
1 file changed, 25 insertions(+), 11 deletions(-)

diff --git a/scheme1/scheme1.P1pp b/scheme1/scheme1.P1pp @@ -697,18 +697,36 @@ # Primitives # ========================================================================= # -# Each primitive sits behind a 16-byte heap object literal in the data -# section: [hdr_word, entry_word]. The tagged value is &obj + 3. -# register_primitives interns the surface name and writes the tagged -# pointer into the symbol's global slot. +# PRIM objects live on the heap so the bump allocator's 8-byte alignment +# is what makes (heap_ptr & 7 == 0) hold; that's what lets `+3` encode +# the HEAP tag cleanly. (A static :prim_sys_exit emitted in the data +# section was at the mercy of preceding code length and could land at +# any 4-byte alignment, producing tag bits 5 or 7 instead of 3.) +# +# register_primitives allocates one 16-byte PRIM per builtin, writes +# the entry-function address into the entry slot, interns the surface +# name, and binds the symbol's global to the HEAP-tagged pointer. +# +# Frame: 16 bytes +# +0 prim ptr (HEAP-tagged, spilled across the intern call) + +%fn(register_primitives, 16, { + # alloc_hdr(bytes=16, hdr_word=HDR.PRIM) -> HEAP-tagged a0 + %li(a0, 16) + %li(a1, %HDR.PRIM) + %call(&alloc_hdr) + # Entry slot is at raw+8 = HEAP+5. la-prefix loads a 32-bit address + # into the low half of t0; the upper half of the 8-byte slot stays + # zero from the cons-zeroed heap, so an %st covers both halves. + %la(t0, &prim_sys_exit_entry) + %st(t0, a0, 5) + %st(a0, sp, 0) -%fn(register_primitives, 0, { %la(a0, &name_sys_exit) %li(a1, 8) %call(&intern) %untag_sym(a0, a0) ; idx - %la(a1, &prim_sys_exit) - %addi(a1, a1, 3) ; tag HEAP + %ld(a1, sp, 0) ; HEAP-tagged prim ptr %call(&sym_set_global) }) @@ -725,10 +743,6 @@ # Read-only data # ========================================================================= -# Primitive object literals (16 bytes each). -:prim_sys_exit -$(%HDR.PRIM) &prim_sys_exit_entry %(0) - # Surface names. Length is hard-coded at the call site; no NUL needed # because intern takes (ptr, len). Aligned padding via "\0" bytes is # fine -- M0 emits ASCII verbatim.