diff --git a/asm/lumbda b/asm/lumbda index 3d970b6..49491e5 100755 Binary files a/asm/lumbda and b/asm/lumbda differ diff --git a/asm/lumbda-full b/asm/lumbda-full index 8df53f9..3ce784a 100755 Binary files a/asm/lumbda-full and b/asm/lumbda-full differ diff --git a/asm/lumbda-full.o b/asm/lumbda-full.o index be7430e..532bbe3 100644 Binary files a/asm/lumbda-full.o and b/asm/lumbda-full.o differ diff --git a/asm/lumbda-gc b/asm/lumbda-gc index 1650778..b0688c6 100755 Binary files a/asm/lumbda-gc and b/asm/lumbda-gc differ diff --git a/asm/lumbda-gc.o b/asm/lumbda-gc.o index 50facba..2b90073 100644 Binary files a/asm/lumbda-gc.o and b/asm/lumbda-gc.o differ diff --git a/asm/lumbda.o b/asm/lumbda.o index 4f3e82e..913a67f 100644 Binary files a/asm/lumbda.o and b/asm/lumbda.o differ diff --git a/asm/lumbda.s b/asm/lumbda.s index 97844e5..907c0c2 100644 --- a/asm/lumbda.s +++ b/asm/lumbda.s @@ -385,6 +385,17 @@ s_hashset: .ascii "#" 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.