boot2

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

commit 4ab05cbfc275848a6b7b04ce814bc4390e1ce666
parent b25bf2dc25aeea993c644a0ec47dad2e7887a58d
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue, 28 Apr 2026 13:01:25 -0700

Keep scheme1 global metadata on main heap

Diffstat:
Mscheme1/scheme1.P1pp | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Atests/scheme1/123-scratch-reset-intern.expected-exit | 1+
Atests/scheme1/123-scratch-reset-intern.scm | 23+++++++++++++++++++++++
Atests/scheme1/124-scratch-record-type.expected-exit | 1+
Atests/scheme1/124-scratch-record-type.scm | 30++++++++++++++++++++++++++++++
5 files changed, 125 insertions(+), 10 deletions(-)

diff --git a/scheme1/scheme1.P1pp b/scheme1/scheme1.P1pp @@ -2938,6 +2938,25 @@ %b(&heap_oom_die) %endscope +# cons_main(car=a0, cdr=a1) -> tagged pair (a0). Allocates in main +# regardless of current heap selection. Used for process-global +# interpreter metadata that is represented as Scheme pairs. +:cons_main +%scope cons_main + %la(t2, &heap_next) + %ld(t0, t2, 0) + %addi(t1, t0, %PAIR.SIZE) + %ld_global(a3, &heap_end) + %bltu(a3, t1, &::oom) + %st(a0, t0, %PAIR.car) + %st(a1, t0, %PAIR.cdr) + %st(t1, t2, 0) + %addi(a0, t0, %TAG.PAIR) + %ret + ::oom + %die(msg_heap_full) +%endscope + # 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 @@ -2957,6 +2976,24 @@ %b(&heap_oom_die) %endscope +# alloc_hdr_main(bytes=a0, hdr_word=a1) -> tagged heap obj (a0), allocated +# in main regardless of current heap selection. +:alloc_hdr_main +%scope alloc_hdr_main + %alignup(a0, a0, 8, t0) + %la(t2, &heap_next) + %ld(t0, t2, 0) + %add(t1, t0, a0) + %ld_global(a3, &heap_end) + %bltu(a3, t1, &::oom) + %st(t1, t2, 0) + %st(a1, t0, 0) + %addi(a0, t0, 3) + %ret + ::oom + %die(msg_heap_full) +%endscope + # list_length(list=a0) -> count (a0). Linear walk; clobbers a0 (used as # the cursor). Callers that need the list afterward must save it first. :list_length @@ -3134,11 +3171,12 @@ %die(msg_symtab_full) ::append_ok - # Copy the name into a stable heap buffer. The caller-provided ptr - # may live in readbuf_buf (parse_atom), which gets overwritten when - # the next source is loaded; symtab entries must outlive that. + # Copy the name into a stable main-heap buffer. The caller-provided + # ptr may live in readbuf_buf (parse_atom), and the current heap may + # be scratch while user code is being read/evaluated. Symtab names + # must outlive both source-buffer reuse and scratch resets. %ldl(a0, name_len) - %call(&alloc_bytes) + %call(&alloc_bytes_main) %ldl(a1, name_ptr) %ldl(a2, name_len) %call(&memcpy) ; returns dst in a0 = stable copy @@ -3998,6 +4036,25 @@ %b(&heap_oom_die) %endscope +# alloc_bytes_main(size=a0) -> raw addr (a0). Untagged data buffer in the +# main heap regardless of current heap selection. Used for interpreter- +# owned stable storage such as symtab names, which must survive scratch +# resets even when user code is currently allocating in scratch. +:alloc_bytes_main +%scope alloc_bytes_main + %alignup(a0, a0, 8, t0) + %la(t2, &heap_next) + %ld(t1, t2, 0) + %add(t0, t1, a0) + %ld_global(a3, &heap_end) + %bltu(a3, t0, &::oom) + %st(t0, t2, 0) + %mov(a0, t1) + %ret + ::oom + %die(msg_heap_full) +%endscope + # bv_capacity_for(n=a0) -> smallest power-of-two ≥ n, minimum 16. Pure # bytevector sizing -- no NUL slack. Callers building "strings" call # bv_capacity_for(raw_len + 1) to reserve room for the trailing NUL. @@ -4603,7 +4660,9 @@ # into the same TD via the prim's data slot. # make_param_prim(entry=a0, data=a1) -> prim (a0). Allocates a 24-byte -# PRIM, sets the entry label and data word. +# PRIM in main, sets the entry label and data word. These PRIMs are +# installed in global bindings by define-record-type, so they must not +# be reclaimed by scratch reset. %fn2(make_param_prim, {entry data}, { %stl(a0, entry) @@ -4611,7 +4670,7 @@ %li(a0, 24) %li(a1, %HDR.PRIM) - %call(&alloc_hdr) + %call(&alloc_hdr_main) %ldl(t0, entry) %heap_st(t0, a0, %PRIM.entry_w) @@ -4736,11 +4795,12 @@ %call(&list_length) %stl(a0, nfields) - # td = alloc_hdr(TD.SIZE, HDR.TD); td.name = type-name; - # td.nfields = nfields; td.fields = NIL (filled below). + # td = alloc_hdr_main(TD.SIZE, HDR.TD); td.name = type-name; + # td.nfields = nfields; td.fields = NIL (filled below). TDs are + # process-global record metadata, not scratch-resident instances. %li(a0, %TD.SIZE) %li(a1, %HDR.TD) - %call(&alloc_hdr) + %call(&alloc_hdr_main) %stl(a0, td) %ldl(t0, rest) %car(t0, t0) @@ -4767,7 +4827,7 @@ %car(t1, t0) %car(a0, t1) %li(a1, %imm_val(%IMM.NIL)) - %call(&cons) + %call(&cons_main) # Splice into list: if head is NIL, head = tail = cell. # Else set-cdr!(tail, cell); tail = cell. %ldl(t1, fl_head) diff --git a/tests/scheme1/123-scratch-reset-intern.expected-exit b/tests/scheme1/123-scratch-reset-intern.expected-exit @@ -0,0 +1 @@ +42 diff --git a/tests/scheme1/123-scratch-reset-intern.scm b/tests/scheme1/123-scratch-reset-intern.scm @@ -0,0 +1,23 @@ +; Newly interned symbol names must not live in scratch. If they do, a +; scratch reset can let a later symbol overwrite an earlier symtab name, +; causing lookup to return the wrong global. + +(define-record-type cell + (mk-cell head tail) + cell? + (head cell-head cell-head-set!)) + +(use-scratch-heap!) +(define s-pair (cons 1 2)) +(reset-scratch-heap!) +(define s-cell (mk-cell 10 20)) + +;; Before the fix, parsing/evaluating s-cell here found the earlier +;; s-pair symtab entry after its scratch-resident name bytes had been +;; overwritten with "s-cell", so record? received the pair. +(if (record? s-cell) 0 (sys-exit 1)) +(if (cell? s-cell) 0 (sys-exit 2)) +(if (= 10 (cell-head s-cell)) 0 (sys-exit 3)) + +(use-main-heap!) +(sys-exit 42) diff --git a/tests/scheme1/124-scratch-record-type.expected-exit b/tests/scheme1/124-scratch-record-type.expected-exit @@ -0,0 +1 @@ +42 diff --git a/tests/scheme1/124-scratch-record-type.scm b/tests/scheme1/124-scratch-record-type.scm @@ -0,0 +1,30 @@ +; define-record-type installs process-global metadata. Defining a type +; while scratch is current must not leave its TD / generated PRIMs / +; field-name list in scratch. + +(use-scratch-heap!) +(define-record-type dyn + (mk-dyn x y) + dyn? + (x dyn-x) + (y dyn-y dyn-y-set!)) +(reset-scratch-heap!) +(use-main-heap!) + +(define d (mk-dyn 7 8)) +(if (dyn? d) 0 (sys-exit 1)) +(if (= 7 (dyn-x d)) 0 (sys-exit 2)) +(if (= 8 (dyn-y d)) 0 (sys-exit 3)) +(dyn-y-set! d 9) +(if (= 9 (dyn-y d)) 0 (sys-exit 4)) + +;; pmatch uses the TD.fields list, so this also checks that the field-name +;; metadata survived the scratch reset. +(if (= 16 + (pmatch d + (($ dyn? (x ,a) (y ,b)) (+ a b)) + (else 0))) + 0 + (sys-exit 5)) + +(sys-exit 42)