commit 56144e7b4c2059d04a00c2c87f8f8423f3ba2c1b
parent 7e769ab87300ff635a3aaf9227db798f9a642ad6
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Fri, 24 Apr 2026 15:17:00 -0700
rm old P1 lisp
Diffstat:
| D | lisp/lisp.M1 | | | 7119 | ------------------------------------------------------------------------------- |
1 file changed, 0 insertions(+), 7119 deletions(-)
diff --git a/lisp/lisp.M1 b/lisp/lisp.M1
@@ -1,7119 +0,0 @@
-## lisp.M1 — Seed Lisp interpreter (portable across aarch64/amd64/riscv64)
-##
-## Steps 1-8 of LISP.md §"Staged implementation plan". On top of the
-## step-6 evaluator, step 8 replaces the embedded `src_text` blob with
-## a runtime file read: _start takes the script path from argv[1],
-## openat/read/closes it into :src_buf, and seeds src_base/src_len for
-## the reader. argc < 2, open failure, and files ≥ 16 KiB each land on
-## a dedicated error() message.
-##
-## Observable test (`make PROG=lisp run-all`): exit 42 on pass when
-## run against `hello.scm`. The script evaluates
-## `(define id (lambda (x) x)) (id 42)` — identity applied to 42,
-## exit status = decoded fixnum of the final result.
-##
-## Tag table (low 3 bits, LISP.md §"Value representation"):
-## 001 fixnum / 010 pair / 011 vector / 100 string / 101 symbol /
-## 110 closure|prim / 111 singleton (nil=0x07)
-##
-## Headered objects carry a 64-bit header before payload:
-## [ 8-bit type | 8-bit gc-flags | 48-bit length-or-arity ]
-## Little-endian: byte 0 is the low byte of length, byte 7 is the type.
-## make_symbol writes the length with a 64-bit store (high 16 bits = 0)
-## and then overwrites byte 7 with type=2 via SB. Type codes: 1=string,
-## 2=symbol, 3=vector, 4=closure, 5=primitive.
-
-
-## ---- tagged-lisp constants ---------------------------------------------
-## Singletons live in the low 6 bits (upper 3 bits distinguish them, low
-## 3 = 111 = singleton tag). Named DEFINEs so intent beats "07000000".
-DEFINE NIL 07000000
-DEFINE TRUE 0F000000
-DEFINE FALSE 17000000
-DEFINE UNSPEC 27000000
-
-DEFINE TAG_FIXNUM 01000000
-DEFINE TAG_PAIR 02000000
-DEFINE TAG_VECTOR 03000000
-DEFINE TAG_STRING 04000000
-DEFINE TAG_SYMBOL 05000000
-DEFINE TAG_PROC 06000000
-
-DEFINE TYPE_STRING 01000000
-DEFINE TYPE_SYMBOL 02000000
-DEFINE TYPE_VECTOR 03000000
-DEFINE TYPE_CLOSURE 04000000
-DEFINE TYPE_PRIM_FIXED 05000000
-DEFINE TYPE_PRIM_VARIADIC 06000000
-
-DEFINE ZERO32 '0000000000000000000000000000000000000000000000000000000000000000'
-
-
-## ---- heap-state --------------------------------------------------------
-## The two arenas are fixed in BSS; only the bump heads are mutable.
-## Upper 4 bytes stay zero because all labels fit below 4 GiB.
-:pair_heap_next &pair_heap_start %0
-:obj_heap_next &obj_heap_start %0
-
-
-## ---- _start ----------------------------------------------------------
-## Step 8 driver: read the .scm file named on the command line into
-## src_buf, then loop over its top-level forms. Each form is evaluated
-## in an empty local env (so unqualified lookups fall through to the
-## global hash) and the last result is kept. Final result is displayed
-## with write (closing the step-5 read-print cycle) and its fixnum
-## payload becomes the exit status — lets test harnesses assert via $?.
-##
-## r4 holds the running last_result once the intern loop finishes. It
-## survives read_expr/eval across iterations because r4 is callee-saved;
-## _start itself has no PROLOGUE and never returns, so its r4 is
-## sovereign. Early in _start r4 doubles as the source fd across the
-## read_file_all CALL (same callee-saved guarantee).
-:_start
- ## Kernel layout at entry: [sp+0]=argc, [sp+8..]=argv[0..argc-1],
- ## then NULL, then envp. We need argc and argv[1] before any CALL
- ## (no PROLOGUE on _start — CALL would bury the kernel frame under
- ## a pushed retaddr on amd64).
- ld_r0,sp,0 ## r0 = argc
- li_r1 %2
- li_br &err_usage
- blt_r0,r1 ## argc < 2 → err_usage
- ld_r2,sp,16 ## r2 = argv[1] (survives SYSCALL)
-
- ## Seed the frame-chain terminator before the first CALL.
- li_r1 &stack_bottom_fp
- mov_r0,sp
- st_r0,r1,0
-
- ## Align both heap bumps to their natural strides and snapshot
- ## the aligned starts in :pair_heap_base / :obj_heap_base. The
- ## :pair_heap_start label may land at any byte offset depending
- ## on what the assembler emitted before it; if a pair sweep then
- ## walked from the raw label with a fixed +16 stride it would
- ## visit gap addresses, never the actual allocations. Pinning
- ## the bump and the bitmap origin to the same aligned address
- ## keeps mark and sweep on the same grid.
- li_r1 &pair_heap_next
- ld_r0,r1,0
- addi_r0,r0,15
- shri_r0,r0,4
- shli_r0,r0,4
- st_r0,r1,0
- li_r1 &pair_heap_base
- st_r0,r1,0
-
- li_r1 &obj_heap_next
- ld_r0,r1,0
- addi_r0,r0,7
- shri_r0,r0,3
- shli_r0,r0,3
- st_r0,r1,0
- li_r1 &obj_heap_base
- st_r0,r1,0
-
- ## openat(AT_FDCWD=-100, argv[1], O_RDONLY=0, mode=0) → r0 = fd.
- ## Use openat rather than open because open is amd64-only
- ## (removed from the asm-generic table used by aarch64/riscv64).
- ## LI zero-extends; low 32 bits 0xFFFFFF9C decode as -100 in int32.
- li_r0 sys_openat
- li_r1 %-100
- li_r3 %0
- li_r4 %0
- syscall
-
- li_br &err_open
- bltz_r0 ## fd < 0 → err_open
-
- ## read_file_all(fd, src_buf, SRC_BUF_SIZE) → r0 = bytes_read.
- ## Stash fd in r4 so it survives the CALL; r4 is callee-saved.
- mov_r4,r0
- mov_r1,r0
- li_r2 &src_buf
- li_r3 %16384 ## 16384-byte capacity
- li_br &read_file_all
- call
-
- ## Filling the buffer exactly means the file was ≥ SRC_BUF_SIZE —
- ## bail out rather than silently truncate.
- li_r1 %16384
- li_br &err_src_too_big
- beq_r0,r1
-
- ## Publish src_base/src_len so the reader can walk the source,
- ## and park (src_buf, bytes_read) in callee-saved r6/r7 so the
- ## tuple survives primitive registration down to the eval_source
- ## CALL below. Capture r0 into r7 NOW — sys_close below will
- ## overwrite r0 with its own return value.
- mov_r7,r0 ## r7 = bytes_read (captured pre-close)
- li_r6 &src_buf ## r6 = &src_buf
- li_r1 &src_len
- st_r7,r1,0
- li_r1 &src_base
- st_r6,r1,0
-
- ## close(fd). r4 still holds fd; SYSCALL clobbers r0 only.
- li_r0 sys_close
- mov_r1,r4
- syscall
-
- ## Intern the special-form symbols up front. eval_pair compares the
- ## car of every compound expression against these pointers, so they
- ## must exist before the first eval call. The intern result (r0 =
- ## tagged sym) is stashed at &sym_* via ST_R0_R2_0 (the target addr
- ## in r2); ST_R0_R1_0 isn't in the P1 op table.
- li_r1 &str_quote
- li_r2 %5
- li_br &intern
- call
- li_r2 &sym_quote
- st_r0,r2,0
-
- li_r1 &str_if
- li_r2 %2
- li_br &intern
- call
- li_r2 &sym_if
- st_r0,r2,0
-
- li_r1 &str_begin
- li_r2 %5
- li_br &intern
- call
- li_r2 &sym_begin
- st_r0,r2,0
-
- li_r1 &str_lambda
- li_r2 %6
- li_br &intern
- call
- li_r2 &sym_lambda
- st_r0,r2,0
-
- li_r1 &str_define
- li_r2 %6
- li_br &intern
- call
- li_r2 &sym_define
- st_r0,r2,0
-
- li_r1 &str_quasiquote
- li_r2 %10
- li_br &intern
- call
- li_r2 &sym_quasiquote
- st_r0,r2,0
-
- li_r1 &str_unquote
- li_r2 %7
- li_br &intern
- call
- li_r2 &sym_unquote
- st_r0,r2,0
-
- li_r1 &str_unquote_splicing
- li_r2 %16
- li_br &intern
- call
- li_r2 &sym_unquote_splicing
- st_r0,r2,0
-
- li_r1 &str_set
- li_r2 %4
- li_br &intern
- call
- li_r2 &sym_set
- st_r0,r2,0
-
- li_r1 &str_let
- li_r2 %3
- li_br &intern
- call
- li_r2 &sym_let
- st_r0,r2,0
-
- li_r1 &str_letstar
- li_r2 %4
- li_br &intern
- call
- li_r2 &sym_letstar
- st_r0,r2,0
-
- li_r1 &str_letrec
- li_r2 %6
- li_br &intern
- call
- li_r2 &sym_letrec
- st_r0,r2,0
-
- li_r1 &str_cond
- li_r2 %4
- li_br &intern
- call
- li_r2 &sym_cond
- st_r0,r2,0
-
- li_r1 &str_else
- li_r2 %4
- li_br &intern
- call
- li_r2 &sym_else
- st_r0,r2,0
-
- ## Register primitives (LISP.md step 10b). Walk prim_table with
- ## r4 = cursor, r5 = saved sym across make_primitive. Entry is
- ## 40 bytes; zero name pointer ends the loop.
- li_r4 &prim_table
-:_start_reg_prim_loop
- ld_r1,r4,0 ## r1 = name ptr
- li_br &_start_reg_prim_done
- beqz_r1
-
- ld_r2,r4,8 ## r2 = length
- li_br &intern
- call ## r0 = tagged sym
- mov_r5,r0
-
- ld_r1,r4,16 ## r1 = code id
- ld_r2,r4,32 ## r2 = arity
- ld_r3,r4,24 ## r3 = type
- li_br &make_primitive
- call ## r0 = tagged prim
-
- mov_r2,r0
- mov_r1,r5
- li_br &gset
- call
-
- addi_r4,r4,40
- li_br &_start_reg_prim_loop
- b
-:_start_reg_prim_done
- ## Evaluate the script read from argv[1]. The Makefile cats
- ## src/prelude.scm ahead of the user script before invoking us,
- ## so map/filter/fold are in scope by the time user code runs.
- mov_r1,r6
- mov_r2,r7
- li_br &eval_source
- call
- mov_r4,r0
-
-:_start_done
- ## Print the final result (write form) + newline for visibility.
- mov_r1,r4
- li_br &write
- call
- li_r1 %10
- li_br &putc
- call
-
- ## Exit status = decoded fixnum of last_result (bit-cast to low 8
- ## bits by the kernel). Non-fixnum results still exit cleanly —
- ## the low 8 bits of the tagged word are used.
- li_r0 sys_exit
- mov_r1,r4
- sari_r1,r1,3
- syscall
-
-
-## ---- read_file_all(r1=fd, r2=buf, r3=cap) -> r0 = total_bytes ------
-## Read loop. r4-r7 are callee-saved AND preserved across SYSCALL, so
-## we can park state there across each read() call. No PROLOGUE needed:
-## we only issue syscalls, never CALL.
-:read_file_all
- mov_r4,r1 ## r4 = fd
- mov_r5,r2 ## r5 = cur buf ptr
- mov_r6,r3 ## r6 = remaining capacity
- li_r7 %0 ## r7 = total_read
-
-:read_file_all_loop
- ## Capacity exhausted → stop; _start checks for the exact-full
- ## case and escalates to err_src_too_big.
- li_br &read_file_all_done
- beqz_r6
-
- li_r0 sys_read
- mov_r1,r4
- mov_r2,r5
- mov_r3,r6
- syscall ## r0 = n (0=EOF, <0=err)
-
- ## n <= 0 → done. Check < 0 first, then == 0.
- li_br &read_file_all_done
- bltz_r0
- li_br &read_file_all_done
- beqz_r0
-
- add_r5,r5,r0 ## buf += n
- sub_r6,r6,r0 ## cap -= n
- add_r7,r7,r0 ## total += n
- li_br &read_file_all_loop
- b
-
-:read_file_all_done
- mov_r0,r7
- ret
-
-
-## ---- write_file_all(r1=fd, r2=buf, r3=len) -> r0 = total|-1 -------
-## Mirror of read_file_all: write until len reaches zero or an error
-## occurs. Returning -1 lets write-file raise a Lisp-facing error.
-:write_file_all
- mov_r4,r1 ## r4 = fd
- mov_r5,r2 ## r5 = cursor
- mov_r6,r3 ## r6 = remaining
- li_r7 %0 ## r7 = total_written
-
-:write_file_all_loop
- li_br &write_file_all_done
- beqz_r6
-
- li_r0 sys_write
- mov_r1,r4
- mov_r2,r5
- mov_r3,r6
- syscall ## r0 = n (<0 error, 0 unexpected stall)
-
- li_br &write_file_all_err
- bltz_r0
- li_br &write_file_all_err
- beqz_r0
-
- add_r5,r5,r0
- sub_r6,r6,r0
- add_r7,r7,r0
- li_br &write_file_all_loop
- b
-
-:write_file_all_done
- mov_r0,r7
- ret
-
-:write_file_all_err
- li_r0 %0
- addi_r0,r0,neg1
- ret
-
-
-## ---- eval_source(r1=base, r2=len) -> r0 = last_result --------------
-## Save the current reader globals, point them at (base,len), evaluate
-## every top-level form, then restore the outer reader state.
-:eval_source
- prologue
- st_r4,sp,24 ## save caller's r4
-
- li_r3 &src_base
- ld_r0,r3,0
- li_r4 &saved_src_base
- st_r0,r4,0
- st_r1,r3,0
-
- li_r3 &src_len
- ld_r0,r3,0
- li_r4 &saved_src_len
- st_r0,r4,0
- st_r2,r3,0
-
- li_r3 &src_cursor
- ld_r0,r3,0
- li_r4 &saved_src_cursor
- st_r0,r4,0
- li_r0 %0
- st_r0,r3,0
-
- li_r3 &src_line
- ld_r0,r3,0
- li_r4 &saved_src_line
- st_r0,r4,0
- li_r0 %1
- st_r0,r3,0
-
- li_r3 &src_col
- ld_r0,r3,0
- li_r4 &saved_src_col
- st_r0,r4,0
- li_r0 %1
- st_r0,r3,0
-
- li_r4 %39 ## running last_result = unspec
-
-:eval_source_loop
- li_br &skip_ws
- call
-
- li_br &peek_char
- call
- li_r1 %0
- addi_r1,r1,neg1
- li_br &eval_source_done
- beq_r0,r1
-
- li_br &read_expr
- call
- mov_r1,r0
- li_r2 %7
- li_br &eval
- call
- mov_r4,r0
- li_br &eval_source_loop
- b
-
-:eval_source_done
- li_r3 &saved_src_base
- ld_r0,r3,0
- li_r1 &src_base
- st_r0,r1,0
-
- li_r3 &saved_src_len
- ld_r0,r3,0
- li_r1 &src_len
- st_r0,r1,0
-
- li_r3 &saved_src_cursor
- ld_r0,r3,0
- li_r1 &src_cursor
- st_r0,r1,0
-
- li_r3 &saved_src_line
- ld_r0,r3,0
- li_r1 &src_line
- st_r0,r1,0
-
- li_r3 &saved_src_col
- ld_r0,r3,0
- li_r1 &src_col
- st_r0,r1,0
-
- mov_r0,r4
- ld_r4,sp,24
- epilogue
- ret
-
-
-## ---- hash(r1=ptr, r2=len) -> r0 = u64 hash --------------------------
-## DJB-style: h' = 31*h + b. Implemented as (h<<5) - h + b to avoid
-## stashing 31 in a register. Uses r6 (zero constant) across the loop;
-## saves/restores it via a PROLOGUE_N1 slot.
-:hash
- prologue
- st_r6,sp,24
-
- li_r6 %0 ## zero const
- li_r0 %0 ## h = 0
-
-:hash_loop
- li_br &hash_done
- beq_r2,r6 ## len == 0 → done
-
- shli_r3,r0,5 ## r3 = h << 5
- sub_r3,r3,r0 ## r3 = h*32 - h = h*31
- lb_r0,r1,0 ## r0 = *ptr (zero-extended byte)
- add_r0,r0,r3 ## r0 = h*31 + b
- addi_r1,r1,1
- addi_r2,r2,neg1
- li_br &hash_loop
- b
-
-:hash_done
- ld_r6,sp,24
- epilogue
- ret
-
-
-## ---- sym_name_equal(r1=tagged_sym, r2=cmp_ptr, r3=cmp_len) -> r0 ---
-## Returns 1 if the symbol's stored name equals `cmp_len` bytes at
-## `cmp_ptr`, else 0. Uses r6 as zero const + r7 as the cmp-byte
-## scratch; both are callee-saved, so PROLOGUE_N2 stashes them.
-:sym_name_equal
- prologue_n2
- st_r6,sp,24
- st_r7,sp,32
-
- addi_r1,r1,neg5 ## r1 = raw sym ptr
- ld_r6,r1,0 ## r6 = header word
- ## Mask low 32 bits: our names fit in 32 bits of the 48-bit length.
- shli_r6,r6,32
- shri_r6,r6,32 ## r6 = sym_len
-
- li_br &sym_name_equal_neq
- bne_r6,r3 ## lengths differ → not equal
-
- addi_r1,r1,8 ## r1 = name bytes ptr
- li_r6 %0 ## zero const
-
-:sym_name_equal_loop
- li_br &sym_name_equal_eq
- beq_r3,r6 ## len == 0 → match
-
- lb_r0,r1,0 ## r0 = sym byte
- lb_r7,r2,0 ## r7 = cmp byte
- li_br &sym_name_equal_neq
- bne_r0,r7
-
- addi_r1,r1,1
- addi_r2,r2,1
- addi_r3,r3,neg1
- li_br &sym_name_equal_loop
- b
-
-:sym_name_equal_eq
- li_r0 %1
- li_br &sym_name_equal_done
- b
-
-:sym_name_equal_neq
- li_r0 %0
-
-:sym_name_equal_done
- ld_r6,sp,24
- ld_r7,sp,32
- epilogue_n2
- ret
-
-
-## ---- make_symbol(r1=src, r2=len) -> r0 = tagged symbol --------------
-## Lays out header + name bytes inline, returning (raw | SYMBOL_TAG=5).
-## Allocation size = 8 (header) + round_up_8(len).
-##
-## Frame layout:
-## [sp + 8] saved r6 (caller's copy)
-## [sp + 16] saved r7
-## [sp + 24] raw_ptr across the copy loop
-:make_symbol
- prologue_n3
- st_r6,sp,24
- st_r7,sp,32
-
- mov_r6,r1 ## r6 = src (mutated during byte copy)
- mov_r7,r2 ## r7 = len (mutated during byte copy)
-
- ## alloc_size = 8 + ((len + 7) >> 3 << 3)
- addi_r1,r7,7
- shri_r1,r1,3
- shli_r1,r1,3
- addi_r1,r1,8
- li_br &alloc
- call ## r0 = raw ptr
-
- st_r0,sp,40 ## stash raw ptr
-
- ## Header: store len in low 48 bits, then stamp type=2 in byte 7.
- ld_r0,sp,40
- st_r7,r0,0
- li_r1 %2
- sb_r1,r0,7
-
- ## Byte copy: dst = r3 (= raw+8), src = r6, len = r7.
- ld_r0,sp,40
- addi_r3,r0,8
- li_r1 %0 ## zero const for loop test
-
-:make_symbol_copy
- li_br &make_symbol_done
- beq_r7,r1
-
- lb_r2,r6,0
- sb_r2,r3,0
- addi_r6,r6,1
- addi_r3,r3,1
- addi_r7,r7,neg1
- li_br &make_symbol_copy
- b
-
-:make_symbol_done
- ld_r0,sp,40
- ori_r0,r0,4 ## set bit 2 (aarch64 bitmask-imm can't do 0b101 directly)
- ori_r0,r0,1 ## set bit 0 → tag = 101 (SYMBOL)
- ld_r6,sp,24
- ld_r7,sp,32
- epilogue_n3
- ret
-
-
-## ---- byte_copy(r1=dst, r2=src, r3=len) -> r0 = dst_end -------------
-## Raw byte copy used by string construction, path marshaling, and
-## string-append. No overlap handling needed in the seed.
-:byte_copy
- li_r0 %0
-:byte_copy_loop
- li_br &byte_copy_done
- beq_r3,r0
- lb_r0,r2,0
- sb_r0,r1,0
- addi_r1,r1,1
- addi_r2,r2,1
- addi_r3,r3,neg1
- li_r0 %0
- li_br &byte_copy_loop
- b
-:byte_copy_done
- mov_r0,r1
- ret
-
-
-## ---- alloc_string(r1=len) -> r0 = tagged string ---------------------
-## Allocates header + zero or more payload bytes, stamps type=1, and
-## leaves the payload for the caller to fill.
-:alloc_string
- prologue
- st_r1,sp,24
-
- addi_r1,r1,7
- shri_r1,r1,3
- shli_r1,r1,3
- addi_r1,r1,8
- li_br &alloc
- call
-
- ld_r1,sp,24
- st_r1,r0,0
- li_r1 %1
- sb_r1,r0,7
- ori_r0,r0,4 ## tag = 100 (string)
- epilogue
- ret
-
-
-## ---- make_string(r1=src, r2=len) -> r0 = tagged string -------------
-:make_string
- prologue_n3
- st_r1,sp,24
- st_r2,sp,32
-
- mov_r1,r2
- li_br &alloc_string
- call
- st_r0,sp,40
-
- addi_r1,r0,neg4 ## raw string ptr
- addi_r1,r1,8 ## dst payload
- ld_r2,sp,24 ## src payload
- ld_r3,sp,32 ## len
- li_br &byte_copy
- call
-
- ld_r0,sp,40
- epilogue_n3
- ret
-
-
-## ---- make_vector(r1=len_raw, r2=init) -> r0 = tagged vector --------
-## Allocates header + len tagged slots, stamps type=3, and fills every
-## element with `init`.
-:make_vector
- prologue_n2
- st_r1,sp,24
- st_r2,sp,32
-
- shli_r1,r1,3
- addi_r1,r1,8
- li_br &alloc
- call
-
- ld_r1,sp,24 ## len_raw
- st_r0,sp,24 ## overwrite slot1 = raw ptr
- st_r1,r0,0
- li_r1 %3
- sb_r1,r0,7
-
- ld_r1,sp,24 ## raw ptr
- ld_r2,sp,32 ## init
- ld_r0,r1,0 ## header word (type|len)
- shli_r0,r0,16 ## mask off type byte
- shri_r0,r0,16 ## r0 = len_raw
- addi_r1,r1,8 ## payload cursor
- li_r3 %0
-:make_vector_loop
- li_br &make_vector_done
- beq_r0,r3
- st_r2,r1,0
- addi_r1,r1,8
- addi_r0,r0,neg1
- li_br &make_vector_loop
- b
-:make_vector_done
- ld_r0,sp,24
- ori_r0,r0,2
- ori_r0,r0,1 ## tag = 011 (vector)
- epilogue_n2
- ret
-
-
-## ---- string_to_c_path(r1=tagged_string) -> r0 = &path_buf ----------
-## Copies the string bytes to a shared NUL-terminated buffer so openat
-## can consume it as a kernel pathname.
-:string_to_c_path
- prologue_n2
- addi_r1,r1,neg4 ## raw string ptr
- ld_r2,r1,0
- shli_r2,r2,16
- shri_r2,r2,16 ## r2 = len
- mov_r3,r2
- li_r0 %256 ## PATH_BUF_SIZE = 256
- li_br &string_to_c_path_ok
- blt_r3,r0
- li_br &err_path_too_long
- b
-:string_to_c_path_ok
- mov_r0,sp
- st_r2,r0,24
- addi_r1,r1,8 ## payload ptr
- st_r1,r0,32
- li_r1 &path_buf
- ld_r2,r0,32
- ld_r3,r0,24
- li_br &byte_copy
- call
- li_r1 %0
- sb_r1,r0,0
- li_r0 &path_buf
- epilogue_n2
- ret
-
-
-## ---- intern(r1=name_ptr, r2=name_len) -> r0 = tagged sym -----------
-## Open-addressing linear probe over a 4096-slot table. Empty slot =
-## 0 word (nil is 0x07, never 0, so this sentinel is unambiguous).
-## Frame layout: slot 1 = saved r6, slot 2 = saved r7, slot 3 = current
-## probe index h.
-:intern
- prologue_n3
- st_r6,sp,24
- st_r7,sp,32
-
- mov_r6,r1 ## r6 = name_ptr (callee-saved copy)
- mov_r7,r2 ## r7 = name_len
-
- li_br &hash
- call ## r0 = 64-bit hash
-
- shli_r0,r0,52
- shri_r0,r0,52 ## r0 = h & 4095
-
- mov_r3,sp
- st_r0,sp,40 ## slot 3 = h
-
-:intern_probe
- ld_r0,sp,40
- shli_r0,r0,3 ## r0 = h * 8
- li_r2 &symbol_table
- add_r2,r2,r0 ## r2 = &symbol_table[h]
- ld_r3,r2,0 ## r3 = slot value
-
- li_br &intern_empty
- beqz_r3 ## slot == 0 → allocate
-
- ## Compare existing symbol to (r6, r7).
- mov_r1,r3 ## r1 = tagged sym
- mov_r2,r6 ## r2 = cmp_ptr
- mov_r3,r7 ## r3 = cmp_len
- li_br &sym_name_equal
- call ## r0 = 0 or 1
-
- li_br &intern_hit
- bnez_r0
-
- ## Advance h = (h+1) & 4095.
- ld_r0,sp,40
- addi_r0,r0,1
- shli_r0,r0,52
- shri_r0,r0,52
- st_r0,sp,40
- li_br &intern_probe
- b
-
-:intern_empty
- ## Allocate a fresh symbol and plant it in the slot.
- mov_r1,r6
- mov_r2,r7
- li_br &make_symbol
- call ## r0 = tagged sym
-
- ld_r1,sp,40
- shli_r1,r1,3
- li_r2 &symbol_table
- add_r2,r2,r1
- st_r0,r2,0 ## write into slot
-
- li_br &intern_done
- b
-
-:intern_hit
- mov_r3,sp
- ld_r0,sp,40
- shli_r0,r0,3
- li_r2 &symbol_table
- add_r2,r2,r0
- ld_r0,r2,0 ## r0 = existing tagged sym
-
-:intern_done
- ld_r6,sp,24
- ld_r7,sp,32
- epilogue_n3
- ret
-
-
-## ---- cons / car / cdr (preserved from step 2) -----------------------
-:cons
- prologue_n2
- st_r1,sp,24
- st_r2,sp,32
- li_br &pair_alloc
- call
- ld_r1,sp,24
- st_r1,r0,0
- ld_r2,sp,32
- st_r2,r0,8
- ori_r0,r0,2
- epilogue_n2
- ret
-
-:car
- addi_r1,r1,neg2
- ld_r0,r1,0
- ret
-
-:cdr
- addi_r1,r1,neg2
- ld_r0,r1,8
- ret
-
-
-## ---- obj_freelist_for_size(size) -> &head cell or 0 -----------------
-:obj_freelist_for_size
- li_r0 %16
- li_br &obj_freelist_16
- beq_r1,r0
- li_r0 %24
- li_br &obj_freelist_24
- beq_r1,r0
- li_r0 %32
- li_br &obj_freelist_32
- beq_r1,r0
- li_r0 %40
- li_br &obj_freelist_40
- beq_r1,r0
- li_r0 %48
- li_br &obj_freelist_48
- beq_r1,r0
- li_r0 %56
- li_br &obj_freelist_56
- beq_r1,r0
- li_r0 %64
- li_br &obj_freelist_64
- beq_r1,r0
- li_r0 %80
- li_br &obj_freelist_80
- beq_r1,r0
- li_r0 %96
- li_br &obj_freelist_96
- beq_r1,r0
- li_r0 %128
- li_br &obj_freelist_128
- beq_r1,r0
- li_r0 %0
- ret
-:obj_freelist_16
- li_r0 &free_list_obj16
- ret
-:obj_freelist_24
- li_r0 &free_list_obj24
- ret
-:obj_freelist_32
- li_r0 &free_list_obj32
- ret
-:obj_freelist_40
- li_r0 &free_list_obj40
- ret
-:obj_freelist_48
- li_r0 &free_list_obj48
- ret
-:obj_freelist_56
- li_r0 &free_list_obj56
- ret
-:obj_freelist_64
- li_r0 &free_list_obj64
- ret
-:obj_freelist_80
- li_r0 &free_list_obj80
- ret
-:obj_freelist_96
- li_r0 &free_list_obj96
- ret
-:obj_freelist_128
- li_r0 &free_list_obj128
- ret
-
-
-## ---- pair_alloc() -> raw pair ptr -----------------------------------
-:pair_alloc
- prologue
-
- li_r3 &free_list_pair
- ld_r0,r3,0
- li_br &pair_alloc_bump
- beqz_r0
- ld_r1,r0,0
- st_r1,r3,0
- epilogue
- ret
-
-:pair_alloc_bump
- li_r3 &pair_heap_next
- ld_r0,r3,0
- addi_r0,r0,7
- shri_r0,r0,3
- shli_r0,r0,3
- addi_r2,r0,16
- li_r1 &pair_heap_end
- li_br &pair_alloc_need_gc
- blt_r1,r2
- st_r2,r3,0
- epilogue
- ret
-
-## GC, then retry. Re-checks the free list first because the sweep
-## populates it with reclaimed pairs; only falls through to the bump
-## path if the list is still empty. A second overflow goes to OOM.
-:pair_alloc_need_gc
- li_br &gc
- call
- li_r3 &free_list_pair
- ld_r0,r3,0
- li_br &pair_alloc_retry_bump
- beqz_r0
- ld_r1,r0,0
- st_r1,r3,0
- epilogue
- ret
-
-:pair_alloc_retry_bump
- li_r3 &pair_heap_next
- ld_r0,r3,0
- addi_r0,r0,7
- shri_r0,r0,3
- shli_r0,r0,3
- addi_r2,r0,16
- li_r1 &pair_heap_end
- li_br &alloc_oom
- blt_r1,r2
- st_r2,r3,0
- epilogue
- ret
-
-
-## ---- alloc / obj_alloc(size) -> raw object ptr ----------------------
-:alloc
-:obj_alloc
- prologue_n2
-
- addi_r1,r1,7
- shri_r1,r1,3
- shli_r1,r1,3
- st_r1,sp,24 ## slot 1 = rounded size
-
- li_br &obj_freelist_for_size
- call
- st_r0,sp,32 ## slot 2 = &free_list head or 0
-
- li_br &obj_alloc_bump
- beqz_r0
- ld_r1,r0,0
- li_br &obj_alloc_bump
- beqz_r1
- ld_r2,r1,8
- ld_r3,sp,32
- st_r2,r3,0
- mov_r0,r1
- epilogue_n2
- ret
-
-:obj_alloc_bump
- ld_r1,sp,24
- li_r3 &obj_heap_next
- ld_r0,r3,0
- addi_r0,r0,7
- shri_r0,r0,3
- shli_r0,r0,3
- add_r2,r0,r1
- li_r1 &obj_heap_end
- li_br &obj_alloc_need_gc
- blt_r1,r2
- st_r2,r3,0
- epilogue_n2
- ret
-
-## GC, then retry. Slot 2 still holds the size-class freelist head
-## (or 0 when no class fits this size). After the sweep, re-check
-## that list before falling through to the bump path. Second overflow
-## escalates to OOM.
-:obj_alloc_need_gc
- li_br &gc
- call
- ld_r3,sp,32 ## &head (or 0 = no size class)
- li_br &obj_alloc_retry_bump
- beqz_r3
- ld_r1,r3,0
- li_br &obj_alloc_retry_bump
- beqz_r1
- ld_r2,r1,8
- st_r2,r3,0
- mov_r0,r1
- epilogue_n2
- ret
-
-:obj_alloc_retry_bump
- ld_r1,sp,24
- li_r3 &obj_heap_next
- ld_r0,r3,0
- addi_r0,r0,7
- shri_r0,r0,3
- shli_r0,r0,3
- add_r2,r0,r1
- li_r1 &obj_heap_end
- li_br &alloc_oom
- blt_r1,r2
- st_r2,r3,0
- epilogue_n2
- ret
-
-
-## ---- obj_size_bytes(raw obj ptr) -> r0 = rounded chunk size ---------
-:obj_size_bytes
- lb_r2,r1,7
- li_r0 %0
- li_br &obj_size_free
- beq_r2,r0
- li_r0 %1
- li_br &obj_size_string
- beq_r2,r0
- li_r0 %2
- li_br &obj_size_symbol
- beq_r2,r0
- li_r0 %3
- li_br &obj_size_vector
- beq_r2,r0
- li_r0 %4
- li_br &obj_size_closure
- beq_r2,r0
- li_r0 %5
- li_br &obj_size_primitive
- beq_r2,r0
- li_r0 %6
- li_br &obj_size_primitive
- beq_r2,r0
- li_r0 %16
- ret
-
-:obj_size_free
- ld_r0,r1,0
- shli_r0,r0,16
- shri_r0,r0,16
- ret
-
-:obj_size_string
-:obj_size_symbol
- ld_r0,r1,0
- shli_r0,r0,16
- shri_r0,r0,16
- addi_r0,r0,15
- shri_r0,r0,3
- shli_r0,r0,3
- ret
-
-:obj_size_vector
- ld_r0,r1,0
- shli_r0,r0,16
- shri_r0,r0,16
- shli_r0,r0,3
- addi_r0,r0,8
- ret
-
-:obj_size_closure
- li_r0 %32
- ret
-
-:obj_size_primitive
- li_r0 %16
- ret
-
-
-## ---- obj_is_live_marked(raw obj ptr) -> r0 = 1 iff marked live -----
-:obj_is_live_marked
- lb_r2,r1,7
- li_br &obj_is_dead
- beqz_r2
- lb_r2,r1,6
- andi_r2,r2,1
- li_br &obj_is_dead
- beqz_r2
- li_r0 %1
- ret
-:obj_is_dead
- li_r0 %0
- ret
-
-
-## ---- obj_mark_seen_or_set(raw obj ptr) -> r0 = 1 iff already marked -
-:obj_mark_seen_or_set
- lb_r2,r1,6
- andi_r3,r2,1
- li_br &obj_mark_seen
- bnez_r3
- ori_r2,r2,1
- sb_r2,r1,6
- li_r0 %0
- ret
-:obj_mark_seen
- li_r0 %1
- ret
-
-
-## ---- pair mark-bitmap helpers ---------------------------------------
-:pair_mark_seen_or_set
- li_r2 &pair_heap_base
- ld_r2,r2,0
- sub_r2,r1,r2
- shri_r2,r2,4 ## slot index
- mov_r3,r2
- shri_r3,r3,3 ## byte index
- li_r0 &pair_mark_bitmap
- add_r0,r0,r3 ## r0 = byte ptr
- lb_r3,r0,0 ## r3 = byte
- andi_r1,r2,7 ## r1 = bit index
- li_r2 %1
- shl_r2,r2,r1 ## r2 = mask
- mov_r1,r3
- and_r1,r1,r2
- li_br &pair_mark_seen
- bnez_r1
- or_r3,r3,r2
- sb_r3,r0,0
- li_r0 %0
- ret
-:pair_mark_seen
- li_r0 %1
- ret
-
-:pair_mark_test
- li_r2 &pair_heap_base
- ld_r2,r2,0
- sub_r2,r1,r2
- shri_r2,r2,4
- mov_r3,r2
- shri_r3,r3,3
- li_r0 &pair_mark_bitmap
- add_r0,r0,r3
- lb_r3,r0,0
- andi_r1,r2,7
- li_r2 %1
- shl_r2,r2,r1
- and_r3,r3,r2
- li_r0 %0
- li_br &pair_mark_test_done
- beqz_r3
- li_r0 %1
-:pair_mark_test_done
- ret
-
-:pair_mark_clear
- li_r2 &pair_heap_base
- ld_r2,r2,0
- sub_r2,r1,r2
- shri_r2,r2,4
- mov_r3,r2
- shri_r3,r3,3
- li_r0 &pair_mark_bitmap
- add_r0,r0,r3
- lb_r3,r0,0
- andi_r1,r2,7
- li_r2 %1
- shl_r2,r2,r1
- sub_r3,r3,r2
- sb_r3,r0,0
- ret
-
-
-## ---- mark_push(tagged value) ----------------------------------------
-:mark_push
- li_r2 &mark_stack_next
- ld_r3,r2,0
- li_r0 &mark_stack_end
- li_br &mark_push_recurse
- beq_r3,r0
- st_r1,r3,0
- addi_r3,r3,8
- st_r3,r2,0
- ret
-## Tail-branch (not CALL) so mark_value returns directly to mark_push's
-## caller. A CALL+RET here would loop on aarch64/riscv64: their native
-## CALL writes retaddr to LR rather than pushing it, so the inner CALL
-## clobbers this routine's own incoming LR and the final RET branches
-## back to itself. mark_push has no prologue and thus no place to spill
-## LR, so tail-branching is the correct shape.
-:mark_push_recurse
- li_br &mark_value
- b
-
-
-## ---- mark_value(tagged value) ---------------------------------------
-:mark_value
- prologue_n4
-
- andi_r0,r1,7
- li_r2 %2
- li_br &mark_value_pair
- beq_r0,r2
- li_r2 %3
- li_br &mark_value_object
- beq_r0,r2
- li_r2 %4
- li_br &mark_value_object
- beq_r0,r2
- li_r2 %5
- li_br &mark_value_object
- beq_r0,r2
- li_r2 %6
- li_br &mark_value_object
- beq_r0,r2
- epilogue_n4
- ret
-
-## Tagged-pair bounds check: a frame slot may hold a raw integer whose
-## low 3 bits coincide with the pair tag (e.g. a small loop counter).
-## Reject anything outside [pair_heap_start, pair_heap_next) so the
-## bitmap index stays in range and dereferencing stays inside our arena.
-:mark_value_pair
- addi_r1,r1,neg2 ## r1 = raw pair ptr
- li_r0 &pair_heap_base
- ld_r0,r0,0
- li_br &mark_value_done
- blt_r1,r0
- li_r0 &pair_heap_next
- ld_r0,r0,0
- li_br &mark_value_pair_inb
- blt_r1,r0
- li_br &mark_value_done
- b
-:mark_value_pair_inb
- st_r1,sp,24 ## slot 1 = raw pair ptr
- li_br &pair_mark_seen_or_set
- call
- li_br &mark_value_done
- bnez_r0
-
- ld_r1,sp,24
- ld_r0,r1,0
- st_r0,sp,32 ## slot 2 = car
- ld_r1,r1,8
- li_br &mark_push
- call
- ld_r1,sp,32
- li_br &mark_push
- call
- li_br &mark_value_done
- b
-
-## Same bounds-check rationale as mark_value_pair. Reject anything
-## outside [obj_heap_start, obj_heap_next) before touching the header
-## byte, so a stale raw pointer in a frame slot can't crash the marker.
-:mark_value_object
- andi_r0,r1,7
- sub_r1,r1,r0 ## raw object ptr
- li_r0 &obj_heap_base
- ld_r0,r0,0
- li_br &mark_value_done
- blt_r1,r0
- li_r0 &obj_heap_next
- ld_r0,r0,0
- li_br &mark_value_object_inb
- blt_r1,r0
- li_br &mark_value_done
- b
-:mark_value_object_inb
- st_r1,sp,24 ## slot 1 = raw object ptr
- li_br &obj_mark_seen_or_set
- call
- li_br &mark_value_done
- bnez_r0
-
- ld_r1,sp,24
- lb_r0,r1,7
- li_r2 %3
- li_br &mark_value_vector
- beq_r0,r2
- li_r2 %4
- li_br &mark_value_closure
- beq_r0,r2
- li_br &mark_value_done
- b
-
-:mark_value_vector
- ld_r0,r1,0
- shli_r0,r0,16
- shri_r0,r0,16
- addi_r2,r1,8
- st_r2,sp,24 ## slot 1 = cursor
- st_r0,sp,32 ## slot 2 = remaining
-
-:mark_value_vector_loop
- ld_r0,sp,32
- li_br &mark_value_done
- beqz_r0
- ld_r2,sp,24
- ld_r1,r2,0
- li_br &mark_push
- call
- ld_r2,sp,24
- addi_r2,r2,8
- st_r2,sp,24
- ld_r0,sp,32
- addi_r0,r0,neg1
- st_r0,sp,32
- li_br &mark_value_vector_loop
- b
-
-:mark_value_closure
- ld_r0,r1,16
- st_r0,sp,32 ## slot 2 = body
- ld_r0,r1,24
- st_r0,sp,40 ## slot 3 = env
- ld_r1,r1,8
- li_br &mark_push
- call
- ld_r1,sp,32
- li_br &mark_push
- call
- ld_r1,sp,40
- li_br &mark_push
- call
-
-:mark_value_done
- epilogue_n4
- ret
-
-
-## ---- mark_frame_slots(raw frame ptr) --------------------------------
-:mark_frame_slots
- prologue_n2
- ld_r0,r1,16
- addi_r2,r1,24
- st_r2,sp,24 ## slot 1 = cursor
- st_r0,sp,32 ## slot 2 = remaining
-
-:mark_frame_slots_loop
- ld_r0,sp,32
- li_br &mark_frame_slots_done
- beqz_r0
- ld_r2,sp,24
- ld_r1,r2,0
- li_br &mark_value
- call
- ld_r2,sp,24
- addi_r2,r2,8
- st_r2,sp,24
- ld_r0,sp,32
- addi_r0,r0,neg1
- st_r0,sp,32
- li_br &mark_frame_slots_loop
- b
-
-:mark_frame_slots_done
- epilogue_n2
- ret
-
-
-## ---- gc_mark_symbol_table() -----------------------------------------
-:gc_mark_symbol_table
- prologue
- li_r1 &symbol_table
- st_r1,sp,24
-
-:gc_mark_symbol_table_loop
- ld_r2,sp,24
- li_r0 &symbol_table_end
- li_br &gc_mark_symbol_table_done
- beq_r2,r0
- ld_r1,r2,0
- li_br &mark_value
- call
- ld_r2,sp,24
- addi_r2,r2,8
- st_r2,sp,24
- li_br &gc_mark_symbol_table_loop
- b
-
-:gc_mark_symbol_table_done
- epilogue
- ret
-
-
-## ---- gc_mark_prim_argv() --------------------------------------------
-## Bound by :prim_argc, the slot count marshal_argv last published.
-## Walking past it would re-mark stale tagged values left over from
-## earlier primitive calls, falsely keeping the objects they pointed
-## to alive across an arbitrary number of GCs.
-:gc_mark_prim_argv
- prologue_n2
- li_r1 &prim_argv
- st_r1,sp,24 ## slot 1 = cursor
- li_r1 &prim_argc
- ld_r1,r1,0
- st_r1,sp,32 ## slot 2 = remaining slots
-
-:gc_mark_prim_argv_loop
- ld_r0,sp,32
- li_br &gc_mark_prim_argv_done
- beqz_r0
- ld_r2,sp,24
- ld_r1,r2,0
- li_br &mark_value
- call
- ld_r2,sp,24
- addi_r2,r2,8
- st_r2,sp,24
- ld_r0,sp,32
- addi_r0,r0,neg1
- st_r0,sp,32
- li_br &gc_mark_prim_argv_loop
- b
-
-:gc_mark_prim_argv_done
- epilogue_n2
- ret
-
-
-## ---- gc_mark_stack_roots() ------------------------------------------
-:gc_mark_stack_roots
- prologue_n2
- li_r1 &gc_root_fp
- ld_r1,r1,0
- st_r1,sp,24 ## slot 1 = current frame ptr
- li_r1 &stack_bottom_fp
- ld_r1,r1,0
- st_r1,sp,32 ## slot 2 = bottom caller sp
-
-:gc_mark_stack_roots_loop
- ld_r1,sp,24
- li_br &gc_mark_stack_roots_done
- beqz_r1
- li_br &mark_frame_slots
- call
- ld_r1,sp,24
- ld_r1,r1,8
- st_r1,sp,24
- ld_r0,sp,32
- li_br &gc_mark_stack_roots_done
- beq_r1,r0
- li_br &gc_mark_stack_roots_loop
- b
-
-:gc_mark_stack_roots_done
- epilogue_n2
- ret
-
-
-## ---- gc_mark_all() ---------------------------------------------------
-:gc_mark_all
- prologue
- li_r1 &mark_stack
- li_r2 &mark_stack_next
- st_r1,r2,0
-
- li_r1 &global_env_cell
- ld_r1,r1,0
- li_br &mark_value
- call
-
- li_br &gc_mark_symbol_table
- call
- li_br &gc_mark_prim_argv
- call
- li_br &gc_mark_stack_roots
- call
-
-:gc_mark_drain
- li_r2 &mark_stack_next
- ld_r0,r2,0
- li_r1 &mark_stack
- li_br &gc_mark_done
- beq_r0,r1
- addi_r0,r0,neg8
- st_r0,r2,0
- ld_r1,r0,0
- li_br &mark_value
- call
- li_br &gc_mark_drain
- b
-
-:gc_mark_done
- epilogue
- ret
-
-
-## ---- gc_clear_freelists() -------------------------------------------
-:gc_clear_freelists
- li_r0 %0
- li_r1 &free_list_pair
- st_r0,r1,0
- li_r1 &free_list_obj16
- st_r0,r1,0
- li_r1 &free_list_obj24
- st_r0,r1,0
- li_r1 &free_list_obj32
- st_r0,r1,0
- li_r1 &free_list_obj40
- st_r0,r1,0
- li_r1 &free_list_obj48
- st_r0,r1,0
- li_r1 &free_list_obj56
- st_r0,r1,0
- li_r1 &free_list_obj64
- st_r0,r1,0
- li_r1 &free_list_obj80
- st_r0,r1,0
- li_r1 &free_list_obj96
- st_r0,r1,0
- li_r1 &free_list_obj128
- st_r0,r1,0
- ret
-
-
-## ---- gc_sweep_pair() -------------------------------------------------
-:gc_sweep_pair
- prologue
- li_r1 &pair_heap_base
- ld_r1,r1,0
- st_r1,sp,24
-
-:gc_sweep_pair_loop
- ld_r1,sp,24
- li_r0 &pair_heap_next
- ld_r0,r0,0
- li_br &gc_sweep_pair_done
- beq_r1,r0
-
- li_br &pair_mark_test
- call
- li_br &gc_sweep_pair_dead
- beqz_r0
-
- ld_r1,sp,24
- li_br &pair_mark_clear
- call
- li_br &gc_sweep_pair_advance
- b
-
-:gc_sweep_pair_dead
- ld_r1,sp,24
- li_r2 &free_list_pair
- ld_r0,r2,0
- st_r0,r1,0
- st_r1,r2,0
-
-:gc_sweep_pair_advance
- ld_r1,sp,24
- addi_r1,r1,16
- st_r1,sp,24
- li_br &gc_sweep_pair_loop
- b
-
-:gc_sweep_pair_done
- epilogue
- ret
-
-
-## ---- gc_sweep_obj() --------------------------------------------------
-:gc_sweep_obj
- prologue_n4
- li_r1 &obj_heap_base
- ld_r1,r1,0
- st_r1,sp,24 ## slot 1 = current ptr
-
-:gc_sweep_obj_loop
- ld_r1,sp,24
- li_r0 &obj_heap_next
- ld_r0,r0,0
- li_br &gc_sweep_obj_done
- beq_r1,r0
-
- li_br &obj_is_live_marked
- call
- li_br &gc_sweep_obj_dead
- beqz_r0
-
- ld_r1,sp,24
- lb_r0,r1,6
- addi_r0,r0,neg1
- sb_r0,r1,6
- li_br &obj_size_bytes
- call
- ld_r1,sp,24
- add_r1,r1,r0
- st_r1,sp,24
- li_br &gc_sweep_obj_loop
- b
-
-:gc_sweep_obj_dead
- ld_r1,sp,24
- li_br &obj_size_bytes
- call
- ld_r1,sp,24
- add_r2,r1,r0
- st_r2,sp,32 ## slot 2 = run end
-
-:gc_sweep_obj_dead_scan
- ld_r2,sp,32
- li_r0 &obj_heap_next
- ld_r0,r0,0
- li_br &gc_sweep_obj_tail_dead
- beq_r2,r0
-
- mov_r1,r2
- li_br &obj_is_live_marked
- call
- li_br &gc_sweep_obj_dead_run_ready
- bnez_r0
-
- ld_r1,sp,32
- li_br &obj_size_bytes
- call
- ld_r2,sp,32
- add_r2,r2,r0
- st_r2,sp,32
- li_br &gc_sweep_obj_dead_scan
- b
-
-:gc_sweep_obj_tail_dead
- ld_r1,sp,24
- li_r0 &obj_heap_next
- st_r1,r0,0
- li_br &gc_sweep_obj_done
- b
-
-:gc_sweep_obj_dead_run_ready
- ld_r1,sp,32
- ld_r2,sp,24
- sub_r1,r1,r2 ## r1 = run size
- st_r1,sp,40 ## slot 3 = run size
- li_br &obj_freelist_for_size
- call
- st_r0,sp,48 ## slot 4 = &free_list head or 0
-
- li_br &gc_sweep_obj_write_pseudo
- beqz_r0
-
- ld_r2,sp,24 ## run start
- ld_r1,sp,40 ## run size
- st_r1,r2,0
- ld_r3,sp,48
- ld_r0,r3,0
- st_r0,r2,8
- st_r2,r3,0
- li_br &gc_sweep_obj_advance
- b
-
-:gc_sweep_obj_write_pseudo
- ld_r2,sp,24
- ld_r1,sp,40
- st_r1,r2,0
-
-:gc_sweep_obj_advance
- ld_r1,sp,32
- st_r1,sp,24
- li_br &gc_sweep_obj_loop
- b
-
-:gc_sweep_obj_done
- epilogue_n4
- ret
-
-
-## ---- gc_sweep_all() --------------------------------------------------
-:gc_sweep_all
- prologue
- li_br &gc_clear_freelists
- call
- li_br &gc_sweep_pair
- call
- li_br &gc_sweep_obj
- call
- epilogue
- ret
-
-
-## ---- gc() ------------------------------------------------------------
-:gc
- prologue
- ld_r1,sp,8 ## metadata: caller frame ptr
- li_r2 &gc_root_fp
- st_r1,r2,0
- li_br &gc_mark_all
- call
- li_br &gc_sweep_all
- call
- epilogue
- ret
-
-
-## ---- error(msg_ptr, msg_len) ----------------------------------------
-:error
- mov_r6,r1
- mov_r7,r2
-
- li_r0 sys_write
- li_r1 %2
- li_r2 &msg_error_prefix
- li_r3 %7
- syscall
-
- li_r0 sys_write
- li_r1 %2
- mov_r2,r6
- mov_r3,r7
- syscall
-
- li_r0 sys_write
- li_r1 %2
- li_r2 &msg_newline
- li_r3 %1
- syscall
-
- li_r0 sys_exit
- li_r1 %1
- syscall
-
-
-## ---- Error landing pads ---------------------------------------------
-:alloc_oom
- li_r1 &msg_oom
- li_r2 %14
- li_br &error
- b
-
-:err_intern_not_eq
- li_r1 &msg_intern_not_eq
- li_r2 %34 ## strlen == 34
- li_br &error
- b
-
-:err_intern_collision
- li_r1 &msg_intern_collision
- li_r2 %44 ## strlen == 44
- li_br &error
- b
-
-:err_usage
- li_r1 &msg_usage
- li_r2 %22 ## strlen("usage: lisp <file.scm>") == 22
- li_br &error
- b
-
-:err_open
- li_r1 &msg_open_fail
- li_r2 %26 ## strlen("failed to open source file") == 26
- li_br &error
- b
-
-:err_src_too_big
- li_r1 &msg_src_too_big
- li_r2 %21 ## strlen("source file too large") == 21
- li_br &error
- b
-
-## err_reader_bad: "error: reader: malformed input at LINE:COL\n" → stderr.
-## Doesn't go through :error because we want line:col tacked on; easier to
-## inline the syscalls than thread a format through error().
-:err_reader_bad
- ## "error: "
- li_r0 sys_write
- li_r1 %2
- li_r2 &msg_error_prefix
- li_r3 %7
- syscall
-
- ## "reader: malformed input"
- li_r0 sys_write
- li_r1 %2
- li_r2 &msg_reader_bad
- li_r3 %23 ## strlen("reader: malformed input") == 23
- syscall
-
- ## " at "
- li_r0 sys_write
- li_r1 %2
- li_r2 &msg_at
- li_r3 %4
- syscall
-
- ## LINE (via display_uint to stderr)
- li_r1 &src_line
- ld_r1,r1,0
- li_r2 %2
- li_br &display_uint
- call
-
- ## ":"
- li_r0 sys_write
- li_r1 %2
- li_r2 &msg_colon
- li_r3 %1
- syscall
-
- ## COL
- li_r1 &src_col
- ld_r1,r1,0
- li_r2 %2
- li_br &display_uint
- call
-
- ## "\n"
- li_r0 sys_write
- li_r1 %2
- li_r2 &msg_newline
- li_r3 %1
- syscall
-
- li_r0 sys_exit
- li_r1 %1
- syscall
-
-
-## ================ Step 4: reader + minimal display ==================
-## Recursive-descent over an in-memory source string at src_base with
-## length src_len. Read one expression. Supports: lists, positive
-## decimal fixnums, symbols (interned). Skips whitespace and `;` line
-## comments. No quote, strings, source-locations, or improper lists
-## in this round — those land in later steps.
-
-
-## ---- Reader state (globals) -----------------------------------------
-## src_base/src_len are set by _start after it reads argv[1] into
-## src_buf. src_cursor starts at 0; src_line/src_col start at 1.
-:src_base %0 %0
-:src_len %0 %0
-:src_cursor %0 %0
-:src_line %1 %0
-:src_col %1 %0
-
-
-## ---- peek_char() -> r0 = char (0..255) or -1 on EOF ----------------
-:peek_char
- li_r1 &src_cursor
- ld_r1,r1,0 ## r1 = cursor
- li_r2 &src_len
- ld_r2,r2,0 ## r2 = len
- li_br &peek_char_inb
- blt_r1,r2 ## cursor < len → in-bounds
- li_r0 %0
- addi_r0,r0,neg1 ## r0 = -1
- ret
-:peek_char_inb
- li_r2 &src_base
- ld_r2,r2,0 ## r2 = base
- add_r2,r2,r1 ## r2 = base + cursor
- lb_r0,r2,0 ## r0 = *ptr (0-ext byte)
- ret
-
-
-## ---- peek2_char() -> r0 = char at cursor+1 (0..255) or -1 on EOF ----
-## Used by the reader to distinguish `,` vs `,@`, `-` vs `-<digit>`, and
-## `0` vs `0x`/`0X` without consuming input.
-:peek2_char
- li_r1 &src_cursor
- ld_r1,r1,0
- addi_r1,r1,1 ## r1 = cursor + 1
- li_r2 &src_len
- ld_r2,r2,0
- li_br &peek2_char_inb
- blt_r1,r2
- li_r0 %0
- addi_r0,r0,neg1
- ret
-:peek2_char_inb
- li_r2 &src_base
- ld_r2,r2,0
- add_r2,r2,r1
- lb_r0,r2,0
- ret
-
-
-## ---- advance_char() — consume current char, track line/col --------
-:advance_char
- prologue
- li_br &peek_char
- call ## r0 = current char (possibly -1 if EOF)
-
- ## cursor++
- li_r1 &src_cursor
- ld_r2,r1,0
- addi_r2,r2,1
- st_r2,r1,0
-
- ## if char == '\n', bump line and reset col; else col++.
- li_r1 %10 ## '\n' = 10
- li_br &advance_newline
- beq_r0,r1
-
- li_r1 &src_col
- ld_r2,r1,0
- addi_r2,r2,1
- st_r2,r1,0
- epilogue
- ret
-
-:advance_newline
- li_r1 &src_line
- ld_r2,r1,0
- addi_r2,r2,1
- st_r2,r1,0
- li_r1 &src_col
- li_r2 %1
- st_r2,r1,0
- epilogue
- ret
-
-
-## ---- skip_ws() — eat whitespace and ; line comments ----------------
-:skip_ws
- prologue
-:skip_ws_loop
- li_br &peek_char
- call ## r0 = next char (or -1)
-
- ## EOF → done
- li_r1 %0
- addi_r1,r1,neg1
- li_br &skip_ws_done
- beq_r0,r1
-
- ## ' ' (0x20)
- li_r1 %32
- li_br &skip_ws_eat
- beq_r0,r1
- ## '\t' (0x09)
- li_r1 %9
- li_br &skip_ws_eat
- beq_r0,r1
- ## '\n' (0x0A)
- li_r1 %10
- li_br &skip_ws_eat
- beq_r0,r1
- ## '\r' (0x0D)
- li_r1 %13
- li_br &skip_ws_eat
- beq_r0,r1
- ## ';' (0x3B)
- li_r1 %59
- li_br &skip_ws_comment
- beq_r0,r1
-
- li_br &skip_ws_done
- b
-
-:skip_ws_eat
- li_br &advance_char
- call
- li_br &skip_ws_loop
- b
-
-:skip_ws_comment
- li_br &advance_char
- call ## eat ';'
-:skip_ws_comment_loop
- li_br &peek_char
- call
- ## EOF → outer loop (will see -1 and exit)
- li_r1 %0
- addi_r1,r1,neg1
- li_br &skip_ws_loop
- beq_r0,r1
- ## '\n' → eat and outer loop
- li_r1 %10
- li_br &skip_ws_eat
- beq_r0,r1
- ## else eat and keep going
- li_br &advance_char
- call
- li_br &skip_ws_comment_loop
- b
-
-:skip_ws_done
- epilogue
- ret
-
-
-## ---- is_digit(c) -> r0 = 0 or 1 ------------------------------------
-## Character already in r1.
-:is_digit
- li_r2 %48 ## '0' = 48
- li_r0 %0 ## default: not digit
- li_br &is_digit_done
- blt_r1,r2 ## c < '0' → not digit
- li_r2 %58 ## '9' + 1 = 58 (:)
- li_br &is_digit_yes
- blt_r1,r2 ## c < ':' → in range
- li_br &is_digit_done
- b
-:is_digit_yes
- li_r0 %1
-:is_digit_done
- ret
-
-
-## ---- read_number() -> r0 = tagged fixnum ----------------------------
-## Reads a non-negative decimal integer, or a hex integer with `0x`/`0X`
-## prefix. Digits are consumed while peek_char is in [0-9] (or [0-9a-fA-F]
-## after the hex prefix). Uses r6 = accumulator across iterations (saved
-## via PROLOGUE_N1 slot 1).
-:read_number
- prologue
- mov_r3,sp
- st_r6,sp,24
-
- li_r6 %0 ## r6 = 0 (accumulator)
-
- ## Detect `0x` / `0X` hex prefix — only triggers if the very first
- ## byte is '0' and the next is 'x' or 'X'. Anything else falls
- ## through to the decimal digit loop (including bare `0` or `07`).
- li_br &peek_char
- call
- li_r1 %48 ## '0'
- li_br &read_number_loop
- bne_r0,r1
- li_br &peek2_char
- call
- li_r1 %120 ## 'x'
- li_br &read_number_hex_start
- beq_r0,r1
- li_r1 %88 ## 'X'
- li_br &read_number_hex_start
- beq_r0,r1
- li_br &read_number_loop
- b
-
-:read_number_hex_start
- li_br &advance_char
- call ## eat '0'
- li_br &advance_char
- call ## eat 'x' / 'X'
-
-:read_number_hex_loop
- li_br &peek_char
- call
- mov_r1,r0
- li_br &hex_digit_val
- call ## r0 = digit (0..15) or -1
- li_r1 %0
- addi_r1,r1,neg1
- li_br &read_number_done
- beq_r0,r1
-
- ## r6 = r6 * 16 + digit. ADDI_IMMS lacks 4-shift, so SHLI 2 twice.
- shli_r6,r6,2
- shli_r6,r6,2
- add_r6,r6,r0
-
- li_br &advance_char
- call
- li_br &read_number_hex_loop
- b
-
-:read_number_loop
- li_br &peek_char
- call ## r0 = char (or -1)
-
- ## If char not a digit, stop.
- mov_r1,r0
- li_br &is_digit
- call ## r0 = 0 or 1
- li_br &read_number_done
- beqz_r0
-
- ## digit = peek - '0'. Re-peek (simpler than stashing).
- li_br &peek_char
- call
- addi_r0,r0,neg48 ## r0 = digit (0..9)
-
- ## r6 = r6 * 10 + digit. Do via (r6 << 3) + (r6 << 1).
- mov_r1,r6
- shli_r1,r6,3 ## r1 = r6 << 3 = r6*8
- mov_r2,r6
- shli_r2,r6,1 ## r2 = r6 << 1 = r6*2
- add_r6,r1,r2 ## r6 = r6*10
- add_r6,r6,r0 ## r6 = r6*10 + digit
-
- li_br &advance_char
- call
- li_br &read_number_loop
- b
-
-:read_number_done
- ## Tag as fixnum (low 3 bits = 001 = shift-left-3 + OR 1).
- shli_r0,r6,3
- ori_r0,r0,1
- ld_r6,sp,24
- epilogue
- ret
-
-
-## ---- hex_digit_val(r1=c) -> r0 = digit in [0..15] or -1 ------------
-## Classifies one byte as a hex digit. Range-checks via BLT then does
-## an arithmetic subtract: digits subtract '0', A-F subtract 55, a-f
-## subtract 87. Non-hex chars (including EOF=-1) return -1.
-:hex_digit_val
- li_r2 %48 ## '0'
- li_br &hex_dv_none
- blt_r1,r2
- li_r2 %58 ## ':' (one past '9')
- li_br &hex_dv_dec
- blt_r1,r2
- li_r2 %65 ## 'A'
- li_br &hex_dv_none
- blt_r1,r2
- li_r2 %71 ## 'G' (one past 'F')
- li_br &hex_dv_upper
- blt_r1,r2
- li_r2 %97 ## 'a'
- li_br &hex_dv_none
- blt_r1,r2
- li_r2 %103 ## 'g' (one past 'f')
- li_br &hex_dv_lower
- blt_r1,r2
-:hex_dv_none
- li_r0 %0
- addi_r0,r0,neg1
- ret
-:hex_dv_dec
- mov_r0,r1
- addi_r0,r0,neg48
- ret
-:hex_dv_upper
- li_r0 %55 ## 'A' - 10 = 55
- sub_r0,r1,r0
- ret
-:hex_dv_lower
- li_r0 %87 ## 'a' - 10 = 87
- sub_r0,r1,r0
- ret
-
-
-## ---- is_delim(c) -> r0 = 0 or 1 ------------------------------------
-## Returns 1 if c is a token delimiter: whitespace, '(', ')', ';', or EOF.
-:is_delim
- li_r0 %1 ## optimistic: delim
- li_r2 %0
- addi_r2,r2,neg1
- li_br &is_delim_done
- beq_r1,r2 ## EOF
- li_r2 %32
- li_br &is_delim_done
- beq_r1,r2
- li_r2 %9
- li_br &is_delim_done
- beq_r1,r2
- li_r2 %10
- li_br &is_delim_done
- beq_r1,r2
- li_r2 %13
- li_br &is_delim_done
- beq_r1,r2
- li_r2 %40 ## '('
- li_br &is_delim_done
- beq_r1,r2
- li_r2 %41 ## ')'
- li_br &is_delim_done
- beq_r1,r2
- li_r2 %59 ## ';'
- li_br &is_delim_done
- beq_r1,r2
- li_r0 %0
-:is_delim_done
- ret
-
-
-## ---- read_symbol() -> r0 = tagged symbol --------------------------
-## Reads until the next delimiter, then calls intern on the byte range.
-## Uses r6 = start cursor, r7 = start pointer (both saved across loop).
-:read_symbol
- prologue_n2
- mov_r3,sp
- st_r6,sp,24
- st_r7,sp,32
-
- ## r7 = &src[cursor] (stored start)
- li_r1 &src_cursor
- ld_r1,r1,0
- li_r2 &src_base
- ld_r2,r2,0
- add_r7,r1,r2 ## r7 = base + cursor
- mov_r6,r1 ## r6 = start cursor
-
-:read_symbol_loop
- li_br &peek_char
- call
- mov_r1,r0
- li_br &is_delim
- call
- li_r1 %1
- li_br &read_symbol_done
- beq_r0,r1 ## delim → stop
-
- li_br &advance_char
- call
- li_br &read_symbol_loop
- b
-
-:read_symbol_done
- ## Length = current cursor - r6.
- li_r1 &src_cursor
- ld_r1,r1,0
- sub_r2,r1,r6 ## r2 = cur - start = len
-
- ## If length 0, malformed (shouldn't happen — caller peeks before).
- li_br &err_reader_bad
- beqz_r2
-
- mov_r1,r7 ## r1 = ptr to first byte
- ## r2 = len (already set)
- li_br &intern
- call
-
- ld_r6,sp,24
- ld_r7,sp,32
- epilogue_n2
- ret
-
-
-## ---- read_string() -> r0 = tagged string --------------------------
-## Reads a double-quoted string into a scratch buffer, handling the
-## four escape forms the seed needs: \n, \t, \\, and \".
-:read_string
- prologue_n2
- st_r6,sp,24
- st_r7,sp,32
-
- li_br &advance_char
- call ## eat opening '"'
-
- li_r6 &reader_string_buf
- li_r7 %0 ## length
-
-:read_string_loop
- li_br &peek_char
- call
- li_r1 %0
- addi_r1,r1,neg1
- li_br &err_reader_bad
- beq_r0,r1 ## EOF in string
-
- li_r1 %34
- li_br &read_string_done
- beq_r0,r1 ## closing '"'
-
- li_r1 %92
- li_br &read_string_escape
- beq_r0,r1
-
- li_r1 %512 ## READER_STRING_BUF_SIZE = 512
- li_br &read_string_store
- blt_r7,r1
- li_br &err_reader_bad
- b
-
-:read_string_store
- sb_r0,r6,0
- addi_r6,r6,1
- addi_r7,r7,1
- li_br &advance_char
- call
- li_br &read_string_loop
- b
-
-:read_string_escape
- li_br &advance_char
- call ## eat '\'
- li_br &peek_char
- call
- li_r1 %0
- addi_r1,r1,neg1
- li_br &err_reader_bad
- beq_r0,r1
-
- li_r1 %110 ## 'n'
- li_br &read_string_escape_nl
- beq_r0,r1
- li_r1 %116 ## 't'
- li_br &read_string_escape_tab
- beq_r0,r1
- li_r1 %92 ## '\\'
- li_br &read_string_store_escaped
- beq_r0,r1
- li_r1 %34 ## '"'
- li_br &read_string_store_escaped
- beq_r0,r1
- li_br &err_reader_bad
- b
-
-:read_string_escape_nl
- li_r0 %10
- li_br &read_string_store_escaped
- b
-
-:read_string_escape_tab
- li_r0 %9
-
-:read_string_store_escaped
- li_r1 %4096
- li_br &read_string_store_escaped_ok
- blt_r7,r1
- li_br &err_reader_bad
- b
-
-:read_string_store_escaped_ok
- sb_r0,r6,0
- addi_r6,r6,1
- addi_r7,r7,1
- li_br &advance_char
- call ## eat escaped payload char
- li_br &read_string_loop
- b
-
-:read_string_done
- li_br &advance_char
- call ## eat closing '"'
- li_r1 &reader_string_buf
- mov_r2,r7
- li_br &make_string
- call
-
- ld_r6,sp,24
- ld_r7,sp,32
- epilogue_n2
- ret
-
-
-## ---- read_list() -> r0 = tagged list --------------------------------
-## Assumes the '(' has already been consumed. Reads expressions until ')'.
-## Builds a list in order using a reversed cons chain, then reverses.
-## Simpler approach here: recursive — read one expr, then cons onto the
-## read_list tail. Stack-bounded but OK for shallow depths.
-##
-## Frame: PROLOGUE_N1 with slot 1 = saved r6 (first element tagged value).
-:read_list
- prologue
-
- li_br &skip_ws
- call
-
- ## If next char is ')', return nil (end of list).
- li_br &peek_char
- call
- li_r1 %41 ## ')'
- li_br &read_list_close
- beq_r0,r1
-
- ## EOF mid-list → error.
- li_r1 %0
- addi_r1,r1,neg1
- li_br &err_reader_bad
- beq_r0,r1
-
- ## '.' followed by a delimiter → dotted tail. `.<alpha>` still
- ## parses as a regular symbol (fall-through to read_expr).
- li_r1 %46 ## '.'
- li_br &read_list_maybe_dotted
- beq_r0,r1
-
- ## Read head element, then tail, then cons them.
- li_br &read_expr
- call ## r0 = head
-
- st_r0,sp,24 ## slot 1 = head (spill across next call)
-
- li_br &read_list
- call ## r0 = tail (recursive)
-
- ## cons(head, tail)
- ld_r1,sp,24
- mov_r2,r0
- li_br &cons
- call
-
- epilogue
- ret
-
-:read_list_maybe_dotted
- li_br &peek2_char
- call
- mov_r1,r0
- li_br &is_delim
- call
- li_r1 %1
- li_br &read_list_dotted
- beq_r0,r1
-
- ## Bare `.` followed by non-delim → fall back to regular item path.
- li_br &read_expr
- call
- st_r0,sp,24
- li_br &read_list
- call
- ld_r1,sp,24
- mov_r2,r0
- li_br &cons
- call
- epilogue
- ret
-
-:read_list_dotted
- li_br &advance_char
- call ## eat '.'
- li_br &skip_ws
- call
- li_br &read_expr
- call ## r0 = dotted tail expr
- st_r0,sp,24
- li_br &skip_ws
- call
- li_br &peek_char
- call
- li_r1 %41 ## ')'
- li_br &err_reader_bad
- bne_r0,r1
- li_br &advance_char
- call ## eat ')'
- ld_r0,sp,24
- epilogue
- ret
-
-:read_list_close
- li_br &advance_char
- call ## eat ')'
- li_r0 NIL ## nil = 0x07
- epilogue
- ret
-
-
-## ---- read_expr() -> r0 = tagged value -------------------------------
-## Dispatches on the first non-whitespace char.
-:read_expr
- prologue
-
- li_br &skip_ws
- call
-
- li_br &peek_char
- call ## r0 = char
-
- ## EOF → error.
- li_r1 %0
- addi_r1,r1,neg1
- li_br &err_reader_bad
- beq_r0,r1
-
- ## '(' → list.
- li_r1 %40
- li_br &read_expr_list
- beq_r0,r1
-
- ## '"' → string literal.
- li_r1 %34
- li_br &read_expr_string
- beq_r0,r1
-
- ## '#' → hash-prefix literal (#t, #f, #\char, #(...)).
- li_r1 %35
- li_br &read_expr_hash
- beq_r0,r1
-
- ## '\'' → (quote x)
- li_r1 %39
- li_br &read_expr_quote
- beq_r0,r1
-
- ## '`' → (quasiquote x)
- li_r1 %96
- li_br &read_expr_quasiquote
- beq_r0,r1
-
- ## ',' → (unquote x) or (unquote-splicing x) if followed by '@'
- li_r1 %44
- li_br &read_expr_unquote
- beq_r0,r1
-
- ## '-' followed by a digit → negative fixnum literal. Plain '-'
- ## still reads as a symbol (the subtraction primitive).
- li_r1 %45
- li_br &read_expr_maybe_neg
- beq_r0,r1
-
- ## digit → number.
- mov_r1,r0
- li_br &is_digit
- call
- li_r1 %1
- li_br &read_expr_number
- beq_r0,r1
-
- ## else → symbol.
- li_br &read_symbol
- call
- epilogue
- ret
-
-:read_expr_list
- li_br &advance_char
- call ## eat '('
- li_br &read_list
- call
- epilogue
- ret
-
-:read_expr_number
- li_br &read_number
- call
- epilogue
- ret
-
-:read_expr_string
- li_br &read_string
- call
- epilogue
- ret
-
-## Hash-prefix literals: `#t`/`#f` singletons, `#\char` ASCII fixnum,
-## `#(...)` vector literal. Inherits read_expr's PROLOGUE.
-:read_expr_hash
- li_br &advance_char
- call ## eat '#'
- li_br &peek_char
- call ## r0 = next char
-
- li_r1 %116 ## 't'
- li_br &read_expr_hash_t
- beq_r0,r1
- li_r1 %102 ## 'f'
- li_br &read_expr_hash_f
- beq_r0,r1
- li_r1 %92 ## '\\'
- li_br &read_expr_hash_char
- beq_r0,r1
- li_r1 %40 ## '('
- li_br &read_expr_hash_vector
- beq_r0,r1
-
- li_br &err_reader_bad
- b
-
-:read_expr_hash_t
- li_br &advance_char
- call ## eat 't'
- li_r0 TRUE ## #t singleton
- epilogue
- ret
-
-:read_expr_hash_f
- li_br &advance_char
- call ## eat 'f'
- li_r0 FALSE ## #f singleton
- epilogue
- ret
-
-:read_expr_hash_char
- li_br &read_char_literal
- call
- epilogue
- ret
-
-:read_expr_hash_vector
- li_br &read_vector_literal
- call
- epilogue
- ret
-
-
-## ---- read_expr_quote / quasiquote / unquote / maybe_neg -------------
-## All inherit read_expr's PROLOGUE; each wraps the downstream value and
-## ret/epilogue out.
-:read_expr_quote
- li_br &advance_char
- call ## eat '\''
- li_r1 &sym_quote
- ld_r1,r1,0
- li_br &read_quoted_wrap
- call
- epilogue
- ret
-
-:read_expr_quasiquote
- li_br &advance_char
- call ## eat '`'
- li_r1 &sym_quasiquote
- ld_r1,r1,0
- li_br &read_quoted_wrap
- call
- epilogue
- ret
-
-:read_expr_unquote
- li_br &advance_char
- call ## eat ','
- li_br &peek_char
- call
- li_r1 %64 ## '@'
- li_br &read_expr_unquote_splicing
- beq_r0,r1
- li_r1 &sym_unquote
- ld_r1,r1,0
- li_br &read_quoted_wrap
- call
- epilogue
- ret
-
-:read_expr_unquote_splicing
- li_br &advance_char
- call ## eat '@'
- li_r1 &sym_unquote_splicing
- ld_r1,r1,0
- li_br &read_quoted_wrap
- call
- epilogue
- ret
-
-:read_expr_maybe_neg
- li_br &peek2_char
- call
- mov_r1,r0
- li_br &is_digit
- call
- li_r1 %1
- li_br &read_expr_negnum
- beq_r0,r1
- ## Not a digit after '-' → treat as a regular symbol (the '-' is
- ## the sub primitive, or a longer ident like `-ident`).
- li_br &read_symbol
- call
- epilogue
- ret
-
-:read_expr_negnum
- li_br &advance_char
- call ## eat '-'
- li_br &read_number
- call ## r0 = tagged positive number
- sari_r0,r0,3 ## decode
- li_r1 %0
- sub_r0,r1,r0 ## r0 = -value
- shli_r0,r0,3
- ori_r0,r0,1 ## retag
- epilogue
- ret
-
-
-## ---- read_quoted_wrap(r1=tagged_sym) -> r0 = (sym expr) -------------
-## Reads one expression and returns (sym . (expr . nil)). Shared by the
-## four reader shorthand forms (quote/quasi/unquote/unquote-splicing).
-:read_quoted_wrap
- prologue
- st_r1,sp,24 ## save tagged sym
- li_br &read_expr
- call ## r0 = expr
- mov_r1,r0
- li_r2 NIL
- li_br &cons
- call ## r0 = (expr . nil)
- mov_r2,r0
- ld_r1,sp,24
- li_br &cons ## r0 = (sym . (expr . nil))
- tail
-
-
-## ---- read_char_literal() -> r0 = tagged fixnum (ASCII) -------------
-## `\` already at cursor. Consumes `\`, then one or more chars. If the
-## first char is followed by a delimiter, that char is the result. If
-## more chars follow, the full span is matched against `space`, `newline`,
-## or `tab`; anything else errors out.
-:read_char_literal
- prologue_n4
- li_br &advance_char
- call ## eat '\\'
-
- li_r1 &src_cursor
- ld_r0,r1,0
- st_r0,sp,24 ## slot 1 = start cursor
-
- li_br &peek_char
- call ## r0 = first char (must not be EOF)
- li_r1 %0
- addi_r1,r1,neg1
- li_br &err_reader_bad
- beq_r0,r1
-
- st_r0,sp,32 ## slot 2 = first char
- li_br &advance_char
- call ## eat first char
-
- li_br &peek_char
- call
- mov_r1,r0
- li_br &is_delim
- call
- li_r1 %1
- li_br &rcl_single
- beq_r0,r1
-
-:rcl_name_loop
- li_br &peek_char
- call
- mov_r1,r0
- li_br &is_delim
- call
- li_r1 %1
- li_br &rcl_compare
- beq_r0,r1
- li_br &advance_char
- call
- li_br &rcl_name_loop
- b
-
-:rcl_compare
- ## r2 = len (cursor - start), r0 = ptr (base + start).
- li_r1 &src_cursor
- ld_r2,r1,0
- ld_r1,sp,24
- sub_r2,r2,r1
- li_r0 &src_base
- ld_r0,r0,0
- add_r0,r0,r1
- li_r1 %5
- li_br &rcl_cmp5
- beq_r2,r1
- li_r1 %7
- li_br &rcl_cmp7
- beq_r2,r1
- li_r1 %3
- li_br &rcl_cmp3
- beq_r2,r1
- li_br &err_reader_bad
- b
-
-:rcl_cmp5
- ## "space" → 32
- lb_r1,r0,0
- li_r2 %115
- li_br &err_reader_bad
- bne_r1,r2
- addi_r0,r0,1
- lb_r1,r0,0
- li_r2 %112
- li_br &err_reader_bad
- bne_r1,r2
- addi_r0,r0,1
- lb_r1,r0,0
- li_r2 %97
- li_br &err_reader_bad
- bne_r1,r2
- addi_r0,r0,1
- lb_r1,r0,0
- li_r2 %99
- li_br &err_reader_bad
- bne_r1,r2
- addi_r0,r0,1
- lb_r1,r0,0
- li_r2 %101
- li_br &err_reader_bad
- bne_r1,r2
- li_r0 %32
- shli_r0,r0,3
- ori_r0,r0,1
- epilogue_n4
- ret
-
-:rcl_cmp7
- ## "newline" → 10
- lb_r1,r0,0
- li_r2 %110
- li_br &err_reader_bad
- bne_r1,r2
- addi_r0,r0,1
- lb_r1,r0,0
- li_r2 %101
- li_br &err_reader_bad
- bne_r1,r2
- addi_r0,r0,1
- lb_r1,r0,0
- li_r2 %119
- li_br &err_reader_bad
- bne_r1,r2
- addi_r0,r0,1
- lb_r1,r0,0
- li_r2 %108
- li_br &err_reader_bad
- bne_r1,r2
- addi_r0,r0,1
- lb_r1,r0,0
- li_r2 %105
- li_br &err_reader_bad
- bne_r1,r2
- addi_r0,r0,1
- lb_r1,r0,0
- li_r2 %110
- li_br &err_reader_bad
- bne_r1,r2
- addi_r0,r0,1
- lb_r1,r0,0
- li_r2 %101
- li_br &err_reader_bad
- bne_r1,r2
- li_r0 %10
- shli_r0,r0,3
- ori_r0,r0,1
- epilogue_n4
- ret
-
-:rcl_cmp3
- ## "tab" → 9
- lb_r1,r0,0
- li_r2 %116
- li_br &err_reader_bad
- bne_r1,r2
- addi_r0,r0,1
- lb_r1,r0,0
- li_r2 %97
- li_br &err_reader_bad
- bne_r1,r2
- addi_r0,r0,1
- lb_r1,r0,0
- li_r2 %98
- li_br &err_reader_bad
- bne_r1,r2
- li_r0 %9
- shli_r0,r0,3
- ori_r0,r0,1
- epilogue_n4
- ret
-
-:rcl_single
- ld_r0,sp,32
- shli_r0,r0,3
- ori_r0,r0,1
- epilogue_n4
- ret
-
-
-## ---- read_vector_literal() -> r0 = tagged vector -------------------
-## `#(` detected; cursor at `(`. Read the body as a list, then convert
-## element-by-element into a freshly-allocated vector.
-:read_vector_literal
- prologue_n3
- li_br &advance_char
- call ## eat '('
- li_br &read_list
- call ## r0 = tagged list
- st_r0,sp,24 ## slot 1 = list head
-
- mov_r1,r0
- li_r2 %0 ## r2 = count
-:rvl_count
- li_r0 NIL
- li_br &rvl_alloc
- beq_r1,r0
- addi_r0,r1,neg2
- ld_r1,r0,8
- addi_r2,r2,1
- li_br &rvl_count
- b
-
-:rvl_alloc
- mov_r1,r2
- li_r2 NIL
- li_br &make_vector
- call ## r0 = tagged vector
- st_r0,sp,32 ## slot 2 = vec
-
- addi_r3,r0,neg3
- addi_r3,r3,8 ## r3 = payload cursor
- ld_r1,sp,24
-
-:rvl_fill
- li_r0 NIL
- li_br &rvl_done
- beq_r1,r0
- addi_r0,r1,neg2
- ld_r2,r0,0
- ld_r1,r0,8
- st_r2,r3,0
- addi_r3,r3,8
- li_br &rvl_fill
- b
-
-:rvl_done
- ld_r0,sp,32
- epilogue_n3
- ret
-
-
-## ---- Display --------------------------------------------------------
-
-## ---- putc(r1=char) — write one byte to fd 1 ------------------------
-:putc_buf %0 %0
-
-:putc
- prologue
- li_r2 &putc_buf
- sb_r1,r2,0 ## *putc_buf = low byte of r1
- li_r0 sys_write
- li_r1 %1
- ## r2 = &putc_buf already
- li_r3 %1
- syscall
- epilogue
- ret
-
-
-## ---- display_uint(r1=u64, r2=fd) — decimal, no sign --------------
-## Writes digits to `digit_buf` right-to-left, then SYS_WRITEs the
-## filled range to fd. 24-byte buffer covers any 61-bit value.
-## fd is spilled into slot 3 because the digit loop reuses r2 as the
-## divisor '10' constant — keeping fd in a reg would require saving
-## another callee-saved, which costs the same.
-:digit_buf '000000000000000000000000000000000000000000000000'
-:digit_buf_end %0
-
-:display_uint
- prologue_n3
- st_r6,sp,24
- st_r7,sp,32
- st_r2,sp,40 ## save fd
-
- li_r6 &digit_buf_end ## r6 = end-of-buffer cursor (moves left)
- mov_r7,r1 ## r7 = value (mutated)
-
- ## Special-case zero.
- li_br &du_loop
- bnez_r7 ## if value != 0, loop; else write '0'
- addi_r6,r6,neg1
- li_r1 %48 ## '0'
- sb_r1,r6,0
- li_br &du_write
- b
-
-:du_loop
- li_br &du_write
- beqz_r7 ## value == 0 → done digit gen
-
- ## digit = value % 10.
- mov_r1,r7
- li_r2 %10
- rem_r1,r1,r2 ## r1 = value % 10
- addi_r1,r1,48 ## r1 += '0'
- addi_r6,r6,neg1
- sb_r1,r6,0 ## *--r6 = digit
-
- ## value = value / 10.
- mov_r1,r7
- li_r2 %10
- div_r1,r1,r2
- mov_r7,r1
-
- li_br &du_loop
- b
-
-:du_write
- ## Stash fd into r0 first — computing len clobbers r3 (we need it
- ## as SP for the slot-3 load), so grab fd before that step.
- ld_r0,sp,40 ## r0 = fd (temp; sys_write will overwrite r0)
- li_r1 &digit_buf_end
- sub_r3,r1,r6 ## r3 = len = digit_buf_end - r6
- mov_r1,r0 ## r1 = fd
- mov_r2,r6 ## r2 = buf
- li_r0 sys_write
- syscall
-
- ld_r6,sp,24
- ld_r7,sp,32
- epilogue_n3
- ret
-
-
-## ---- display(r1=tagged) — dispatch on tag -------------------------
-## display owns a PROLOGUE_N2 frame even though its own body only needs
-## retaddr — display_pair dispatches into this frame (via BEQ, not CALL)
-## and uses slots 1/2 to save caller's r6/r7. Giving display_pair its own
-## prologue would leak display's frame on return: the stacked PROLOGUE +
-## PROLOGUE_N2 sum doesn't match display_pair's single EPILOGUE_N2, so
-## recursive calls drift sp and the saved r6/r7 slots point off-frame.
-:display
- prologue_n2
-
- ## nil = 0x07
- li_r2 NIL
- li_br &display_nil
- beq_r1,r2
-
- ## #t = 0x0F
- li_r2 TRUE
- li_br &display_true
- beq_r1,r2
-
- ## #f = 0x17
- li_r2 FALSE
- li_br &display_false
- beq_r1,r2
-
- ## Low-3-bit tag.
- mov_r2,r1
- andi_r1,r1,7 ## r1 = tag
- li_r3 TAG_FIXNUM
- li_br &display_fixnum
- beq_r1,r3
- li_r3 TAG_PAIR
- li_br &display_pair
- beq_r1,r3
- li_r3 TAG_SYMBOL
- li_br &display_symbol
- beq_r1,r3
- li_r3 TAG_STRING
- li_br &display_string
- beq_r1,r3
-
- ## Unknown — treat as "?"
- li_r1 %63 ## '?'
- li_br &putc
- call
- epilogue_n2
- ret
-
-:display_nil
- li_r1 %40 ## '('
- li_br &putc
- call
- li_r1 %41 ## ')'
- li_br &putc
- call
- epilogue_n2
- ret
-
-## #t and #f share display's PROLOGUE_N2 frame (reached via BEQ from
-## display or write — display_fixnum/symbol pattern).
-:display_true
- li_r1 %35 ## '#'
- li_br &putc
- call
- li_r1 %116 ## 't'
- li_br &putc
- call
- epilogue_n2
- ret
-
-:display_false
- li_r1 %35 ## '#'
- li_br &putc
- call
- li_r1 %102 ## 'f'
- li_br &putc
- call
- epilogue_n2
- ret
-
-:display_fixnum
- mov_r1,r2
- sari_r1,r1,3 ## signed shift; value (assume non-negative here)
- li_r2 %1 ## fd = stdout
- li_br &display_uint
- call
- epilogue_n2
- ret
-
-:display_symbol
- ## r2 = tagged symbol. Strip tag, read header, then write bytes.
- addi_r2,r2,neg5 ## r2 = raw header ptr (tag=0b101 → -5)
- ld_r3,r2,0 ## r3 = header
- ## Low 48 bits = length. Mask by SHL/SHR 16.
- shli_r3,r3,32 ## clear top 16
- shri_r3,r3,32 ## r3 = length (0..2^48)
- addi_r2,r2,8 ## r2 = name ptr
- li_r0 sys_write
- li_r1 %1
- syscall
- epilogue_n2
- ret
-
-:display_pair
- ## r2 = tagged pair. Shares display's PROLOGUE_N2 frame.
- st_r6,sp,24 ## save r6 into display's slot 1
- st_r7,sp,32 ## save r7 into display's slot 2
-
- mov_r6,r2 ## r6 = tagged pair (we'll walk cdr chain)
-
- li_r1 %40 ## '('
- li_br &putc
- call
-
-:display_pair_loop
- ## car(r6)
- mov_r1,r6
- li_br &car
- call
- mov_r1,r0
- li_br &display
- call
-
- ## cdr(r6)
- mov_r1,r6
- li_br &cdr
- call
- mov_r6,r0
-
- ## If cdr == nil, done.
- li_r1 NIL
- li_br &display_pair_close
- beq_r6,r1
-
- ## If cdr is a pair, space + loop.
- mov_r1,r6
- andi_r1,r1,7
- li_r2 TAG_PAIR
- li_br &display_pair_continue
- beq_r1,r2
-
- ## Otherwise, improper: " . x)".
- li_r1 %32 ## ' '
- li_br &putc
- call
- li_r1 %46 ## '.'
- li_br &putc
- call
- li_r1 %32
- li_br &putc
- call
- mov_r1,r6
- li_br &display
- call
- li_br &display_pair_close
- b
-
-:display_pair_continue
- li_r1 %32 ## ' '
- li_br &putc
- call
- li_br &display_pair_loop
- b
-
-:display_pair_close
- li_r1 %41 ## ')'
- li_br &putc
- call
-
- ld_r6,sp,24
- ld_r7,sp,32
- epilogue_n2
- ret
-
-
-## ---- display_string(r2=tagged_string) ------------------------------
-## Tail-called from display's dispatcher with r2 holding the tagged
-## string (low-3-bit tag = 100 = 4). Strips the tag, reads the 48-bit
-## length out of the 8-byte header, then writes the payload bytes to
-## stdout. Shares display's PROLOGUE_N2 frame, so EPILOGUE_N2 + RET
-## unwinds back to display's caller.
-:display_string
- addi_r2,r2,neg4 ## r2 = raw header ptr
- ld_r3,r2,0 ## r3 = header word
- shli_r3,r3,32
- shri_r3,r3,32 ## r3 = length (low 48 bits)
- addi_r2,r2,8 ## r2 = payload ptr
- li_r0 sys_write
- li_r1 %1
- syscall
- epilogue_n2
- ret
-
-
-## ---- write(r1=val) -------------------------------------------------
-## Printer form: strings get quoted with escapes honored, everything
-## else renders identically to display. Sharing display_nil/fixnum/
-## symbol works because write's PROLOGUE_N2 frame matches display's —
-## those handlers unwind the right amount on their own EPILOGUE_N2.
-:write
- prologue_n2
-
- li_r2 NIL
- li_br &display_nil
- beq_r1,r2 ## nil
-
- li_r2 TRUE
- li_br &display_true
- beq_r1,r2 ## #t
-
- li_r2 FALSE
- li_br &display_false
- beq_r1,r2 ## #f
-
- mov_r2,r1
- andi_r1,r1,7 ## r1 = tag
- li_r3 TAG_FIXNUM
- li_br &display_fixnum
- beq_r1,r3
- li_r3 TAG_PAIR
- li_br &write_pair
- beq_r1,r3
- li_r3 TAG_SYMBOL
- li_br &display_symbol
- beq_r1,r3
- li_r3 TAG_STRING
- li_br &write_string
- beq_r1,r3
-
- ## Unknown (closure/prim, etc.) — emit '?' placeholder.
- li_r1 %63
- li_br &putc
- call
- epilogue_n2
- ret
-
-
-## ---- write_string(r2=tagged_string) --------------------------------
-## Emits "..." with `"` and `\` escaped as `\"` and `\\`. Shares
-## write's PROLOGUE_N2 frame; r6=cursor, r7=remaining-bytes saved in
-## the two slots across putc calls.
-:write_string
- st_r6,sp,24
- st_r7,sp,32
-
- addi_r2,r2,neg4
- ld_r3,r2,0
- shli_r3,r3,32
- shri_r3,r3,32 ## r3 = length
- addi_r2,r2,8 ## r2 = payload ptr
- mov_r6,r2 ## r6 = cursor
- mov_r7,r3 ## r7 = length
-
- li_r1 %34 ## '"'
- li_br &putc
- call
-
-:write_string_loop
- li_br &write_string_done
- beqz_r7 ## length == 0 → done
-
- lb_r1,r6,0 ## r1 = *cursor
-
- li_r2 %34 ## '"'
- li_br &write_string_escape
- beq_r1,r2
-
- li_r2 %92 ## '\\'
- li_br &write_string_escape
- beq_r1,r2
-
- li_br &putc
- call
-
-:write_string_next
- addi_r6,r6,1
- addi_r7,r7,neg1
- li_br &write_string_loop
- b
-
-:write_string_escape
- ## Emit '\\' then re-emit the char (cursor still points to it).
- li_r1 %92
- li_br &putc
- call
- lb_r1,r6,0
- li_br &putc
- call
- li_br &write_string_next
- b
-
-:write_string_done
- li_r1 %34 ## '"'
- li_br &putc
- call
- ld_r6,sp,24
- ld_r7,sp,32
- epilogue_n2
- ret
-
-
-## ---- write_pair(r2=tagged_pair) ------------------------------------
-## Mirror of display_pair but recurses through write (strings quoted).
-## Shares write's PROLOGUE_N2 frame.
-:write_pair
- st_r6,sp,24
- st_r7,sp,32
-
- mov_r6,r2 ## r6 = tagged pair cursor
-
- li_r1 %40 ## '('
- li_br &putc
- call
-
-:write_pair_loop
- mov_r1,r6
- li_br &car
- call
- mov_r1,r0
- li_br &write
- call
-
- mov_r1,r6
- li_br &cdr
- call
- mov_r6,r0
-
- li_r1 NIL
- li_br &write_pair_close
- beq_r6,r1
-
- mov_r1,r6
- andi_r1,r1,7
- li_r2 TAG_PAIR
- li_br &write_pair_continue
- beq_r1,r2
-
- ## improper tail: " . x)"
- li_r1 %32
- li_br &putc
- call
- li_r1 %46
- li_br &putc
- call
- li_r1 %32
- li_br &putc
- call
- mov_r1,r6
- li_br &write
- call
- li_br &write_pair_close
- b
-
-:write_pair_continue
- li_r1 %32
- li_br &putc
- call
- li_br &write_pair_loop
- b
-
-:write_pair_close
- li_r1 %41 ## ')'
- li_br &putc
- call
-
- ld_r6,sp,24
- ld_r7,sp,32
- epilogue_n2
- ret
-
-
-## ================ Step 6: Eval =======================================
-## Recursive-descent evaluator. Compound forms dispatch by comparing
-## the car against cached interned pointers (sym_quote/if/begin/
-## lambda/define, seeded in _start). Anything else is a function
-## application: callee and args get evaluated, then apply runs the
-## closure. Local env is a flat alist of (sym . val) pairs; global
-## bindings live in a singly-linked alist hanging off global_env_cell.
-
-## ---- gset(r1=sym, r2=val) ------------------------------------------
-## Prepends (sym . val) to the global alist. Last-write-wins semantics
-## fall out naturally because lookup_alist returns the leftmost match.
-:gset
- prologue_n2
- st_r1,sp,24 ## save sym
- st_r2,sp,32 ## save val
-
- li_br &cons
- call ## r0 = (sym . val)
-
- mov_r1,r0
- li_r2 &global_env_cell
- ld_r2,r2,0 ## r2 = old global alist
- li_br &cons
- call ## r0 = new alist
-
- li_r2 &global_env_cell
- st_r0,r2,0
- epilogue_n2
- ret
-
-
-## ---- lookup_alist(r1=sym, r2=env) -> r0 = pair or nil ---------------
-## Walks alist env returning the (sym . val) cons cell that matches, or
-## nil on miss. Caller distinguishes via tag/nil check.
-:lookup_alist
- prologue_n3
- mov_r3,sp
- st_r1,sp,24 ## slot 1 = sym
- st_r2,sp,32 ## slot 2 = cursor
-
-:lookup_alist_loop
- mov_r3,sp
- ld_r2,sp,32
- li_r1 %7
- li_br &lookup_alist_miss
- beq_r2,r1 ## cursor == nil → miss
-
- ## pair = car(cursor)
- mov_r1,r2
- li_br &car
- call ## r0 = (k . v)
-
- st_r0,sp,40 ## slot 3 = pair
-
- mov_r1,r0
- li_br &car
- call ## r0 = key
-
- mov_r3,sp
- ld_r1,sp,24
- li_br &lookup_alist_hit
- beq_r0,r1
-
- ## advance cursor := cdr(cursor)
- ld_r1,sp,32
- li_br &cdr
- call
- st_r0,sp,32
- li_br &lookup_alist_loop
- b
-
-:lookup_alist_hit
- ld_r0,sp,40
- epilogue_n3
- ret
-
-:lookup_alist_miss
- li_r0 %7
- epilogue_n3
- ret
-
-
-## ---- lookup(r1=sym, r2=env) -> r0 = value --------------------------
-## Local first, global second. Errors out via &err_unbound on miss.
-:lookup
- prologue_n2
- st_r1,sp,24 ## save sym for possible global retry
-
- li_br &lookup_alist
- call ## r0 = local pair or nil
-
- li_r1 %7
- li_br &lookup_global
- beq_r0,r1
-
- mov_r1,r0
- li_br &cdr
- call ## r0 = value
- epilogue_n2
- ret
-
-:lookup_global
- ld_r1,sp,24
- li_r2 &global_env_cell
- ld_r2,r2,0
- li_br &lookup_alist
- call
-
- li_r1 %7
- li_br &err_unbound
- beq_r0,r1
-
- mov_r1,r0
- li_br &cdr
- call
- epilogue_n2
- ret
-
-
-## ---- env_extend(r1=names, r2=vals, r3=env) -> r0 = new env ---------
-## For each (name, val) pair, prepends (name . val) to env. Iterative;
-## uses a 4-slot frame because the per-iteration state is (names,
-## vals, env-acc, name-temp) and cons clobbers r0-r3 so name has to
-## live somewhere stable across the val-car call.
-:env_extend
- prologue_n4
- mov_r0,r3 ## save env before clobbering r3
- mov_r3,sp
- st_r1,sp,24 ## slot 1 = names
- st_r2,sp,32 ## slot 2 = vals
- st_r0,sp,40 ## slot 3 = env accumulator
-
-:env_extend_loop
- mov_r3,sp
- ld_r1,sp,24
- li_r2 %7
- li_br &env_extend_done
- beq_r1,r2 ## names == nil → done
-
- ld_r1,sp,32
- li_r2 %7
- li_br &err_arity
- beq_r1,r2 ## vals == nil → arity error
-
- ## name = car(names)
- ld_r1,sp,24
- li_br &car
- call ## r0 = name
- st_r0,sp,48 ## slot 4 = name
-
- ## val = car(vals)
- ld_r1,sp,32
- li_br &car
- call ## r0 = val
-
- ## pair = cons(name, val)
- mov_r2,r0
- ld_r1,sp,48
- li_br &cons
- call ## r0 = (name . val)
-
- ## env := cons(pair, env)
- mov_r1,r0
- ld_r2,sp,40
- li_br &cons
- call ## r0 = new env
-
- st_r0,sp,40
-
- ## names := cdr(names)
- ld_r1,sp,24
- li_br &cdr
- call
- st_r0,sp,24
-
- ## vals := cdr(vals)
- ld_r1,sp,32
- li_br &cdr
- call
- st_r0,sp,32
-
- li_br &env_extend_loop
- b
-
-:env_extend_done
- ld_r0,sp,40
- epilogue_n4
- ret
-
-
-## ---- make_closure(r1=params, r2=body, r3=env) -> r0 = tagged -------
-## 32-byte heap object: [header(type=4) | params | body | env]. Tag=6
-## (110) matches the combined closure/prim tag band. Pre-zeroed BSS
-## heap lets us skip writing the header's low 7 bytes and only stamp
-## byte 7 with the type code.
-:make_closure
- prologue_n3
- mov_r0,r3 ## save env
- st_r1,sp,24 ## slot 1 = params
- st_r2,sp,32 ## slot 2 = body
- st_r0,sp,40 ## slot 3 = env
-
- li_r1 %32 ## 32 bytes
- li_br &alloc
- call ## r0 = raw ptr
-
- li_r1 %4
- sb_r1,r0,7 ## type = 4 (closure)
-
- ld_r1,sp,24
- st_r1,r0,8
- ld_r1,sp,32
- st_r1,r0,16
- ld_r1,sp,40
- st_r1,r0,24
-
- ori_r0,r0,4
- ori_r0,r0,2 ## tag = 0b110 = 6
-
- epilogue_n3
- ret
-
-
-## ---- make_primitive(r1=code_id, r2=arity, r3=type) -> r0 = tagged --
-## 16-byte heap object: [header | code_id]. header byte 7 = type
-## (5=fixed, 6=variadic); byte 0 = arity. Shares tag 0b110 with
-## closures so apply's tag check still works; the type byte forks
-## there. Arity is ignored for variadic (type 6) — callers pass 0.
-:make_primitive
- prologue_n3
- mov_r0,r3 ## save type → r0 (frees r3)
- st_r1,sp,24 ## slot 1 = code_id
- st_r2,sp,32 ## slot 2 = arity
- st_r0,sp,40 ## slot 3 = type
-
- li_r1 %16 ## 16 bytes
- li_br &alloc
- call ## r0 = raw ptr
-
- ld_r1,sp,40
- sb_r1,r0,7 ## byte 7 = type
- ld_r1,sp,32
- sb_r1,r0,0 ## byte 0 = arity
- ld_r1,sp,24
- st_r1,r0,8 ## +8 = code id
-
- ori_r0,r0,4
- ori_r0,r0,2 ## tag = 0b110 = 6
-
- epilogue_n3
- ret
-
-
-## ---- eval_args(r1=args, r2=env) -> r0 = evaluated args list --------
-## Recursive map: eval each, cons the results left-to-right.
-:eval_args
- prologue_n3
- mov_r3,sp
- st_r1,sp,24 ## slot 1 = args cursor
- st_r2,sp,32 ## slot 2 = env
-
- li_r2 %7
- li_br &eval_args_done
- beq_r1,r2
-
- ## head = eval(car(args), env)
- li_br &car
- call
- mov_r1,r0
- ld_r2,sp,32
- li_br &eval
- call ## r0 = head value
-
- st_r0,sp,40
-
- ## tail = eval_args(cdr(args), env)
- ld_r1,sp,24
- li_br &cdr
- call
- mov_r1,r0
- ld_r2,sp,32
- li_br &eval_args
- call ## r0 = tail
-
- mov_r2,r0
- ld_r1,sp,40
- li_br &cons ## tail: cons is last work in this frame
- tail_n3
-
-:eval_args_done
- li_r0 %7
- epilogue_n3
- ret
-
-
-## ---- apply(r1=callee, r2=args) -> r0 = result ----------------------
-## Callee must have tag 0b110 (closure/prim band). Header byte 7
-## discriminates: 4=closure (existing path), 5=prim-fixed,
-## 6=prim-variadic. Frame has 4 slots: callee-then-params, args,
-## body, closure-env (prim paths repurpose slots 3/4 for code_id /
-## expected_arity).
-:apply
- prologue_n4
- mov_r3,sp
- st_r1,sp,24 ## slot 1 = callee (tagged)
- st_r2,sp,32 ## slot 2 = args
-
- andi_r1,r1,7
- li_r0 TAG_PROC
- li_br &err_not_callable
- bne_r1,r0
-
- mov_r3,sp
- ld_r1,sp,24
- addi_r1,r1,neg6 ## r1 = raw ptr
-
- ## Fork on header type byte.
- lb_r0,r1,7 ## r0 = type
- li_r2 TYPE_PRIM_FIXED
- li_br &apply_prim_fixed
- beq_r0,r2
- li_r2 TYPE_PRIM_VARIADIC
- li_br &apply_prim_variadic
- beq_r0,r2
- ## Fall through: type == 4 (closure).
-
- ld_r0,r1,8 ## params
- st_r0,sp,24 ## slot 1 = params (overwrite)
- ld_r0,r1,16 ## body
- st_r0,sp,40 ## slot 3 = body
- ld_r0,r1,24 ## closure env
- st_r0,sp,48 ## slot 4 = closure env
-
- ## env_extend(params, args, closure_env)
- ld_r1,sp,24
- ld_r2,sp,32
- ld_r3,sp,48
- li_br &env_extend
- call ## r0 = new env
-
- mov_r2,r0
- ld_r1,sp,40 ## r1 = body
- li_br &eval
- tail_n4 ## Scheme tail call: closure body
-
-
-## ---- apply_prim_fixed (r1 = raw prim ptr, inside apply's frame) ----
-## Loads arity + code_id, marshals args, arity-checks, then unwinds
-## apply's frame and B's to prim_dispatch. The cascade there tail-
-## enters the body with r1=argc, r2=argv — body RETs directly to
-## apply's caller since EPILOGUE_N4 already restored lr.
-:apply_prim_fixed
- ## r3 is still sp from the MOV above; slot 3/4 are free.
- lb_r0,r1,0 ## r0 = expected arity
- st_r0,r3,48 ## slot 4 = expected arity
- ld_r0,r1,8 ## r0 = code id
- st_r0,r3,40 ## slot 3 = code id
-
- ld_r1,r3,32 ## r1 = args
- li_br &marshal_argv
- call ## r0 = argc, r2 = &prim_argv
-
- mov_r3,sp
- ld_r1,sp,48
- li_br &err_arity
- bne_r0,r1
-
- mov_r1,r0
- ld_r3,sp,40 ## r3 = code id
- epilogue_n4
- li_br &prim_dispatch
- b
-
-
-## ---- apply_prim_variadic (r1 = raw prim ptr) ----------------------
-:apply_prim_variadic
- ld_r0,r1,8 ## r0 = code id
- st_r0,r3,40 ## slot 3 = code id
-
- ld_r1,r3,32 ## r1 = args
- li_br &marshal_argv
- call ## r0 = argc, r2 = &prim_argv
-
- mov_r1,r0
- ld_r3,sp,40 ## r3 = code id
- epilogue_n4
- li_br &prim_dispatch
- b
-
-
-## ---- marshal_argv(r1 = args list) -> r0 = argc, r2 = &prim_argv ---
-## Walks the tagged-pair list, storing each car into prim_argv[i*8].
-## Overflow beyond PRIM_ARGV_SLOTS is an error (err_too_many_args).
-:marshal_argv
- prologue
- li_r2 &prim_argv ## r2 = cursor
- li_r3 %0 ## r3 = count
-
-:marshal_argv_loop
- li_r0 %7
- li_br &marshal_argv_done
- beq_r1,r0 ## list == nil → done
-
- li_r0 %32 ## PRIM_ARGV_SLOTS = 32
- li_br &err_too_many_args
- beq_r3,r0
-
- addi_r0,r1,neg2 ## r0 = raw pair ptr
- ld_r1,r0,0 ## r1 = car (we'll load cdr next)
- st_r1,r2,0 ## *cursor = car
- ld_r1,r0,8 ## r1 = cdr → next list
- addi_r2,r2,8 ## cursor += 8
- addi_r3,r3,1 ## count++
- li_br &marshal_argv_loop
- b
-
-:marshal_argv_done
- ## Publish the count so the GC root walker only marks the live
- ## prefix of prim_argv. Stale slots beyond the current call would
- ## otherwise look like roots and pin freed objects across GCs.
- li_r0 &prim_argc
- st_r3,r0,0
- mov_r0,r3 ## r0 = count
- li_r2 &prim_argv ## r2 = buffer base
- epilogue
- ret
-
-
-## ---- prim_dispatch(r1=argc, r2=argv, r3=code_id) -------------------
-## Cascade dispatch — r3 is matched against each code id, and on
-## match we B to the primitive body. Bodies are leaf: r1/r2 are
-## already set up, and RET returns to apply's caller (since apply
-## already ran EPILOGUE_N4 before branching here).
-:prim_dispatch
- li_br &prim_add
- beqz_r3
- li_r0 %1
- li_br &prim_sub
- beq_r3,r0
- li_r0 %2
- li_br &prim_mul
- beq_r3,r0
- li_r0 %3
- li_br &prim_div
- beq_r3,r0
- li_r0 %4
- li_br &prim_mod
- beq_r3,r0
- li_r0 %5
- li_br &prim_numeq
- beq_r3,r0
- li_r0 %6
- li_br &prim_lt
- beq_r3,r0
- li_r0 %7
- li_br &prim_gt
- beq_r3,r0
- li_r0 %8
- li_br &prim_min
- beq_r3,r0
- li_r0 %9
- li_br &prim_max
- beq_r3,r0
- li_r0 %10
- li_br &prim_bitand
- beq_r3,r0
- li_r0 %11
- li_br &prim_bitor
- beq_r3,r0
- li_r0 %12
- li_br &prim_bitxor
- beq_r3,r0
- li_r0 %13
- li_br &prim_bitnot
- beq_r3,r0
- li_r0 %14
- li_br &prim_ashift
- beq_r3,r0
- li_r0 %15
- li_br &prim_numberp
- beq_r3,r0
- li_r0 %16
- li_br &prim_symbolp
- beq_r3,r0
- li_r0 %17
- li_br &prim_stringp
- beq_r3,r0
- li_r0 %18
- li_br &prim_vectorp
- beq_r3,r0
- li_r0 %19
- li_br &prim_procp
- beq_r3,r0
- li_r0 %20
- li_br &prim_eqp
- beq_r3,r0
- li_r0 %21
- li_br &prim_cons
- beq_r3,r0
- li_r0 %22
- li_br &prim_car
- beq_r3,r0
- li_r0 %23
- li_br &prim_cdr
- beq_r3,r0
- li_r0 %24
- li_br &prim_pairp
- beq_r3,r0
- li_r0 %25
- li_br &prim_nullp
- beq_r3,r0
- li_r0 %26
- li_br &prim_list
- beq_r3,r0
- li_r0 %27
- li_br &prim_append
- beq_r3,r0
- li_r0 %28
- li_br &prim_string_length
- beq_r3,r0
- li_r0 %29
- li_br &prim_string_ref
- beq_r3,r0
- li_r0 %30
- li_br &prim_substring
- beq_r3,r0
- li_r0 %31
- li_br &prim_string_append
- beq_r3,r0
- li_r0 %32
- li_br &prim_string_to_symbol
- beq_r3,r0
- li_r0 %33
- li_br &prim_symbol_to_string
- beq_r3,r0
- li_r0 %34
- li_br &prim_make_vector
- beq_r3,r0
- li_r0 %35
- li_br &prim_vector_ref
- beq_r3,r0
- li_r0 %36
- li_br &prim_vector_set
- beq_r3,r0
- li_r0 %37
- li_br &prim_vector_length
- beq_r3,r0
- li_r0 %38
- li_br &prim_display
- beq_r3,r0
- li_r0 %39
- li_br &prim_write
- beq_r3,r0
- li_r0 %40
- li_br &prim_newline
- beq_r3,r0
- li_r0 %41
- li_br &prim_format
- beq_r3,r0
- li_r0 %42
- li_br &prim_error
- beq_r3,r0
- li_r0 %43
- li_br &prim_read_file
- beq_r3,r0
- li_r0 %44
- li_br &prim_write_file
- beq_r3,r0
- li_r0 %45
- li_br &prim_apply
- beq_r3,r0
-
- li_br &err_bad_prim
- b
-
-
-## ---- Primitive bodies ----------------------------------------------
-## Convention: entry with r1=argc (raw int), r2=argv (ptr into the
-## prim_argv buffer of tagged values). Leaf code: may use r0-r3
-## freely; must not touch r4-r7 (still hold apply's caller's values
-## since EPILOGUE_N4 ran before this branch). End with RET — that
-## returns to apply's caller, because lr was restored by EPILOGUE_N4.
-
-## (+ ...) — variadic sum. Tagged fixnum (v<<3)|1: untag each, add
-## to decoded accumulator, retag at end.
-:prim_add
- li_r3 %0 ## acc = 0
- shli_r1,r1,3
- add_r1,r1,r2 ## r1 = argv_end
-:prim_add_loop
- li_br &prim_add_done
- beq_r2,r1
- ld_r0,r2,0
- sari_r0,r0,3
- add_r3,r3,r0
- addi_r2,r2,8
- li_br &prim_add_loop
- b
-:prim_add_done
- shli_r0,r3,3
- ori_r0,r0,1
- ret
-
-
-## (- x) negates; (- x y ...) folds subtraction from the left.
-:prim_sub
- li_r0 %1
- li_br &prim_sub_negate
- beq_r1,r0 ## argc == 1 → unary negate
-
- ld_r3,r2,0
- sari_r3,r3,3 ## r3 = decoded first
- addi_r2,r2,8
- addi_r1,r1,neg1
- shli_r1,r1,3
- add_r1,r1,r2 ## r1 = argv_end
-:prim_sub_loop
- li_br &prim_sub_done
- beq_r2,r1
- ld_r0,r2,0
- sari_r0,r0,3
- sub_r3,r3,r0
- addi_r2,r2,8
- li_br &prim_sub_loop
- b
-:prim_sub_done
- shli_r0,r3,3
- ori_r0,r0,1
- ret
-:prim_sub_negate
- ld_r3,r2,0
- sari_r3,r3,3
- li_r0 %0
- sub_r3,r0,r3
- shli_r0,r3,3
- ori_r0,r0,1
- ret
-
-
-## (* ...) — variadic product. Identity 1.
-:prim_mul
- li_r3 %1 ## acc = 1
- shli_r1,r1,3
- add_r1,r1,r2
-:prim_mul_loop
- li_br &prim_mul_done
- beq_r2,r1
- ld_r0,r2,0
- sari_r0,r0,3
- mul_r3,r3,r0
- addi_r2,r2,8
- li_br &prim_mul_loop
- b
-:prim_mul_done
- shli_r0,r3,3
- ori_r0,r0,1
- ret
-
-
-## (/ x y) — binary integer division (signed).
-:prim_div
- ld_r3,r2,0
- sari_r3,r3,3
- ld_r0,r2,8
- sari_r0,r0,3
- div_r3,r3,r0
- shli_r0,r3,3
- ori_r0,r0,1
- ret
-
-
-## (% x y) — binary signed remainder. Scheme calls this `modulo`
-## / `remainder`; we bind the primitive as `%` to stay fixnum-only
-## and avoid the sign-convention split.
-:prim_mod
- ld_r3,r2,0
- sari_r3,r3,3
- ld_r0,r2,8
- sari_r0,r0,3
- rem_r3,r3,r0
- shli_r0,r3,3
- ori_r0,r0,1
- ret
-
-
-## (= x y) — binary fixnum equality. Because tagged fixnums share
-## the same low-3-bit tag (001) and the payload is bit-identical
-## for equal values, we compare the tagged words directly.
-:prim_numeq
- ld_r3,r2,0
- ld_r0,r2,8
- li_br &prim_true
- beq_r3,r0
- li_r0 FALSE ## #f
- ret
-
-## Shared "return #t" tail used by comparison/predicate primitives.
-:prim_true
- li_r0 TRUE
- ret
-
-
-## (< x y). Tagged-fixnum order preserves decoded order (shift-by-3
-## is monotone for 61-bit signed values).
-:prim_lt
- ld_r3,r2,0
- ld_r0,r2,8
- li_br &prim_true
- blt_r3,r0
- li_r0 %23
- ret
-
-
-## (> x y) ≡ (< y x).
-:prim_gt
- ld_r3,r2,0
- ld_r0,r2,8
- li_br &prim_true
- blt_r0,r3
- li_r0 %23
- ret
-
-
-## (<=, >=, zero?, negative?, positive?, abs) live in the Scheme
-## prelude now — see src/prelude.scm.
-
-
-## (min ...) — variadic; first arg is seed, each later arg replaces
-## acc if strictly smaller. Comparing tagged fixnums directly keeps
-## the body untagging-free.
-:prim_min
- ld_r3,r2,0 ## r3 = acc (tagged)
- addi_r2,r2,8
- addi_r1,r1,neg1
- shli_r1,r1,3
- add_r1,r1,r2
-:prim_min_loop
- li_br &prim_min_done
- beq_r2,r1
- ld_r0,r2,0
- li_br &prim_min_skip
- blt_r3,r0 ## acc < x → keep acc
- mov_r3,r0
-:prim_min_skip
- addi_r2,r2,8
- li_br &prim_min_loop
- b
-:prim_min_done
- mov_r0,r3
- ret
-
-
-## (max ...) — acc replaces on strictly greater.
-:prim_max
- ld_r3,r2,0
- addi_r2,r2,8
- addi_r1,r1,neg1
- shli_r1,r1,3
- add_r1,r1,r2
-:prim_max_loop
- li_br &prim_max_done
- beq_r2,r1
- ld_r0,r2,0
- li_br &prim_max_skip
- blt_r0,r3 ## x < acc → keep acc
- mov_r3,r0
-:prim_max_skip
- addi_r2,r2,8
- li_br &prim_max_loop
- b
-:prim_max_done
- mov_r0,r3
- ret
-
-
-## (bit-and ...) — variadic fold. Identity is all-ones; we seed the
-## decoded accumulator with -1 which ANDs as identity. Tag bits are
-## preserved through AND on tagged fixnums, but we still untag/retag
-## to match the shape of the other variadic bit ops.
-:prim_bitand
- li_r3 %0
- addi_r3,r3,neg1 ## r3 = -1
- shli_r1,r1,3
- add_r1,r1,r2
-:prim_bitand_loop
- li_br &prim_bitand_done
- beq_r2,r1
- ld_r0,r2,0
- sari_r0,r0,3
- and_r3,r3,r0
- addi_r2,r2,8
- li_br &prim_bitand_loop
- b
-:prim_bitand_done
- shli_r0,r3,3
- ori_r0,r0,1
- ret
-
-
-## (bit-or ...) — identity 0.
-:prim_bitor
- li_r3 %0
- shli_r1,r1,3
- add_r1,r1,r2
-:prim_bitor_loop
- li_br &prim_bitor_done
- beq_r2,r1
- ld_r0,r2,0
- sari_r0,r0,3
- or_r3,r3,r0
- addi_r2,r2,8
- li_br &prim_bitor_loop
- b
-:prim_bitor_done
- shli_r0,r3,3
- ori_r0,r0,1
- ret
-
-
-## (bit-xor ...) — identity 0.
-:prim_bitxor
- li_r3 %0
- shli_r1,r1,3
- add_r1,r1,r2
-:prim_bitxor_loop
- li_br &prim_bitxor_done
- beq_r2,r1
- ld_r0,r2,0
- sari_r0,r0,3
- xor_r3,r3,r0
- addi_r2,r2,8
- li_br &prim_bitxor_loop
- b
-:prim_bitxor_done
- shli_r0,r3,3
- ori_r0,r0,1
- ret
-
-
-## (bit-not x) — ~x = -1 - x.
-:prim_bitnot
- ld_r3,r2,0
- sari_r3,r3,3
- li_r0 %0
- addi_r0,r0,neg1 ## r0 = -1
- sub_r3,r0,r3 ## r3 = -1 - r3
- shli_r0,r3,3
- ori_r0,r0,1
- ret
-
-
-## (arithmetic-shift n k) — k>=0 left shift, k<0 arithmetic right.
-:prim_ashift
- ld_r3,r2,0
- sari_r3,r3,3 ## r3 = n (decoded)
- ld_r0,r2,8
- sari_r0,r0,3 ## r0 = k (decoded, signed)
- li_br &prim_ashift_neg
- bltz_r0 ## k < 0 → right shift by -k
- shl_r3,r3,r0
- li_br &prim_ashift_done
- b
-:prim_ashift_neg
- li_r1 %0
- sub_r0,r1,r0 ## r0 = -k
- sar_r3,r3,r0
-:prim_ashift_done
- shli_r0,r3,3
- ori_r0,r0,1
- ret
-
-
-## (number? x) — true iff tag is 1 (fixnum).
-:prim_numberp
- ld_r3,r2,0
- andi_r3,r3,7
- li_r0 TAG_FIXNUM
- li_br &prim_true
- beq_r3,r0
- li_r0 FALSE
- ret
-
-
-## (symbol? x) — tag is 5.
-:prim_symbolp
- ld_r3,r2,0
- andi_r3,r3,7
- li_r0 TAG_SYMBOL
- li_br &prim_true
- beq_r3,r0
- li_r0 FALSE
- ret
-
-
-## (string? x) — tag is 4.
-:prim_stringp
- ld_r3,r2,0
- andi_r3,r3,7
- li_r0 TAG_STRING
- li_br &prim_true
- beq_r3,r0
- li_r0 FALSE
- ret
-
-
-## (vector? x) — tag is 3.
-:prim_vectorp
- ld_r3,r2,0
- andi_r3,r3,7
- li_r0 TAG_VECTOR
- li_br &prim_true
- beq_r3,r0
- li_r0 FALSE
- ret
-
-
-## (procedure? x) — tag 6 AND header type ∈ {4,5,6}. Any of the three
-## heads (closure, prim-fixed, prim-variadic) count; the only other
-## tag-6 value shape (if we add one later) would need explicit
-## enumeration here.
-:prim_procp
- ld_r3,r2,0
- mov_r0,r3
- andi_r0,r0,7
- li_r1 TAG_PROC
- li_br &prim_procp_false
- bne_r0,r1 ## tag != 6 → #f
-
- addi_r3,r3,neg6 ## strip tag
- lb_r0,r3,7 ## r0 = type byte
- li_r1 TYPE_CLOSURE
- li_br &prim_true
- beq_r0,r1
- li_r1 TYPE_PRIM_FIXED
- li_br &prim_true
- beq_r0,r1
- li_r1 TYPE_PRIM_VARIADIC
- li_br &prim_true
- beq_r0,r1
-:prim_procp_false
- li_r0 FALSE
- ret
-
-
-## (eq? x y) — pointer/bit identity. Fixnums, interned symbols, and
-## singletons all compare correctly this way; pairs/strings/vectors/
-## closures compare by heap address (distinct allocations are #f).
-:prim_eqp
- ld_r3,r2,0
- ld_r0,r2,8
- li_br &prim_true
- beq_r3,r0
- li_r0 FALSE
- ret
-
-
-## ---- Step-10d list-core primitives ---------------------------------
-## (cons a d) — wraps the internal `cons` helper which calls alloc;
-## need PROLOGUE to save lr across the call.
-:prim_cons
- prologue
- ld_r1,r2,0 ## r1 = car arg
- ld_r2,r2,8 ## r2 = cdr arg (uses old r2 base)
- li_br &cons
- call
- epilogue
- ret
-
-
-## (car p) — unsafe (no tag check). Inlined: strip 0b010 tag, load +0.
-:prim_car
- ld_r1,r2,0
- addi_r1,r1,neg2
- ld_r0,r1,0
- ret
-
-
-## (cdr p) — symmetric.
-:prim_cdr
- ld_r1,r2,0
- addi_r1,r1,neg2
- ld_r0,r1,8
- ret
-
-
-## (pair? x) — tag is 2.
-:prim_pairp
- ld_r3,r2,0
- andi_r3,r3,7
- li_r0 TAG_PAIR
- li_br &prim_true
- beq_r3,r0
- li_r0 %23
- ret
-
-
-## (null? x) — equals nil singleton (0x07).
-:prim_nullp
- ld_r3,r2,0
- li_r0 NIL
- li_br &prim_true
- beq_r3,r0
- li_r0 %23
- ret
-
-
-## (list ...) — variadic; build (a b c ...) by walking argv from the
-## tail and consing onto an accumulator (initially nil). Walking from
-## the tail avoids needing a reverse pass.
-:prim_list
- prologue_n3
- mov_r3,sp
- li_r0 NIL ## nil
- st_r0,sp,24 ## slot1 = accumulator
- st_r2,sp,40 ## slot3 = base
- shli_r1,r1,3 ## r1 = argc * 8
- add_r1,r1,r2 ## r1 = end ptr (one past last)
- st_r1,sp,32 ## slot2 = cursor
-:prim_list_loop
- mov_r3,sp
- ld_r0,sp,32 ## cursor
- ld_r1,sp,40 ## base
- li_br &prim_list_done
- beq_r0,r1
- addi_r0,r0,neg8 ## cursor -= 8
- st_r0,sp,32
- ld_r1,r0,0 ## r1 = arg
- ld_r2,sp,24 ## r2 = accumulator
- li_br &cons
- call
- st_r0,sp,24 ## accumulator = new pair
- li_br &prim_list_loop
- b
-:prim_list_done
- ld_r0,sp,24
- epilogue_n3
- ret
-
-
-## (length, list?) live in the Scheme prelude now — see
-## src/prelude.scm.
-
-
-## append_one(r1=xs, r2=ys) -> r0 = xs ++ ys. Copies xs's spine; ys
-## is shared by reference. Recursive — bounded by xs's length.
-:append_one
- prologue_n2
- li_r0 %7
- li_br &append_one_base
- beq_r1,r0
- st_r1,sp,24 ## save xs
- st_r2,sp,32 ## save ys
- addi_r0,r1,neg2
- ld_r1,r0,8 ## r1 = cdr(xs)
- li_br &append_one
- call ## r0 = appended tail
- mov_r2,r0
- ld_r0,sp,24
- addi_r0,r0,neg2
- ld_r1,r0,0 ## r1 = car(xs)
- li_br &cons
- call
- epilogue_n2
- ret
-:append_one_base
- mov_r0,r2
- epilogue_n2
- ret
-
-
-## (append ...) — variadic; the last argument is shared (not copied),
-## all earlier arguments have their spines copied. Right-to-left fold.
-:prim_append
- prologue_n3
- li_br &prim_append_zero
- beqz_r1
- mov_r3,sp
- st_r2,sp,24 ## slot1 = argv base
- addi_r1,r1,neg1 ## r1 = argc-1
- shli_r1,r1,3 ## r1 = (argc-1)*8
- add_r1,r1,r2 ## r1 = &argv[argc-1]
- st_r1,sp,40 ## slot3 = cursor
- ld_r0,r1,0 ## r0 = last arg
- st_r0,sp,32 ## slot2 = result
-:prim_append_loop
- mov_r3,sp
- ld_r0,sp,40 ## cursor
- ld_r1,sp,24 ## base
- li_br &prim_append_done
- beq_r0,r1
- addi_r0,r0,neg8
- st_r0,sp,40
- ld_r1,r0,0 ## r1 = arg at cursor
- ld_r2,sp,32 ## r2 = result
- li_br &append_one
- call
- st_r0,sp,32
- li_br &prim_append_loop
- b
-:prim_append_done
- ld_r0,sp,32
- epilogue_n3
- ret
-:prim_append_zero
- li_r0 NIL
- epilogue_n3
- ret
-
-
-## (reverse, assoc, member) live in the Scheme prelude now — see
-## src/prelude.scm.
-
-
-## ---- (string-length s) — fixnum length out of header ---------------
-:prim_string_length
- ld_r1,r2,0
- addi_r1,r1,neg4 ## raw header
- ld_r0,r1,0
- shli_r0,r0,16
- shri_r0,r0,16 ## low 48b = length
- shli_r0,r0,3
- ori_r0,r0,1 ## fixnum encode
- ret
-
-
-## ---- (string-ref s i) — zero-extended byte as fixnum ---------------
-:prim_string_ref
- ld_r1,r2,0 ## tagged string
- ld_r0,r2,8 ## tagged idx
- sari_r0,r0,3 ## raw idx
- addi_r1,r1,neg4 ## raw header
- addi_r1,r1,8 ## payload base
- add_r1,r1,r0 ## r1 = payload + idx
- lb_r0,r1,0
- shli_r0,r0,3
- ori_r0,r0,1
- ret
-
-
-## ---- (substring s start end) — copies s[start:end] -----------------
-:prim_substring
- prologue_n3
- st_r6,sp,24
- st_r7,sp,32
-
- ld_r1,r2,0 ## tagged string
- ld_r0,r2,8 ## tagged start
- ld_r3,r2,16 ## tagged end
- sari_r0,r0,3 ## raw start
- sari_r3,r3,3 ## raw end
-
- addi_r1,r1,neg4 ## raw header
- addi_r1,r1,8 ## payload base
- sub_r3,r3,r0 ## r3 = end - start (save len before r0 clobber)
- mov_r7,r3 ## r7 = len
- mov_r2,r0 ## r2 = start
- add_r6,r1,r2 ## src = payload + start
-
- mov_r1,r6
- mov_r2,r7
- li_br &make_string
- call
-
- ld_r6,sp,24
- ld_r7,sp,32
- epilogue_n3
- ret
-
-
-## ---- (string-append ...) — variadic concat, two-pass ---------------
-## Pass 1 walks each arg's header to total length; pass 2 alloc_string
-## then byte_copy each payload. argv is always &prim_argv so we don't
-## need to spill the r2 it came in on.
-:prim_string_append
- prologue_n4
- st_r6,sp,24 ## save r6 (cursor/total reuse)
- st_r7,sp,32 ## save r7 (i)
- st_r1,sp,40 ## slot3 = argc
-
- li_r6 %0 ## r6 = total length
- li_r7 %0 ## r7 = i
-:psa_lp1
- mov_r3,sp
- ld_r0,r3,40 ## argc
- li_br &psa_done1
- beq_r7,r0
-
- li_r0 &prim_argv
- shli_r3,r7,3
- add_r0,r0,r3
- ld_r0,r0,0 ## tagged str
- addi_r0,r0,neg4
- ld_r0,r0,0 ## header
- shli_r0,r0,16
- shri_r0,r0,16 ## len
- add_r6,r6,r0
- addi_r7,r7,1
- li_br &psa_lp1
- b
-
-:psa_done1
- mov_r1,r6 ## total len
- li_br &alloc_string
- call ## r0 = tagged result
- mov_r3,sp
- st_r0,r3,48 ## slot4 = tagged result
-
- ## r6 = payload cursor
- addi_r6,r0,neg4
- addi_r6,r6,8
- li_r7 %0 ## i = 0
-
-:psa_lp2
- mov_r3,sp
- ld_r0,r3,40
- li_br &psa_done2
- beq_r7,r0
-
- li_r0 &prim_argv
- shli_r3,r7,3
- add_r0,r0,r3
- ld_r2,r0,0 ## tagged str
- addi_r2,r2,neg4 ## raw header
- ld_r3,r2,0
- shli_r3,r3,32
- shri_r3,r3,32 ## len
- addi_r2,r2,8 ## src payload
- mov_r1,r6 ## dst
- li_br &byte_copy
- call ## r0 = dst end
- mov_r6,r0
- addi_r7,r7,1
- li_br &psa_lp2
- b
-
-:psa_done2
- mov_r3,sp
- ld_r0,r3,48
- ld_r6,sp,24
- ld_r7,sp,32
- epilogue_n4
- ret
-
-
-## ---- (string->symbol s) — intern the string's bytes ----------------
-:prim_string_to_symbol
- prologue
- ld_r0,r2,0
- addi_r0,r0,neg4
- ld_r2,r0,0
- shli_r2,r2,16
- shri_r2,r2,16 ## len
- addi_r1,r0,8 ## payload
- li_br &intern
- call
- epilogue
- ret
-
-
-## ---- (symbol->string sym) — copy sym name into a fresh string -----
-:prim_symbol_to_string
- prologue
- ld_r0,r2,0
- addi_r0,r0,neg5 ## raw sym ptr
- ld_r2,r0,0
- shli_r2,r2,16
- shri_r2,r2,16 ## len
- addi_r1,r0,8 ## src payload
- li_br &make_string
- call
- epilogue
- ret
-
-
-## ---- (make-vector n init) — n-slot vector filled with init --------
-:prim_make_vector
- prologue
- ld_r1,r2,0 ## tagged n
- sari_r1,r1,3 ## raw n
- ld_r2,r2,8 ## init (tagged)
- li_br &make_vector
- call
- epilogue
- ret
-
-
-## ---- (vector-ref v i) — unsafe slot read ---------------------------
-:prim_vector_ref
- ld_r1,r2,0
- ld_r3,r2,8
- sari_r3,r3,3 ## raw idx
- addi_r1,r1,neg3 ## raw vec
- addi_r1,r1,8 ## payload base
- shli_r3,r3,3 ## idx*8
- mov_r2,r3 ## stage in r2 (no ADD_R1_R1_R3)
- add_r1,r1,r2
- ld_r0,r1,0
- ret
-
-
-## ---- (vector-set! v i val) -> unspec --------------------------------
-:prim_vector_set
- ld_r1,r2,0
- ld_r3,r2,8
- ld_r0,r2,16
- sari_r3,r3,3
- addi_r1,r1,neg3
- addi_r1,r1,8
- shli_r3,r3,3
- mov_r2,r3 ## stage idx*8 in r2
- add_r1,r1,r2
- st_r0,r1,0
- li_r0 UNSPEC
- ret
-
-
-## ---- (vector-length v) — fixnum slot count -------------------------
-:prim_vector_length
- ld_r1,r2,0
- addi_r1,r1,neg3
- ld_r0,r1,0
- shli_r0,r0,16
- shri_r0,r0,16
- shli_r0,r0,3
- ori_r0,r0,1
- ret
-
-
-## (vector->list, list->vector) live in the Scheme prelude now —
-## see src/prelude.scm.
-
-
-## ---- (display x) — runtime printer (unspec result) ------------------
-:prim_display
- prologue
- ld_r1,r2,0
- li_br &display
- call
- li_r0 UNSPEC
- epilogue
- ret
-
-
-## ---- (write x) — quoting printer ------------------------------------
-:prim_write
- prologue
- ld_r1,r2,0
- li_br &write
- call
- li_r0 UNSPEC
- epilogue
- ret
-
-
-## ---- (newline) — emit '\n' -----------------------------------------
-:prim_newline
- prologue
- li_r1 %10
- li_br &putc
- call
- li_r0 UNSPEC
- epilogue
- ret
-
-
-## ---- (format fmt args...) — minimal printf-ish ---------------------
-## Supported directives: ~a (display), ~s (write), ~d (decimal fixnum),
-## ~% (newline), ~~ (literal '~'). Unknown directives consume their char
-## silently. argv is &prim_argv; arg_index counts past the fmt string.
-:prim_format
- prologue_n4
- st_r6,sp,24
- st_r7,sp,32
-
- mov_r3,sp
- st_r1,r3,40 ## slot3 = argc
- li_r0 %1
- st_r0,r3,48 ## slot4 = arg_index (skip fmt)
-
- ld_r0,r2,0 ## tagged fmt string
- addi_r0,r0,neg4
- ld_r7,r0,0
- shli_r7,r7,16
- shri_r7,r7,16 ## r7 = len
- addi_r6,r0,8 ## r6 = cursor
-
-:pf_loop
- li_br &pf_done
- beqz_r7
-
- lb_r0,r6,0
- li_r1 %126 ## '~'
- li_br &pf_directive
- beq_r0,r1
-
- mov_r1,r0
- li_br &putc
- call
- addi_r6,r6,1
- addi_r7,r7,neg1
- li_br &pf_loop
- b
-
-:pf_directive
- addi_r6,r6,1
- addi_r7,r7,neg1
- li_br &pf_done
- beqz_r7
-
- lb_r0,r6,0
- addi_r6,r6,1
- addi_r7,r7,neg1
-
- li_r1 %37 ## '%'
- li_br &pf_newline
- beq_r0,r1
-
- li_r1 %126 ## '~'
- li_br &pf_emit_tilde
- beq_r0,r1
-
- ## a/s/d — fetch arg
- li_r1 &prim_argv
- mov_r3,sp
- ld_r2,r3,48
- shli_r2,r2,3
- add_r1,r1,r2
- ld_r1,r1,0 ## r1 = tagged arg
- ld_r2,r3,48
- addi_r2,r2,1
- st_r2,r3,48
-
- li_r2 %97 ## 'a'
- li_br &pf_dir_a
- beq_r0,r2
- li_r2 %115 ## 's'
- li_br &pf_dir_s
- beq_r0,r2
- li_r2 %100 ## 'd'
- li_br &pf_dir_d
- beq_r0,r2
-
- li_br &pf_loop
- b
-
-:pf_dir_a
- li_br &display
- call
- li_br &pf_loop
- b
-
-:pf_dir_s
- li_br &write
- call
- li_br &pf_loop
- b
-
-:pf_dir_d
- sari_r1,r1,3
- li_r2 %1 ## fd = 1 (stdout)
- li_br &display_uint
- call
- li_br &pf_loop
- b
-
-:pf_newline
- li_r1 %10
- li_br &putc
- call
- li_br &pf_loop
- b
-
-:pf_emit_tilde
- li_r1 %126
- li_br &putc
- call
- li_br &pf_loop
- b
-
-:pf_done
- li_r0 UNSPEC
- ld_r6,sp,24
- ld_r7,sp,32
- epilogue_n4
- ret
-
-
-## ---- (error msg) — print and exit(1) -------------------------------
-:prim_error
- ld_r0,r2,0
- addi_r0,r0,neg4
- ld_r2,r0,0
- shli_r2,r2,16
- shri_r2,r2,16
- addi_r1,r0,8
- li_br &error
- b
-
-
-## ---- (read-file path) — slurp into a fresh string ------------------
-## Uses io_buf as scratch (512B); make_string copies into the heap.
-:prim_read_file
- prologue_n3
- st_r6,sp,24
- st_r7,sp,32
-
- ld_r1,r2,0
- li_br &string_to_c_path
- call ## r0 = &path_buf
-
- mov_r2,r0
- li_r0 sys_openat
- li_r1 %-100
- li_r3 %0
- li_r4 %0
- syscall ## r0 = fd or err
-
- li_br &err_open
- bltz_r0
-
- mov_r6,r0 ## r6 = fd
-
- mov_r1,r6
- li_r2 &io_buf
- li_r3 %512
- li_br &read_file_all
- call ## r0 = bytes_read
- mov_r7,r0
-
- li_r0 sys_close
- mov_r1,r6
- syscall
-
- li_r1 &io_buf
- mov_r2,r7
- li_br &make_string
- call
-
- ld_r6,sp,24
- ld_r7,sp,32
- epilogue_n3
- ret
-
-
-## ---- (write-file path data) — overwrite/truncate ------------------
-:prim_write_file
- prologue_n3
- st_r6,sp,24
- st_r7,sp,32
-
- ld_r7,r2,8 ## r7 = tagged data string
-
- ld_r1,r2,0 ## tagged path
- li_br &string_to_c_path
- call ## r0 = &path_buf
-
- mov_r2,r0
- li_r0 sys_openat
- li_r1 %-100
- li_r3 %577 ## O_WRONLY|O_CREAT|O_TRUNC
- li_r4 %420 ## mode 0644
- syscall ## r0 = fd
-
- li_br &err_open
- bltz_r0
-
- mov_r6,r0 ## r6 = fd
-
- mov_r1,r7
- addi_r1,r1,neg4 ## raw header
- ld_r3,r1,0
- shli_r3,r3,32
- shri_r3,r3,32 ## len
- addi_r2,r1,8 ## payload
- mov_r1,r6 ## fd
- li_br &write_file_all
- call
-
- li_r0 sys_close
- mov_r1,r6
- syscall
-
- li_r0 UNSPEC
- ld_r6,sp,24
- ld_r7,sp,32
- epilogue_n3
- ret
-
-
-## (equal?) lives in the Scheme prelude now — see src/prelude.scm.
-
-
-## ---- (apply proc arg ... last-list) ---------------------------------
-## argc >= 2. Build args = (arg1 ... arg{n-1} ++ last-list), then call
-## the C-level apply with (callee, args).
-:prim_apply
- prologue_n4
- st_r6,sp,24
- st_r7,sp,32
-
- ld_r6,r2,0 ## r6 = callee
-
- ## acc = argv[argc-1] (the trailing list)
- addi_r3,r1,neg1
- shli_r0,r3,3
- li_r7 &prim_argv
- add_r7,r7,r0
- ld_r7,r7,0 ## r7 = acc
-
- addi_r3,r1,neg2 ## i = argc - 2
-:papply_lp
- li_r0 %1
- li_br &papply_done
- blt_r3,r0 ## i < 1 → done
-
- shli_r0,r3,3
- li_r1 &prim_argv
- add_r1,r1,r0
- ld_r1,r1,0 ## r1 = arg
-
- st_r3,sp,40 ## save i
-
- mov_r2,r7
- li_br &cons
- call ## r0 = (arg . acc)
- mov_r7,r0
-
- mov_r3,sp
- ld_r3,r3,40
- addi_r3,r3,neg1
- li_br &papply_lp
- b
-
-:papply_done
- mov_r1,r6
- mov_r2,r7
- li_br &apply
- call
-
- ld_r6,sp,24
- ld_r7,sp,32
- epilogue_n4
- ret
-
-
-
-## ---- eval(r1=expr, r2=env) -> r0 = value ---------------------------
-## Self-evaluating: nil/fixnum/string/closure. Symbols → lookup. Pairs
-## → eval_pair (special-form or application dispatch).
-:eval
- prologue_n3
- mov_r3,sp
- st_r1,sp,24 ## slot 1 = expr
- st_r2,sp,32 ## slot 2 = env
-
- li_r2 %7
- li_br &eval_self_slot1
- beq_r1,r2
-
- andi_r1,r1,7 ## r1 = tag
-
- li_r2 %1
- li_br &eval_self_slot1
- beq_r1,r2 ## fixnum
-
- li_r2 %5
- li_br &eval_sym
- beq_r1,r2
-
- li_r2 %2
- li_br &eval_pair
- beq_r1,r2
-
- ## Other tags (string, closure) self-evaluate.
- li_br &eval_self_slot1
- b
-
-:eval_self_slot1
- ld_r0,sp,24
- epilogue_n3
- ret
-
-:eval_sym
- ld_r1,sp,24
- ld_r2,sp,32
- li_br &lookup
- tail_n3
-
-:eval_pair
- ## Compound expression. Dispatch on car against cached sym_*
- ## pointers; otherwise treat as function application.
- ld_r1,sp,24
- li_br &car
- call ## r0 = callee-expr
-
- li_r1 &sym_quote
- ld_r1,r1,0
- li_br &eval_quote
- beq_r0,r1
-
- li_r1 &sym_if
- ld_r1,r1,0
- li_br &eval_if
- beq_r0,r1
-
- li_r1 &sym_begin
- ld_r1,r1,0
- li_br &eval_begin
- beq_r0,r1
-
- li_r1 &sym_lambda
- ld_r1,r1,0
- li_br &eval_lambda
- beq_r0,r1
-
- li_r1 &sym_define
- ld_r1,r1,0
- li_br &eval_define
- beq_r0,r1
-
- li_r1 &sym_set
- ld_r1,r1,0
- li_br &eval_set
- beq_r0,r1
-
- li_r1 &sym_let
- ld_r1,r1,0
- li_br &eval_let
- beq_r0,r1
-
- li_r1 &sym_letstar
- ld_r1,r1,0
- li_br &eval_letstar
- beq_r0,r1
-
- li_r1 &sym_letrec
- ld_r1,r1,0
- li_br &eval_letrec
- beq_r0,r1
-
- li_r1 &sym_cond
- ld_r1,r1,0
- li_br &eval_cond
- beq_r0,r1
-
- li_r1 &sym_quasiquote
- ld_r1,r1,0
- li_br &eval_quasiquote
- beq_r0,r1
-
- ## Application: callee = eval(callee-expr, env)
- mov_r1,r0
- ld_r2,sp,32
- li_br &eval
- call ## r0 = callee value
-
- st_r0,sp,40 ## slot 3 = callee
-
- ## args = eval_args(cdr(expr), env)
- ld_r1,sp,24
- li_br &cdr
- call
- mov_r1,r0
- ld_r2,sp,32
- li_br &eval_args
- call ## r0 = args list
-
- mov_r2,r0
- ld_r1,sp,40
- li_br &apply
- tail_n3 ## Scheme tail call: application
-
-
-## ---- eval_quote / eval_if / eval_begin -----------------------------
-## All run inside eval's PROLOGUE_N3 frame: slot 1 = expr, slot 2 =
-## env, slot 3 = per-form scratch.
-:eval_quote
- ld_r1,sp,24
- li_br &cdr
- call ## r0 = (x)
- mov_r1,r0
- li_br &car
- tail_n3 ## Scheme tail: quote result = datum
-
-:eval_if
- ## (if cond then else). Save (then else) tail into slot 3, eval
- ## cond, branch to the correct arm.
- ld_r1,sp,24
- li_br &cdr
- call ## r0 = (cond then else)
- mov_r1,r0
- li_br &cdr
- call ## r0 = (then else)
- st_r0,sp,40
-
- ld_r1,sp,24
- li_br &cdr
- call ## r0 = (cond then else)
- mov_r1,r0
- li_br &car
- call ## r0 = cond expr
- mov_r1,r0
- ld_r2,sp,32
- li_br &eval
- call ## r0 = cond value
-
- li_r1 FALSE
- li_br &eval_if_else
- beq_r0,r1
-
- ## Then branch: eval car(slot3)
- ld_r1,sp,40
- li_br &car
- call
- mov_r1,r0
- ld_r2,sp,32
- li_br &eval ## Scheme tail: then-branch
- tail_n3
-
-:eval_if_else
- ## Else branch: eval car(cdr(slot3))
- ld_r1,sp,40
- li_br &cdr
- call
- mov_r1,r0
- li_br &car
- call
- mov_r1,r0
- ld_r2,sp,32
- li_br &eval ## Scheme tail: else-branch
- tail_n3
-
-:eval_begin
- ## (begin e1 e2 ... en). Slot 1 = cursor over the body list. The
- ## last form (en) is Scheme tail-position, so the loop peeks
- ## cdr(cursor): nil next → TAIL eval the current head; otherwise
- ## CALL eval and advance. Slot 3 bridges the saved next-cursor
- ## across the CALL eval in the non-tail branch.
- ld_r1,sp,24
- li_br &cdr
- call ## r0 = body list
- mov_r3,sp
- st_r0,sp,24 ## slot 1 := cursor
-
-:eval_begin_loop
- mov_r3,sp
- ld_r1,sp,24
- li_r2 %7
- li_br &eval_begin_empty
- beq_r1,r2 ## cursor == nil → empty begin → nil
-
- ## next = cdr(cursor)
- li_br &cdr
- call ## r0 = next
-
- li_r1 %7
- li_br &eval_begin_tail
- beq_r0,r1 ## next == nil → tail-eval last form
-
- ## Non-tail: stash next in slot 3, CALL eval(car(cursor), env),
- ## discard result, advance cursor := next.
- st_r0,sp,40 ## slot 3 := next
- ld_r1,sp,24
- li_br &car
- call
- mov_r1,r0
- ld_r2,sp,32
- li_br &eval
- call
-
- ld_r0,sp,40
- st_r0,sp,24 ## cursor := next
- li_br &eval_begin_loop
- b
-
-:eval_begin_tail
- ## Scheme tail: TAIL eval(car(cursor), env). Frame torn down.
- ld_r1,sp,24
- li_br &car
- call ## r0 = head expr
- mov_r1,r0
- ld_r2,sp,32
- li_br &eval
- tail_n3
-
-:eval_begin_empty
- li_r0 %7
- epilogue_n3
- ret
-
-:eval_lambda
- ## (lambda params body1 body2 ...). Collect body-list, rewrite any
- ## leading (define …) forms into a letrec (step 12e), then stash
- ## the resulting single expression as the closure's body.
- ld_r1,sp,24
- li_br &cdr
- call ## r0 = (params body1 body2 ...)
- st_r0,sp,40 ## slot 3 = after-head
-
- mov_r1,r0
- li_br &car
- call ## r0 = params
- st_r0,sp,24 ## slot 1 := params
-
- ld_r1,sp,40
- li_br &cdr
- call ## r0 = body list
- mov_r1,r0
- li_br &rewrite_lambda_body
- call ## r0 = rewritten single expr
-
- mov_r2,r0
- ld_r1,sp,24 ## r1 = params
- ld_r3,sp,32 ## r3 = env
- li_br &make_closure
- tail_n3
-
-:eval_define
- ## (define sym val-expr). gset-binds the evaluated val into the
- ## global alist, returns nil.
- ld_r1,sp,24
- li_br &cdr
- call ## r0 = (sym val-expr)
- st_r0,sp,40 ## slot 3 := (sym val-expr)
-
- mov_r1,r0
- li_br &car
- call ## r0 = sym
- st_r0,sp,24 ## slot 1 := sym
-
- ld_r1,sp,40
- li_br &cdr
- call ## r0 = (val-expr)
- mov_r1,r0
- li_br &car
- call ## r0 = val-expr
- mov_r1,r0
- ld_r2,sp,32
- li_br &eval
- call ## r0 = val
-
- mov_r2,r0
- ld_r1,sp,24
- li_br &gset
- call
-
- li_r0 %7
- epilogue_n3
- ret
-
-
-## ---- eval_set (step 12a) -------------------------------------------
-## (set! sym val-expr). Evaluates val-expr, then mutates the first
-## binding of sym found in local→global search order.
-:eval_set
- ld_r1,sp,24
- li_br &cdr
- call ## r0 = (sym val-expr)
- mov_r1,r0
- li_br &car
- call ## r0 = sym
- st_r0,sp,40 ## slot 3 = sym
-
- ld_r1,sp,24
- li_br &cdr
- call
- mov_r1,r0
- li_br &cdr
- call
- mov_r1,r0
- li_br &car
- call ## r0 = val-expr
- mov_r1,r0
- ld_r2,sp,32
- li_br &eval
- call ## r0 = val
-
- mov_r3,r0
- ld_r1,sp,40
- ld_r2,sp,32
- li_br &set_binding
- tail_n3
-
-
-## ---- set_binding(r1=sym, r2=env, r3=val) -> r0 = unspec -------------
-## Looks up the first (sym . v) cell in env (local alist first, then
-## global) and mutates the cdr to val. Errors out on unbound sym.
-:set_binding
- prologue_n3
- st_r3,sp,40 ## slot 3 = val
- st_r1,sp,24 ## slot 1 = sym (for global fallback)
-
- li_br &lookup_alist
- call ## r0 = (sym . v) pair or nil
- li_r1 NIL
- li_br &set_binding_global
- beq_r0,r1
-
- ld_r1,sp,40
- addi_r0,r0,neg2 ## raw pair ptr
- st_r1,r0,8 ## mutate cdr
- li_r0 UNSPEC
- epilogue_n3
- ret
-
-:set_binding_global
- ld_r1,sp,24
- li_r2 &global_env_cell
- ld_r2,r2,0
- li_br &lookup_alist
- call
- li_r1 NIL
- li_br &err_unbound
- beq_r0,r1
-
- ld_r1,sp,40
- addi_r0,r0,neg2
- st_r1,r0,8
- li_r0 UNSPEC
- epilogue_n3
- ret
-
-
-## ---- eval_let (step 12b) -------------------------------------------
-## (let ((n1 e1) …) body…). Each RHS is evaluated in the *outer* env;
-## then body evaluates in env extended with all new bindings.
-:eval_let
- ld_r1,sp,24
- li_br &cdr
- call ## r0 = (bindings body…)
- st_r0,sp,40 ## slot 3 = after-head
-
- mov_r1,r0
- li_br &car
- call ## r0 = bindings
- mov_r1,r0
- ld_r2,sp,32
- li_br &build_let_env
- call ## r0 = new env
- st_r0,sp,32 ## slot 2 = new env
-
- ld_r1,sp,40
- li_br &cdr
- call ## r0 = body list
- mov_r2,r0
- li_r1 &sym_begin
- ld_r1,r1,0
- li_br &cons
- call ## r0 = (begin . body)
- mov_r1,r0
- ld_r2,sp,32
- li_br &eval
- tail_n3
-
-
-## ---- eval_letstar --------------------------------------------------
-## (let* ((n1 e1) …) body…). Each RHS is evaluated in the env built
-## from previous bindings, then body evaluates in the final env.
-:eval_letstar
- ld_r1,sp,24
- li_br &cdr
- call
- st_r0,sp,40
-
- mov_r1,r0
- li_br &car
- call
- mov_r1,r0
- ld_r2,sp,32
- li_br &build_letstar_env
- call
- st_r0,sp,32
-
- ld_r1,sp,40
- li_br &cdr
- call
- mov_r2,r0
- li_r1 &sym_begin
- ld_r1,r1,0
- li_br &cons
- call
- mov_r1,r0
- ld_r2,sp,32
- li_br &eval
- tail_n3
-
-
-## ---- eval_letrec ---------------------------------------------------
-## (letrec ((n1 e1) …) body…). Pre-binds each name to UNSPEC, then
-## walks the binding list a second time evaluating each RHS in the
-## fully pre-bound env and mutating the binding cell.
-:eval_letrec
- ld_r1,sp,24
- li_br &cdr
- call
- st_r0,sp,40
-
- mov_r1,r0
- li_br &car
- call
- mov_r1,r0
- ld_r2,sp,32
- li_br &build_letrec_env
- call
- st_r0,sp,32
-
- ld_r1,sp,40
- li_br &cdr
- call
- mov_r2,r0
- li_r1 &sym_begin
- ld_r1,r1,0
- li_br &cons
- call
- mov_r1,r0
- ld_r2,sp,32
- li_br &eval
- tail_n3
-
-
-## ---- eval_cond (step 12c) ------------------------------------------
-## (cond (test body…) … (else body…)). Walks clauses in order, evaluates
-## each test; on truthy test or literal `else`, evaluates body as a begin.
-## Returns unspec if no clause fires.
-:eval_cond
- ld_r1,sp,24
- li_br &cdr
- call ## r0 = clauses
- st_r0,sp,24 ## slot 1 = cursor
-
-:eval_cond_loop
- ld_r1,sp,24
- li_r0 NIL
- li_br &eval_cond_done
- beq_r1,r0
-
- li_br &car
- call ## r0 = clause
- st_r0,sp,40 ## slot 3 = clause
-
- mov_r1,r0
- li_br &car
- call ## r0 = test
-
- li_r1 &sym_else
- ld_r1,r1,0
- li_br &eval_cond_body
- beq_r0,r1
-
- mov_r1,r0
- ld_r2,sp,32
- li_br &eval
- call ## r0 = test value
-
- li_r1 FALSE
- li_br &eval_cond_next
- beq_r0,r1
-
-:eval_cond_body
- ld_r1,sp,40
- li_br &cdr
- call ## r0 = body list
- mov_r2,r0
- li_r1 &sym_begin
- ld_r1,r1,0
- li_br &cons
- call
- mov_r1,r0
- ld_r2,sp,32
- li_br &eval
- tail_n3
-
-:eval_cond_next
- ld_r1,sp,24
- li_br &cdr
- call
- st_r0,sp,24
- li_br &eval_cond_loop
- b
-
-:eval_cond_done
- li_r0 UNSPEC
- epilogue_n3
- ret
-
-
-## ---- eval_quasiquote (step 12d) -------------------------------------
-## (quasiquote template). Delegates to quasi_expand which walks the
-## template recursively, handling `,x` (unquote) and `,@x`
-## (unquote-splicing) forms.
-:eval_quasiquote
- ld_r1,sp,24
- li_br &cdr
- call
- mov_r1,r0
- li_br &car
- call ## r0 = template
- mov_r1,r0
- ld_r2,sp,32
- li_br &quasi_expand
- tail_n3
-
-
-## ---- build_let_env(r1=bindings, r2=outer_env) -> r0 = new_env ------
-## Builds a fresh alist extending outer_env. Each RHS expression is
-## evaluated in the *outer* env — the let semantics.
-:build_let_env
- prologue_n4
- st_r1,sp,24 ## slot 1 = cursor
- st_r2,sp,32 ## slot 2 = outer env
- st_r2,sp,40 ## slot 3 = new env acc
-
-:ble_loop
- ld_r1,sp,24
- li_r0 NIL
- li_br &ble_done
- beq_r1,r0
-
- li_br &car
- call ## r0 = (name expr)
- st_r0,sp,48 ## slot 4 = (name expr)
-
- mov_r1,r0
- li_br &cdr
- call
- mov_r1,r0
- li_br &car
- call ## r0 = expr
-
- mov_r1,r0
- ld_r2,sp,32 ## outer env
- li_br &eval
- call ## r0 = val
-
- mov_r2,r0
- ld_r1,sp,48
- addi_r1,r1,neg2
- ld_r1,r1,0 ## r1 = name
- li_br &cons
- call ## r0 = (name . val)
-
- mov_r1,r0
- ld_r2,sp,40
- li_br &cons
- call ## r0 = extended env
- st_r0,sp,40
-
- ld_r1,sp,24
- li_br &cdr
- call
- st_r0,sp,24
- li_br &ble_loop
- b
-
-:ble_done
- ld_r0,sp,40
- epilogue_n4
- ret
-
-
-## ---- build_letstar_env(r1=bindings, r2=outer_env) -> r0 = new_env --
-## Like build_let_env, but each RHS is evaluated in the env accumulated
-## so far (not the fixed outer env).
-:build_letstar_env
- prologue_n4
- st_r1,sp,24
- st_r2,sp,40 ## slot 3 = current env
-
-:bls_loop
- ld_r1,sp,24
- li_r0 NIL
- li_br &bls_done
- beq_r1,r0
-
- li_br &car
- call
- st_r0,sp,48
-
- mov_r1,r0
- li_br &cdr
- call
- mov_r1,r0
- li_br &car
- call ## r0 = expr
-
- mov_r1,r0
- ld_r2,sp,40 ## current env
- li_br &eval
- call ## r0 = val
-
- mov_r2,r0
- ld_r1,sp,48
- addi_r1,r1,neg2
- ld_r1,r1,0
- li_br &cons
- call
-
- mov_r1,r0
- ld_r2,sp,40
- li_br &cons
- call
- st_r0,sp,40
-
- ld_r1,sp,24
- li_br &cdr
- call
- st_r0,sp,24
- li_br &bls_loop
- b
-
-:bls_done
- ld_r0,sp,40
- epilogue_n4
- ret
-
-
-## ---- build_letrec_env(r1=bindings, r2=outer_env) -> r0 = new_env --
-## Two-pass: pass 1 seeds each name with UNSPEC; pass 2 evaluates each
-## RHS in the fully pre-bound env and mutates the binding's cdr via
-## set_binding. Supports mutual recursion.
-:build_letrec_env
- prologue_n4
- st_r1,sp,24 ## slot 1 = cursor
- st_r1,sp,32 ## slot 2 = original bindings (for pass 2)
- st_r2,sp,40 ## slot 3 = new env
-
-:blr_pre_loop
- ld_r1,sp,24
- li_r0 NIL
- li_br &blr_pre_done
- beq_r1,r0
-
- li_br &car
- call ## r0 = (name expr)
- mov_r1,r0
- li_br &car
- call ## r0 = name
-
- mov_r1,r0
- li_r2 UNSPEC
- li_br &cons
- call ## r0 = (name . UNSPEC)
-
- mov_r1,r0
- ld_r2,sp,40
- li_br &cons
- call
- st_r0,sp,40
-
- ld_r1,sp,24
- li_br &cdr
- call
- st_r0,sp,24
- li_br &blr_pre_loop
- b
-
-:blr_pre_done
- ld_r1,sp,32
- st_r1,sp,24 ## reset cursor
-
-:blr_eval_loop
- ld_r1,sp,24
- li_r0 NIL
- li_br &blr_eval_done
- beq_r1,r0
-
- li_br &car
- call
- st_r0,sp,48 ## slot 4 = (name expr)
-
- mov_r1,r0
- li_br &cdr
- call
- mov_r1,r0
- li_br &car
- call ## r0 = expr
-
- mov_r1,r0
- ld_r2,sp,40
- li_br &eval
- call ## r0 = val
-
- mov_r3,r0
- ld_r1,sp,48
- addi_r1,r1,neg2
- ld_r1,r1,0 ## r1 = name
- ld_r2,sp,40
- li_br &set_binding
- call
-
- ld_r1,sp,24
- li_br &cdr
- call
- st_r0,sp,24
- li_br &blr_eval_loop
- b
-
-:blr_eval_done
- ld_r0,sp,40
- epilogue_n4
- ret
-
-
-## ---- rewrite_lambda_body(r1=body_list) -> r0 = single body expr ----
-## Collects leading (define name val) forms from a lambda body and
-## rewrites the body into (letrec ((n1 v1) …) body-tail…) if any are
-## present. Otherwise returns either the single body form or a
-## (begin . body) wrapper.
-:rewrite_lambda_body
- prologue_n4
- st_r1,sp,24 ## slot 1 = cursor
- li_r0 NIL
- st_r0,sp,32 ## slot 2 = reversed bindings acc
-
-:rlb_loop
- ld_r1,sp,24
- li_r0 NIL
- li_br &rlb_done
- beq_r1,r0
-
- li_br &car
- call ## r0 = body form
- mov_r1,r0
- andi_r0,r0,7
- li_r2 TAG_PAIR
- li_br &rlb_done
- bne_r0,r2
-
- st_r1,sp,40 ## slot 3 = form
- li_br &car
- call ## r0 = head
- li_r1 &sym_define
- ld_r1,r1,0
- li_br &rlb_done
- bne_r0,r1
-
- ld_r1,sp,40
- li_br &cdr
- call ## r0 = (name val)
-
- mov_r1,r0
- ld_r2,sp,32
- li_br &cons
- call
- st_r0,sp,32
-
- ld_r1,sp,24
- li_br &cdr
- call
- st_r0,sp,24
- li_br &rlb_loop
- b
-
-:rlb_done
- ld_r1,sp,32
- li_r0 NIL
- li_br &rlb_no_defs
- beq_r1,r0
-
- ld_r1,sp,32
- li_br &list_reverse
- call
- st_r0,sp,32
-
- ld_r1,sp,32
- ld_r2,sp,24
- li_br &cons
- call ## r0 = (bindings . body-tail)
- mov_r2,r0
- li_r1 &sym_letrec
- ld_r1,r1,0
- li_br &cons
- tail_n4
-
-:rlb_no_defs
- ld_r1,sp,24
- li_br &cdr
- call
- li_r1 NIL
- li_br &rlb_single
- beq_r0,r1
-
- ld_r2,sp,24
- li_r1 &sym_begin
- ld_r1,r1,0
- li_br &cons
- tail_n4
-
-:rlb_single
- ld_r1,sp,24
- li_br &car
- tail_n4
-
-
-## ---- list_reverse(r1=list) -> r0 = reversed list --------------------
-## Simple tail-consing reverse; used by rewrite_lambda_body to restore
-## source-order bindings.
-:list_reverse
- prologue_n2
- st_r1,sp,24
- li_r0 NIL
- st_r0,sp,32
-
-:lrv_loop
- ld_r1,sp,24
- li_r0 NIL
- li_br &lrv_done
- beq_r1,r0
-
- li_br &car
- call
- mov_r1,r0
- ld_r2,sp,32
- li_br &cons
- call
- st_r0,sp,32
-
- ld_r1,sp,24
- li_br &cdr
- call
- st_r0,sp,24
- li_br &lrv_loop
- b
-
-:lrv_done
- ld_r0,sp,32
- epilogue_n2
- ret
-
-
-## ---- quasi_expand(r1=template, r2=env) -> r0 = value ---------------
-## Top-level quasiquote walker. Non-pair templates self-evaluate. A pair
-## whose car is `unquote` evaluates the first element of its cdr.
-## Otherwise the pair is walked as a list via quasi_list.
-:quasi_expand
- prologue_n3
- st_r1,sp,24
- st_r2,sp,32
-
- mov_r0,r1
- andi_r0,r0,7
- li_r2 TAG_PAIR
- li_br &qe_pair
- beq_r0,r2
- ld_r0,sp,24
- epilogue_n3
- ret
-
-:qe_pair
- ld_r1,sp,24
- li_br &car
- call
- li_r1 &sym_unquote
- ld_r1,r1,0
- li_br &qe_unquote
- beq_r0,r1
-
- ld_r1,sp,24
- ld_r2,sp,32
- li_br &quasi_list
- tail_n3
-
-:qe_unquote
- ld_r1,sp,24
- li_br &cdr
- call
- mov_r1,r0
- li_br &car
- call
- mov_r1,r0
- ld_r2,sp,32
- li_br &eval
- tail_n3
-
-
-## ---- quasi_list(r1=list, r2=env) -> r0 = expanded list --------------
-## Recursively expands each element. If an element is (unquote-splicing X),
-## evaluates X and appends the resulting list into the output; else it
-## recursively calls quasi_expand on the element.
-:quasi_list
- prologue_n4
- st_r1,sp,24 ## slot 1 = cursor
- st_r2,sp,32 ## slot 2 = env
-
- li_r0 NIL
- li_br &ql_nil
- beq_r1,r0
-
- mov_r0,r1
- andi_r0,r0,7
- li_r2 TAG_PAIR
- li_br &ql_nonpair
- bne_r0,r2
-
- li_br &car
- call ## r0 = head
- st_r0,sp,40 ## slot 3 = head
-
- mov_r1,r0
- andi_r0,r0,7
- li_r2 TAG_PAIR
- li_br &ql_head_reg
- bne_r0,r2
-
- ld_r1,sp,40
- li_br &car
- call ## r0 = car(head)
- li_r1 &sym_unquote_splicing
- ld_r1,r1,0
- li_br &ql_splice
- beq_r0,r1
-
-:ql_head_reg
- ld_r1,sp,40
- ld_r2,sp,32
- li_br &quasi_expand
- call
- st_r0,sp,40 ## slot 3 = expanded head
-
- ld_r1,sp,24
- li_br &cdr
- call
- mov_r1,r0
- ld_r2,sp,32
- li_br &quasi_list
- call
-
- mov_r2,r0
- ld_r1,sp,40
- li_br &cons
- tail_n4
-
-:ql_splice
- ld_r1,sp,40
- li_br &cdr
- call
- mov_r1,r0
- li_br &car
- call ## r0 = X (the spliced expression)
- mov_r1,r0
- ld_r2,sp,32
- li_br &eval
- call ## r0 = evaluated list
- st_r0,sp,40
-
- ld_r1,sp,24
- li_br &cdr
- call
- mov_r1,r0
- ld_r2,sp,32
- li_br &quasi_list
- call
-
- mov_r2,r0
- ld_r1,sp,40
- li_br &append_one
- tail_n4
-
-:ql_nil
- li_r0 NIL
- epilogue_n4
- ret
-
-:ql_nonpair
- ld_r0,sp,24
- epilogue_n4
- ret
-
-
-## ---- Step-6 error landing pads --------------------------------------
-:err_unbound
- li_r1 &msg_unbound
- li_r2 %14 ## strlen("unbound symbol") == 14
- li_br &error
- b
-
-:err_arity
- li_r1 &msg_arity
- li_r2 %14 ## strlen("arity mismatch") == 14
- li_br &error
- b
-
-:err_not_callable
- li_r1 &msg_not_callable
- li_r2 %12 ## strlen("not callable") == 12
- li_br &error
- b
-
-:err_too_many_args
- li_r1 &msg_too_many_args
- li_r2 %21 ## strlen("primitive argc > 32") == 21
- li_br &error
- b
-
-:err_bad_prim
- li_r1 &msg_bad_prim
- li_r2 %20 ## strlen("unknown primitive id") == 20
- li_br &error
- b
-
-:err_path_too_long
- li_r1 &msg_path_too_long
- li_r2 %18
- li_br &error
- b
-
-
-## ---- Static strings -------------------------------------------------
-:msg_newline
-"
-"
-:msg_error_prefix "error: "
-:msg_oom "heap exhausted"
-:msg_intern_not_eq "intern: foo a != foo b (same name)"
-:msg_intern_collision "intern: foo == bar (distinct names collided)"
-:msg_reader_bad "reader: malformed input"
-:msg_at " at "
-:msg_colon ":"
-:msg_unbound "unbound symbol"
-:msg_arity "arity mismatch"
-:msg_not_callable "not callable"
-:msg_usage "usage: lisp <file.scm>"
-:msg_open_fail "failed to open source file"
-:msg_src_too_big "source file too large"
-:msg_too_many_args "primitive argc > 32"
-:msg_bad_prim "unknown primitive id"
-:msg_path_too_long "file path too long"
-
-## Interned name samples used by the self-test.
-:str_foo "foo"
-:str_bar "bar"
-
-## Special-form name strings. Lengths are baked into _start's intern
-## calls (update both sides together).
-:str_quote "quote"
-:str_if "if"
-:str_begin "begin"
-:str_lambda "lambda"
-:str_define "define"
-:str_quasiquote "quasiquote"
-:str_unquote "unquote"
-:str_unquote_splicing "unquote-splicing"
-:str_set "set!"
-:str_let "let"
-:str_letstar "let*"
-:str_letrec "letrec"
-:str_cond "cond"
-:str_else "else"
-
-## Primitive name strings (step 10b). The registration table below
-## holds (ptr, len, code_id, type, arity) for each. _start walks the
-## table, interns each name, and binds a freshly-built primitive into
-## global_env.
-:str_prim_plus "+"
-:str_prim_minus "-"
-:str_prim_mul "*"
-:str_prim_div "/"
-:str_prim_mod "%"
-:str_prim_numeq "="
-:str_prim_lt "<"
-:str_prim_gt ">"
-:str_prim_min "min"
-:str_prim_max "max"
-:str_prim_bitand "bit-and"
-:str_prim_bitor "bit-or"
-:str_prim_bitxor "bit-xor"
-:str_prim_bitnot "bit-not"
-:str_prim_ashift "arithmetic-shift"
-:str_prim_numberp "number?"
-:str_prim_symbolp "symbol?"
-:str_prim_stringp "string?"
-:str_prim_vectorp "vector?"
-:str_prim_procp "procedure?"
-:str_prim_eqp "eq?"
-
-## Step-10d list-core names.
-:str_prim_cons "cons"
-:str_prim_car "car"
-:str_prim_cdr "cdr"
-:str_prim_pairp "pair?"
-:str_prim_nullp "null?"
-:str_prim_list "list"
-:str_prim_append "append"
-
-## Step-10e string / vector / I/O / equal? / apply names.
-:str_prim_string_length "string-length"
-:str_prim_string_ref "string-ref"
-:str_prim_substring "substring"
-:str_prim_string_append "string-append"
-:str_prim_string_to_symbol "string->symbol"
-:str_prim_symbol_to_string "symbol->string"
-:str_prim_make_vector "make-vector"
-:str_prim_vector_ref "vector-ref"
-:str_prim_vector_set "vector-set!"
-:str_prim_vector_length "vector-length"
-:str_prim_display "display"
-:str_prim_write "write"
-:str_prim_newline "newline"
-:str_prim_format "format"
-:str_prim_error "error"
-:str_prim_read_file "read-file"
-:str_prim_write_file "write-file"
-:str_prim_apply "apply"
-
-
-## Registration table. 40-byte records: ptr(8) + len(8) + code_id(8) +
-## type(8) + arity(8). End-sentinel = zero name pointer. _start iterates
-## with ADDI +40.
-:prim_table
-## Primitives promoted to the Scheme prelude (src/prelude.scm) and no
-## longer registered here: <=, >=, zero?, negative?, positive?, abs,
-## length, list?, reverse, assoc, member, vector->list, list->vector,
-## equal?. Code ids are contiguous 0..45 after that removal.
-## (+ ...) variadic — code 0
-&str_prim_plus %0
-%1 %0
-%0 %0
-%6 %0
-%0 %0
-## (- x ...) variadic — code 1 (unary-negate branch in body)
-&str_prim_minus %0
-%1 %0
-%1 %0
-%6 %0
-%0 %0
-## (* ...) variadic — code 2
-&str_prim_mul %0
-%1 %0
-%2 %0
-%6 %0
-%0 %0
-## (/ x y) fixed 2 — code 3
-&str_prim_div %0
-%1 %0
-%3 %0
-%5 %0
-%2 %0
-## (% x y) fixed 2 — code 4
-&str_prim_mod %0
-%1 %0
-%4 %0
-%5 %0
-%2 %0
-## (= x y) fixed 2 — code 5
-&str_prim_numeq %0
-%1 %0
-%5 %0
-%5 %0
-%2 %0
-## (< x y) fixed 2 — code 6
-&str_prim_lt %0
-%1 %0
-%6 %0
-%5 %0
-%2 %0
-## (> x y) fixed 2 — code 7
-&str_prim_gt %0
-%1 %0
-%7 %0
-%5 %0
-%2 %0
-## (min ...) variadic — code 8 (≥1 enforced by body load)
-&str_prim_min %0
-%3 %0
-%8 %0
-%6 %0
-%0 %0
-## (max ...) variadic — code 9
-&str_prim_max %0
-%3 %0
-%9 %0
-%6 %0
-%0 %0
-## (bit-and ...) variadic — code 10
-&str_prim_bitand %0
-%7 %0
-%10 %0
-%6 %0
-%0 %0
-## (bit-or ...) variadic — code 11
-&str_prim_bitor %0
-%6 %0
-%11 %0
-%6 %0
-%0 %0
-## (bit-xor ...) variadic — code 12
-&str_prim_bitxor %0
-%7 %0
-%12 %0
-%6 %0
-%0 %0
-## (bit-not x) fixed 1 — code 13
-&str_prim_bitnot %0
-%7 %0
-%13 %0
-%5 %0
-%1 %0
-## (arithmetic-shift n k) fixed 2 — code 14
-&str_prim_ashift %0
-%16 %0
-%14 %0
-%5 %0
-%2 %0
-## (number? x) fixed 1 — code 15
-&str_prim_numberp %0
-%7 %0
-%15 %0
-%5 %0
-%1 %0
-## (symbol? x) fixed 1 — code 16
-&str_prim_symbolp %0
-%7 %0
-%16 %0
-%5 %0
-%1 %0
-## (string? x) fixed 1 — code 17
-&str_prim_stringp %0
-%7 %0
-%17 %0
-%5 %0
-%1 %0
-## (vector? x) fixed 1 — code 18
-&str_prim_vectorp %0
-%7 %0
-%18 %0
-%5 %0
-%1 %0
-## (procedure? x) fixed 1 — code 19
-&str_prim_procp %0
-%10 %0
-%19 %0
-%5 %0
-%1 %0
-## (eq? x y) fixed 2 — code 20
-&str_prim_eqp %0
-%3 %0
-%20 %0
-%5 %0
-%2 %0
-## (cons a d) fixed 2 — code 21
-&str_prim_cons %0
-%4 %0
-%21 %0
-%5 %0
-%2 %0
-## (car p) fixed 1 — code 22
-&str_prim_car %0
-%3 %0
-%22 %0
-%5 %0
-%1 %0
-## (cdr p) fixed 1 — code 23
-&str_prim_cdr %0
-%3 %0
-%23 %0
-%5 %0
-%1 %0
-## (pair? x) fixed 1 — code 24
-&str_prim_pairp %0
-%5 %0
-%24 %0
-%5 %0
-%1 %0
-## (null? x) fixed 1 — code 25
-&str_prim_nullp %0
-%5 %0
-%25 %0
-%5 %0
-%1 %0
-## (list ...) variadic — code 26
-&str_prim_list %0
-%4 %0
-%26 %0
-%6 %0
-%0 %0
-## (append ...) variadic — code 27
-&str_prim_append %0
-%6 %0
-%27 %0
-%6 %0
-%0 %0
-## (string-length s) fixed 1 — code 28
-&str_prim_string_length %0
-%13 %0
-%28 %0
-%5 %0
-%1 %0
-## (string-ref s i) fixed 2 — code 29
-&str_prim_string_ref %0
-%10 %0
-%29 %0
-%5 %0
-%2 %0
-## (substring s start end) fixed 3 — code 30
-&str_prim_substring %0
-%9 %0
-%30 %0
-%5 %0
-%3 %0
-## (string-append ...) variadic — code 31
-&str_prim_string_append %0
-%13 %0
-%31 %0
-%6 %0
-%0 %0
-## (string->symbol s) fixed 1 — code 32
-&str_prim_string_to_symbol %0
-%14 %0
-%32 %0
-%5 %0
-%1 %0
-## (symbol->string sym) fixed 1 — code 33
-&str_prim_symbol_to_string %0
-%14 %0
-%33 %0
-%5 %0
-%1 %0
-## (make-vector n init) fixed 2 — code 34
-&str_prim_make_vector %0
-%11 %0
-%34 %0
-%5 %0
-%2 %0
-## (vector-ref v i) fixed 2 — code 35
-&str_prim_vector_ref %0
-%10 %0
-%35 %0
-%5 %0
-%2 %0
-## (vector-set! v i x) fixed 3 — code 36
-&str_prim_vector_set %0
-%11 %0
-%36 %0
-%5 %0
-%3 %0
-## (vector-length v) fixed 1 — code 37
-&str_prim_vector_length %0
-%13 %0
-%37 %0
-%5 %0
-%1 %0
-## (display x) fixed 1 — code 38
-&str_prim_display %0
-%7 %0
-%38 %0
-%5 %0
-%1 %0
-## (write x) fixed 1 — code 39
-&str_prim_write %0
-%5 %0
-%39 %0
-%5 %0
-%1 %0
-## (newline) fixed 0 — code 40
-&str_prim_newline %0
-%7 %0
-%40 %0
-%5 %0
-%0 %0
-## (format fmt ...) variadic — code 41
-&str_prim_format %0
-%6 %0
-%41 %0
-%6 %0
-%0 %0
-## (error msg) fixed 1 — code 42
-&str_prim_error %0
-%5 %0
-%42 %0
-%5 %0
-%1 %0
-## (read-file path) fixed 1 — code 43
-&str_prim_read_file %0
-%9 %0
-%43 %0
-%5 %0
-%1 %0
-## (write-file path data) fixed 2 — code 44
-&str_prim_write_file %0
-%10 %0
-%44 %0
-%5 %0
-%2 %0
-## (apply proc arg ... last-list) variadic — code 45
-&str_prim_apply %0
-%5 %0
-%45 %0
-%6 %0
-%0 %0
-## End sentinel: zero name pointer.
-%0 %0
-%0 %0
-%0 %0
-%0 %0
-%0 %0
-
-
-## ---- Special-form symbol slots --------------------------------------
-## Zero-initialized; _start populates each slot with the interned
-## tagged-symbol pointer so eval_pair can dispatch by pointer identity.
-:sym_quote %0 %0
-:sym_if %0 %0
-:sym_begin %0 %0
-:sym_lambda %0 %0
-:sym_define %0 %0
-:sym_quasiquote %0 %0
-:sym_unquote %0 %0
-:sym_unquote_splicing %0 %0
-:sym_set %0 %0
-:sym_let %0 %0
-:sym_letstar %0 %0
-:sym_letrec %0 %0
-:sym_cond %0 %0
-:sym_else %0 %0
-
-## Global-binding alist head. Zero-initialized (which is a valid
-## untagged pair/nil sentinel? No — nil = 0x07. Seed to nil on entry
-## from _start; the zero here would be interpreted as a pair pointer
-## otherwise).
-:global_env_cell NIL %0
-
-
-## --------------------------------------------------------------------
-## :ELF_end marks the end of the initialised image — everything above
-## is code and data with real bytes, everything below is BSS: labels
-## only, no real content. The ELF header declares p_filesz ending
-## here and p_memsz ending at :ELF_bss_end, so the kernel zero-fills
-## the tail at load time and the on-disk file stops at :ELF_end (a
-## post-link truncate drops the trailing zero bytes). Labels here
-## still need `%0`/`ZERO32` placeholders so hex2's IP keeps advancing
-## and label addresses after them remain correct; the bytes those
-## emit get truncated away and replaced by kernel zeros.
-## --------------------------------------------------------------------
-:ELF_end
-
-:stack_bottom_fp %0 %0
-:gc_root_fp %0 %0
-
-## Aligned mirrors of :pair_heap_start / :obj_heap_start. _start fills
-## these once, so every GC code path can derive the same canonical
-## start address that the bump pointer actually used for its first
-## allocation. See the alignment block in _start for the rationale.
-:pair_heap_base %0 %0
-:obj_heap_base %0 %0
-
-## Live prefix length of :prim_argv, set by marshal_argv on every
-## primitive call. The GC root walker uses this as the upper bound
-## of the prim_argv scan. Initialised to 0 so a GC that fires before
-## the first primitive call walks zero slots.
-:prim_argc %0 %0
-
-:free_list_pair %0 %0
-:free_list_obj16 %0 %0
-:free_list_obj24 %0 %0
-:free_list_obj32 %0 %0
-:free_list_obj40 %0 %0
-:free_list_obj48 %0 %0
-:free_list_obj56 %0 %0
-:free_list_obj64 %0 %0
-:free_list_obj80 %0 %0
-:free_list_obj96 %0 %0
-:free_list_obj128 %0 %0
-
-## mark_stack_next lives in BSS: gc_mark_all re-seeds it to &mark_stack
-## at the top of every mark pass (see :gc_mark_all), so the initial
-## value is dead — zero is fine.
-:mark_stack_next %0 %0
-:pair_mark_bitmap
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-
-:mark_stack
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-:mark_stack_end
-
-
-## ---- Primitive argv scratch buffer (32 slots × 8B = 256B) -----------
-## apply fills this with tagged values before cascading to a primitive
-## body. 32 slots is generous for the step-10 surface; extend if later
-## primitives need more. Zeroed so a stray read sees the 0 tagged
-## sentinel (not a valid value — harmless).
-:prim_argv
-ZERO32 ZERO32
-ZERO32 ZERO32
-ZERO32 ZERO32
-ZERO32 ZERO32
-
-## Reader-state save area for eval_source().
-:saved_src_base %0 %0
-:saved_src_len %0 %0
-:saved_src_cursor %0 %0
-:saved_src_line %0 %0
-:saved_src_col %0 %0
-
-## Shared buffers for pathname marshaling, string literal decoding, and
-## file I/O primitives.
-:path_buf
-ZERO32 ZERO32
-ZERO32 ZERO32
-ZERO32 ZERO32
-ZERO32 ZERO32
-
-:reader_string_buf
-ZERO32 ZERO32
-ZERO32 ZERO32
-ZERO32 ZERO32
-ZERO32 ZERO32
-ZERO32 ZERO32
-ZERO32 ZERO32
-ZERO32 ZERO32
-ZERO32 ZERO32
-
-:io_buf
-ZERO32 ZERO32
-ZERO32 ZERO32
-ZERO32 ZERO32
-ZERO32 ZERO32
-ZERO32 ZERO32
-ZERO32 ZERO32
-ZERO32 ZERO32
-ZERO32 ZERO32
-
-
-## ---- Symbol table (4096 slots × 8 bytes = 32 KiB) -------------------
-## Open-addressing hash table. Empty slot = 0 (no valid tagged value
-## is 0). LISP.md §GC §Roots makes this a named BSS root.
-:symbol_table
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-:symbol_table_end
-
-
-## ---- Heap arena (64 KiB split 32 KiB / 32 KiB for GC bring-up) -------
-:heap_start
-:pair_heap_start
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-:pair_heap_end
-:obj_heap_start
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-:obj_heap_end
-:heap_tail
-
-
-## ---- Source buffer (16 KiB) -----------------------------------------
-## Receives the contents of the file named by argv[1]. Sized to cover
-## the step-9 test fixtures with headroom; widen once those exist.
-## Kept out of the Scheme heap so the reader's raw byte pointers don't
-## fight the bump allocator.
-:src_buf
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
-:src_buf_end
-
-:ELF_bss_end