boot2

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

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

Document deep-copy promotion status

Diffstat:
Adocs/DEEP-COPY.md | 120+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 120 insertions(+), 0 deletions(-)

diff --git a/docs/DEEP-COPY.md b/docs/DEEP-COPY.md @@ -0,0 +1,120 @@ +# Generic two-heap promotion + +`deep-copy` is the generic Scheme-level copier used to promote object +graphs from the scratch heap into the currently selected heap, normally +main. It was added to avoid writing one promotion walker per cc.scm +record type. + +## Status + +Done: + +- scheme1 record-introspection primitives: + `record?`, `record-td`, `record-ref`, `record-set!`, + `make-record/td`, `td-nfields`, `td-name`, and `heap-in-current?`. +- prelude `deep-copy` and `make-deep-copy-context`. +- Tests: + `tests/scheme1/121-record-introspection.scm`, + `tests/scheme1/122-deep-copy.scm`, + `tests/scheme1/123-scratch-reset-intern.scm`, and + `tests/scheme1/124-scratch-record-type.scm`. +- Main-only allocation for interpreter-global metadata that must + survive scratch resets: + symtab name bytes, record type descriptors, generated record + primitives, and TD field-name lists. + +Still open: + +- cc.scm still uses its hand-written `promote-*` walkers at the + parse-decl-or-fn boundary. The remaining migration is to replace + those walkers with `deep-copy`. + +## Current API + +```scheme +(define ctx (make-deep-copy-context)) +(define promoted (deep-copy ctx scratch-root)) +``` + +The destination is whichever heap is current when `deep-copy` allocates. +For scratch-to-main promotion, switch to main before calling it. + +The context is a boxed alist mapping original object identity to copied +object identity. It preserves shared substructure and breaks cycles by +registering an eager stand-in before recursively filling fields. + +## Copy Semantics + +`deep-copy` handles: + +- symbols, fixnums, immediates: returned as-is +- objects already in the current heap: returned as-is +- pairs: recursively copies car and cdr +- bytevectors: copies bytes with `bytevector-copy` +- records: allocates `make-record/td`, registers the stand-in, then + recursively copies each record slot +- procedures: rejected with an error + +This is deliberately suited to cc.scm parse graphs, which are made of +pairs, bytevectors, records, symbols, fixnums, booleans, and `#f`. + +## Record Introspection Primitives + +| primitive | semantics | +|---|---| +| `(record? obj)` | true iff `obj` is HEAP-tagged with `HDR.REC` | +| `(record-td obj)` | returns the TD pointer stored in the record | +| `(record-ref obj i)` | reads field `i`; no bounds check | +| `(record-set! obj i v)` | writes field `i`; no bounds check | +| `(make-record/td td)` | allocates a record in the current heap and fills slots with `UNSPEC` | +| `(td-nfields td)` | returns the TD field count | +| `(td-name td)` | returns the record type name symbol | +| `(heap-in-current? obj)` | true iff a pointer object is inside the selected heap arena | + +These primitives are unsafe by convention, like `heap-rewind!` and +`reset-scratch-heap!`: callers must provide valid record values, TDs, +and field indices. + +## Remaining cc.scm Rewrite + +The current cc.scm promotion layer has a per-type map and walkers: +`promote-bv`, `promote-loc`, `promote-tok`, `promote-ctype`, +`promote-sym`, `promote-macro`, and helper list walkers. These can +collapse to a single `deep-copy` context per parse boundary. + +The pending-completions step remains distinct: it mutates pre-existing +main-heap ctypes whose `ext` field was temporarily pointed at scratch. +That slot should be rewritten with `deep-copy` before the scratch reset. + +Sketch: + +```scheme +(define (rewrite-pending-completions! ctx) + (for-each + (lambda (c) + (cond ((heap-in-main? c) + (ctype-ext-set! c (deep-copy ctx (ctype-ext c)))))) + %promote-pending-completions)) + +(define (promote-roots! w ctx) + (rewrite-pending-completions! ctx) + (let ((tn (world-tags w))) + (world-tags-set! w (cons (deep-copy ctx (car tn)) (cdr tn)))) + (let ((sn (world-scope w))) + (world-scope-set! w (cons (deep-copy ctx (car sn)) (cdr sn)))) + (world-str-pool-set! w (deep-copy ctx (world-str-pool w)))) +``` + +Iterator buffers and macro tables should use the same context so shared +objects stay shared across all promoted roots in one boundary. + +## Notes + +- The context alist currently lives wherever it is created. For cc.scm + promotion this should be main, matching the existing `%promote-map` + behavior. +- `heap-in-current?` short-circuits before the context lookup, so + re-copying an already promoted object returns immediately. +- `make-record/td` zero-fills fields with `UNSPEC`. This is important + for cyclic records: a back-edge can see the stand-in before all fields + are filled, but the object is always well-formed.