boot2

Playing with the boostrap
git clone https://git.ryansepassi.com/git/boot2.git
Log | Files | Refs | README

commit 56144e7b4c2059d04a00c2c87f8f8423f3ba2c1b
parent 7e769ab87300ff635a3aaf9227db798f9a642ad6
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Fri, 24 Apr 2026 15:17:00 -0700

rm old P1 lisp

Diffstat:
Dlisp/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