Two coupled defects surfaced during ecdsa cross-tier validation against the asm tier. Defect #28 — _start ignored argv. Invoking `asm/lumbda-gc file.lsp` silently discarded argv[1] and dropped into a REPL that blocked on a pty when run under SSH. Walk argc/argv after init_builtins + prelude load and before repl_top: for each argv[i] starting at i=1, skip arg if it begins with '-' (flag stub), otherwise allocate a Scheme string from the C string, wrap in a 1-element arg list, dispatch through apply_proc_raw on the BI_LOAD builtin. If any non-flag arg ran, jump to repl_exit instead of entering the REPL. Mirrors the c/main.c script-mode semantics. The RET_VAL macro on the builtin return path pops r12/rbp/rbx in an order that corrupts %rbp (it restores the pre-call %r12 into rbp), so the loop counter saves %rbp around the apply_proc_raw call. Defect #30 — eq? was only present under CL_FULL. The plain `lumbda` and `lumbda-gc` binaries shipped without the alias `(define eq? eqv?)`, so any .lsp expecting eq? (every cross-tier file we own) hit "unbound variable: eq?" the moment it tried a status check. Lift that single alias into a new always-on `default_prelude` block with its own `load_default_prelude` loader (modelled after load_cl_full_prelude), and call it unconditionally from _start between rng_seed and the CL_FULL block. Verification: - `make asm-build` clean - `make asm-test`: 158 passed, 0 failed (full suite green) - `(eq? 1 1)` -> #t on all three tiers via stdin pipe AND file arg - `~/git/lumbda/asm/lumbda-gc /tmp/asm-test.lsp` exits 0 with #t printed
This commit is contained in:
parent
98d0e35a4e
commit
297ae976e2
7 changed files with 155 additions and 0 deletions
BIN
asm/lumbda
BIN
asm/lumbda
Binary file not shown.
BIN
asm/lumbda-full
BIN
asm/lumbda-full
Binary file not shown.
Binary file not shown.
BIN
asm/lumbda-gc
BIN
asm/lumbda-gc
Binary file not shown.
BIN
asm/lumbda-gc.o
BIN
asm/lumbda-gc.o
Binary file not shown.
BIN
asm/lumbda.o
BIN
asm/lumbda.o
Binary file not shown.
155
asm/lumbda.s
155
asm/lumbda.s
|
|
@ -385,6 +385,17 @@ s_hashset: .ascii "#<hash-set>"
|
|||
err_ht_miss: .ascii "Error: hash-table-ref: missing key\n"
|
||||
.equ err_ht_miss_len, . - err_ht_miss
|
||||
|
||||
# ── Default prelude — always loaded, every binary tier ───────────
|
||||
# Defines the Scheme-side aliases that cross-tier .lsp code expects
|
||||
# to exist on the plain `lumbda` and `lumbda-gc` builds (not just
|
||||
# `lumbda-full`). Kept minimal: every entry here ships in every
|
||||
# binary, so add only universally-needed forms. (Defect #30 in our
|
||||
# ecdsa cross-tier validation: `eq?` was only present under CL_FULL,
|
||||
# leaving `lumbda` and `lumbda-gc` with `eqv?` but no `eq?`.)
|
||||
default_prelude:
|
||||
.ascii "(define eq? eqv?)\n"
|
||||
.equ default_prelude_len, . - default_prelude
|
||||
|
||||
.ifdef CL_FULL
|
||||
# ── CL_FULL prelude — auto-loaded Scheme definitions ──────────────
|
||||
# Runs once at init, after builtins + RNG seed, before the REPL.
|
||||
|
|
@ -720,12 +731,105 @@ _start:
|
|||
xorq %rdi, %rdi
|
||||
call rng_seed
|
||||
|
||||
# Always-on prelude — defines `eq?` (and any future cross-tier
|
||||
# aliases). Required for parity with our Python + C tiers, which
|
||||
# carry these via their own preludes / native builtins.
|
||||
call load_default_prelude
|
||||
|
||||
.ifdef CL_FULL
|
||||
# Auto-load the CL_FULL prelude (caddr, 1+, list-ref, assq, …).
|
||||
# Runs before the REPL so user code sees the helpers immediately.
|
||||
call load_cl_full_prelude
|
||||
.endif
|
||||
|
||||
# ─── argv script-mode handling ───────────────────────────────
|
||||
# Stack layout at _start entry (preserved through here — the
|
||||
# only %rsp mutations above are paired sub/add 256 around ioctl):
|
||||
# 0(%rsp) = argc, 8(%rsp) = argv[0], 16(%rsp) = argv[1], …
|
||||
# For each argv[i] (i >= 1):
|
||||
# * Skip args starting with '-' (flag stubs, no-op for now).
|
||||
# * Otherwise treat as a script path: build a Scheme string,
|
||||
# wrap in a 1-element arg list, dispatch (load "path") via
|
||||
# apply_proc_raw on the BI_LOAD builtin.
|
||||
# If any non-flag arg was consumed, exit after processing all
|
||||
# args instead of entering the REPL. Mirrors c/main.c:234-252.
|
||||
#
|
||||
# %rbx = argc (callee-saved across apply_proc_raw).
|
||||
# %rbp = current argv index (1-based).
|
||||
# %r13 is the heap limit, so we cannot use it here — use a stack
|
||||
# slot for the "any-script-loaded" flag instead.
|
||||
movq 0(%rsp), %rbx # argc
|
||||
cmpq $1, %rbx
|
||||
jle .argv_done # argc <= 1 → no args, fall through
|
||||
pushq $0 # [rsp+0] = any-script-loaded flag (0 = none yet)
|
||||
movq $1, %rbp # i = 1 (skip argv[0])
|
||||
.argv_loop:
|
||||
cmpq %rbx, %rbp
|
||||
jge .argv_loop_done
|
||||
# argv pointer table sits at 8(%rsp_orig). We pushed one qword
|
||||
# (the flag), so add 8 to compensate.
|
||||
movq 16(%rsp,%rbp,8), %rdi # argv[i] = C string pointer
|
||||
# Skip if starts with '-'
|
||||
cmpb $'-', (%rdi)
|
||||
je .argv_next
|
||||
# Compute strlen of argv[i] → %rcx
|
||||
movq %rdi, %rcx
|
||||
.argv_strlen:
|
||||
cmpb $0, (%rcx)
|
||||
je .argv_strlen_done
|
||||
incq %rcx
|
||||
jmp .argv_strlen
|
||||
.argv_strlen_done:
|
||||
subq %rdi, %rcx # %rcx = length
|
||||
# Allocate Scheme string: heap_alloc(8 + length)
|
||||
pushq %rdi # save C string pointer
|
||||
pushq %rcx # save length
|
||||
leaq 8(%rcx), %rdi
|
||||
call heap_alloc
|
||||
.ifdef GC_NAIVE
|
||||
movb $HT_STRING, -7(%rax)
|
||||
.endif
|
||||
popq %rcx # restore length
|
||||
popq %rdi # restore C string pointer
|
||||
movq %rcx, (%rax) # store 8-byte length header
|
||||
# Copy bytes from argv[i] into payload at 8(%rax)
|
||||
xorq %rdx, %rdx
|
||||
.argv_copy:
|
||||
cmpq %rcx, %rdx
|
||||
jge .argv_copy_done
|
||||
movb (%rdi,%rdx), %sil
|
||||
movb %sil, 8(%rax,%rdx)
|
||||
incq %rdx
|
||||
jmp .argv_copy
|
||||
.argv_copy_done:
|
||||
orq $TAG_STRING, %rax # tag as Scheme string
|
||||
# Build 1-element arg list: (cons str NIL)
|
||||
movq %rax, %rdi
|
||||
movq $VAL_NIL, %rsi
|
||||
call make_pair
|
||||
movq %rax, %rsi # args list
|
||||
# BI_LOAD builtin value = (BI_LOAD << 3) | TAG_BUILTIN
|
||||
movq $((BI_LOAD << 3) | TAG_BUILTIN), %rdi
|
||||
# apply_proc_raw → bi_load returns via RET_VAL, which pops
|
||||
# r12/rbp/rbx in an order that leaves %rbp corrupted (it gets
|
||||
# the pre-call %r12 instead of pre-call %rbp). Save %rbp ourselves
|
||||
# across the call so our loop counter survives. %rbx (argc) is
|
||||
# restored cleanly.
|
||||
pushq %rbp
|
||||
call apply_proc_raw
|
||||
popq %rbp
|
||||
# Mark that at least one script ran.
|
||||
movq $1, 0(%rsp)
|
||||
.argv_next:
|
||||
incq %rbp
|
||||
jmp .argv_loop
|
||||
.argv_loop_done:
|
||||
# Pop flag; if non-zero, exit (don't enter REPL).
|
||||
popq %rax
|
||||
testq %rax, %rax
|
||||
jnz repl_exit
|
||||
.argv_done:
|
||||
|
||||
# Set up a generous stack area (16 KB stack frame for deep recursion)
|
||||
# Actually the OS already gave us a stack, so we're fine.
|
||||
|
||||
|
|
@ -6525,6 +6629,57 @@ bi_portal_resume:
|
|||
RET_VAL
|
||||
.endif
|
||||
|
||||
# ============================================================
|
||||
# load_default_prelude — parse and eval the always-on prelude.
|
||||
# Mirrors load_cl_full_prelude but applies to every binary tier
|
||||
# (default, GC, FULL). Defines `eq?` and any other always-on
|
||||
# aliases listed at default_prelude. Saves + restores reader input
|
||||
# state so neither REPL nor argv scripts see the prelude bytes.
|
||||
# Called from _start after init_builtins / rng_seed, unconditionally.
|
||||
load_default_prelude:
|
||||
pushq %rbp
|
||||
|
||||
# Save current input state.
|
||||
movq input_buf_ptr(%rip), %rax
|
||||
pushq %rax
|
||||
movq input_pos(%rip), %rax
|
||||
pushq %rax
|
||||
movq input_end(%rip), %rax
|
||||
pushq %rax
|
||||
movq input_is_file(%rip), %rax
|
||||
pushq %rax
|
||||
|
||||
# Install prelude string as input.
|
||||
leaq default_prelude(%rip), %rax
|
||||
movq %rax, input_buf_ptr(%rip)
|
||||
movq $0, input_pos(%rip)
|
||||
movq $default_prelude_len, input_end(%rip)
|
||||
movq $1, input_is_file(%rip) # EOF at end; do not refill
|
||||
|
||||
.ldp_loop:
|
||||
call scheme_read
|
||||
testq %rax, %rax
|
||||
jz .ldp_done
|
||||
movq %rax, %rdi
|
||||
movq %r14, %rsi # global env
|
||||
call eval
|
||||
# .ev_define mutates %r14 directly; no post-eval env update needed.
|
||||
jmp .ldp_loop
|
||||
|
||||
.ldp_done:
|
||||
# Restore input state.
|
||||
popq %rax
|
||||
movq %rax, input_is_file(%rip)
|
||||
popq %rax
|
||||
movq %rax, input_end(%rip)
|
||||
popq %rax
|
||||
movq %rax, input_pos(%rip)
|
||||
popq %rax
|
||||
movq %rax, input_buf_ptr(%rip)
|
||||
|
||||
popq %rbp
|
||||
ret
|
||||
|
||||
# ============================================================
|
||||
.ifdef CL_FULL
|
||||
# load_cl_full_prelude — parse and eval the embedded prelude string.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue