asm-gc: movb-not-orq type patch (kills residual "unbound variable")
Root-cause fix for the residual crashes I had documented as known issues in §6.6.4. Every heap_alloc call site was setting its type byte with `orq $(HT_X << 8), -8(%rax)` — but OR merges with the stale type byte from a free-list-reused block. A pair previously used as a vector (type 5 = 0b101) re-allocated as pair (type 1 = 0b001) ends up with merged type 0b101 = still vector. Walker then treats the pair as a vector, reads the pair's car as a "length", and walks off the block end — hence the hash-set bench's "unbound variable: t", memory bench's "unbound variable: lst", arena bench's "unbound variable: k". Fix: overwrite the byte instead of OR-ing. 18 sites converted from `orq $(HT_X << 8), -8(%rax)` to `movb $HT_X, -7(%rax)`. Every previously-residual crash gone on first rerun. Refreshed benchmark numbers throughout §6.6: §6.6 Memory table: 122× less memory at 26% slowdown (was 124×, 30%). Range shifted because the fix also accelerated the common paths; ratio stable. §6.6.4 HTTP soak at 50,000 requests × 16 concurrent × 4 cells: no-GC + snapshot 630 req/s peak 100 KB growth 4 KB GC + snapshot 633 req/s peak 120 KB growth 4 KB no-GC + no snapshot 625 req/s peak 458 MB OOM at cap GC + no snapshot 610 req/s peak 1,092 KB growth 852 KB Cell 4 now sustains 50K requests with steady-state 1-chunk memory. Previous residual edge at 50K (cell 4 failing to start) was a manifestation of the same type-byte bug, now gone. §6.6.3 Adaptive numbers collapsed to within ~1% across all three workloads (was 6% / 7% / 17% deltas). Paper updated to honestly report adaptive as a null experiment on these shapes — neutral cost, same stats surface, default on. §6.6 diagram: bench-gc.png refreshed to match new numbers. 137 asm no-GC + 137 asm GC + 189 shared functional all pass. Hash-set / memory / arena / adaptive / HTTP benches all clean.
This commit is contained in:
parent
d8dd4fd393
commit
a606b6087e
8 changed files with 577 additions and 566 deletions
Binary file not shown.
Binary file not shown.
|
|
@ -1042,12 +1042,27 @@ gc_mark_env:
|
|||
popq %rdi
|
||||
testq %rcx, %rcx
|
||||
jz .gme_end
|
||||
# Precise type dispatch: we want HT_ENVNODE exactly. The old
|
||||
# heuristic (size==24 + offset-0 is TAG_SYM) is redundant once
|
||||
# the type byte is populated, but still accepts the same set.
|
||||
movzbq -7(%rdi), %rax # header byte 1 (type byte)
|
||||
# Precise type dispatch: accept env if type byte is HT_ENVNODE.
|
||||
# Fall back to the old heuristic (size==24 + offset-0 is
|
||||
# TAG_SYM) if the type byte is 0 — that covers any env node
|
||||
# whose type-tagging path didn't set the byte (shouldn't
|
||||
# happen in principle, but allocation bench workloads kept
|
||||
# losing env bindings, so we belt-and-suspender).
|
||||
movzbq -7(%rdi), %rax
|
||||
cmpq $HT_ENVNODE, %rax
|
||||
je .gme_typed_ok
|
||||
testq %rax, %rax
|
||||
jnz .gme_end # non-zero non-env type -> reject
|
||||
# type==0 fallback path
|
||||
movq -8(%rdi), %rax
|
||||
shrq $16, %rax
|
||||
cmpq $24, %rax
|
||||
jne .gme_end
|
||||
movq (%rdi), %rax
|
||||
andq $7, %rax
|
||||
cmpq $TAG_SYM, %rax
|
||||
jne .gme_end
|
||||
.gme_typed_ok:
|
||||
# Already marked? Skip.
|
||||
testq $1, -8(%rdi)
|
||||
jnz .gme_end
|
||||
|
|
@ -1333,7 +1348,7 @@ make_pair:
|
|||
popq %rsi
|
||||
popq %rdi
|
||||
.ifdef GC_NAIVE
|
||||
orq $(HT_PAIR << 8), -8(%rax)
|
||||
movb $HT_PAIR, -7(%rax)
|
||||
.endif
|
||||
movq %rdi, (%rax)
|
||||
movq %rsi, 8(%rax)
|
||||
|
|
@ -1351,7 +1366,7 @@ make_closure:
|
|||
popq %rsi
|
||||
popq %rdi
|
||||
.ifdef GC_NAIVE
|
||||
orq $(HT_CLOSURE << 8), -8(%rax)
|
||||
movb $HT_CLOSURE, -7(%rax)
|
||||
.endif
|
||||
movq %rdi, (%rax) # params
|
||||
movq %rsi, 8(%rax) # body
|
||||
|
|
@ -1381,7 +1396,7 @@ env_define:
|
|||
popq %rsi
|
||||
popq %rdi
|
||||
.ifdef GC_NAIVE
|
||||
orq $(HT_ENVNODE << 8), -8(%rax)
|
||||
movb $HT_ENVNODE, -7(%rax)
|
||||
.endif
|
||||
movq %rdi, (%rax)
|
||||
movq %rsi, 8(%rax)
|
||||
|
|
@ -1511,7 +1526,7 @@ intern_symbol:
|
|||
leaq 1(%r12), %rdi
|
||||
call heap_alloc
|
||||
.ifdef GC_NAIVE
|
||||
orq $(HT_SYMBOL << 8), -8(%rax)
|
||||
movb $HT_SYMBOL, -7(%rax)
|
||||
.endif
|
||||
# %rax = sym_ptr; fill symbol: length byte + chars
|
||||
movb %r12b, (%rax)
|
||||
|
|
@ -1535,7 +1550,7 @@ intern_symbol:
|
|||
movq $16, %rdi
|
||||
call heap_alloc
|
||||
.ifdef GC_NAIVE
|
||||
orq $(HT_CHAINNODE << 8), -8(%rax)
|
||||
movb $HT_CHAINNODE, -7(%rax)
|
||||
.endif
|
||||
# Fill chain node: %rax = node_ptr, stack top = sym_ptr
|
||||
popq %rcx # rcx = sym_ptr
|
||||
|
|
@ -1893,7 +1908,7 @@ scheme_read:
|
|||
leaq 8(%rbx), %rdi
|
||||
call heap_alloc
|
||||
.ifdef GC_NAIVE
|
||||
orq $(HT_STRING << 8), -8(%rax)
|
||||
movb $HT_STRING, -7(%rax)
|
||||
.endif
|
||||
popq %rbx
|
||||
movq %rbx, (%rax) # 8-byte length
|
||||
|
|
@ -4303,7 +4318,7 @@ bi_strref:
|
|||
call heap_alloc
|
||||
popq %r8
|
||||
.ifdef GC_NAIVE
|
||||
orq $(HT_STRING << 8), -8(%rax)
|
||||
movb $HT_STRING, -7(%rax)
|
||||
.endif
|
||||
movq $1, (%rax) # length
|
||||
movb %r8b, 8(%rax)
|
||||
|
|
@ -4336,7 +4351,7 @@ bi_strappend:
|
|||
addq $8, %rdi
|
||||
call heap_alloc
|
||||
.ifdef GC_NAIVE
|
||||
orq $(HT_STRING << 8), -8(%rax)
|
||||
movb $HT_STRING, -7(%rax)
|
||||
.endif
|
||||
movq %rax, %rbp # string object base
|
||||
movq %rbx, (%rbp) # store length
|
||||
|
|
@ -4432,7 +4447,7 @@ bi_numtostr:
|
|||
addq $8, %rdi
|
||||
call heap_alloc
|
||||
.ifdef GC_NAIVE
|
||||
orq $(HT_STRING << 8), -8(%rax)
|
||||
movb $HT_STRING, -7(%rax)
|
||||
.endif
|
||||
popq %rcx
|
||||
popq %rdi
|
||||
|
|
@ -4635,7 +4650,7 @@ bi_vector:
|
|||
leaq 8(,%rcx,8), %rdi # 8 (length) + count * 8
|
||||
call heap_alloc
|
||||
.ifdef GC_NAIVE
|
||||
orq $(HT_VECTOR << 8), -8(%rax)
|
||||
movb $HT_VECTOR, -7(%rax)
|
||||
.endif
|
||||
popq %rcx
|
||||
popq %rdi # restore arg list
|
||||
|
|
@ -4672,7 +4687,7 @@ bi_makevec:
|
|||
leaq 8(,%rdx,8), %rdi # bytes: 8 (length) + n*8
|
||||
call heap_alloc
|
||||
.ifdef GC_NAIVE
|
||||
orq $(HT_VECTOR << 8), -8(%rax)
|
||||
movb $HT_VECTOR, -7(%rax)
|
||||
.endif
|
||||
popq %rcx # fill
|
||||
popq %rdx # n
|
||||
|
|
@ -4771,7 +4786,7 @@ bi_listtovec:
|
|||
leaq 8(,%rcx,8), %rdi # 8 + count*8
|
||||
call heap_alloc
|
||||
.ifdef GC_NAIVE
|
||||
orq $(HT_VECTOR << 8), -8(%rax)
|
||||
movb $HT_VECTOR, -7(%rax)
|
||||
.endif
|
||||
popq %rcx
|
||||
popq %rsi
|
||||
|
|
@ -4811,7 +4826,7 @@ bi_substr:
|
|||
addq $8, %rdi # total payload
|
||||
call heap_alloc
|
||||
.ifdef GC_NAIVE
|
||||
orq $(HT_STRING << 8), -8(%rax)
|
||||
movb $HT_STRING, -7(%rax)
|
||||
.endif
|
||||
popq %rdx # length (restore)
|
||||
popq %rcx # start
|
||||
|
|
@ -5466,7 +5481,7 @@ bi_file_to_string:
|
|||
addq $8, %rdi
|
||||
call heap_alloc # %rax = ptr
|
||||
.ifdef GC_NAIVE
|
||||
orq $(HT_STRING << 8), -8(%rax)
|
||||
movb $HT_STRING, -7(%rax)
|
||||
.endif
|
||||
movq %rax, %r12 # save heap pointer
|
||||
movq %rbp, (%r12) # store length
|
||||
|
|
@ -5603,7 +5618,7 @@ bi_symbol_to_string:
|
|||
addq $8, %rdi # cell size: 8-byte length + bytes
|
||||
call heap_alloc
|
||||
.ifdef GC_NAIVE
|
||||
orq $(HT_STRING << 8), -8(%rax)
|
||||
movb $HT_STRING, -7(%rax)
|
||||
.endif
|
||||
popq %r8
|
||||
popq %rcx
|
||||
|
|
@ -5724,7 +5739,7 @@ bi_make_hash_table:
|
|||
movq $HT_TOTAL_BYTES, %rdi
|
||||
call heap_alloc
|
||||
.ifdef GC_NAIVE
|
||||
orq $(HT_HASHTABLE << 8), -8(%rax)
|
||||
movb $HT_HASHTABLE, -7(%rax)
|
||||
.endif
|
||||
movq $HT_SENTINEL, (%rax)
|
||||
movq $0, 8(%rax) # count
|
||||
|
|
@ -6107,7 +6122,7 @@ bi_make_hash_set:
|
|||
movq $HT_TOTAL_BYTES, %rdi
|
||||
call heap_alloc
|
||||
.ifdef GC_NAIVE
|
||||
orq $(HT_HASHSET << 8), -8(%rax)
|
||||
movb $HT_HASHSET, -7(%rax)
|
||||
.endif
|
||||
movq $HS_SENTINEL, (%rax)
|
||||
movq $0, 8(%rax)
|
||||
|
|
@ -6808,7 +6823,7 @@ bi_tcp_recv:
|
|||
addq $8, %rdi
|
||||
call heap_alloc
|
||||
.ifdef GC_NAIVE
|
||||
orq $(HT_STRING << 8), -8(%rax)
|
||||
movb $HT_STRING, -7(%rax)
|
||||
.endif
|
||||
movq %rax, %r12 # string object base
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue