asm: case as always-on special form (defect #32)

ecdsa cross-tier validation on lumbda-gc and bump-only lumbda blocked
on `case` being unbound. R7RS standard control form — present on
Python (lumbda.py) and C (c/) tiers, but on asm reachable only via
cl_full_prelude's define-macro form (carved into lumbda-full only).
Mirrors commit 865be28 (when/unless via Path B dispatch table).

Path B (special-form dispatch table extension):
  - sf_case length-prefixed symbol name
  - sym_case_val interned at init_special_forms
  - dispatch case in .eval_top alongside .ev_when / .ev_unless
    (placed before the .ifdef CL_FULL macro-lookup block so the
     dispatch shadow takes precedence over the cl_full_prelude macro
     on lumbda-full — no conflict, the macro just becomes dead code)
  - .ev_case evaluator: eval key once, push on stack, walk clauses;
    each clause's datum list compared by pointer equality (eqv? on
    the asm tier — fixnums, symbols, booleans, characters, nil are
    all interned/unboxed to unique values). `else` matches uncondi-
    tionally. Match → .ev_begin (TCO). No match → .ev_begin_void.

Available on every asm tier (plain `lumbda`, `lumbda-gc`,
`lumbda-full`). Binary size impact:
  lumbda     60768 → 61040  (+272, +0.45%)
  lumbda-gc  69496 → 69768  (+272, +0.39%)
  lumbda-full 72000 → 72264 (+264, +0.37%)

All 158 asm tests still pass. Sanity tests: single-datum, multi-datum,
symbol key (eqv?), else, empty body, no-match, nested case — all
correct on all three tiers.

ecdsa search.lsp now produces byte-identical winner (v3-clifford-only
score 0) across five tiers: Python, C, asm-bump, asm-gc, asm-full.
This commit is contained in:
russell@unturf.com 2026-06-04 13:04:34 -04:00
parent 865be28091
commit ebcf1625c7
No known key found for this signature in database

View file

@ -248,6 +248,7 @@ sf_or: .byte 2; .ascii "or"
sf_else: .byte 4; .ascii "else"
sf_when: .byte 4; .ascii "when"
sf_unless: .byte 6; .ascii "unless"
sf_case: .byte 4; .ascii "case"
# Builtin names (length-prefixed)
bn_add: .byte 1; .ascii "+"
@ -558,6 +559,7 @@ sym_or_val: .quad 0
sym_else_val: .quad 0
sym_when_val: .quad 0
sym_unless_val: .quad 0
sym_case_val: .quad 0
# ============================================================
.bss
@ -2819,6 +2821,8 @@ eval:
je .ev_when
cmpq sym_unless_val(%rip), %rbx
je .ev_unless
cmpq sym_case_val(%rip), %rbx
je .ev_case
.ifdef CL_FULL
cmpq sym_quasiquote_val(%rip), %rbx
je .ev_quasiquote
@ -3571,6 +3575,68 @@ env_set_both:
.ev_unless_run:
jmp .ev_begin
# ---- case ----
# (case key clause ...) where each clause is
# ((datum ...) body...) or (else body...)
# Evaluate `key` once, then walk clauses; for the first clause whose
# datum list contains a value `eqv?` to key, evaluate body via begin
# (TCO). `else` matches unconditionally and must be last. If no clause
# matches, return void. R7RS standard form Path B mirrors the just-
# shipped `when`/`unless` fix (commit 865be28); previously case lived
# only in cl_full_prelude as a define-macro form, leaving lumbda-gc
# and bump-only lumbda unable to run ecdsa search.lsp.
# eqv? on the asm tier is pointer equality (fixnums, symbols, bools,
# chars, nil are all interned/unboxed to unique values).
.ev_case:
# %r12 = (key . clauses)
cmpq $VAL_NIL, %r12
je .ev_begin_void # (case) void (degenerate)
movq %r12, %rax
andq $-8, %rax
movq (%rax), %rdi # key expr
movq 8(%rax), %r12 # clauses
pushq %r12 # save clauses
movq %rbp, %rsi
call eval
popq %r12 # restore clauses
pushq %rax # save key value on stack
# Loop over clauses; key sits at (%rsp).
.ev_case_loop:
cmpq $VAL_NIL, %r12
je .ev_case_void
movq %r12, %rax
andq $-8, %rax
movq (%rax), %rbx # current clause
movq 8(%rax), %r12 # rest clauses
# Clause shape: (datums . body) or (else . body)
movq %rbx, %rax
andq $-8, %rax
movq (%rax), %rdx # datums (or `else` symbol)
movq 8(%rax), %rcx # body forms
# else clause matches unconditionally
cmpq sym_else_val(%rip), %rdx
je .ev_case_match
# Walk datum list; key is at (%rsp)
movq (%rsp), %rdi
.ev_case_dloop:
cmpq $VAL_NIL, %rdx
je .ev_case_loop # exhausted datums next clause
movq %rdx, %rax
andq $-8, %rax
movq (%rax), %rsi # datum
movq 8(%rax), %rdx # rest datums
cmpq %rdi, %rsi # eqv? pointer compare
je .ev_case_match
jmp .ev_case_dloop
.ev_case_match:
# %rcx = body forms. Discard saved key, run body via begin (TCO).
addq $8, %rsp
movq %rcx, %r12
jmp .ev_begin
.ev_case_void:
addq $8, %rsp # discard saved key
jmp .ev_begin_void
.ifdef CL_FULL
# ---- quasiquote (CL_FULL only) ----
# (quasiquote template) walks `template`:
@ -8668,6 +8734,10 @@ init_special_forms:
call intern_static
movq %rax, sym_unless_val(%rip)
leaq sf_case(%rip), %rdi
call intern_static
movq %rax, sym_case_val(%rip)
# Intern the multi-value marker symbol used by (values) and
# (call-with-values). Stored once at init.
leaq bn_mval_tag(%rip), %rdi