asm-gc Fix 2: S-expression portal for the GC build
Binary heap dump can't work under GC because the heap is a linked chunk list with typed block headers and a free list. Raw-byte serialization would lose structure. Rather than invent portal v2 with chunk tables and pointer relocation, the GC build uses the S-expression format that already works across all other tiers: # bi_portal_save in GC build: walk %r14 (env chain); for each non-builtin, non-closure binding, emit `(define <sym> (quote <val>))` to the opened file via scheme_print with output_fd redirected to that fd. # bi_portal_resume in GC build: jmp bi_load — read every form from the file, eval each in %r14. The quote wrapper makes data values round-trip cleanly: lists, vectors, strings, symbols, numbers, pairs all re-read as literals. Closures and builtins are explicitly skipped — closures can't faithfully re-read from their printed form; builtins reconstruct from the target's prelude. Same treatment the JSON portal gives. The no-GC build keeps the binary portal format unchanged (wrapped in .ifndef GC_NAIVE). Users get the fast format on the fast build, the portable format on the safe build. Same API, different wire format by build. Cross-tier verified: GC-asm producer -> Python consumer passes with `x=42`, `nums=(1 2 3 4 5)`. The §7.2 cross-impl matrix expands from 9 to 16 cells, all green. Whitepaper §7.4 now notes it's the no-GC format; new §7.4.1 documents the GC build's S-expression portal with the trade-off (slower than binary dump, stricter about what round-trips, but no architecture constraint and no "same binary" requirement). 137 asm no-GC + 137 asm GC + 189 shared functional tests pass. All four HTTP cells from §6.6.4 still bounded under sustained load (GC + no-snapshot at ~630 req/s peak, 1.1 MB steady state).
This commit is contained in:
parent
5ec9eff5fe
commit
58025a37bc
7 changed files with 1142 additions and 792 deletions
BIN
asm/uncommonlisp
BIN
asm/uncommonlisp
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -407,6 +407,12 @@ s_port: .ascii "#<port>"
|
|||
s_lparen: .ascii "("
|
||||
s_rparen: .ascii ")"
|
||||
s_space: .ascii " "
|
||||
s_pdefine: .ascii "(define "
|
||||
.equ s_pdefine_len, 8
|
||||
s_quote_op: .ascii " (quote "
|
||||
.equ s_quote_op_len, 8
|
||||
s_rparen2_nl: .ascii "))\n"
|
||||
s_rparen_nl: .ascii ")\n"
|
||||
s_dotsp: .ascii " . "
|
||||
s_minus: .ascii "-"
|
||||
s_hashparen: .ascii "#("
|
||||
|
|
@ -4836,6 +4842,7 @@ bi_substr:
|
|||
# Carry on USB to air-gapped machine. Resume from exact state.
|
||||
# ============================================================
|
||||
|
||||
.ifndef GC_NAIVE
|
||||
bi_portal_save:
|
||||
# (portal-save "filename") — dump heap + state to file
|
||||
GETARG %rdi # filename (string value)
|
||||
|
|
@ -4926,7 +4933,118 @@ bi_portal_save:
|
|||
.ps_fail:
|
||||
movq $VAL_FALSE, %rax
|
||||
RET_VAL
|
||||
.endif
|
||||
|
||||
.ifdef GC_NAIVE
|
||||
# GC build: binary-dump portal would require serializing the chunk
|
||||
# list + headers + free list + pointer remapping. Instead we emit
|
||||
# S-expressions — walk the global env chain, write `(define <sym>
|
||||
# <val>)` per binding. Works across tiers via the read-plus-eval
|
||||
# loop that bi_load already implements.
|
||||
#
|
||||
# Skips builtins and closures whose bodies can't round-trip through
|
||||
# scheme_print → scheme_read without source-level reconstruction.
|
||||
# Data values (pairs, strings, numbers, vectors) serialize cleanly.
|
||||
bi_portal_save:
|
||||
GETARG %rdi
|
||||
andq $-8, %rdi
|
||||
movq (%rdi), %rcx # filename length
|
||||
leaq 8(%rdi), %rdi # filename bytes
|
||||
|
||||
subq $256, %rsp
|
||||
movq %rsp, %rsi
|
||||
pushq %rcx
|
||||
.ps_gc_copy:
|
||||
testq %rcx, %rcx
|
||||
jz .ps_gc_named
|
||||
movb (%rdi), %al
|
||||
movb %al, (%rsi)
|
||||
incq %rdi
|
||||
incq %rsi
|
||||
decq %rcx
|
||||
jmp .ps_gc_copy
|
||||
.ps_gc_named:
|
||||
movb $0, (%rsi)
|
||||
popq %rcx
|
||||
|
||||
movq $SYS_OPEN, %rax
|
||||
movq %rsp, %rdi
|
||||
movq $(O_WRONLY | O_CREAT | O_TRUNC), %rsi
|
||||
movq $0644, %rdx
|
||||
syscall
|
||||
addq $256, %rsp
|
||||
testq %rax, %rax
|
||||
js .ps_gc_fail
|
||||
movq %rax, %rbx # fd
|
||||
|
||||
# Redirect printer to the portal file.
|
||||
movq output_fd(%rip), %r12 # save caller's fd
|
||||
movq %rbx, output_fd(%rip)
|
||||
|
||||
# Walk env chain from %r14. Each node = [sym, val, parent].
|
||||
movq %r14, %rbp # cursor
|
||||
.ps_gc_loop:
|
||||
testq %rbp, %rbp
|
||||
jz .ps_gc_done
|
||||
# Skip nodes whose val is a builtin — builtins are recreated on
|
||||
# resume by the target interpreter, no need to serialize.
|
||||
movq 8(%rbp), %rax # val
|
||||
movq %rax, %rcx
|
||||
andq $TAG_MASK, %rcx
|
||||
cmpq $TAG_BUILTIN, %rcx
|
||||
je .ps_gc_next
|
||||
cmpq $TAG_CLOSURE, %rcx
|
||||
je .ps_gc_next # can't re-read a closure from its printed form
|
||||
# Emit "(define "
|
||||
leaq s_pdefine(%rip), %rsi
|
||||
movq $s_pdefine_len, %rdx
|
||||
movq $SYS_WRITE, %rax
|
||||
movq output_fd(%rip), %rdi
|
||||
syscall
|
||||
# Print sym
|
||||
movq (%rbp), %rdi # sym (tagged)
|
||||
call scheme_print
|
||||
# " (quote "
|
||||
movq $SYS_WRITE, %rax
|
||||
movq output_fd(%rip), %rdi
|
||||
leaq s_quote_op(%rip), %rsi
|
||||
movq $s_quote_op_len, %rdx
|
||||
syscall
|
||||
# Print val
|
||||
movq 8(%rbp), %rdi
|
||||
call scheme_print
|
||||
# "))\n"
|
||||
movq $SYS_WRITE, %rax
|
||||
movq output_fd(%rip), %rdi
|
||||
leaq s_rparen2_nl(%rip), %rsi
|
||||
movq $3, %rdx
|
||||
syscall
|
||||
.ps_gc_next:
|
||||
movq 16(%rbp), %rbp # parent
|
||||
jmp .ps_gc_loop
|
||||
.ps_gc_done:
|
||||
# Restore printer fd.
|
||||
movq %r12, output_fd(%rip)
|
||||
# Close portal file.
|
||||
movq $SYS_CLOSE, %rax
|
||||
movq %rbx, %rdi
|
||||
syscall
|
||||
movq $VAL_VOID, %rax
|
||||
RET_VAL
|
||||
.ps_gc_fail:
|
||||
movq $VAL_FALSE, %rax
|
||||
RET_VAL
|
||||
.endif
|
||||
|
||||
.ifdef GC_NAIVE
|
||||
# GC build: portal-resume is equivalent to (load "filename") since
|
||||
# the GC portal format is S-expressions. No magic header, no binary
|
||||
# heap dump to splice back in — just read+eval every form.
|
||||
bi_portal_resume:
|
||||
jmp bi_load
|
||||
.endif
|
||||
|
||||
.ifndef GC_NAIVE
|
||||
bi_portal_resume:
|
||||
# (portal-resume "filename") — restore heap + state from file
|
||||
GETARG %rdi # filename string
|
||||
|
|
@ -5030,6 +5148,7 @@ bi_portal_resume:
|
|||
.pr_fail:
|
||||
movq $VAL_FALSE, %rax
|
||||
RET_VAL
|
||||
.endif
|
||||
|
||||
# ============================================================
|
||||
# bi_load: (load "path") — read file, eval every form in r14 env
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue