asm-gc portal v1: explicit version header + resume-side version check
GC-build portal files now start with ";; lumbda-portal v1\n". The line is a Scheme comment the reader already skips, so loading a v1 file via bi_load works unchanged. What's new is that portal-resume actively validates the header before delegating to bi_load: file starts with ";; lumbda-portal v1\n" -> load normally file starts with ";;" but different text -> return #f (rejected) file does not start with ";;" at all -> load as legacy (back-compat) The check reads the first 32 bytes of the file, compares the first two bytes against ";;", and on match compares the full 20-byte v1 prefix. Closes the versioning-friction concern a SEW reviewer raised after reading §7.4.1: we can now add a v2 format with new syntax (complex numbers, records, whatever) without older consumers silently parsing new files into garbage — they will cleanly return #f. Verified: v1 portal -> resume loads all bindings, returns #<void> v2 portal -> resume returns #f without evaluating any forms legacy portal (no ;; header) -> resume loads normally missing file -> resume returns #f The deeper framing worth writing down: this is a migration format for handoff across process / tier / machine, not an archive format. If archival becomes a real use case it earns its own format with proper schema evolution and a builtin-rename table. The v1 tag is the minimum hook that lets v2 happen cleanly when someone needs it. 137 asm no-GC + 137 asm GC still pass. §6.6.4 HTTP cells still green at 10K: GC + no-snapshot at 543 req/s, peak 1.1 MB, 972 KB growth (one chunk, steady state).
This commit is contained in:
parent
a606b6087e
commit
9e54c7aaf1
5 changed files with 477 additions and 358 deletions
Binary file not shown.
Binary file not shown.
|
|
@ -416,6 +416,14 @@ s_rparen_nl: .ascii ")\n"
|
|||
s_dotsp: .ascii " . "
|
||||
s_minus: .ascii "-"
|
||||
s_hashparen: .ascii "#("
|
||||
# Portal v1 header. GC-build portal-save emits this as the first
|
||||
# line of every output file; portal-resume reads the first bytes
|
||||
# and verifies the prefix before loading. A file starting with ";;"
|
||||
# that does NOT match is rejected as an unsupported version. A file
|
||||
# that does not start with ";;" at all is accepted as legacy
|
||||
# (pre-v1) and loaded normally for back-compat.
|
||||
s_portal_v1: .ascii ";; lumbda-portal v1\n"
|
||||
.equ s_portal_v1_len, 20
|
||||
|
||||
# Interned special form symbols (filled at init)
|
||||
.align 8
|
||||
|
|
@ -4992,6 +5000,14 @@ bi_portal_save:
|
|||
js .ps_gc_fail
|
||||
movq %rax, %rbx # fd
|
||||
|
||||
# Emit v1 header. resume() will read this first line and bail
|
||||
# if it doesn't match, letting us evolve the format cleanly.
|
||||
movq $SYS_WRITE, %rax
|
||||
movq %rbx, %rdi
|
||||
leaq s_portal_v1(%rip), %rsi
|
||||
movq $s_portal_v1_len, %rdx
|
||||
syscall
|
||||
|
||||
# Redirect printer to the portal file.
|
||||
movq output_fd(%rip), %r12 # save caller's fd
|
||||
movq %rbx, output_fd(%rip)
|
||||
|
|
@ -5052,11 +5068,103 @@ bi_portal_save:
|
|||
.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.
|
||||
# GC build: portal-resume reads the v1 header, then delegates to
|
||||
# bi_load for the actual form-by-form evaluation. Header check
|
||||
# lets us reject unknown future versions cleanly instead of
|
||||
# getting a mysterious parse error on new syntax.
|
||||
#
|
||||
# Policy:
|
||||
# file starts with ";; lumbda-portal v1\n" -> load
|
||||
# file starts with ";;" but different -> VAL_FALSE (reject)
|
||||
# file does not start with ";;" at all -> load as legacy
|
||||
bi_portal_resume:
|
||||
# Peek at arg without consuming %r12 — bi_load needs its own
|
||||
# GETARG to re-read the filename after we've validated the
|
||||
# header.
|
||||
movq %r12, %rax
|
||||
andq $-8, %rax
|
||||
movq (%rax), %rbx # filename (tagged string)
|
||||
andq $-8, %rbx
|
||||
movq (%rbx), %rcx # string length
|
||||
leaq 8(%rbx), %rsi # bytes
|
||||
|
||||
# Null-terminate on stack (256-byte slot).
|
||||
subq $256, %rsp
|
||||
movq %rsp, %rdi
|
||||
movq %rcx, %rdx
|
||||
.prg_cp:
|
||||
testq %rdx, %rdx
|
||||
jz .prg_cp_done
|
||||
movb (%rsi), %al
|
||||
movb %al, (%rdi)
|
||||
incq %rsi
|
||||
incq %rdi
|
||||
decq %rdx
|
||||
jmp .prg_cp
|
||||
.prg_cp_done:
|
||||
movb $0, (%rdi)
|
||||
|
||||
# Open read-only.
|
||||
movq $SYS_OPEN, %rax
|
||||
movq %rsp, %rdi
|
||||
movq $O_RDONLY, %rsi
|
||||
xorq %rdx, %rdx
|
||||
syscall
|
||||
addq $256, %rsp
|
||||
testq %rax, %rax
|
||||
js .prg_fail
|
||||
movq %rax, %rbx # fd
|
||||
|
||||
# Read up to 32 bytes to inspect the header.
|
||||
subq $32, %rsp
|
||||
movq $SYS_READ, %rax
|
||||
movq %rbx, %rdi
|
||||
movq %rsp, %rsi
|
||||
movq $32, %rdx
|
||||
syscall
|
||||
# rax = bytes actually read. If <2 we can't tell what it is — accept.
|
||||
cmpq $2, %rax
|
||||
jl .prg_close_accept
|
||||
# Is this a comment line (starts with ";;")?
|
||||
movzwl (%rsp), %eax
|
||||
cmpl $0x3b3b, %eax # ";;"
|
||||
jne .prg_close_accept # no header marker — legacy file, accept
|
||||
# Header present. Must match s_portal_v1 exactly for the first
|
||||
# s_portal_v1_len bytes.
|
||||
leaq s_portal_v1(%rip), %rdi
|
||||
movq %rsp, %rsi
|
||||
movq $s_portal_v1_len, %rcx
|
||||
.prg_match:
|
||||
movb (%rdi), %al
|
||||
cmpb (%rsi), %al
|
||||
jne .prg_bad_version
|
||||
incq %rdi
|
||||
incq %rsi
|
||||
decq %rcx
|
||||
jnz .prg_match
|
||||
# Fall through — header matches.
|
||||
|
||||
.prg_close_accept:
|
||||
addq $32, %rsp
|
||||
movq $SYS_CLOSE, %rax
|
||||
movq %rbx, %rdi
|
||||
syscall
|
||||
# Delegate to bi_load, which re-opens the file and reads every
|
||||
# form. The v1 header itself is a ";; comment" line the reader
|
||||
# already skips.
|
||||
jmp bi_load
|
||||
|
||||
.prg_bad_version:
|
||||
addq $32, %rsp
|
||||
movq $SYS_CLOSE, %rax
|
||||
movq %rbx, %rdi
|
||||
syscall
|
||||
movq $VAL_FALSE, %rax
|
||||
RET_VAL
|
||||
|
||||
.prg_fail:
|
||||
movq $VAL_FALSE, %rax
|
||||
RET_VAL
|
||||
.endif
|
||||
|
||||
.ifndef GC_NAIVE
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue