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:
| A | docs/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.