asm: when/unless as always-on special forms (defect #31)

ecdsa cross-tier validation blocked on `when` and `unless` being
unbound on the asm tier. R7RS standard control forms — present on
Python (lumbda.py) and C (c/) tiers, but absent on asm because the
existing macro facility (define-macro) only ships under CL_FULL.

Path B chosen (special-form dispatch table extension):
  - sf_when / sf_unless length-prefixed symbol names
  - sym_when_val / sym_unless_val interned at init_special_forms
  - dispatch cases in .eval_top alongside .ev_and / .ev_or
  - .ev_when / .ev_unless evaluators reuse .ev_begin for the body
    branch and .ev_begin_void for the skip branch (TCO preserved)

Available on every asm tier (plain `lumbda`, `lumbda-gc`,
`lumbda-full`). Binary size impact:
  lumbda     60488 → 60768  (+280, +0.46%)
  lumbda-gc  69224 → 69496  (+272, +0.39%)
  lumbda-full 71720 → 72000 (+280, +0.39%)

All 158 asm tests still pass. Tested truthy/falsy/multi-form bodies
on all three tiers. ecdsa search.lsp now runs on asm-full with output
byte-identical to Python tier (v3-clifford-only winner, score 0).

Note: ecdsa search.lsp also depends on `case`, which is only present
under CL_FULL (carved into cl_full_prelude as a define-macro form).
That gap blocks lumbda-gc cross-tier validation and is out of scope
for this commit.
This commit is contained in:
russell@unturf.com 2026-06-04 12:58:52 -04:00
parent 297ae976e2
commit 865be28091
No known key found for this signature in database

View file

@ -246,6 +246,8 @@ sf_define_mac: .byte 12; .ascii "define-macro"
sf_and: .byte 3; .ascii "and"
sf_or: .byte 2; .ascii "or"
sf_else: .byte 4; .ascii "else"
sf_when: .byte 4; .ascii "when"
sf_unless: .byte 6; .ascii "unless"
# Builtin names (length-prefixed)
bn_add: .byte 1; .ascii "+"
@ -554,6 +556,8 @@ sym_define_mac_val: .quad 0
sym_and_val: .quad 0
sym_or_val: .quad 0
sym_else_val: .quad 0
sym_when_val: .quad 0
sym_unless_val: .quad 0
# ============================================================
.bss
@ -2811,6 +2815,10 @@ eval:
je .ev_and
cmpq sym_or_val(%rip), %rbx
je .ev_or
cmpq sym_when_val(%rip), %rbx
je .ev_when
cmpq sym_unless_val(%rip), %rbx
je .ev_unless
.ifdef CL_FULL
cmpq sym_quasiquote_val(%rip), %rbx
je .ev_quasiquote
@ -3517,6 +3525,52 @@ env_set_both:
popq %rbx
ret
# ---- when ----
# (when test body...) if test is truthy, eval body as begin;
# else return void. Available on every asm tier (defect #31 in our
# ecdsa cross-tier validation: when/unless are R7RS standard but
# previously only reachable on tiers carrying define-macro).
.ev_when:
# %r12 = (test body...)
cmpq $VAL_NIL, %r12
je .ev_begin_void # (when) void (degenerate)
movq %r12, %rax
andq $-8, %rax
movq (%rax), %rdi # test expr
movq 8(%rax), %r12 # body forms
pushq %r12
movq %rbp, %rsi
call eval
popq %r12
cmpq $VAL_FALSE, %rax
je .ev_begin_void
cmpq $VAL_NIL, %rax
je .ev_begin_void
# Truthy run body via begin (TCO on last form)
jmp .ev_begin
# ---- unless ----
# (unless test body...) inverse of when.
.ev_unless:
cmpq $VAL_NIL, %r12
je .ev_begin_void # (unless) void
movq %r12, %rax
andq $-8, %rax
movq (%rax), %rdi # test expr
movq 8(%rax), %r12 # body forms
pushq %r12
movq %rbp, %rsi
call eval
popq %r12
cmpq $VAL_FALSE, %rax
je .ev_unless_run
cmpq $VAL_NIL, %rax
je .ev_unless_run
# Truthy skip body, return void
jmp .ev_begin_void
.ev_unless_run:
jmp .ev_begin
.ifdef CL_FULL
# ---- quasiquote (CL_FULL only) ----
# (quasiquote template) walks `template`:
@ -8606,6 +8660,14 @@ init_special_forms:
call intern_static
movq %rax, sym_else_val(%rip)
leaq sf_when(%rip), %rdi
call intern_static
movq %rax, sym_when_val(%rip)
leaq sf_unless(%rip), %rdi
call intern_static
movq %rax, sym_unless_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