boot2

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

lisp.M1 (172121B)


      1 ## lisp.M1 — Seed Lisp interpreter (portable across aarch64/amd64/riscv64)
      2 ##
      3 ## Steps 1-8 of LISP.md §"Staged implementation plan". On top of the
      4 ## step-6 evaluator, step 8 replaces the embedded `src_text` blob with
      5 ## a runtime file read: _start takes the script path from argv[1],
      6 ## openat/read/closes it into :src_buf, and seeds src_base/src_len for
      7 ## the reader. argc < 2, open failure, and files ≥ 16 KiB each land on
      8 ## a dedicated error() message.
      9 ##
     10 ## Observable test (`make PROG=lisp run-all`): exit 42 on pass when
     11 ## run against `hello.scm`. The script evaluates
     12 ## `(define id (lambda (x) x)) (id 42)` — identity applied to 42,
     13 ## exit status = decoded fixnum of the final result.
     14 ##
     15 ## Tag table (low 3 bits, LISP.md §"Value representation"):
     16 ##   001 fixnum / 010 pair / 011 vector / 100 string / 101 symbol /
     17 ##   110 closure|prim / 111 singleton (nil=0x07)
     18 ##
     19 ## Headered objects carry a 64-bit header before payload:
     20 ##   [ 8-bit type | 8-bit gc-flags | 48-bit length-or-arity ]
     21 ## Little-endian: byte 0 is the low byte of length, byte 7 is the type.
     22 ## make_symbol writes the length with a 64-bit store (high 16 bits = 0)
     23 ## and then overwrites byte 7 with type=2 via SB. Type codes: 1=string,
     24 ## 2=symbol, 3=vector, 4=closure, 5=primitive.
     25 
     26 
     27 ## ---- tagged-lisp constants ---------------------------------------------
     28 ## Singletons live in the low 6 bits (upper 3 bits distinguish them, low
     29 ## 3 = 111 = singleton tag). Named DEFINEs so intent beats "07000000".
     30 DEFINE NIL    07000000
     31 DEFINE TRUE   0F000000
     32 DEFINE FALSE  17000000
     33 DEFINE UNSPEC 27000000
     34 
     35 DEFINE TAG_FIXNUM 01000000
     36 DEFINE TAG_PAIR   02000000
     37 DEFINE TAG_VECTOR 03000000
     38 DEFINE TAG_STRING 04000000
     39 DEFINE TAG_SYMBOL 05000000
     40 DEFINE TAG_PROC   06000000
     41 
     42 DEFINE TYPE_STRING        01000000
     43 DEFINE TYPE_SYMBOL        02000000
     44 DEFINE TYPE_VECTOR        03000000
     45 DEFINE TYPE_CLOSURE       04000000
     46 DEFINE TYPE_PRIM_FIXED    05000000
     47 DEFINE TYPE_PRIM_VARIADIC 06000000
     48 
     49 DEFINE ZERO32 '0000000000000000000000000000000000000000000000000000000000000000'
     50 
     51 
     52 ## ---- heap-state --------------------------------------------------------
     53 ## The two arenas are fixed in BSS; only the bump heads are mutable.
     54 ## Upper 4 bytes stay zero because all labels fit below 4 GiB.
     55 :pair_heap_next  &pair_heap_start %0
     56 :obj_heap_next   &obj_heap_start %0
     57 
     58 
     59 ## ---- _start ----------------------------------------------------------
     60 ## Step 8 driver: read the .scm file named on the command line into
     61 ## src_buf, then loop over its top-level forms. Each form is evaluated
     62 ## in an empty local env (so unqualified lookups fall through to the
     63 ## global hash) and the last result is kept. Final result is displayed
     64 ## with write (closing the step-5 read-print cycle) and its fixnum
     65 ## payload becomes the exit status — lets test harnesses assert via $?.
     66 ##
     67 ## r4 holds the running last_result once the intern loop finishes. It
     68 ## survives read_expr/eval across iterations because r4 is callee-saved;
     69 ## _start itself has no PROLOGUE and never returns, so its r4 is
     70 ## sovereign. Early in _start r4 doubles as the source fd across the
     71 ## read_file_all CALL (same callee-saved guarantee).
     72 :_start
     73     ## Kernel layout at entry: [sp+0]=argc, [sp+8..]=argv[0..argc-1],
     74     ## then NULL, then envp. We need argc and argv[1] before any CALL
     75     ## (no PROLOGUE on _start — CALL would bury the kernel frame under
     76     ## a pushed retaddr on amd64).
     77     ld_r0,sp,0                   ## r0 = argc
     78     li_r1 %2
     79     li_br &err_usage
     80     blt_r0,r1                    ## argc < 2 → err_usage
     81     ld_r2,sp,16                  ## r2 = argv[1] (survives SYSCALL)
     82 
     83     ## Seed the frame-chain terminator before the first CALL.
     84     li_r1 &stack_bottom_fp
     85     mov_r0,sp
     86     st_r0,r1,0
     87 
     88     ## Align both heap bumps to their natural strides and snapshot
     89     ## the aligned starts in :pair_heap_base / :obj_heap_base. The
     90     ## :pair_heap_start label may land at any byte offset depending
     91     ## on what the assembler emitted before it; if a pair sweep then
     92     ## walked from the raw label with a fixed +16 stride it would
     93     ## visit gap addresses, never the actual allocations. Pinning
     94     ## the bump and the bitmap origin to the same aligned address
     95     ## keeps mark and sweep on the same grid.
     96     li_r1 &pair_heap_next
     97     ld_r0,r1,0
     98     addi_r0,r0,15
     99     shri_r0,r0,4
    100     shli_r0,r0,4
    101     st_r0,r1,0
    102     li_r1 &pair_heap_base
    103     st_r0,r1,0
    104 
    105     li_r1 &obj_heap_next
    106     ld_r0,r1,0
    107     addi_r0,r0,7
    108     shri_r0,r0,3
    109     shli_r0,r0,3
    110     st_r0,r1,0
    111     li_r1 &obj_heap_base
    112     st_r0,r1,0
    113 
    114     ## openat(AT_FDCWD=-100, argv[1], O_RDONLY=0, mode=0) → r0 = fd.
    115     ## Use openat rather than open because open is amd64-only
    116     ## (removed from the asm-generic table used by aarch64/riscv64).
    117     ## LI zero-extends; low 32 bits 0xFFFFFF9C decode as -100 in int32.
    118     li_r0 sys_openat
    119     li_r1 %-100
    120     li_r3 %0
    121     li_r4 %0
    122     syscall
    123 
    124     li_br &err_open
    125     bltz_r0                    ## fd < 0 → err_open
    126 
    127     ## read_file_all(fd, src_buf, SRC_BUF_SIZE) → r0 = bytes_read.
    128     ## Stash fd in r4 so it survives the CALL; r4 is callee-saved.
    129     mov_r4,r0
    130     mov_r1,r0
    131     li_r2 &src_buf
    132     li_r3 %16384  ## 16384-byte capacity
    133     li_br &read_file_all
    134     call
    135 
    136     ## Filling the buffer exactly means the file was ≥ SRC_BUF_SIZE —
    137     ## bail out rather than silently truncate.
    138     li_r1 %16384
    139     li_br &err_src_too_big
    140     beq_r0,r1
    141 
    142     ## Publish src_base/src_len so the reader can walk the source,
    143     ## and park (src_buf, bytes_read) in callee-saved r6/r7 so the
    144     ## tuple survives primitive registration down to the eval_source
    145     ## CALL below. Capture r0 into r7 NOW — sys_close below will
    146     ## overwrite r0 with its own return value.
    147     mov_r7,r0                    ## r7 = bytes_read (captured pre-close)
    148     li_r6 &src_buf  ## r6 = &src_buf
    149     li_r1 &src_len
    150     st_r7,r1,0
    151     li_r1 &src_base
    152     st_r6,r1,0
    153 
    154     ## close(fd). r4 still holds fd; SYSCALL clobbers r0 only.
    155     li_r0 sys_close
    156     mov_r1,r4
    157     syscall
    158 
    159     ## Intern the special-form symbols up front. eval_pair compares the
    160     ## car of every compound expression against these pointers, so they
    161     ## must exist before the first eval call. The intern result (r0 =
    162     ## tagged sym) is stashed at &sym_* via ST_R0_R2_0 (the target addr
    163     ## in r2); ST_R0_R1_0 isn't in the P1 op table.
    164     li_r1 &str_quote
    165     li_r2 %5
    166     li_br &intern
    167     call
    168     li_r2 &sym_quote
    169     st_r0,r2,0
    170 
    171     li_r1 &str_if
    172     li_r2 %2
    173     li_br &intern
    174     call
    175     li_r2 &sym_if
    176     st_r0,r2,0
    177 
    178     li_r1 &str_begin
    179     li_r2 %5
    180     li_br &intern
    181     call
    182     li_r2 &sym_begin
    183     st_r0,r2,0
    184 
    185     li_r1 &str_lambda
    186     li_r2 %6
    187     li_br &intern
    188     call
    189     li_r2 &sym_lambda
    190     st_r0,r2,0
    191 
    192     li_r1 &str_define
    193     li_r2 %6
    194     li_br &intern
    195     call
    196     li_r2 &sym_define
    197     st_r0,r2,0
    198 
    199     li_r1 &str_quasiquote
    200     li_r2 %10
    201     li_br &intern
    202     call
    203     li_r2 &sym_quasiquote
    204     st_r0,r2,0
    205 
    206     li_r1 &str_unquote
    207     li_r2 %7
    208     li_br &intern
    209     call
    210     li_r2 &sym_unquote
    211     st_r0,r2,0
    212 
    213     li_r1 &str_unquote_splicing
    214     li_r2 %16
    215     li_br &intern
    216     call
    217     li_r2 &sym_unquote_splicing
    218     st_r0,r2,0
    219 
    220     li_r1 &str_set
    221     li_r2 %4
    222     li_br &intern
    223     call
    224     li_r2 &sym_set
    225     st_r0,r2,0
    226 
    227     li_r1 &str_let
    228     li_r2 %3
    229     li_br &intern
    230     call
    231     li_r2 &sym_let
    232     st_r0,r2,0
    233 
    234     li_r1 &str_letstar
    235     li_r2 %4
    236     li_br &intern
    237     call
    238     li_r2 &sym_letstar
    239     st_r0,r2,0
    240 
    241     li_r1 &str_letrec
    242     li_r2 %6
    243     li_br &intern
    244     call
    245     li_r2 &sym_letrec
    246     st_r0,r2,0
    247 
    248     li_r1 &str_cond
    249     li_r2 %4
    250     li_br &intern
    251     call
    252     li_r2 &sym_cond
    253     st_r0,r2,0
    254 
    255     li_r1 &str_else
    256     li_r2 %4
    257     li_br &intern
    258     call
    259     li_r2 &sym_else
    260     st_r0,r2,0
    261 
    262     ## Register primitives (LISP.md step 10b). Walk prim_table with
    263     ## r4 = cursor, r5 = saved sym across make_primitive. Entry is
    264     ## 40 bytes; zero name pointer ends the loop.
    265     li_r4 &prim_table
    266 :_start_reg_prim_loop
    267     ld_r1,r4,0                       ## r1 = name ptr
    268     li_br &_start_reg_prim_done
    269     beqz_r1
    270 
    271     ld_r2,r4,8                       ## r2 = length
    272     li_br &intern
    273     call                             ## r0 = tagged sym
    274     mov_r5,r0
    275 
    276     ld_r1,r4,16                      ## r1 = code id
    277     ld_r2,r4,32                      ## r2 = arity
    278     ld_r3,r4,24                      ## r3 = type
    279     li_br &make_primitive
    280     call                             ## r0 = tagged prim
    281 
    282     mov_r2,r0
    283     mov_r1,r5
    284     li_br &gset
    285     call
    286 
    287     addi_r4,r4,40
    288     li_br &_start_reg_prim_loop
    289     b
    290 :_start_reg_prim_done
    291     ## Evaluate the script read from argv[1]. The Makefile cats
    292     ## src/prelude.scm ahead of the user script before invoking us,
    293     ## so map/filter/fold are in scope by the time user code runs.
    294     mov_r1,r6
    295     mov_r2,r7
    296     li_br &eval_source
    297     call
    298     mov_r4,r0
    299 
    300 :_start_done
    301     ## Print the final result (write form) + newline for visibility.
    302     mov_r1,r4
    303     li_br &write
    304     call
    305     li_r1 %10
    306     li_br &putc
    307     call
    308 
    309     ## Exit status = decoded fixnum of last_result (bit-cast to low 8
    310     ## bits by the kernel). Non-fixnum results still exit cleanly —
    311     ## the low 8 bits of the tagged word are used.
    312     li_r0 sys_exit
    313     mov_r1,r4
    314     sari_r1,r1,3
    315     syscall
    316 
    317 
    318 ## ---- read_file_all(r1=fd, r2=buf, r3=cap) -> r0 = total_bytes ------
    319 ## Read loop. r4-r7 are callee-saved AND preserved across SYSCALL, so
    320 ## we can park state there across each read() call. No PROLOGUE needed:
    321 ## we only issue syscalls, never CALL.
    322 :read_file_all
    323     mov_r4,r1                    ## r4 = fd
    324     mov_r5,r2                    ## r5 = cur buf ptr
    325     mov_r6,r3                    ## r6 = remaining capacity
    326     li_r7 %0  ## r7 = total_read
    327 
    328 :read_file_all_loop
    329     ## Capacity exhausted → stop; _start checks for the exact-full
    330     ## case and escalates to err_src_too_big.
    331     li_br &read_file_all_done
    332     beqz_r6
    333 
    334     li_r0 sys_read
    335     mov_r1,r4
    336     mov_r2,r5
    337     mov_r3,r6
    338     syscall                      ## r0 = n (0=EOF, <0=err)
    339 
    340     ## n <= 0 → done. Check < 0 first, then == 0.
    341     li_br &read_file_all_done
    342     bltz_r0
    343     li_br &read_file_all_done
    344     beqz_r0
    345 
    346     add_r5,r5,r0                 ## buf += n
    347     sub_r6,r6,r0                 ## cap -= n
    348     add_r7,r7,r0                 ## total += n
    349     li_br &read_file_all_loop
    350     b
    351 
    352 :read_file_all_done
    353     mov_r0,r7
    354     ret
    355 
    356 
    357 ## ---- write_file_all(r1=fd, r2=buf, r3=len) -> r0 = total|-1 -------
    358 ## Mirror of read_file_all: write until len reaches zero or an error
    359 ## occurs. Returning -1 lets write-file raise a Lisp-facing error.
    360 :write_file_all
    361     mov_r4,r1                    ## r4 = fd
    362     mov_r5,r2                    ## r5 = cursor
    363     mov_r6,r3                    ## r6 = remaining
    364     li_r7 %0  ## r7 = total_written
    365 
    366 :write_file_all_loop
    367     li_br &write_file_all_done
    368     beqz_r6
    369 
    370     li_r0 sys_write
    371     mov_r1,r4
    372     mov_r2,r5
    373     mov_r3,r6
    374     syscall                      ## r0 = n (<0 error, 0 unexpected stall)
    375 
    376     li_br &write_file_all_err
    377     bltz_r0
    378     li_br &write_file_all_err
    379     beqz_r0
    380 
    381     add_r5,r5,r0
    382     sub_r6,r6,r0
    383     add_r7,r7,r0
    384     li_br &write_file_all_loop
    385     b
    386 
    387 :write_file_all_done
    388     mov_r0,r7
    389     ret
    390 
    391 :write_file_all_err
    392     li_r0 %0
    393     addi_r0,r0,neg1
    394     ret
    395 
    396 
    397 ## ---- eval_source(r1=base, r2=len) -> r0 = last_result --------------
    398 ## Save the current reader globals, point them at (base,len), evaluate
    399 ## every top-level form, then restore the outer reader state.
    400 :eval_source
    401     prologue
    402     st_r4,sp,24                       ## save caller's r4
    403 
    404     li_r3 &src_base
    405     ld_r0,r3,0
    406     li_r4 &saved_src_base
    407     st_r0,r4,0
    408     st_r1,r3,0
    409 
    410     li_r3 &src_len
    411     ld_r0,r3,0
    412     li_r4 &saved_src_len
    413     st_r0,r4,0
    414     st_r2,r3,0
    415 
    416     li_r3 &src_cursor
    417     ld_r0,r3,0
    418     li_r4 &saved_src_cursor
    419     st_r0,r4,0
    420     li_r0 %0
    421     st_r0,r3,0
    422 
    423     li_r3 &src_line
    424     ld_r0,r3,0
    425     li_r4 &saved_src_line
    426     st_r0,r4,0
    427     li_r0 %1
    428     st_r0,r3,0
    429 
    430     li_r3 &src_col
    431     ld_r0,r3,0
    432     li_r4 &saved_src_col
    433     st_r0,r4,0
    434     li_r0 %1
    435     st_r0,r3,0
    436 
    437     li_r4 %39  ## running last_result = unspec
    438 
    439 :eval_source_loop
    440     li_br &skip_ws
    441     call
    442 
    443     li_br &peek_char
    444     call
    445     li_r1 %0
    446     addi_r1,r1,neg1
    447     li_br &eval_source_done
    448     beq_r0,r1
    449 
    450     li_br &read_expr
    451     call
    452     mov_r1,r0
    453     li_r2 %7
    454     li_br &eval
    455     call
    456     mov_r4,r0
    457     li_br &eval_source_loop
    458     b
    459 
    460 :eval_source_done
    461     li_r3 &saved_src_base
    462     ld_r0,r3,0
    463     li_r1 &src_base
    464     st_r0,r1,0
    465 
    466     li_r3 &saved_src_len
    467     ld_r0,r3,0
    468     li_r1 &src_len
    469     st_r0,r1,0
    470 
    471     li_r3 &saved_src_cursor
    472     ld_r0,r3,0
    473     li_r1 &src_cursor
    474     st_r0,r1,0
    475 
    476     li_r3 &saved_src_line
    477     ld_r0,r3,0
    478     li_r1 &src_line
    479     st_r0,r1,0
    480 
    481     li_r3 &saved_src_col
    482     ld_r0,r3,0
    483     li_r1 &src_col
    484     st_r0,r1,0
    485 
    486     mov_r0,r4
    487     ld_r4,sp,24
    488     epilogue
    489     ret
    490 
    491 
    492 ## ---- hash(r1=ptr, r2=len) -> r0 = u64 hash --------------------------
    493 ## DJB-style: h' = 31*h + b. Implemented as (h<<5) - h + b to avoid
    494 ## stashing 31 in a register. Uses r6 (zero constant) across the loop;
    495 ## saves/restores it via a PROLOGUE_N1 slot.
    496 :hash
    497     prologue
    498     st_r6,sp,24
    499 
    500     li_r6 %0  ## zero const
    501     li_r0 %0  ## h = 0
    502 
    503 :hash_loop
    504     li_br &hash_done
    505     beq_r2,r6                    ## len == 0 → done
    506 
    507     shli_r3,r0,5                 ## r3 = h << 5
    508     sub_r3,r3,r0                 ## r3 = h*32 - h = h*31
    509     lb_r0,r1,0                   ## r0 = *ptr (zero-extended byte)
    510     add_r0,r0,r3                 ## r0 = h*31 + b
    511     addi_r1,r1,1
    512     addi_r2,r2,neg1
    513     li_br &hash_loop
    514     b
    515 
    516 :hash_done
    517     ld_r6,sp,24
    518     epilogue
    519     ret
    520 
    521 
    522 ## ---- sym_name_equal(r1=tagged_sym, r2=cmp_ptr, r3=cmp_len) -> r0 ---
    523 ## Returns 1 if the symbol's stored name equals `cmp_len` bytes at
    524 ## `cmp_ptr`, else 0. Uses r6 as zero const + r7 as the cmp-byte
    525 ## scratch; both are callee-saved, so PROLOGUE_N2 stashes them.
    526 :sym_name_equal
    527     prologue_n2
    528     st_r6,sp,24
    529     st_r7,sp,32
    530 
    531     addi_r1,r1,neg5              ## r1 = raw sym ptr
    532     ld_r6,r1,0                   ## r6 = header word
    533     ## Mask low 32 bits: our names fit in 32 bits of the 48-bit length.
    534     shli_r6,r6,32
    535     shri_r6,r6,32                ## r6 = sym_len
    536 
    537     li_br &sym_name_equal_neq
    538     bne_r6,r3                    ## lengths differ → not equal
    539 
    540     addi_r1,r1,8                 ## r1 = name bytes ptr
    541     li_r6 %0  ## zero const
    542 
    543 :sym_name_equal_loop
    544     li_br &sym_name_equal_eq
    545     beq_r3,r6                    ## len == 0 → match
    546 
    547     lb_r0,r1,0                   ## r0 = sym byte
    548     lb_r7,r2,0                   ## r7 = cmp byte
    549     li_br &sym_name_equal_neq
    550     bne_r0,r7
    551 
    552     addi_r1,r1,1
    553     addi_r2,r2,1
    554     addi_r3,r3,neg1
    555     li_br &sym_name_equal_loop
    556     b
    557 
    558 :sym_name_equal_eq
    559     li_r0 %1
    560     li_br &sym_name_equal_done
    561     b
    562 
    563 :sym_name_equal_neq
    564     li_r0 %0
    565 
    566 :sym_name_equal_done
    567     ld_r6,sp,24
    568     ld_r7,sp,32
    569     epilogue_n2
    570     ret
    571 
    572 
    573 ## ---- make_symbol(r1=src, r2=len) -> r0 = tagged symbol --------------
    574 ## Lays out header + name bytes inline, returning (raw | SYMBOL_TAG=5).
    575 ## Allocation size = 8 (header) + round_up_8(len).
    576 ##
    577 ## Frame layout:
    578 ##   [sp + 8]  saved r6 (caller's copy)
    579 ##   [sp + 16] saved r7
    580 ##   [sp + 24] raw_ptr across the copy loop
    581 :make_symbol
    582     prologue_n3
    583     st_r6,sp,24
    584     st_r7,sp,32
    585 
    586     mov_r6,r1                    ## r6 = src (mutated during byte copy)
    587     mov_r7,r2                    ## r7 = len (mutated during byte copy)
    588 
    589     ## alloc_size = 8 + ((len + 7) >> 3 << 3)
    590     addi_r1,r7,7
    591     shri_r1,r1,3
    592     shli_r1,r1,3
    593     addi_r1,r1,8
    594     li_br &alloc
    595     call                         ## r0 = raw ptr
    596 
    597     st_r0,sp,40                  ## stash raw ptr
    598 
    599     ## Header: store len in low 48 bits, then stamp type=2 in byte 7.
    600     ld_r0,sp,40
    601     st_r7,r0,0
    602     li_r1 %2
    603     sb_r1,r0,7
    604 
    605     ## Byte copy: dst = r3 (= raw+8), src = r6, len = r7.
    606     ld_r0,sp,40
    607     addi_r3,r0,8
    608     li_r1 %0  ## zero const for loop test
    609 
    610 :make_symbol_copy
    611     li_br &make_symbol_done
    612     beq_r7,r1
    613 
    614     lb_r2,r6,0
    615     sb_r2,r3,0
    616     addi_r6,r6,1
    617     addi_r3,r3,1
    618     addi_r7,r7,neg1
    619     li_br &make_symbol_copy
    620     b
    621 
    622 :make_symbol_done
    623     ld_r0,sp,40
    624     ori_r0,r0,4                  ## set bit 2 (aarch64 bitmask-imm can't do 0b101 directly)
    625     ori_r0,r0,1                  ## set bit 0 → tag = 101 (SYMBOL)
    626     ld_r6,sp,24
    627     ld_r7,sp,32
    628     epilogue_n3
    629     ret
    630 
    631 
    632 ## ---- byte_copy(r1=dst, r2=src, r3=len) -> r0 = dst_end -------------
    633 ## Raw byte copy used by string construction, path marshaling, and
    634 ## string-append. No overlap handling needed in the seed.
    635 :byte_copy
    636     li_r0 %0
    637 :byte_copy_loop
    638     li_br &byte_copy_done
    639     beq_r3,r0
    640     lb_r0,r2,0
    641     sb_r0,r1,0
    642     addi_r1,r1,1
    643     addi_r2,r2,1
    644     addi_r3,r3,neg1
    645     li_r0 %0
    646     li_br &byte_copy_loop
    647     b
    648 :byte_copy_done
    649     mov_r0,r1
    650     ret
    651 
    652 
    653 ## ---- alloc_string(r1=len) -> r0 = tagged string ---------------------
    654 ## Allocates header + zero or more payload bytes, stamps type=1, and
    655 ## leaves the payload for the caller to fill.
    656 :alloc_string
    657     prologue
    658     st_r1,sp,24
    659 
    660     addi_r1,r1,7
    661     shri_r1,r1,3
    662     shli_r1,r1,3
    663     addi_r1,r1,8
    664     li_br &alloc
    665     call
    666 
    667     ld_r1,sp,24
    668     st_r1,r0,0
    669     li_r1 %1
    670     sb_r1,r0,7
    671     ori_r0,r0,4                      ## tag = 100 (string)
    672     epilogue
    673     ret
    674 
    675 
    676 ## ---- make_string(r1=src, r2=len) -> r0 = tagged string -------------
    677 :make_string
    678     prologue_n3
    679     st_r1,sp,24
    680     st_r2,sp,32
    681 
    682     mov_r1,r2
    683     li_br &alloc_string
    684     call
    685     st_r0,sp,40
    686 
    687     addi_r1,r0,neg4                  ## raw string ptr
    688     addi_r1,r1,8                     ## dst payload
    689     ld_r2,sp,24                       ## src payload
    690     ld_r3,sp,32                      ## len
    691     li_br &byte_copy
    692     call
    693 
    694     ld_r0,sp,40
    695     epilogue_n3
    696     ret
    697 
    698 
    699 ## ---- make_vector(r1=len_raw, r2=init) -> r0 = tagged vector --------
    700 ## Allocates header + len tagged slots, stamps type=3, and fills every
    701 ## element with `init`.
    702 :make_vector
    703     prologue_n2
    704     st_r1,sp,24
    705     st_r2,sp,32
    706 
    707     shli_r1,r1,3
    708     addi_r1,r1,8
    709     li_br &alloc
    710     call
    711 
    712     ld_r1,sp,24                       ## len_raw
    713     st_r0,sp,24                       ## overwrite slot1 = raw ptr
    714     st_r1,r0,0
    715     li_r1 %3
    716     sb_r1,r0,7
    717 
    718     ld_r1,sp,24                       ## raw ptr
    719     ld_r2,sp,32                      ## init
    720     ld_r0,r1,0                       ## header word (type|len)
    721     shli_r0,r0,16                    ## mask off type byte
    722     shri_r0,r0,16                    ## r0 = len_raw
    723     addi_r1,r1,8                     ## payload cursor
    724     li_r3 %0
    725 :make_vector_loop
    726     li_br &make_vector_done
    727     beq_r0,r3
    728     st_r2,r1,0
    729     addi_r1,r1,8
    730     addi_r0,r0,neg1
    731     li_br &make_vector_loop
    732     b
    733 :make_vector_done
    734     ld_r0,sp,24
    735     ori_r0,r0,2
    736     ori_r0,r0,1                      ## tag = 011 (vector)
    737     epilogue_n2
    738     ret
    739 
    740 
    741 ## ---- string_to_c_path(r1=tagged_string) -> r0 = &path_buf ----------
    742 ## Copies the string bytes to a shared NUL-terminated buffer so openat
    743 ## can consume it as a kernel pathname.
    744 :string_to_c_path
    745     prologue_n2
    746     addi_r1,r1,neg4                  ## raw string ptr
    747     ld_r2,r1,0
    748     shli_r2,r2,16
    749     shri_r2,r2,16                    ## r2 = len
    750     mov_r3,r2
    751     li_r0 %256  ## PATH_BUF_SIZE = 256
    752     li_br &string_to_c_path_ok
    753     blt_r3,r0
    754     li_br &err_path_too_long
    755     b
    756 :string_to_c_path_ok
    757     mov_r0,sp
    758     st_r2,r0,24
    759     addi_r1,r1,8                     ## payload ptr
    760     st_r1,r0,32
    761     li_r1 &path_buf
    762     ld_r2,r0,32
    763     ld_r3,r0,24
    764     li_br &byte_copy
    765     call
    766     li_r1 %0
    767     sb_r1,r0,0
    768     li_r0 &path_buf
    769     epilogue_n2
    770     ret
    771 
    772 
    773 ## ---- intern(r1=name_ptr, r2=name_len) -> r0 = tagged sym -----------
    774 ## Open-addressing linear probe over a 4096-slot table. Empty slot =
    775 ## 0 word (nil is 0x07, never 0, so this sentinel is unambiguous).
    776 ## Frame layout: slot 1 = saved r6, slot 2 = saved r7, slot 3 = current
    777 ## probe index h.
    778 :intern
    779     prologue_n3
    780     st_r6,sp,24
    781     st_r7,sp,32
    782 
    783     mov_r6,r1                    ## r6 = name_ptr (callee-saved copy)
    784     mov_r7,r2                    ## r7 = name_len
    785 
    786     li_br &hash
    787     call                         ## r0 = 64-bit hash
    788 
    789     shli_r0,r0,52
    790     shri_r0,r0,52                ## r0 = h & 4095
    791 
    792     mov_r3,sp
    793     st_r0,sp,40                  ## slot 3 = h
    794 
    795 :intern_probe
    796     ld_r0,sp,40
    797     shli_r0,r0,3                 ## r0 = h * 8
    798     li_r2 &symbol_table
    799     add_r2,r2,r0                 ## r2 = &symbol_table[h]
    800     ld_r3,r2,0                   ## r3 = slot value
    801 
    802     li_br &intern_empty
    803     beqz_r3                    ## slot == 0 → allocate
    804 
    805     ## Compare existing symbol to (r6, r7).
    806     mov_r1,r3                    ## r1 = tagged sym
    807     mov_r2,r6                    ## r2 = cmp_ptr
    808     mov_r3,r7                    ## r3 = cmp_len
    809     li_br &sym_name_equal
    810     call                         ## r0 = 0 or 1
    811 
    812     li_br &intern_hit
    813     bnez_r0
    814 
    815     ## Advance h = (h+1) & 4095.
    816     ld_r0,sp,40
    817     addi_r0,r0,1
    818     shli_r0,r0,52
    819     shri_r0,r0,52
    820     st_r0,sp,40
    821     li_br &intern_probe
    822     b
    823 
    824 :intern_empty
    825     ## Allocate a fresh symbol and plant it in the slot.
    826     mov_r1,r6
    827     mov_r2,r7
    828     li_br &make_symbol
    829     call                         ## r0 = tagged sym
    830 
    831     ld_r1,sp,40
    832     shli_r1,r1,3
    833     li_r2 &symbol_table
    834     add_r2,r2,r1
    835     st_r0,r2,0                   ## write into slot
    836 
    837     li_br &intern_done
    838     b
    839 
    840 :intern_hit
    841     mov_r3,sp
    842     ld_r0,sp,40
    843     shli_r0,r0,3
    844     li_r2 &symbol_table
    845     add_r2,r2,r0
    846     ld_r0,r2,0                   ## r0 = existing tagged sym
    847 
    848 :intern_done
    849     ld_r6,sp,24
    850     ld_r7,sp,32
    851     epilogue_n3
    852     ret
    853 
    854 
    855 ## ---- cons / car / cdr (preserved from step 2) -----------------------
    856 :cons
    857     prologue_n2
    858     st_r1,sp,24
    859     st_r2,sp,32
    860     li_br &pair_alloc
    861     call
    862     ld_r1,sp,24
    863     st_r1,r0,0
    864     ld_r2,sp,32
    865     st_r2,r0,8
    866     ori_r0,r0,2
    867     epilogue_n2
    868     ret
    869 
    870 :car
    871     addi_r1,r1,neg2
    872     ld_r0,r1,0
    873     ret
    874 
    875 :cdr
    876     addi_r1,r1,neg2
    877     ld_r0,r1,8
    878     ret
    879 
    880 
    881 ## ---- obj_freelist_for_size(size) -> &head cell or 0 -----------------
    882 :obj_freelist_for_size
    883     li_r0 %16
    884     li_br &obj_freelist_16
    885     beq_r1,r0
    886     li_r0 %24
    887     li_br &obj_freelist_24
    888     beq_r1,r0
    889     li_r0 %32
    890     li_br &obj_freelist_32
    891     beq_r1,r0
    892     li_r0 %40
    893     li_br &obj_freelist_40
    894     beq_r1,r0
    895     li_r0 %48
    896     li_br &obj_freelist_48
    897     beq_r1,r0
    898     li_r0 %56
    899     li_br &obj_freelist_56
    900     beq_r1,r0
    901     li_r0 %64
    902     li_br &obj_freelist_64
    903     beq_r1,r0
    904     li_r0 %80
    905     li_br &obj_freelist_80
    906     beq_r1,r0
    907     li_r0 %96
    908     li_br &obj_freelist_96
    909     beq_r1,r0
    910     li_r0 %128
    911     li_br &obj_freelist_128
    912     beq_r1,r0
    913     li_r0 %0
    914     ret
    915 :obj_freelist_16
    916     li_r0 &free_list_obj16
    917     ret
    918 :obj_freelist_24
    919     li_r0 &free_list_obj24
    920     ret
    921 :obj_freelist_32
    922     li_r0 &free_list_obj32
    923     ret
    924 :obj_freelist_40
    925     li_r0 &free_list_obj40
    926     ret
    927 :obj_freelist_48
    928     li_r0 &free_list_obj48
    929     ret
    930 :obj_freelist_56
    931     li_r0 &free_list_obj56
    932     ret
    933 :obj_freelist_64
    934     li_r0 &free_list_obj64
    935     ret
    936 :obj_freelist_80
    937     li_r0 &free_list_obj80
    938     ret
    939 :obj_freelist_96
    940     li_r0 &free_list_obj96
    941     ret
    942 :obj_freelist_128
    943     li_r0 &free_list_obj128
    944     ret
    945 
    946 
    947 ## ---- pair_alloc() -> raw pair ptr -----------------------------------
    948 :pair_alloc
    949     prologue
    950 
    951     li_r3 &free_list_pair
    952     ld_r0,r3,0
    953     li_br &pair_alloc_bump
    954     beqz_r0
    955     ld_r1,r0,0
    956     st_r1,r3,0
    957     epilogue
    958     ret
    959 
    960 :pair_alloc_bump
    961     li_r3 &pair_heap_next
    962     ld_r0,r3,0
    963     addi_r0,r0,7
    964     shri_r0,r0,3
    965     shli_r0,r0,3
    966     addi_r2,r0,16
    967     li_r1 &pair_heap_end
    968     li_br &pair_alloc_need_gc
    969     blt_r1,r2
    970     st_r2,r3,0
    971     epilogue
    972     ret
    973 
    974 ## GC, then retry. Re-checks the free list first because the sweep
    975 ## populates it with reclaimed pairs; only falls through to the bump
    976 ## path if the list is still empty. A second overflow goes to OOM.
    977 :pair_alloc_need_gc
    978     li_br &gc
    979     call
    980     li_r3 &free_list_pair
    981     ld_r0,r3,0
    982     li_br &pair_alloc_retry_bump
    983     beqz_r0
    984     ld_r1,r0,0
    985     st_r1,r3,0
    986     epilogue
    987     ret
    988 
    989 :pair_alloc_retry_bump
    990     li_r3 &pair_heap_next
    991     ld_r0,r3,0
    992     addi_r0,r0,7
    993     shri_r0,r0,3
    994     shli_r0,r0,3
    995     addi_r2,r0,16
    996     li_r1 &pair_heap_end
    997     li_br &alloc_oom
    998     blt_r1,r2
    999     st_r2,r3,0
   1000     epilogue
   1001     ret
   1002 
   1003 
   1004 ## ---- alloc / obj_alloc(size) -> raw object ptr ----------------------
   1005 :alloc
   1006 :obj_alloc
   1007     prologue_n2
   1008 
   1009     addi_r1,r1,7
   1010     shri_r1,r1,3
   1011     shli_r1,r1,3
   1012     st_r1,sp,24                      ## slot 1 = rounded size
   1013 
   1014     li_br &obj_freelist_for_size
   1015     call
   1016     st_r0,sp,32                      ## slot 2 = &free_list head or 0
   1017 
   1018     li_br &obj_alloc_bump
   1019     beqz_r0
   1020     ld_r1,r0,0
   1021     li_br &obj_alloc_bump
   1022     beqz_r1
   1023     ld_r2,r1,8
   1024     ld_r3,sp,32
   1025     st_r2,r3,0
   1026     mov_r0,r1
   1027     epilogue_n2
   1028     ret
   1029 
   1030 :obj_alloc_bump
   1031     ld_r1,sp,24
   1032     li_r3 &obj_heap_next
   1033     ld_r0,r3,0
   1034     addi_r0,r0,7
   1035     shri_r0,r0,3
   1036     shli_r0,r0,3
   1037     add_r2,r0,r1
   1038     li_r1 &obj_heap_end
   1039     li_br &obj_alloc_need_gc
   1040     blt_r1,r2
   1041     st_r2,r3,0
   1042     epilogue_n2
   1043     ret
   1044 
   1045 ## GC, then retry. Slot 2 still holds the size-class freelist head
   1046 ## (or 0 when no class fits this size). After the sweep, re-check
   1047 ## that list before falling through to the bump path. Second overflow
   1048 ## escalates to OOM.
   1049 :obj_alloc_need_gc
   1050     li_br &gc
   1051     call
   1052     ld_r3,sp,32                      ## &head (or 0 = no size class)
   1053     li_br &obj_alloc_retry_bump
   1054     beqz_r3
   1055     ld_r1,r3,0
   1056     li_br &obj_alloc_retry_bump
   1057     beqz_r1
   1058     ld_r2,r1,8
   1059     st_r2,r3,0
   1060     mov_r0,r1
   1061     epilogue_n2
   1062     ret
   1063 
   1064 :obj_alloc_retry_bump
   1065     ld_r1,sp,24
   1066     li_r3 &obj_heap_next
   1067     ld_r0,r3,0
   1068     addi_r0,r0,7
   1069     shri_r0,r0,3
   1070     shli_r0,r0,3
   1071     add_r2,r0,r1
   1072     li_r1 &obj_heap_end
   1073     li_br &alloc_oom
   1074     blt_r1,r2
   1075     st_r2,r3,0
   1076     epilogue_n2
   1077     ret
   1078 
   1079 
   1080 ## ---- obj_size_bytes(raw obj ptr) -> r0 = rounded chunk size ---------
   1081 :obj_size_bytes
   1082     lb_r2,r1,7
   1083     li_r0 %0
   1084     li_br &obj_size_free
   1085     beq_r2,r0
   1086     li_r0 %1
   1087     li_br &obj_size_string
   1088     beq_r2,r0
   1089     li_r0 %2
   1090     li_br &obj_size_symbol
   1091     beq_r2,r0
   1092     li_r0 %3
   1093     li_br &obj_size_vector
   1094     beq_r2,r0
   1095     li_r0 %4
   1096     li_br &obj_size_closure
   1097     beq_r2,r0
   1098     li_r0 %5
   1099     li_br &obj_size_primitive
   1100     beq_r2,r0
   1101     li_r0 %6
   1102     li_br &obj_size_primitive
   1103     beq_r2,r0
   1104     li_r0 %16
   1105     ret
   1106 
   1107 :obj_size_free
   1108     ld_r0,r1,0
   1109     shli_r0,r0,16
   1110     shri_r0,r0,16
   1111     ret
   1112 
   1113 :obj_size_string
   1114 :obj_size_symbol
   1115     ld_r0,r1,0
   1116     shli_r0,r0,16
   1117     shri_r0,r0,16
   1118     addi_r0,r0,15
   1119     shri_r0,r0,3
   1120     shli_r0,r0,3
   1121     ret
   1122 
   1123 :obj_size_vector
   1124     ld_r0,r1,0
   1125     shli_r0,r0,16
   1126     shri_r0,r0,16
   1127     shli_r0,r0,3
   1128     addi_r0,r0,8
   1129     ret
   1130 
   1131 :obj_size_closure
   1132     li_r0 %32
   1133     ret
   1134 
   1135 :obj_size_primitive
   1136     li_r0 %16
   1137     ret
   1138 
   1139 
   1140 ## ---- obj_is_live_marked(raw obj ptr) -> r0 = 1 iff marked live -----
   1141 :obj_is_live_marked
   1142     lb_r2,r1,7
   1143     li_br &obj_is_dead
   1144     beqz_r2
   1145     lb_r2,r1,6
   1146     andi_r2,r2,1
   1147     li_br &obj_is_dead
   1148     beqz_r2
   1149     li_r0 %1
   1150     ret
   1151 :obj_is_dead
   1152     li_r0 %0
   1153     ret
   1154 
   1155 
   1156 ## ---- obj_mark_seen_or_set(raw obj ptr) -> r0 = 1 iff already marked -
   1157 :obj_mark_seen_or_set
   1158     lb_r2,r1,6
   1159     andi_r3,r2,1
   1160     li_br &obj_mark_seen
   1161     bnez_r3
   1162     ori_r2,r2,1
   1163     sb_r2,r1,6
   1164     li_r0 %0
   1165     ret
   1166 :obj_mark_seen
   1167     li_r0 %1
   1168     ret
   1169 
   1170 
   1171 ## ---- pair mark-bitmap helpers ---------------------------------------
   1172 :pair_mark_seen_or_set
   1173     li_r2 &pair_heap_base
   1174     ld_r2,r2,0
   1175     sub_r2,r1,r2
   1176     shri_r2,r2,4                     ## slot index
   1177     mov_r3,r2
   1178     shri_r3,r3,3                     ## byte index
   1179     li_r0 &pair_mark_bitmap
   1180     add_r0,r0,r3                     ## r0 = byte ptr
   1181     lb_r3,r0,0                       ## r3 = byte
   1182     andi_r1,r2,7                     ## r1 = bit index
   1183     li_r2 %1
   1184     shl_r2,r2,r1                     ## r2 = mask
   1185     mov_r1,r3
   1186     and_r1,r1,r2
   1187     li_br &pair_mark_seen
   1188     bnez_r1
   1189     or_r3,r3,r2
   1190     sb_r3,r0,0
   1191     li_r0 %0
   1192     ret
   1193 :pair_mark_seen
   1194     li_r0 %1
   1195     ret
   1196 
   1197 :pair_mark_test
   1198     li_r2 &pair_heap_base
   1199     ld_r2,r2,0
   1200     sub_r2,r1,r2
   1201     shri_r2,r2,4
   1202     mov_r3,r2
   1203     shri_r3,r3,3
   1204     li_r0 &pair_mark_bitmap
   1205     add_r0,r0,r3
   1206     lb_r3,r0,0
   1207     andi_r1,r2,7
   1208     li_r2 %1
   1209     shl_r2,r2,r1
   1210     and_r3,r3,r2
   1211     li_r0 %0
   1212     li_br &pair_mark_test_done
   1213     beqz_r3
   1214     li_r0 %1
   1215 :pair_mark_test_done
   1216     ret
   1217 
   1218 :pair_mark_clear
   1219     li_r2 &pair_heap_base
   1220     ld_r2,r2,0
   1221     sub_r2,r1,r2
   1222     shri_r2,r2,4
   1223     mov_r3,r2
   1224     shri_r3,r3,3
   1225     li_r0 &pair_mark_bitmap
   1226     add_r0,r0,r3
   1227     lb_r3,r0,0
   1228     andi_r1,r2,7
   1229     li_r2 %1
   1230     shl_r2,r2,r1
   1231     sub_r3,r3,r2
   1232     sb_r3,r0,0
   1233     ret
   1234 
   1235 
   1236 ## ---- mark_push(tagged value) ----------------------------------------
   1237 :mark_push
   1238     li_r2 &mark_stack_next
   1239     ld_r3,r2,0
   1240     li_r0 &mark_stack_end
   1241     li_br &mark_push_recurse
   1242     beq_r3,r0
   1243     st_r1,r3,0
   1244     addi_r3,r3,8
   1245     st_r3,r2,0
   1246     ret
   1247 ## Tail-branch (not CALL) so mark_value returns directly to mark_push's
   1248 ## caller. A CALL+RET here would loop on aarch64/riscv64: their native
   1249 ## CALL writes retaddr to LR rather than pushing it, so the inner CALL
   1250 ## clobbers this routine's own incoming LR and the final RET branches
   1251 ## back to itself. mark_push has no prologue and thus no place to spill
   1252 ## LR, so tail-branching is the correct shape.
   1253 :mark_push_recurse
   1254     li_br &mark_value
   1255     b
   1256 
   1257 
   1258 ## ---- mark_value(tagged value) ---------------------------------------
   1259 :mark_value
   1260     prologue_n4
   1261 
   1262     andi_r0,r1,7
   1263     li_r2 %2
   1264     li_br &mark_value_pair
   1265     beq_r0,r2
   1266     li_r2 %3
   1267     li_br &mark_value_object
   1268     beq_r0,r2
   1269     li_r2 %4
   1270     li_br &mark_value_object
   1271     beq_r0,r2
   1272     li_r2 %5
   1273     li_br &mark_value_object
   1274     beq_r0,r2
   1275     li_r2 %6
   1276     li_br &mark_value_object
   1277     beq_r0,r2
   1278     epilogue_n4
   1279     ret
   1280 
   1281 ## Tagged-pair bounds check: a frame slot may hold a raw integer whose
   1282 ## low 3 bits coincide with the pair tag (e.g. a small loop counter).
   1283 ## Reject anything outside [pair_heap_start, pair_heap_next) so the
   1284 ## bitmap index stays in range and dereferencing stays inside our arena.
   1285 :mark_value_pair
   1286     addi_r1,r1,neg2                  ## r1 = raw pair ptr
   1287     li_r0 &pair_heap_base
   1288     ld_r0,r0,0
   1289     li_br &mark_value_done
   1290     blt_r1,r0
   1291     li_r0 &pair_heap_next
   1292     ld_r0,r0,0
   1293     li_br &mark_value_pair_inb
   1294     blt_r1,r0
   1295     li_br &mark_value_done
   1296     b
   1297 :mark_value_pair_inb
   1298     st_r1,sp,24                      ## slot 1 = raw pair ptr
   1299     li_br &pair_mark_seen_or_set
   1300     call
   1301     li_br &mark_value_done
   1302     bnez_r0
   1303 
   1304     ld_r1,sp,24
   1305     ld_r0,r1,0
   1306     st_r0,sp,32                      ## slot 2 = car
   1307     ld_r1,r1,8
   1308     li_br &mark_push
   1309     call
   1310     ld_r1,sp,32
   1311     li_br &mark_push
   1312     call
   1313     li_br &mark_value_done
   1314     b
   1315 
   1316 ## Same bounds-check rationale as mark_value_pair. Reject anything
   1317 ## outside [obj_heap_start, obj_heap_next) before touching the header
   1318 ## byte, so a stale raw pointer in a frame slot can't crash the marker.
   1319 :mark_value_object
   1320     andi_r0,r1,7
   1321     sub_r1,r1,r0                     ## raw object ptr
   1322     li_r0 &obj_heap_base
   1323     ld_r0,r0,0
   1324     li_br &mark_value_done
   1325     blt_r1,r0
   1326     li_r0 &obj_heap_next
   1327     ld_r0,r0,0
   1328     li_br &mark_value_object_inb
   1329     blt_r1,r0
   1330     li_br &mark_value_done
   1331     b
   1332 :mark_value_object_inb
   1333     st_r1,sp,24                      ## slot 1 = raw object ptr
   1334     li_br &obj_mark_seen_or_set
   1335     call
   1336     li_br &mark_value_done
   1337     bnez_r0
   1338 
   1339     ld_r1,sp,24
   1340     lb_r0,r1,7
   1341     li_r2 %3
   1342     li_br &mark_value_vector
   1343     beq_r0,r2
   1344     li_r2 %4
   1345     li_br &mark_value_closure
   1346     beq_r0,r2
   1347     li_br &mark_value_done
   1348     b
   1349 
   1350 :mark_value_vector
   1351     ld_r0,r1,0
   1352     shli_r0,r0,16
   1353     shri_r0,r0,16
   1354     addi_r2,r1,8
   1355     st_r2,sp,24                      ## slot 1 = cursor
   1356     st_r0,sp,32                      ## slot 2 = remaining
   1357 
   1358 :mark_value_vector_loop
   1359     ld_r0,sp,32
   1360     li_br &mark_value_done
   1361     beqz_r0
   1362     ld_r2,sp,24
   1363     ld_r1,r2,0
   1364     li_br &mark_push
   1365     call
   1366     ld_r2,sp,24
   1367     addi_r2,r2,8
   1368     st_r2,sp,24
   1369     ld_r0,sp,32
   1370     addi_r0,r0,neg1
   1371     st_r0,sp,32
   1372     li_br &mark_value_vector_loop
   1373     b
   1374 
   1375 :mark_value_closure
   1376     ld_r0,r1,16
   1377     st_r0,sp,32                      ## slot 2 = body
   1378     ld_r0,r1,24
   1379     st_r0,sp,40                      ## slot 3 = env
   1380     ld_r1,r1,8
   1381     li_br &mark_push
   1382     call
   1383     ld_r1,sp,32
   1384     li_br &mark_push
   1385     call
   1386     ld_r1,sp,40
   1387     li_br &mark_push
   1388     call
   1389 
   1390 :mark_value_done
   1391     epilogue_n4
   1392     ret
   1393 
   1394 
   1395 ## ---- mark_frame_slots(raw frame ptr) --------------------------------
   1396 :mark_frame_slots
   1397     prologue_n2
   1398     ld_r0,r1,16
   1399     addi_r2,r1,24
   1400     st_r2,sp,24                      ## slot 1 = cursor
   1401     st_r0,sp,32                      ## slot 2 = remaining
   1402 
   1403 :mark_frame_slots_loop
   1404     ld_r0,sp,32
   1405     li_br &mark_frame_slots_done
   1406     beqz_r0
   1407     ld_r2,sp,24
   1408     ld_r1,r2,0
   1409     li_br &mark_value
   1410     call
   1411     ld_r2,sp,24
   1412     addi_r2,r2,8
   1413     st_r2,sp,24
   1414     ld_r0,sp,32
   1415     addi_r0,r0,neg1
   1416     st_r0,sp,32
   1417     li_br &mark_frame_slots_loop
   1418     b
   1419 
   1420 :mark_frame_slots_done
   1421     epilogue_n2
   1422     ret
   1423 
   1424 
   1425 ## ---- gc_mark_symbol_table() -----------------------------------------
   1426 :gc_mark_symbol_table
   1427     prologue
   1428     li_r1 &symbol_table
   1429     st_r1,sp,24
   1430 
   1431 :gc_mark_symbol_table_loop
   1432     ld_r2,sp,24
   1433     li_r0 &symbol_table_end
   1434     li_br &gc_mark_symbol_table_done
   1435     beq_r2,r0
   1436     ld_r1,r2,0
   1437     li_br &mark_value
   1438     call
   1439     ld_r2,sp,24
   1440     addi_r2,r2,8
   1441     st_r2,sp,24
   1442     li_br &gc_mark_symbol_table_loop
   1443     b
   1444 
   1445 :gc_mark_symbol_table_done
   1446     epilogue
   1447     ret
   1448 
   1449 
   1450 ## ---- gc_mark_prim_argv() --------------------------------------------
   1451 ## Bound by :prim_argc, the slot count marshal_argv last published.
   1452 ## Walking past it would re-mark stale tagged values left over from
   1453 ## earlier primitive calls, falsely keeping the objects they pointed
   1454 ## to alive across an arbitrary number of GCs.
   1455 :gc_mark_prim_argv
   1456     prologue_n2
   1457     li_r1 &prim_argv
   1458     st_r1,sp,24                      ## slot 1 = cursor
   1459     li_r1 &prim_argc
   1460     ld_r1,r1,0
   1461     st_r1,sp,32                      ## slot 2 = remaining slots
   1462 
   1463 :gc_mark_prim_argv_loop
   1464     ld_r0,sp,32
   1465     li_br &gc_mark_prim_argv_done
   1466     beqz_r0
   1467     ld_r2,sp,24
   1468     ld_r1,r2,0
   1469     li_br &mark_value
   1470     call
   1471     ld_r2,sp,24
   1472     addi_r2,r2,8
   1473     st_r2,sp,24
   1474     ld_r0,sp,32
   1475     addi_r0,r0,neg1
   1476     st_r0,sp,32
   1477     li_br &gc_mark_prim_argv_loop
   1478     b
   1479 
   1480 :gc_mark_prim_argv_done
   1481     epilogue_n2
   1482     ret
   1483 
   1484 
   1485 ## ---- gc_mark_stack_roots() ------------------------------------------
   1486 :gc_mark_stack_roots
   1487     prologue_n2
   1488     li_r1 &gc_root_fp
   1489     ld_r1,r1,0
   1490     st_r1,sp,24                      ## slot 1 = current frame ptr
   1491     li_r1 &stack_bottom_fp
   1492     ld_r1,r1,0
   1493     st_r1,sp,32                      ## slot 2 = bottom caller sp
   1494 
   1495 :gc_mark_stack_roots_loop
   1496     ld_r1,sp,24
   1497     li_br &gc_mark_stack_roots_done
   1498     beqz_r1
   1499     li_br &mark_frame_slots
   1500     call
   1501     ld_r1,sp,24
   1502     ld_r1,r1,8
   1503     st_r1,sp,24
   1504     ld_r0,sp,32
   1505     li_br &gc_mark_stack_roots_done
   1506     beq_r1,r0
   1507     li_br &gc_mark_stack_roots_loop
   1508     b
   1509 
   1510 :gc_mark_stack_roots_done
   1511     epilogue_n2
   1512     ret
   1513 
   1514 
   1515 ## ---- gc_mark_all() ---------------------------------------------------
   1516 :gc_mark_all
   1517     prologue
   1518     li_r1 &mark_stack
   1519     li_r2 &mark_stack_next
   1520     st_r1,r2,0
   1521 
   1522     li_r1 &global_env_cell
   1523     ld_r1,r1,0
   1524     li_br &mark_value
   1525     call
   1526 
   1527     li_br &gc_mark_symbol_table
   1528     call
   1529     li_br &gc_mark_prim_argv
   1530     call
   1531     li_br &gc_mark_stack_roots
   1532     call
   1533 
   1534 :gc_mark_drain
   1535     li_r2 &mark_stack_next
   1536     ld_r0,r2,0
   1537     li_r1 &mark_stack
   1538     li_br &gc_mark_done
   1539     beq_r0,r1
   1540     addi_r0,r0,neg8
   1541     st_r0,r2,0
   1542     ld_r1,r0,0
   1543     li_br &mark_value
   1544     call
   1545     li_br &gc_mark_drain
   1546     b
   1547 
   1548 :gc_mark_done
   1549     epilogue
   1550     ret
   1551 
   1552 
   1553 ## ---- gc_clear_freelists() -------------------------------------------
   1554 :gc_clear_freelists
   1555     li_r0 %0
   1556     li_r1 &free_list_pair
   1557     st_r0,r1,0
   1558     li_r1 &free_list_obj16
   1559     st_r0,r1,0
   1560     li_r1 &free_list_obj24
   1561     st_r0,r1,0
   1562     li_r1 &free_list_obj32
   1563     st_r0,r1,0
   1564     li_r1 &free_list_obj40
   1565     st_r0,r1,0
   1566     li_r1 &free_list_obj48
   1567     st_r0,r1,0
   1568     li_r1 &free_list_obj56
   1569     st_r0,r1,0
   1570     li_r1 &free_list_obj64
   1571     st_r0,r1,0
   1572     li_r1 &free_list_obj80
   1573     st_r0,r1,0
   1574     li_r1 &free_list_obj96
   1575     st_r0,r1,0
   1576     li_r1 &free_list_obj128
   1577     st_r0,r1,0
   1578     ret
   1579 
   1580 
   1581 ## ---- gc_sweep_pair() -------------------------------------------------
   1582 :gc_sweep_pair
   1583     prologue
   1584     li_r1 &pair_heap_base
   1585     ld_r1,r1,0
   1586     st_r1,sp,24
   1587 
   1588 :gc_sweep_pair_loop
   1589     ld_r1,sp,24
   1590     li_r0 &pair_heap_next
   1591     ld_r0,r0,0
   1592     li_br &gc_sweep_pair_done
   1593     beq_r1,r0
   1594 
   1595     li_br &pair_mark_test
   1596     call
   1597     li_br &gc_sweep_pair_dead
   1598     beqz_r0
   1599 
   1600     ld_r1,sp,24
   1601     li_br &pair_mark_clear
   1602     call
   1603     li_br &gc_sweep_pair_advance
   1604     b
   1605 
   1606 :gc_sweep_pair_dead
   1607     ld_r1,sp,24
   1608     li_r2 &free_list_pair
   1609     ld_r0,r2,0
   1610     st_r0,r1,0
   1611     st_r1,r2,0
   1612 
   1613 :gc_sweep_pair_advance
   1614     ld_r1,sp,24
   1615     addi_r1,r1,16
   1616     st_r1,sp,24
   1617     li_br &gc_sweep_pair_loop
   1618     b
   1619 
   1620 :gc_sweep_pair_done
   1621     epilogue
   1622     ret
   1623 
   1624 
   1625 ## ---- gc_sweep_obj() --------------------------------------------------
   1626 :gc_sweep_obj
   1627     prologue_n4
   1628     li_r1 &obj_heap_base
   1629     ld_r1,r1,0
   1630     st_r1,sp,24                      ## slot 1 = current ptr
   1631 
   1632 :gc_sweep_obj_loop
   1633     ld_r1,sp,24
   1634     li_r0 &obj_heap_next
   1635     ld_r0,r0,0
   1636     li_br &gc_sweep_obj_done
   1637     beq_r1,r0
   1638 
   1639     li_br &obj_is_live_marked
   1640     call
   1641     li_br &gc_sweep_obj_dead
   1642     beqz_r0
   1643 
   1644     ld_r1,sp,24
   1645     lb_r0,r1,6
   1646     addi_r0,r0,neg1
   1647     sb_r0,r1,6
   1648     li_br &obj_size_bytes
   1649     call
   1650     ld_r1,sp,24
   1651     add_r1,r1,r0
   1652     st_r1,sp,24
   1653     li_br &gc_sweep_obj_loop
   1654     b
   1655 
   1656 :gc_sweep_obj_dead
   1657     ld_r1,sp,24
   1658     li_br &obj_size_bytes
   1659     call
   1660     ld_r1,sp,24
   1661     add_r2,r1,r0
   1662     st_r2,sp,32                      ## slot 2 = run end
   1663 
   1664 :gc_sweep_obj_dead_scan
   1665     ld_r2,sp,32
   1666     li_r0 &obj_heap_next
   1667     ld_r0,r0,0
   1668     li_br &gc_sweep_obj_tail_dead
   1669     beq_r2,r0
   1670 
   1671     mov_r1,r2
   1672     li_br &obj_is_live_marked
   1673     call
   1674     li_br &gc_sweep_obj_dead_run_ready
   1675     bnez_r0
   1676 
   1677     ld_r1,sp,32
   1678     li_br &obj_size_bytes
   1679     call
   1680     ld_r2,sp,32
   1681     add_r2,r2,r0
   1682     st_r2,sp,32
   1683     li_br &gc_sweep_obj_dead_scan
   1684     b
   1685 
   1686 :gc_sweep_obj_tail_dead
   1687     ld_r1,sp,24
   1688     li_r0 &obj_heap_next
   1689     st_r1,r0,0
   1690     li_br &gc_sweep_obj_done
   1691     b
   1692 
   1693 :gc_sweep_obj_dead_run_ready
   1694     ld_r1,sp,32
   1695     ld_r2,sp,24
   1696     sub_r1,r1,r2                     ## r1 = run size
   1697     st_r1,sp,40                      ## slot 3 = run size
   1698     li_br &obj_freelist_for_size
   1699     call
   1700     st_r0,sp,48                      ## slot 4 = &free_list head or 0
   1701 
   1702     li_br &gc_sweep_obj_write_pseudo
   1703     beqz_r0
   1704 
   1705     ld_r2,sp,24                      ## run start
   1706     ld_r1,sp,40                      ## run size
   1707     st_r1,r2,0
   1708     ld_r3,sp,48
   1709     ld_r0,r3,0
   1710     st_r0,r2,8
   1711     st_r2,r3,0
   1712     li_br &gc_sweep_obj_advance
   1713     b
   1714 
   1715 :gc_sweep_obj_write_pseudo
   1716     ld_r2,sp,24
   1717     ld_r1,sp,40
   1718     st_r1,r2,0
   1719 
   1720 :gc_sweep_obj_advance
   1721     ld_r1,sp,32
   1722     st_r1,sp,24
   1723     li_br &gc_sweep_obj_loop
   1724     b
   1725 
   1726 :gc_sweep_obj_done
   1727     epilogue_n4
   1728     ret
   1729 
   1730 
   1731 ## ---- gc_sweep_all() --------------------------------------------------
   1732 :gc_sweep_all
   1733     prologue
   1734     li_br &gc_clear_freelists
   1735     call
   1736     li_br &gc_sweep_pair
   1737     call
   1738     li_br &gc_sweep_obj
   1739     call
   1740     epilogue
   1741     ret
   1742 
   1743 
   1744 ## ---- gc() ------------------------------------------------------------
   1745 :gc
   1746     prologue
   1747     ld_r1,sp,8                       ## metadata: caller frame ptr
   1748     li_r2 &gc_root_fp
   1749     st_r1,r2,0
   1750     li_br &gc_mark_all
   1751     call
   1752     li_br &gc_sweep_all
   1753     call
   1754     epilogue
   1755     ret
   1756 
   1757 
   1758 ## ---- error(msg_ptr, msg_len) ----------------------------------------
   1759 :error
   1760     mov_r6,r1
   1761     mov_r7,r2
   1762 
   1763     li_r0 sys_write
   1764     li_r1 %2
   1765     li_r2 &msg_error_prefix
   1766     li_r3 %7
   1767     syscall
   1768 
   1769     li_r0 sys_write
   1770     li_r1 %2
   1771     mov_r2,r6
   1772     mov_r3,r7
   1773     syscall
   1774 
   1775     li_r0 sys_write
   1776     li_r1 %2
   1777     li_r2 &msg_newline
   1778     li_r3 %1
   1779     syscall
   1780 
   1781     li_r0 sys_exit
   1782     li_r1 %1
   1783     syscall
   1784 
   1785 
   1786 ## ---- Error landing pads ---------------------------------------------
   1787 :alloc_oom
   1788     li_r1 &msg_oom
   1789     li_r2 %14
   1790     li_br &error
   1791     b
   1792 
   1793 :err_intern_not_eq
   1794     li_r1 &msg_intern_not_eq
   1795     li_r2 %34  ## strlen == 34
   1796     li_br &error
   1797     b
   1798 
   1799 :err_intern_collision
   1800     li_r1 &msg_intern_collision
   1801     li_r2 %44  ## strlen == 44
   1802     li_br &error
   1803     b
   1804 
   1805 :err_usage
   1806     li_r1 &msg_usage
   1807     li_r2 %22  ## strlen("usage: lisp <file.scm>") == 22
   1808     li_br &error
   1809     b
   1810 
   1811 :err_open
   1812     li_r1 &msg_open_fail
   1813     li_r2 %26  ## strlen("failed to open source file") == 26
   1814     li_br &error
   1815     b
   1816 
   1817 :err_src_too_big
   1818     li_r1 &msg_src_too_big
   1819     li_r2 %21  ## strlen("source file too large") == 21
   1820     li_br &error
   1821     b
   1822 
   1823 ## err_reader_bad: "error: reader: malformed input at LINE:COL\n" → stderr.
   1824 ## Doesn't go through :error because we want line:col tacked on; easier to
   1825 ## inline the syscalls than thread a format through error().
   1826 :err_reader_bad
   1827     ## "error: "
   1828     li_r0 sys_write
   1829     li_r1 %2
   1830     li_r2 &msg_error_prefix
   1831     li_r3 %7
   1832     syscall
   1833 
   1834     ## "reader: malformed input"
   1835     li_r0 sys_write
   1836     li_r1 %2
   1837     li_r2 &msg_reader_bad
   1838     li_r3 %23  ## strlen("reader: malformed input") == 23
   1839     syscall
   1840 
   1841     ## " at "
   1842     li_r0 sys_write
   1843     li_r1 %2
   1844     li_r2 &msg_at
   1845     li_r3 %4
   1846     syscall
   1847 
   1848     ## LINE (via display_uint to stderr)
   1849     li_r1 &src_line
   1850     ld_r1,r1,0
   1851     li_r2 %2
   1852     li_br &display_uint
   1853     call
   1854 
   1855     ## ":"
   1856     li_r0 sys_write
   1857     li_r1 %2
   1858     li_r2 &msg_colon
   1859     li_r3 %1
   1860     syscall
   1861 
   1862     ## COL
   1863     li_r1 &src_col
   1864     ld_r1,r1,0
   1865     li_r2 %2
   1866     li_br &display_uint
   1867     call
   1868 
   1869     ## "\n"
   1870     li_r0 sys_write
   1871     li_r1 %2
   1872     li_r2 &msg_newline
   1873     li_r3 %1
   1874     syscall
   1875 
   1876     li_r0 sys_exit
   1877     li_r1 %1
   1878     syscall
   1879 
   1880 
   1881 ## ================ Step 4: reader + minimal display ==================
   1882 ## Recursive-descent over an in-memory source string at src_base with
   1883 ## length src_len. Read one expression. Supports: lists, positive
   1884 ## decimal fixnums, symbols (interned). Skips whitespace and `;` line
   1885 ## comments. No quote, strings, source-locations, or improper lists
   1886 ## in this round — those land in later steps.
   1887 
   1888 
   1889 ## ---- Reader state (globals) -----------------------------------------
   1890 ## src_base/src_len are set by _start after it reads argv[1] into
   1891 ## src_buf. src_cursor starts at 0; src_line/src_col start at 1.
   1892 :src_base  %0 %0
   1893 :src_len  %0 %0
   1894 :src_cursor  %0 %0
   1895 :src_line  %1 %0
   1896 :src_col  %1 %0
   1897 
   1898 
   1899 ## ---- peek_char() -> r0 = char (0..255) or -1 on EOF ----------------
   1900 :peek_char
   1901     li_r1 &src_cursor
   1902     ld_r1,r1,0                       ## r1 = cursor
   1903     li_r2 &src_len
   1904     ld_r2,r2,0                       ## r2 = len
   1905     li_br &peek_char_inb
   1906     blt_r1,r2                        ## cursor < len → in-bounds
   1907     li_r0 %0
   1908     addi_r0,r0,neg1                  ## r0 = -1
   1909     ret
   1910 :peek_char_inb
   1911     li_r2 &src_base
   1912     ld_r2,r2,0                       ## r2 = base
   1913     add_r2,r2,r1                     ## r2 = base + cursor
   1914     lb_r0,r2,0                       ## r0 = *ptr (0-ext byte)
   1915     ret
   1916 
   1917 
   1918 ## ---- peek2_char() -> r0 = char at cursor+1 (0..255) or -1 on EOF ----
   1919 ## Used by the reader to distinguish `,` vs `,@`, `-` vs `-<digit>`, and
   1920 ## `0` vs `0x`/`0X` without consuming input.
   1921 :peek2_char
   1922     li_r1 &src_cursor
   1923     ld_r1,r1,0
   1924     addi_r1,r1,1                     ## r1 = cursor + 1
   1925     li_r2 &src_len
   1926     ld_r2,r2,0
   1927     li_br &peek2_char_inb
   1928     blt_r1,r2
   1929     li_r0 %0
   1930     addi_r0,r0,neg1
   1931     ret
   1932 :peek2_char_inb
   1933     li_r2 &src_base
   1934     ld_r2,r2,0
   1935     add_r2,r2,r1
   1936     lb_r0,r2,0
   1937     ret
   1938 
   1939 
   1940 ## ---- advance_char() — consume current char, track line/col --------
   1941 :advance_char
   1942     prologue
   1943     li_br &peek_char
   1944     call                             ## r0 = current char (possibly -1 if EOF)
   1945 
   1946     ## cursor++
   1947     li_r1 &src_cursor
   1948     ld_r2,r1,0
   1949     addi_r2,r2,1
   1950     st_r2,r1,0
   1951 
   1952     ## if char == '\n', bump line and reset col; else col++.
   1953     li_r1 %10  ## '\n' = 10
   1954     li_br &advance_newline
   1955     beq_r0,r1
   1956 
   1957     li_r1 &src_col
   1958     ld_r2,r1,0
   1959     addi_r2,r2,1
   1960     st_r2,r1,0
   1961     epilogue
   1962     ret
   1963 
   1964 :advance_newline
   1965     li_r1 &src_line
   1966     ld_r2,r1,0
   1967     addi_r2,r2,1
   1968     st_r2,r1,0
   1969     li_r1 &src_col
   1970     li_r2 %1
   1971     st_r2,r1,0
   1972     epilogue
   1973     ret
   1974 
   1975 
   1976 ## ---- skip_ws() — eat whitespace and ; line comments ----------------
   1977 :skip_ws
   1978     prologue
   1979 :skip_ws_loop
   1980     li_br &peek_char
   1981     call                             ## r0 = next char (or -1)
   1982 
   1983     ## EOF → done
   1984     li_r1 %0
   1985     addi_r1,r1,neg1
   1986     li_br &skip_ws_done
   1987     beq_r0,r1
   1988 
   1989     ## ' ' (0x20)
   1990     li_r1 %32
   1991     li_br &skip_ws_eat
   1992     beq_r0,r1
   1993     ## '\t' (0x09)
   1994     li_r1 %9
   1995     li_br &skip_ws_eat
   1996     beq_r0,r1
   1997     ## '\n' (0x0A)
   1998     li_r1 %10
   1999     li_br &skip_ws_eat
   2000     beq_r0,r1
   2001     ## '\r' (0x0D)
   2002     li_r1 %13
   2003     li_br &skip_ws_eat
   2004     beq_r0,r1
   2005     ## ';' (0x3B)
   2006     li_r1 %59
   2007     li_br &skip_ws_comment
   2008     beq_r0,r1
   2009 
   2010     li_br &skip_ws_done
   2011     b
   2012 
   2013 :skip_ws_eat
   2014     li_br &advance_char
   2015     call
   2016     li_br &skip_ws_loop
   2017     b
   2018 
   2019 :skip_ws_comment
   2020     li_br &advance_char
   2021     call                             ## eat ';'
   2022 :skip_ws_comment_loop
   2023     li_br &peek_char
   2024     call
   2025     ## EOF → outer loop (will see -1 and exit)
   2026     li_r1 %0
   2027     addi_r1,r1,neg1
   2028     li_br &skip_ws_loop
   2029     beq_r0,r1
   2030     ## '\n' → eat and outer loop
   2031     li_r1 %10
   2032     li_br &skip_ws_eat
   2033     beq_r0,r1
   2034     ## else eat and keep going
   2035     li_br &advance_char
   2036     call
   2037     li_br &skip_ws_comment_loop
   2038     b
   2039 
   2040 :skip_ws_done
   2041     epilogue
   2042     ret
   2043 
   2044 
   2045 ## ---- is_digit(c) -> r0 = 0 or 1 ------------------------------------
   2046 ## Character already in r1.
   2047 :is_digit
   2048     li_r2 %48  ## '0' = 48
   2049     li_r0 %0  ## default: not digit
   2050     li_br &is_digit_done
   2051     blt_r1,r2                        ## c < '0' → not digit
   2052     li_r2 %58  ## '9' + 1 = 58 (:)
   2053     li_br &is_digit_yes
   2054     blt_r1,r2                        ## c < ':' → in range
   2055     li_br &is_digit_done
   2056     b
   2057 :is_digit_yes
   2058     li_r0 %1
   2059 :is_digit_done
   2060     ret
   2061 
   2062 
   2063 ## ---- read_number() -> r0 = tagged fixnum ----------------------------
   2064 ## Reads a non-negative decimal integer, or a hex integer with `0x`/`0X`
   2065 ## prefix. Digits are consumed while peek_char is in [0-9] (or [0-9a-fA-F]
   2066 ## after the hex prefix). Uses r6 = accumulator across iterations (saved
   2067 ## via PROLOGUE_N1 slot 1).
   2068 :read_number
   2069     prologue
   2070     mov_r3,sp
   2071     st_r6,sp,24
   2072 
   2073     li_r6 %0  ## r6 = 0 (accumulator)
   2074 
   2075     ## Detect `0x` / `0X` hex prefix — only triggers if the very first
   2076     ## byte is '0' and the next is 'x' or 'X'. Anything else falls
   2077     ## through to the decimal digit loop (including bare `0` or `07`).
   2078     li_br &peek_char
   2079     call
   2080     li_r1 %48  ## '0'
   2081     li_br &read_number_loop
   2082     bne_r0,r1
   2083     li_br &peek2_char
   2084     call
   2085     li_r1 %120  ## 'x'
   2086     li_br &read_number_hex_start
   2087     beq_r0,r1
   2088     li_r1 %88  ## 'X'
   2089     li_br &read_number_hex_start
   2090     beq_r0,r1
   2091     li_br &read_number_loop
   2092     b
   2093 
   2094 :read_number_hex_start
   2095     li_br &advance_char
   2096     call                             ## eat '0'
   2097     li_br &advance_char
   2098     call                             ## eat 'x' / 'X'
   2099 
   2100 :read_number_hex_loop
   2101     li_br &peek_char
   2102     call
   2103     mov_r1,r0
   2104     li_br &hex_digit_val
   2105     call                             ## r0 = digit (0..15) or -1
   2106     li_r1 %0
   2107     addi_r1,r1,neg1
   2108     li_br &read_number_done
   2109     beq_r0,r1
   2110 
   2111     ## r6 = r6 * 16 + digit. ADDI_IMMS lacks 4-shift, so SHLI 2 twice.
   2112     shli_r6,r6,2
   2113     shli_r6,r6,2
   2114     add_r6,r6,r0
   2115 
   2116     li_br &advance_char
   2117     call
   2118     li_br &read_number_hex_loop
   2119     b
   2120 
   2121 :read_number_loop
   2122     li_br &peek_char
   2123     call                             ## r0 = char (or -1)
   2124 
   2125     ## If char not a digit, stop.
   2126     mov_r1,r0
   2127     li_br &is_digit
   2128     call                             ## r0 = 0 or 1
   2129     li_br &read_number_done
   2130     beqz_r0
   2131 
   2132     ## digit = peek - '0'. Re-peek (simpler than stashing).
   2133     li_br &peek_char
   2134     call
   2135     addi_r0,r0,neg48                 ## r0 = digit (0..9)
   2136 
   2137     ## r6 = r6 * 10 + digit. Do via (r6 << 3) + (r6 << 1).
   2138     mov_r1,r6
   2139     shli_r1,r6,3                     ## r1 = r6 << 3 = r6*8
   2140     mov_r2,r6
   2141     shli_r2,r6,1                     ## r2 = r6 << 1 = r6*2
   2142     add_r6,r1,r2                     ## r6 = r6*10
   2143     add_r6,r6,r0                     ## r6 = r6*10 + digit
   2144 
   2145     li_br &advance_char
   2146     call
   2147     li_br &read_number_loop
   2148     b
   2149 
   2150 :read_number_done
   2151     ## Tag as fixnum (low 3 bits = 001 = shift-left-3 + OR 1).
   2152     shli_r0,r6,3
   2153     ori_r0,r0,1
   2154     ld_r6,sp,24
   2155     epilogue
   2156     ret
   2157 
   2158 
   2159 ## ---- hex_digit_val(r1=c) -> r0 = digit in [0..15] or -1 ------------
   2160 ## Classifies one byte as a hex digit. Range-checks via BLT then does
   2161 ## an arithmetic subtract: digits subtract '0', A-F subtract 55, a-f
   2162 ## subtract 87. Non-hex chars (including EOF=-1) return -1.
   2163 :hex_digit_val
   2164     li_r2 %48  ## '0'
   2165     li_br &hex_dv_none
   2166     blt_r1,r2
   2167     li_r2 %58  ## ':' (one past '9')
   2168     li_br &hex_dv_dec
   2169     blt_r1,r2
   2170     li_r2 %65  ## 'A'
   2171     li_br &hex_dv_none
   2172     blt_r1,r2
   2173     li_r2 %71  ## 'G' (one past 'F')
   2174     li_br &hex_dv_upper
   2175     blt_r1,r2
   2176     li_r2 %97  ## 'a'
   2177     li_br &hex_dv_none
   2178     blt_r1,r2
   2179     li_r2 %103  ## 'g' (one past 'f')
   2180     li_br &hex_dv_lower
   2181     blt_r1,r2
   2182 :hex_dv_none
   2183     li_r0 %0
   2184     addi_r0,r0,neg1
   2185     ret
   2186 :hex_dv_dec
   2187     mov_r0,r1
   2188     addi_r0,r0,neg48
   2189     ret
   2190 :hex_dv_upper
   2191     li_r0 %55  ## 'A' - 10 = 55
   2192     sub_r0,r1,r0
   2193     ret
   2194 :hex_dv_lower
   2195     li_r0 %87  ## 'a' - 10 = 87
   2196     sub_r0,r1,r0
   2197     ret
   2198 
   2199 
   2200 ## ---- is_delim(c) -> r0 = 0 or 1 ------------------------------------
   2201 ## Returns 1 if c is a token delimiter: whitespace, '(', ')', ';', or EOF.
   2202 :is_delim
   2203     li_r0 %1  ## optimistic: delim
   2204     li_r2 %0
   2205     addi_r2,r2,neg1
   2206     li_br &is_delim_done
   2207     beq_r1,r2                        ## EOF
   2208     li_r2 %32
   2209     li_br &is_delim_done
   2210     beq_r1,r2
   2211     li_r2 %9
   2212     li_br &is_delim_done
   2213     beq_r1,r2
   2214     li_r2 %10
   2215     li_br &is_delim_done
   2216     beq_r1,r2
   2217     li_r2 %13
   2218     li_br &is_delim_done
   2219     beq_r1,r2
   2220     li_r2 %40  ## '('
   2221     li_br &is_delim_done
   2222     beq_r1,r2
   2223     li_r2 %41  ## ')'
   2224     li_br &is_delim_done
   2225     beq_r1,r2
   2226     li_r2 %59  ## ';'
   2227     li_br &is_delim_done
   2228     beq_r1,r2
   2229     li_r0 %0
   2230 :is_delim_done
   2231     ret
   2232 
   2233 
   2234 ## ---- read_symbol() -> r0 = tagged symbol --------------------------
   2235 ## Reads until the next delimiter, then calls intern on the byte range.
   2236 ## Uses r6 = start cursor, r7 = start pointer (both saved across loop).
   2237 :read_symbol
   2238     prologue_n2
   2239     mov_r3,sp
   2240     st_r6,sp,24
   2241     st_r7,sp,32
   2242 
   2243     ## r7 = &src[cursor] (stored start)
   2244     li_r1 &src_cursor
   2245     ld_r1,r1,0
   2246     li_r2 &src_base
   2247     ld_r2,r2,0
   2248     add_r7,r1,r2                     ## r7 = base + cursor
   2249     mov_r6,r1                        ## r6 = start cursor
   2250 
   2251 :read_symbol_loop
   2252     li_br &peek_char
   2253     call
   2254     mov_r1,r0
   2255     li_br &is_delim
   2256     call
   2257     li_r1 %1
   2258     li_br &read_symbol_done
   2259     beq_r0,r1                        ## delim → stop
   2260 
   2261     li_br &advance_char
   2262     call
   2263     li_br &read_symbol_loop
   2264     b
   2265 
   2266 :read_symbol_done
   2267     ## Length = current cursor - r6.
   2268     li_r1 &src_cursor
   2269     ld_r1,r1,0
   2270     sub_r2,r1,r6                     ## r2 = cur - start = len
   2271 
   2272     ## If length 0, malformed (shouldn't happen — caller peeks before).
   2273     li_br &err_reader_bad
   2274     beqz_r2
   2275 
   2276     mov_r1,r7                        ## r1 = ptr to first byte
   2277     ## r2 = len (already set)
   2278     li_br &intern
   2279     call
   2280 
   2281     ld_r6,sp,24
   2282     ld_r7,sp,32
   2283     epilogue_n2
   2284     ret
   2285 
   2286 
   2287 ## ---- read_string() -> r0 = tagged string --------------------------
   2288 ## Reads a double-quoted string into a scratch buffer, handling the
   2289 ## four escape forms the seed needs: \n, \t, \\, and \".
   2290 :read_string
   2291     prologue_n2
   2292     st_r6,sp,24
   2293     st_r7,sp,32
   2294 
   2295     li_br &advance_char
   2296     call                             ## eat opening '"'
   2297 
   2298     li_r6 &reader_string_buf
   2299     li_r7 %0  ## length
   2300 
   2301 :read_string_loop
   2302     li_br &peek_char
   2303     call
   2304     li_r1 %0
   2305     addi_r1,r1,neg1
   2306     li_br &err_reader_bad
   2307     beq_r0,r1                        ## EOF in string
   2308 
   2309     li_r1 %34
   2310     li_br &read_string_done
   2311     beq_r0,r1                        ## closing '"'
   2312 
   2313     li_r1 %92
   2314     li_br &read_string_escape
   2315     beq_r0,r1
   2316 
   2317     li_r1 %512  ## READER_STRING_BUF_SIZE = 512
   2318     li_br &read_string_store
   2319     blt_r7,r1
   2320     li_br &err_reader_bad
   2321     b
   2322 
   2323 :read_string_store
   2324     sb_r0,r6,0
   2325     addi_r6,r6,1
   2326     addi_r7,r7,1
   2327     li_br &advance_char
   2328     call
   2329     li_br &read_string_loop
   2330     b
   2331 
   2332 :read_string_escape
   2333     li_br &advance_char
   2334     call                             ## eat '\'
   2335     li_br &peek_char
   2336     call
   2337     li_r1 %0
   2338     addi_r1,r1,neg1
   2339     li_br &err_reader_bad
   2340     beq_r0,r1
   2341 
   2342     li_r1 %110  ## 'n'
   2343     li_br &read_string_escape_nl
   2344     beq_r0,r1
   2345     li_r1 %116  ## 't'
   2346     li_br &read_string_escape_tab
   2347     beq_r0,r1
   2348     li_r1 %92  ## '\\'
   2349     li_br &read_string_store_escaped
   2350     beq_r0,r1
   2351     li_r1 %34  ## '"'
   2352     li_br &read_string_store_escaped
   2353     beq_r0,r1
   2354     li_br &err_reader_bad
   2355     b
   2356 
   2357 :read_string_escape_nl
   2358     li_r0 %10
   2359     li_br &read_string_store_escaped
   2360     b
   2361 
   2362 :read_string_escape_tab
   2363     li_r0 %9
   2364 
   2365 :read_string_store_escaped
   2366     li_r1 %4096
   2367     li_br &read_string_store_escaped_ok
   2368     blt_r7,r1
   2369     li_br &err_reader_bad
   2370     b
   2371 
   2372 :read_string_store_escaped_ok
   2373     sb_r0,r6,0
   2374     addi_r6,r6,1
   2375     addi_r7,r7,1
   2376     li_br &advance_char
   2377     call                             ## eat escaped payload char
   2378     li_br &read_string_loop
   2379     b
   2380 
   2381 :read_string_done
   2382     li_br &advance_char
   2383     call                             ## eat closing '"'
   2384     li_r1 &reader_string_buf
   2385     mov_r2,r7
   2386     li_br &make_string
   2387     call
   2388 
   2389     ld_r6,sp,24
   2390     ld_r7,sp,32
   2391     epilogue_n2
   2392     ret
   2393 
   2394 
   2395 ## ---- read_list() -> r0 = tagged list --------------------------------
   2396 ## Assumes the '(' has already been consumed. Reads expressions until ')'.
   2397 ## Builds a list in order using a reversed cons chain, then reverses.
   2398 ## Simpler approach here: recursive — read one expr, then cons onto the
   2399 ## read_list tail. Stack-bounded but OK for shallow depths.
   2400 ##
   2401 ## Frame: PROLOGUE_N1 with slot 1 = saved r6 (first element tagged value).
   2402 :read_list
   2403     prologue
   2404 
   2405     li_br &skip_ws
   2406     call
   2407 
   2408     ## If next char is ')', return nil (end of list).
   2409     li_br &peek_char
   2410     call
   2411     li_r1 %41  ## ')'
   2412     li_br &read_list_close
   2413     beq_r0,r1
   2414 
   2415     ## EOF mid-list → error.
   2416     li_r1 %0
   2417     addi_r1,r1,neg1
   2418     li_br &err_reader_bad
   2419     beq_r0,r1
   2420 
   2421     ## '.' followed by a delimiter → dotted tail. `.<alpha>` still
   2422     ## parses as a regular symbol (fall-through to read_expr).
   2423     li_r1 %46  ## '.'
   2424     li_br &read_list_maybe_dotted
   2425     beq_r0,r1
   2426 
   2427     ## Read head element, then tail, then cons them.
   2428     li_br &read_expr
   2429     call                             ## r0 = head
   2430 
   2431     st_r0,sp,24                       ## slot 1 = head (spill across next call)
   2432 
   2433     li_br &read_list
   2434     call                             ## r0 = tail (recursive)
   2435 
   2436     ## cons(head, tail)
   2437     ld_r1,sp,24
   2438     mov_r2,r0
   2439     li_br &cons
   2440     call
   2441 
   2442     epilogue
   2443     ret
   2444 
   2445 :read_list_maybe_dotted
   2446     li_br &peek2_char
   2447     call
   2448     mov_r1,r0
   2449     li_br &is_delim
   2450     call
   2451     li_r1 %1
   2452     li_br &read_list_dotted
   2453     beq_r0,r1
   2454 
   2455     ## Bare `.` followed by non-delim → fall back to regular item path.
   2456     li_br &read_expr
   2457     call
   2458     st_r0,sp,24
   2459     li_br &read_list
   2460     call
   2461     ld_r1,sp,24
   2462     mov_r2,r0
   2463     li_br &cons
   2464     call
   2465     epilogue
   2466     ret
   2467 
   2468 :read_list_dotted
   2469     li_br &advance_char
   2470     call                             ## eat '.'
   2471     li_br &skip_ws
   2472     call
   2473     li_br &read_expr
   2474     call                             ## r0 = dotted tail expr
   2475     st_r0,sp,24
   2476     li_br &skip_ws
   2477     call
   2478     li_br &peek_char
   2479     call
   2480     li_r1 %41  ## ')'
   2481     li_br &err_reader_bad
   2482     bne_r0,r1
   2483     li_br &advance_char
   2484     call                             ## eat ')'
   2485     ld_r0,sp,24
   2486     epilogue
   2487     ret
   2488 
   2489 :read_list_close
   2490     li_br &advance_char
   2491     call                             ## eat ')'
   2492     li_r0 NIL  ## nil = 0x07
   2493     epilogue
   2494     ret
   2495 
   2496 
   2497 ## ---- read_expr() -> r0 = tagged value -------------------------------
   2498 ## Dispatches on the first non-whitespace char.
   2499 :read_expr
   2500     prologue
   2501 
   2502     li_br &skip_ws
   2503     call
   2504 
   2505     li_br &peek_char
   2506     call                             ## r0 = char
   2507 
   2508     ## EOF → error.
   2509     li_r1 %0
   2510     addi_r1,r1,neg1
   2511     li_br &err_reader_bad
   2512     beq_r0,r1
   2513 
   2514     ## '(' → list.
   2515     li_r1 %40
   2516     li_br &read_expr_list
   2517     beq_r0,r1
   2518 
   2519     ## '"' → string literal.
   2520     li_r1 %34
   2521     li_br &read_expr_string
   2522     beq_r0,r1
   2523 
   2524     ## '#' → hash-prefix literal (#t, #f, #\char, #(...)).
   2525     li_r1 %35
   2526     li_br &read_expr_hash
   2527     beq_r0,r1
   2528 
   2529     ## '\'' → (quote x)
   2530     li_r1 %39
   2531     li_br &read_expr_quote
   2532     beq_r0,r1
   2533 
   2534     ## '`' → (quasiquote x)
   2535     li_r1 %96
   2536     li_br &read_expr_quasiquote
   2537     beq_r0,r1
   2538 
   2539     ## ',' → (unquote x) or (unquote-splicing x) if followed by '@'
   2540     li_r1 %44
   2541     li_br &read_expr_unquote
   2542     beq_r0,r1
   2543 
   2544     ## '-' followed by a digit → negative fixnum literal. Plain '-'
   2545     ## still reads as a symbol (the subtraction primitive).
   2546     li_r1 %45
   2547     li_br &read_expr_maybe_neg
   2548     beq_r0,r1
   2549 
   2550     ## digit → number.
   2551     mov_r1,r0
   2552     li_br &is_digit
   2553     call
   2554     li_r1 %1
   2555     li_br &read_expr_number
   2556     beq_r0,r1
   2557 
   2558     ## else → symbol.
   2559     li_br &read_symbol
   2560     call
   2561     epilogue
   2562     ret
   2563 
   2564 :read_expr_list
   2565     li_br &advance_char
   2566     call                             ## eat '('
   2567     li_br &read_list
   2568     call
   2569     epilogue
   2570     ret
   2571 
   2572 :read_expr_number
   2573     li_br &read_number
   2574     call
   2575     epilogue
   2576     ret
   2577 
   2578 :read_expr_string
   2579     li_br &read_string
   2580     call
   2581     epilogue
   2582     ret
   2583 
   2584 ## Hash-prefix literals: `#t`/`#f` singletons, `#\char` ASCII fixnum,
   2585 ## `#(...)` vector literal. Inherits read_expr's PROLOGUE.
   2586 :read_expr_hash
   2587     li_br &advance_char
   2588     call                             ## eat '#'
   2589     li_br &peek_char
   2590     call                             ## r0 = next char
   2591 
   2592     li_r1 %116  ## 't'
   2593     li_br &read_expr_hash_t
   2594     beq_r0,r1
   2595     li_r1 %102  ## 'f'
   2596     li_br &read_expr_hash_f
   2597     beq_r0,r1
   2598     li_r1 %92  ## '\\'
   2599     li_br &read_expr_hash_char
   2600     beq_r0,r1
   2601     li_r1 %40  ## '('
   2602     li_br &read_expr_hash_vector
   2603     beq_r0,r1
   2604 
   2605     li_br &err_reader_bad
   2606     b
   2607 
   2608 :read_expr_hash_t
   2609     li_br &advance_char
   2610     call                             ## eat 't'
   2611     li_r0 TRUE  ## #t singleton
   2612     epilogue
   2613     ret
   2614 
   2615 :read_expr_hash_f
   2616     li_br &advance_char
   2617     call                             ## eat 'f'
   2618     li_r0 FALSE  ## #f singleton
   2619     epilogue
   2620     ret
   2621 
   2622 :read_expr_hash_char
   2623     li_br &read_char_literal
   2624     call
   2625     epilogue
   2626     ret
   2627 
   2628 :read_expr_hash_vector
   2629     li_br &read_vector_literal
   2630     call
   2631     epilogue
   2632     ret
   2633 
   2634 
   2635 ## ---- read_expr_quote / quasiquote / unquote / maybe_neg -------------
   2636 ## All inherit read_expr's PROLOGUE; each wraps the downstream value and
   2637 ## ret/epilogue out.
   2638 :read_expr_quote
   2639     li_br &advance_char
   2640     call                             ## eat '\''
   2641     li_r1 &sym_quote
   2642     ld_r1,r1,0
   2643     li_br &read_quoted_wrap
   2644     call
   2645     epilogue
   2646     ret
   2647 
   2648 :read_expr_quasiquote
   2649     li_br &advance_char
   2650     call                             ## eat '`'
   2651     li_r1 &sym_quasiquote
   2652     ld_r1,r1,0
   2653     li_br &read_quoted_wrap
   2654     call
   2655     epilogue
   2656     ret
   2657 
   2658 :read_expr_unquote
   2659     li_br &advance_char
   2660     call                             ## eat ','
   2661     li_br &peek_char
   2662     call
   2663     li_r1 %64  ## '@'
   2664     li_br &read_expr_unquote_splicing
   2665     beq_r0,r1
   2666     li_r1 &sym_unquote
   2667     ld_r1,r1,0
   2668     li_br &read_quoted_wrap
   2669     call
   2670     epilogue
   2671     ret
   2672 
   2673 :read_expr_unquote_splicing
   2674     li_br &advance_char
   2675     call                             ## eat '@'
   2676     li_r1 &sym_unquote_splicing
   2677     ld_r1,r1,0
   2678     li_br &read_quoted_wrap
   2679     call
   2680     epilogue
   2681     ret
   2682 
   2683 :read_expr_maybe_neg
   2684     li_br &peek2_char
   2685     call
   2686     mov_r1,r0
   2687     li_br &is_digit
   2688     call
   2689     li_r1 %1
   2690     li_br &read_expr_negnum
   2691     beq_r0,r1
   2692     ## Not a digit after '-' → treat as a regular symbol (the '-' is
   2693     ## the sub primitive, or a longer ident like `-ident`).
   2694     li_br &read_symbol
   2695     call
   2696     epilogue
   2697     ret
   2698 
   2699 :read_expr_negnum
   2700     li_br &advance_char
   2701     call                             ## eat '-'
   2702     li_br &read_number
   2703     call                             ## r0 = tagged positive number
   2704     sari_r0,r0,3                     ## decode
   2705     li_r1 %0
   2706     sub_r0,r1,r0                     ## r0 = -value
   2707     shli_r0,r0,3
   2708     ori_r0,r0,1                      ## retag
   2709     epilogue
   2710     ret
   2711 
   2712 
   2713 ## ---- read_quoted_wrap(r1=tagged_sym) -> r0 = (sym expr) -------------
   2714 ## Reads one expression and returns (sym . (expr . nil)). Shared by the
   2715 ## four reader shorthand forms (quote/quasi/unquote/unquote-splicing).
   2716 :read_quoted_wrap
   2717     prologue
   2718     st_r1,sp,24                       ## save tagged sym
   2719     li_br &read_expr
   2720     call                             ## r0 = expr
   2721     mov_r1,r0
   2722     li_r2 NIL
   2723     li_br &cons
   2724     call                             ## r0 = (expr . nil)
   2725     mov_r2,r0
   2726     ld_r1,sp,24
   2727     li_br &cons                      ## r0 = (sym . (expr . nil))
   2728     tail
   2729 
   2730 
   2731 ## ---- read_char_literal() -> r0 = tagged fixnum (ASCII) -------------
   2732 ## `\` already at cursor. Consumes `\`, then one or more chars. If the
   2733 ## first char is followed by a delimiter, that char is the result. If
   2734 ## more chars follow, the full span is matched against `space`, `newline`,
   2735 ## or `tab`; anything else errors out.
   2736 :read_char_literal
   2737     prologue_n4
   2738     li_br &advance_char
   2739     call                             ## eat '\\'
   2740 
   2741     li_r1 &src_cursor
   2742     ld_r0,r1,0
   2743     st_r0,sp,24                       ## slot 1 = start cursor
   2744 
   2745     li_br &peek_char
   2746     call                             ## r0 = first char (must not be EOF)
   2747     li_r1 %0
   2748     addi_r1,r1,neg1
   2749     li_br &err_reader_bad
   2750     beq_r0,r1
   2751 
   2752     st_r0,sp,32                      ## slot 2 = first char
   2753     li_br &advance_char
   2754     call                             ## eat first char
   2755 
   2756     li_br &peek_char
   2757     call
   2758     mov_r1,r0
   2759     li_br &is_delim
   2760     call
   2761     li_r1 %1
   2762     li_br &rcl_single
   2763     beq_r0,r1
   2764 
   2765 :rcl_name_loop
   2766     li_br &peek_char
   2767     call
   2768     mov_r1,r0
   2769     li_br &is_delim
   2770     call
   2771     li_r1 %1
   2772     li_br &rcl_compare
   2773     beq_r0,r1
   2774     li_br &advance_char
   2775     call
   2776     li_br &rcl_name_loop
   2777     b
   2778 
   2779 :rcl_compare
   2780     ## r2 = len (cursor - start), r0 = ptr (base + start).
   2781     li_r1 &src_cursor
   2782     ld_r2,r1,0
   2783     ld_r1,sp,24
   2784     sub_r2,r2,r1
   2785     li_r0 &src_base
   2786     ld_r0,r0,0
   2787     add_r0,r0,r1
   2788     li_r1 %5
   2789     li_br &rcl_cmp5
   2790     beq_r2,r1
   2791     li_r1 %7
   2792     li_br &rcl_cmp7
   2793     beq_r2,r1
   2794     li_r1 %3
   2795     li_br &rcl_cmp3
   2796     beq_r2,r1
   2797     li_br &err_reader_bad
   2798     b
   2799 
   2800 :rcl_cmp5
   2801     ## "space" → 32
   2802     lb_r1,r0,0
   2803     li_r2 %115
   2804     li_br &err_reader_bad
   2805     bne_r1,r2
   2806     addi_r0,r0,1
   2807     lb_r1,r0,0
   2808     li_r2 %112
   2809     li_br &err_reader_bad
   2810     bne_r1,r2
   2811     addi_r0,r0,1
   2812     lb_r1,r0,0
   2813     li_r2 %97
   2814     li_br &err_reader_bad
   2815     bne_r1,r2
   2816     addi_r0,r0,1
   2817     lb_r1,r0,0
   2818     li_r2 %99
   2819     li_br &err_reader_bad
   2820     bne_r1,r2
   2821     addi_r0,r0,1
   2822     lb_r1,r0,0
   2823     li_r2 %101
   2824     li_br &err_reader_bad
   2825     bne_r1,r2
   2826     li_r0 %32
   2827     shli_r0,r0,3
   2828     ori_r0,r0,1
   2829     epilogue_n4
   2830     ret
   2831 
   2832 :rcl_cmp7
   2833     ## "newline" → 10
   2834     lb_r1,r0,0
   2835     li_r2 %110
   2836     li_br &err_reader_bad
   2837     bne_r1,r2
   2838     addi_r0,r0,1
   2839     lb_r1,r0,0
   2840     li_r2 %101
   2841     li_br &err_reader_bad
   2842     bne_r1,r2
   2843     addi_r0,r0,1
   2844     lb_r1,r0,0
   2845     li_r2 %119
   2846     li_br &err_reader_bad
   2847     bne_r1,r2
   2848     addi_r0,r0,1
   2849     lb_r1,r0,0
   2850     li_r2 %108
   2851     li_br &err_reader_bad
   2852     bne_r1,r2
   2853     addi_r0,r0,1
   2854     lb_r1,r0,0
   2855     li_r2 %105
   2856     li_br &err_reader_bad
   2857     bne_r1,r2
   2858     addi_r0,r0,1
   2859     lb_r1,r0,0
   2860     li_r2 %110
   2861     li_br &err_reader_bad
   2862     bne_r1,r2
   2863     addi_r0,r0,1
   2864     lb_r1,r0,0
   2865     li_r2 %101
   2866     li_br &err_reader_bad
   2867     bne_r1,r2
   2868     li_r0 %10
   2869     shli_r0,r0,3
   2870     ori_r0,r0,1
   2871     epilogue_n4
   2872     ret
   2873 
   2874 :rcl_cmp3
   2875     ## "tab" → 9
   2876     lb_r1,r0,0
   2877     li_r2 %116
   2878     li_br &err_reader_bad
   2879     bne_r1,r2
   2880     addi_r0,r0,1
   2881     lb_r1,r0,0
   2882     li_r2 %97
   2883     li_br &err_reader_bad
   2884     bne_r1,r2
   2885     addi_r0,r0,1
   2886     lb_r1,r0,0
   2887     li_r2 %98
   2888     li_br &err_reader_bad
   2889     bne_r1,r2
   2890     li_r0 %9
   2891     shli_r0,r0,3
   2892     ori_r0,r0,1
   2893     epilogue_n4
   2894     ret
   2895 
   2896 :rcl_single
   2897     ld_r0,sp,32
   2898     shli_r0,r0,3
   2899     ori_r0,r0,1
   2900     epilogue_n4
   2901     ret
   2902 
   2903 
   2904 ## ---- read_vector_literal() -> r0 = tagged vector -------------------
   2905 ## `#(` detected; cursor at `(`. Read the body as a list, then convert
   2906 ## element-by-element into a freshly-allocated vector.
   2907 :read_vector_literal
   2908     prologue_n3
   2909     li_br &advance_char
   2910     call                             ## eat '('
   2911     li_br &read_list
   2912     call                             ## r0 = tagged list
   2913     st_r0,sp,24                       ## slot 1 = list head
   2914 
   2915     mov_r1,r0
   2916     li_r2 %0                         ## r2 = count
   2917 :rvl_count
   2918     li_r0 NIL
   2919     li_br &rvl_alloc
   2920     beq_r1,r0
   2921     addi_r0,r1,neg2
   2922     ld_r1,r0,8
   2923     addi_r2,r2,1
   2924     li_br &rvl_count
   2925     b
   2926 
   2927 :rvl_alloc
   2928     mov_r1,r2
   2929     li_r2 NIL
   2930     li_br &make_vector
   2931     call                             ## r0 = tagged vector
   2932     st_r0,sp,32                      ## slot 2 = vec
   2933 
   2934     addi_r3,r0,neg3
   2935     addi_r3,r3,8                     ## r3 = payload cursor
   2936     ld_r1,sp,24
   2937 
   2938 :rvl_fill
   2939     li_r0 NIL
   2940     li_br &rvl_done
   2941     beq_r1,r0
   2942     addi_r0,r1,neg2
   2943     ld_r2,r0,0
   2944     ld_r1,r0,8
   2945     st_r2,r3,0
   2946     addi_r3,r3,8
   2947     li_br &rvl_fill
   2948     b
   2949 
   2950 :rvl_done
   2951     ld_r0,sp,32
   2952     epilogue_n3
   2953     ret
   2954 
   2955 
   2956 ## ---- Display --------------------------------------------------------
   2957 
   2958 ## ---- putc(r1=char) — write one byte to fd 1 ------------------------
   2959 :putc_buf  %0 %0
   2960 
   2961 :putc
   2962     prologue
   2963     li_r2 &putc_buf
   2964     sb_r1,r2,0                       ## *putc_buf = low byte of r1
   2965     li_r0 sys_write
   2966     li_r1 %1
   2967     ## r2 = &putc_buf already
   2968     li_r3 %1
   2969     syscall
   2970     epilogue
   2971     ret
   2972 
   2973 
   2974 ## ---- display_uint(r1=u64, r2=fd) — decimal, no sign --------------
   2975 ## Writes digits to `digit_buf` right-to-left, then SYS_WRITEs the
   2976 ## filled range to fd. 24-byte buffer covers any 61-bit value.
   2977 ## fd is spilled into slot 3 because the digit loop reuses r2 as the
   2978 ## divisor '10' constant — keeping fd in a reg would require saving
   2979 ## another callee-saved, which costs the same.
   2980 :digit_buf  '000000000000000000000000000000000000000000000000'
   2981 :digit_buf_end  %0
   2982 
   2983 :display_uint
   2984     prologue_n3
   2985     st_r6,sp,24
   2986     st_r7,sp,32
   2987     st_r2,sp,40                      ## save fd
   2988 
   2989     li_r6 &digit_buf_end  ## r6 = end-of-buffer cursor (moves left)
   2990     mov_r7,r1                        ## r7 = value (mutated)
   2991 
   2992     ## Special-case zero.
   2993     li_br &du_loop
   2994     bnez_r7                        ## if value != 0, loop; else write '0'
   2995     addi_r6,r6,neg1
   2996     li_r1 %48  ## '0'
   2997     sb_r1,r6,0
   2998     li_br &du_write
   2999     b
   3000 
   3001 :du_loop
   3002     li_br &du_write
   3003     beqz_r7                        ## value == 0 → done digit gen
   3004 
   3005     ## digit = value % 10.
   3006     mov_r1,r7
   3007     li_r2 %10
   3008     rem_r1,r1,r2                     ## r1 = value % 10
   3009     addi_r1,r1,48                    ## r1 += '0'
   3010     addi_r6,r6,neg1
   3011     sb_r1,r6,0                       ## *--r6 = digit
   3012 
   3013     ## value = value / 10.
   3014     mov_r1,r7
   3015     li_r2 %10
   3016     div_r1,r1,r2
   3017     mov_r7,r1
   3018 
   3019     li_br &du_loop
   3020     b
   3021 
   3022 :du_write
   3023     ## Stash fd into r0 first — computing len clobbers r3 (we need it
   3024     ## as SP for the slot-3 load), so grab fd before that step.
   3025     ld_r0,sp,40                      ## r0 = fd (temp; sys_write will overwrite r0)
   3026     li_r1 &digit_buf_end
   3027     sub_r3,r1,r6                     ## r3 = len = digit_buf_end - r6
   3028     mov_r1,r0                        ## r1 = fd
   3029     mov_r2,r6                        ## r2 = buf
   3030     li_r0 sys_write
   3031     syscall
   3032 
   3033     ld_r6,sp,24
   3034     ld_r7,sp,32
   3035     epilogue_n3
   3036     ret
   3037 
   3038 
   3039 ## ---- display(r1=tagged) — dispatch on tag -------------------------
   3040 ## display owns a PROLOGUE_N2 frame even though its own body only needs
   3041 ## retaddr — display_pair dispatches into this frame (via BEQ, not CALL)
   3042 ## and uses slots 1/2 to save caller's r6/r7. Giving display_pair its own
   3043 ## prologue would leak display's frame on return: the stacked PROLOGUE +
   3044 ## PROLOGUE_N2 sum doesn't match display_pair's single EPILOGUE_N2, so
   3045 ## recursive calls drift sp and the saved r6/r7 slots point off-frame.
   3046 :display
   3047     prologue_n2
   3048 
   3049     ## nil = 0x07
   3050     li_r2 NIL
   3051     li_br &display_nil
   3052     beq_r1,r2
   3053 
   3054     ## #t = 0x0F
   3055     li_r2 TRUE
   3056     li_br &display_true
   3057     beq_r1,r2
   3058 
   3059     ## #f = 0x17
   3060     li_r2 FALSE
   3061     li_br &display_false
   3062     beq_r1,r2
   3063 
   3064     ## Low-3-bit tag.
   3065     mov_r2,r1
   3066     andi_r1,r1,7                     ## r1 = tag
   3067     li_r3 TAG_FIXNUM
   3068     li_br &display_fixnum
   3069     beq_r1,r3
   3070     li_r3 TAG_PAIR
   3071     li_br &display_pair
   3072     beq_r1,r3
   3073     li_r3 TAG_SYMBOL
   3074     li_br &display_symbol
   3075     beq_r1,r3
   3076     li_r3 TAG_STRING
   3077     li_br &display_string
   3078     beq_r1,r3
   3079 
   3080     ## Unknown — treat as "?"
   3081     li_r1 %63  ## '?'
   3082     li_br &putc
   3083     call
   3084     epilogue_n2
   3085     ret
   3086 
   3087 :display_nil
   3088     li_r1 %40  ## '('
   3089     li_br &putc
   3090     call
   3091     li_r1 %41  ## ')'
   3092     li_br &putc
   3093     call
   3094     epilogue_n2
   3095     ret
   3096 
   3097 ## #t and #f share display's PROLOGUE_N2 frame (reached via BEQ from
   3098 ## display or write — display_fixnum/symbol pattern).
   3099 :display_true
   3100     li_r1 %35  ## '#'
   3101     li_br &putc
   3102     call
   3103     li_r1 %116  ## 't'
   3104     li_br &putc
   3105     call
   3106     epilogue_n2
   3107     ret
   3108 
   3109 :display_false
   3110     li_r1 %35  ## '#'
   3111     li_br &putc
   3112     call
   3113     li_r1 %102  ## 'f'
   3114     li_br &putc
   3115     call
   3116     epilogue_n2
   3117     ret
   3118 
   3119 :display_fixnum
   3120     mov_r1,r2
   3121     sari_r1,r1,3                     ## signed shift; value (assume non-negative here)
   3122     li_r2 %1  ## fd = stdout
   3123     li_br &display_uint
   3124     call
   3125     epilogue_n2
   3126     ret
   3127 
   3128 :display_symbol
   3129     ## r2 = tagged symbol. Strip tag, read header, then write bytes.
   3130     addi_r2,r2,neg5                  ## r2 = raw header ptr (tag=0b101 → -5)
   3131     ld_r3,r2,0                       ## r3 = header
   3132     ## Low 48 bits = length. Mask by SHL/SHR 16.
   3133     shli_r3,r3,32                    ## clear top 16
   3134     shri_r3,r3,32                    ## r3 = length (0..2^48)
   3135     addi_r2,r2,8                     ## r2 = name ptr
   3136     li_r0 sys_write
   3137     li_r1 %1
   3138     syscall
   3139     epilogue_n2
   3140     ret
   3141 
   3142 :display_pair
   3143     ## r2 = tagged pair. Shares display's PROLOGUE_N2 frame.
   3144     st_r6,sp,24                       ## save r6 into display's slot 1
   3145     st_r7,sp,32                      ## save r7 into display's slot 2
   3146 
   3147     mov_r6,r2                        ## r6 = tagged pair (we'll walk cdr chain)
   3148 
   3149     li_r1 %40  ## '('
   3150     li_br &putc
   3151     call
   3152 
   3153 :display_pair_loop
   3154     ## car(r6)
   3155     mov_r1,r6
   3156     li_br &car
   3157     call
   3158     mov_r1,r0
   3159     li_br &display
   3160     call
   3161 
   3162     ## cdr(r6)
   3163     mov_r1,r6
   3164     li_br &cdr
   3165     call
   3166     mov_r6,r0
   3167 
   3168     ## If cdr == nil, done.
   3169     li_r1 NIL
   3170     li_br &display_pair_close
   3171     beq_r6,r1
   3172 
   3173     ## If cdr is a pair, space + loop.
   3174     mov_r1,r6
   3175     andi_r1,r1,7
   3176     li_r2 TAG_PAIR
   3177     li_br &display_pair_continue
   3178     beq_r1,r2
   3179 
   3180     ## Otherwise, improper: " . x)".
   3181     li_r1 %32  ## ' '
   3182     li_br &putc
   3183     call
   3184     li_r1 %46  ## '.'
   3185     li_br &putc
   3186     call
   3187     li_r1 %32
   3188     li_br &putc
   3189     call
   3190     mov_r1,r6
   3191     li_br &display
   3192     call
   3193     li_br &display_pair_close
   3194     b
   3195 
   3196 :display_pair_continue
   3197     li_r1 %32  ## ' '
   3198     li_br &putc
   3199     call
   3200     li_br &display_pair_loop
   3201     b
   3202 
   3203 :display_pair_close
   3204     li_r1 %41  ## ')'
   3205     li_br &putc
   3206     call
   3207 
   3208     ld_r6,sp,24
   3209     ld_r7,sp,32
   3210     epilogue_n2
   3211     ret
   3212 
   3213 
   3214 ## ---- display_string(r2=tagged_string) ------------------------------
   3215 ## Tail-called from display's dispatcher with r2 holding the tagged
   3216 ## string (low-3-bit tag = 100 = 4). Strips the tag, reads the 48-bit
   3217 ## length out of the 8-byte header, then writes the payload bytes to
   3218 ## stdout. Shares display's PROLOGUE_N2 frame, so EPILOGUE_N2 + RET
   3219 ## unwinds back to display's caller.
   3220 :display_string
   3221     addi_r2,r2,neg4                  ## r2 = raw header ptr
   3222     ld_r3,r2,0                       ## r3 = header word
   3223     shli_r3,r3,32
   3224     shri_r3,r3,32                    ## r3 = length (low 48 bits)
   3225     addi_r2,r2,8                     ## r2 = payload ptr
   3226     li_r0 sys_write
   3227     li_r1 %1
   3228     syscall
   3229     epilogue_n2
   3230     ret
   3231 
   3232 
   3233 ## ---- write(r1=val) -------------------------------------------------
   3234 ## Printer form: strings get quoted with escapes honored, everything
   3235 ## else renders identically to display. Sharing display_nil/fixnum/
   3236 ## symbol works because write's PROLOGUE_N2 frame matches display's —
   3237 ## those handlers unwind the right amount on their own EPILOGUE_N2.
   3238 :write
   3239     prologue_n2
   3240 
   3241     li_r2 NIL
   3242     li_br &display_nil
   3243     beq_r1,r2                        ## nil
   3244 
   3245     li_r2 TRUE
   3246     li_br &display_true
   3247     beq_r1,r2                        ## #t
   3248 
   3249     li_r2 FALSE
   3250     li_br &display_false
   3251     beq_r1,r2                        ## #f
   3252 
   3253     mov_r2,r1
   3254     andi_r1,r1,7                     ## r1 = tag
   3255     li_r3 TAG_FIXNUM
   3256     li_br &display_fixnum
   3257     beq_r1,r3
   3258     li_r3 TAG_PAIR
   3259     li_br &write_pair
   3260     beq_r1,r3
   3261     li_r3 TAG_SYMBOL
   3262     li_br &display_symbol
   3263     beq_r1,r3
   3264     li_r3 TAG_STRING
   3265     li_br &write_string
   3266     beq_r1,r3
   3267 
   3268     ## Unknown (closure/prim, etc.) — emit '?' placeholder.
   3269     li_r1 %63
   3270     li_br &putc
   3271     call
   3272     epilogue_n2
   3273     ret
   3274 
   3275 
   3276 ## ---- write_string(r2=tagged_string) --------------------------------
   3277 ## Emits "..." with `"` and `\` escaped as `\"` and `\\`. Shares
   3278 ## write's PROLOGUE_N2 frame; r6=cursor, r7=remaining-bytes saved in
   3279 ## the two slots across putc calls.
   3280 :write_string
   3281     st_r6,sp,24
   3282     st_r7,sp,32
   3283 
   3284     addi_r2,r2,neg4
   3285     ld_r3,r2,0
   3286     shli_r3,r3,32
   3287     shri_r3,r3,32                    ## r3 = length
   3288     addi_r2,r2,8                     ## r2 = payload ptr
   3289     mov_r6,r2                        ## r6 = cursor
   3290     mov_r7,r3                        ## r7 = length
   3291 
   3292     li_r1 %34  ## '"'
   3293     li_br &putc
   3294     call
   3295 
   3296 :write_string_loop
   3297     li_br &write_string_done
   3298     beqz_r7                        ## length == 0 → done
   3299 
   3300     lb_r1,r6,0                       ## r1 = *cursor
   3301 
   3302     li_r2 %34  ## '"'
   3303     li_br &write_string_escape
   3304     beq_r1,r2
   3305 
   3306     li_r2 %92  ## '\\'
   3307     li_br &write_string_escape
   3308     beq_r1,r2
   3309 
   3310     li_br &putc
   3311     call
   3312 
   3313 :write_string_next
   3314     addi_r6,r6,1
   3315     addi_r7,r7,neg1
   3316     li_br &write_string_loop
   3317     b
   3318 
   3319 :write_string_escape
   3320     ## Emit '\\' then re-emit the char (cursor still points to it).
   3321     li_r1 %92
   3322     li_br &putc
   3323     call
   3324     lb_r1,r6,0
   3325     li_br &putc
   3326     call
   3327     li_br &write_string_next
   3328     b
   3329 
   3330 :write_string_done
   3331     li_r1 %34  ## '"'
   3332     li_br &putc
   3333     call
   3334     ld_r6,sp,24
   3335     ld_r7,sp,32
   3336     epilogue_n2
   3337     ret
   3338 
   3339 
   3340 ## ---- write_pair(r2=tagged_pair) ------------------------------------
   3341 ## Mirror of display_pair but recurses through write (strings quoted).
   3342 ## Shares write's PROLOGUE_N2 frame.
   3343 :write_pair
   3344     st_r6,sp,24
   3345     st_r7,sp,32
   3346 
   3347     mov_r6,r2                        ## r6 = tagged pair cursor
   3348 
   3349     li_r1 %40  ## '('
   3350     li_br &putc
   3351     call
   3352 
   3353 :write_pair_loop
   3354     mov_r1,r6
   3355     li_br &car
   3356     call
   3357     mov_r1,r0
   3358     li_br &write
   3359     call
   3360 
   3361     mov_r1,r6
   3362     li_br &cdr
   3363     call
   3364     mov_r6,r0
   3365 
   3366     li_r1 NIL
   3367     li_br &write_pair_close
   3368     beq_r6,r1
   3369 
   3370     mov_r1,r6
   3371     andi_r1,r1,7
   3372     li_r2 TAG_PAIR
   3373     li_br &write_pair_continue
   3374     beq_r1,r2
   3375 
   3376     ## improper tail: " . x)"
   3377     li_r1 %32
   3378     li_br &putc
   3379     call
   3380     li_r1 %46
   3381     li_br &putc
   3382     call
   3383     li_r1 %32
   3384     li_br &putc
   3385     call
   3386     mov_r1,r6
   3387     li_br &write
   3388     call
   3389     li_br &write_pair_close
   3390     b
   3391 
   3392 :write_pair_continue
   3393     li_r1 %32
   3394     li_br &putc
   3395     call
   3396     li_br &write_pair_loop
   3397     b
   3398 
   3399 :write_pair_close
   3400     li_r1 %41  ## ')'
   3401     li_br &putc
   3402     call
   3403 
   3404     ld_r6,sp,24
   3405     ld_r7,sp,32
   3406     epilogue_n2
   3407     ret
   3408 
   3409 
   3410 ## ================ Step 6: Eval =======================================
   3411 ## Recursive-descent evaluator. Compound forms dispatch by comparing
   3412 ## the car against cached interned pointers (sym_quote/if/begin/
   3413 ## lambda/define, seeded in _start). Anything else is a function
   3414 ## application: callee and args get evaluated, then apply runs the
   3415 ## closure. Local env is a flat alist of (sym . val) pairs; global
   3416 ## bindings live in a singly-linked alist hanging off global_env_cell.
   3417 
   3418 ## ---- gset(r1=sym, r2=val) ------------------------------------------
   3419 ## Prepends (sym . val) to the global alist. Last-write-wins semantics
   3420 ## fall out naturally because lookup_alist returns the leftmost match.
   3421 :gset
   3422     prologue_n2
   3423     st_r1,sp,24                       ## save sym
   3424     st_r2,sp,32                      ## save val
   3425 
   3426     li_br &cons
   3427     call                             ## r0 = (sym . val)
   3428 
   3429     mov_r1,r0
   3430     li_r2 &global_env_cell
   3431     ld_r2,r2,0                       ## r2 = old global alist
   3432     li_br &cons
   3433     call                             ## r0 = new alist
   3434 
   3435     li_r2 &global_env_cell
   3436     st_r0,r2,0
   3437     epilogue_n2
   3438     ret
   3439 
   3440 
   3441 ## ---- lookup_alist(r1=sym, r2=env) -> r0 = pair or nil ---------------
   3442 ## Walks alist env returning the (sym . val) cons cell that matches, or
   3443 ## nil on miss. Caller distinguishes via tag/nil check.
   3444 :lookup_alist
   3445     prologue_n3
   3446     mov_r3,sp
   3447     st_r1,sp,24                       ## slot 1 = sym
   3448     st_r2,sp,32                      ## slot 2 = cursor
   3449 
   3450 :lookup_alist_loop
   3451     mov_r3,sp
   3452     ld_r2,sp,32
   3453     li_r1 %7
   3454     li_br &lookup_alist_miss
   3455     beq_r2,r1                        ## cursor == nil → miss
   3456 
   3457     ## pair = car(cursor)
   3458     mov_r1,r2
   3459     li_br &car
   3460     call                             ## r0 = (k . v)
   3461 
   3462     st_r0,sp,40                      ## slot 3 = pair
   3463 
   3464     mov_r1,r0
   3465     li_br &car
   3466     call                             ## r0 = key
   3467 
   3468     mov_r3,sp
   3469     ld_r1,sp,24
   3470     li_br &lookup_alist_hit
   3471     beq_r0,r1
   3472 
   3473     ## advance cursor := cdr(cursor)
   3474     ld_r1,sp,32
   3475     li_br &cdr
   3476     call
   3477     st_r0,sp,32
   3478     li_br &lookup_alist_loop
   3479     b
   3480 
   3481 :lookup_alist_hit
   3482     ld_r0,sp,40
   3483     epilogue_n3
   3484     ret
   3485 
   3486 :lookup_alist_miss
   3487     li_r0 %7
   3488     epilogue_n3
   3489     ret
   3490 
   3491 
   3492 ## ---- lookup(r1=sym, r2=env) -> r0 = value --------------------------
   3493 ## Local first, global second. Errors out via &err_unbound on miss.
   3494 :lookup
   3495     prologue_n2
   3496     st_r1,sp,24                       ## save sym for possible global retry
   3497 
   3498     li_br &lookup_alist
   3499     call                             ## r0 = local pair or nil
   3500 
   3501     li_r1 %7
   3502     li_br &lookup_global
   3503     beq_r0,r1
   3504 
   3505     mov_r1,r0
   3506     li_br &cdr
   3507     call                             ## r0 = value
   3508     epilogue_n2
   3509     ret
   3510 
   3511 :lookup_global
   3512     ld_r1,sp,24
   3513     li_r2 &global_env_cell
   3514     ld_r2,r2,0
   3515     li_br &lookup_alist
   3516     call
   3517 
   3518     li_r1 %7
   3519     li_br &err_unbound
   3520     beq_r0,r1
   3521 
   3522     mov_r1,r0
   3523     li_br &cdr
   3524     call
   3525     epilogue_n2
   3526     ret
   3527 
   3528 
   3529 ## ---- env_extend(r1=names, r2=vals, r3=env) -> r0 = new env ---------
   3530 ## For each (name, val) pair, prepends (name . val) to env. Iterative;
   3531 ## uses a 4-slot frame because the per-iteration state is (names,
   3532 ## vals, env-acc, name-temp) and cons clobbers r0-r3 so name has to
   3533 ## live somewhere stable across the val-car call.
   3534 :env_extend
   3535     prologue_n4
   3536     mov_r0,r3                        ## save env before clobbering r3
   3537     mov_r3,sp
   3538     st_r1,sp,24                       ## slot 1 = names
   3539     st_r2,sp,32                      ## slot 2 = vals
   3540     st_r0,sp,40                      ## slot 3 = env accumulator
   3541 
   3542 :env_extend_loop
   3543     mov_r3,sp
   3544     ld_r1,sp,24
   3545     li_r2 %7
   3546     li_br &env_extend_done
   3547     beq_r1,r2                        ## names == nil → done
   3548 
   3549     ld_r1,sp,32
   3550     li_r2 %7
   3551     li_br &err_arity
   3552     beq_r1,r2                        ## vals == nil → arity error
   3553 
   3554     ## name = car(names)
   3555     ld_r1,sp,24
   3556     li_br &car
   3557     call                             ## r0 = name
   3558     st_r0,sp,48                      ## slot 4 = name
   3559 
   3560     ## val = car(vals)
   3561     ld_r1,sp,32
   3562     li_br &car
   3563     call                             ## r0 = val
   3564 
   3565     ## pair = cons(name, val)
   3566     mov_r2,r0
   3567     ld_r1,sp,48
   3568     li_br &cons
   3569     call                             ## r0 = (name . val)
   3570 
   3571     ## env := cons(pair, env)
   3572     mov_r1,r0
   3573     ld_r2,sp,40
   3574     li_br &cons
   3575     call                             ## r0 = new env
   3576 
   3577     st_r0,sp,40
   3578 
   3579     ## names := cdr(names)
   3580     ld_r1,sp,24
   3581     li_br &cdr
   3582     call
   3583     st_r0,sp,24
   3584 
   3585     ## vals := cdr(vals)
   3586     ld_r1,sp,32
   3587     li_br &cdr
   3588     call
   3589     st_r0,sp,32
   3590 
   3591     li_br &env_extend_loop
   3592     b
   3593 
   3594 :env_extend_done
   3595     ld_r0,sp,40
   3596     epilogue_n4
   3597     ret
   3598 
   3599 
   3600 ## ---- make_closure(r1=params, r2=body, r3=env) -> r0 = tagged -------
   3601 ## 32-byte heap object: [header(type=4) | params | body | env]. Tag=6
   3602 ## (110) matches the combined closure/prim tag band. Pre-zeroed BSS
   3603 ## heap lets us skip writing the header's low 7 bytes and only stamp
   3604 ## byte 7 with the type code.
   3605 :make_closure
   3606     prologue_n3
   3607     mov_r0,r3                        ## save env
   3608     st_r1,sp,24                       ## slot 1 = params
   3609     st_r2,sp,32                      ## slot 2 = body
   3610     st_r0,sp,40                      ## slot 3 = env
   3611 
   3612     li_r1 %32  ## 32 bytes
   3613     li_br &alloc
   3614     call                             ## r0 = raw ptr
   3615 
   3616     li_r1 %4
   3617     sb_r1,r0,7                       ## type = 4 (closure)
   3618 
   3619     ld_r1,sp,24
   3620     st_r1,r0,8
   3621     ld_r1,sp,32
   3622     st_r1,r0,16
   3623     ld_r1,sp,40
   3624     st_r1,r0,24
   3625 
   3626     ori_r0,r0,4
   3627     ori_r0,r0,2                      ## tag = 0b110 = 6
   3628 
   3629     epilogue_n3
   3630     ret
   3631 
   3632 
   3633 ## ---- make_primitive(r1=code_id, r2=arity, r3=type) -> r0 = tagged --
   3634 ## 16-byte heap object: [header | code_id]. header byte 7 = type
   3635 ## (5=fixed, 6=variadic); byte 0 = arity. Shares tag 0b110 with
   3636 ## closures so apply's tag check still works; the type byte forks
   3637 ## there. Arity is ignored for variadic (type 6) — callers pass 0.
   3638 :make_primitive
   3639     prologue_n3
   3640     mov_r0,r3                        ## save type → r0 (frees r3)
   3641     st_r1,sp,24                       ## slot 1 = code_id
   3642     st_r2,sp,32                      ## slot 2 = arity
   3643     st_r0,sp,40                      ## slot 3 = type
   3644 
   3645     li_r1 %16  ## 16 bytes
   3646     li_br &alloc
   3647     call                             ## r0 = raw ptr
   3648 
   3649     ld_r1,sp,40
   3650     sb_r1,r0,7                       ## byte 7 = type
   3651     ld_r1,sp,32
   3652     sb_r1,r0,0                       ## byte 0 = arity
   3653     ld_r1,sp,24
   3654     st_r1,r0,8                       ## +8 = code id
   3655 
   3656     ori_r0,r0,4
   3657     ori_r0,r0,2                      ## tag = 0b110 = 6
   3658 
   3659     epilogue_n3
   3660     ret
   3661 
   3662 
   3663 ## ---- eval_args(r1=args, r2=env) -> r0 = evaluated args list --------
   3664 ## Recursive map: eval each, cons the results left-to-right.
   3665 :eval_args
   3666     prologue_n3
   3667     mov_r3,sp
   3668     st_r1,sp,24                       ## slot 1 = args cursor
   3669     st_r2,sp,32                      ## slot 2 = env
   3670 
   3671     li_r2 %7
   3672     li_br &eval_args_done
   3673     beq_r1,r2
   3674 
   3675     ## head = eval(car(args), env)
   3676     li_br &car
   3677     call
   3678     mov_r1,r0
   3679     ld_r2,sp,32
   3680     li_br &eval
   3681     call                             ## r0 = head value
   3682 
   3683     st_r0,sp,40
   3684 
   3685     ## tail = eval_args(cdr(args), env)
   3686     ld_r1,sp,24
   3687     li_br &cdr
   3688     call
   3689     mov_r1,r0
   3690     ld_r2,sp,32
   3691     li_br &eval_args
   3692     call                             ## r0 = tail
   3693 
   3694     mov_r2,r0
   3695     ld_r1,sp,40
   3696     li_br &cons  ## tail: cons is last work in this frame
   3697     tail_n3
   3698 
   3699 :eval_args_done
   3700     li_r0 %7
   3701     epilogue_n3
   3702     ret
   3703 
   3704 
   3705 ## ---- apply(r1=callee, r2=args) -> r0 = result ----------------------
   3706 ## Callee must have tag 0b110 (closure/prim band). Header byte 7
   3707 ## discriminates: 4=closure (existing path), 5=prim-fixed,
   3708 ## 6=prim-variadic. Frame has 4 slots: callee-then-params, args,
   3709 ## body, closure-env (prim paths repurpose slots 3/4 for code_id /
   3710 ## expected_arity).
   3711 :apply
   3712     prologue_n4
   3713     mov_r3,sp
   3714     st_r1,sp,24                       ## slot 1 = callee (tagged)
   3715     st_r2,sp,32                      ## slot 2 = args
   3716 
   3717     andi_r1,r1,7
   3718     li_r0 TAG_PROC
   3719     li_br &err_not_callable
   3720     bne_r1,r0
   3721 
   3722     mov_r3,sp
   3723     ld_r1,sp,24
   3724     addi_r1,r1,neg6                  ## r1 = raw ptr
   3725 
   3726     ## Fork on header type byte.
   3727     lb_r0,r1,7                       ## r0 = type
   3728     li_r2 TYPE_PRIM_FIXED
   3729     li_br &apply_prim_fixed
   3730     beq_r0,r2
   3731     li_r2 TYPE_PRIM_VARIADIC
   3732     li_br &apply_prim_variadic
   3733     beq_r0,r2
   3734     ## Fall through: type == 4 (closure).
   3735 
   3736     ld_r0,r1,8                       ## params
   3737     st_r0,sp,24                       ## slot 1 = params (overwrite)
   3738     ld_r0,r1,16                      ## body
   3739     st_r0,sp,40                      ## slot 3 = body
   3740     ld_r0,r1,24                      ## closure env
   3741     st_r0,sp,48                      ## slot 4 = closure env
   3742 
   3743     ## env_extend(params, args, closure_env)
   3744     ld_r1,sp,24
   3745     ld_r2,sp,32
   3746     ld_r3,sp,48
   3747     li_br &env_extend
   3748     call                             ## r0 = new env
   3749 
   3750     mov_r2,r0
   3751     ld_r1,sp,40                      ## r1 = body
   3752     li_br &eval
   3753     tail_n4                          ## Scheme tail call: closure body
   3754 
   3755 
   3756 ## ---- apply_prim_fixed (r1 = raw prim ptr, inside apply's frame) ----
   3757 ## Loads arity + code_id, marshals args, arity-checks, then unwinds
   3758 ## apply's frame and B's to prim_dispatch. The cascade there tail-
   3759 ## enters the body with r1=argc, r2=argv — body RETs directly to
   3760 ## apply's caller since EPILOGUE_N4 already restored lr.
   3761 :apply_prim_fixed
   3762     ## r3 is still sp from the MOV above; slot 3/4 are free.
   3763     lb_r0,r1,0                       ## r0 = expected arity
   3764     st_r0,r3,48                      ## slot 4 = expected arity
   3765     ld_r0,r1,8                       ## r0 = code id
   3766     st_r0,r3,40                      ## slot 3 = code id
   3767 
   3768     ld_r1,r3,32                      ## r1 = args
   3769     li_br &marshal_argv
   3770     call                             ## r0 = argc, r2 = &prim_argv
   3771 
   3772     mov_r3,sp
   3773     ld_r1,sp,48
   3774     li_br &err_arity
   3775     bne_r0,r1
   3776 
   3777     mov_r1,r0
   3778     ld_r3,sp,40                      ## r3 = code id
   3779     epilogue_n4
   3780     li_br &prim_dispatch
   3781     b
   3782 
   3783 
   3784 ## ---- apply_prim_variadic (r1 = raw prim ptr) ----------------------
   3785 :apply_prim_variadic
   3786     ld_r0,r1,8                       ## r0 = code id
   3787     st_r0,r3,40                      ## slot 3 = code id
   3788 
   3789     ld_r1,r3,32                      ## r1 = args
   3790     li_br &marshal_argv
   3791     call                             ## r0 = argc, r2 = &prim_argv
   3792 
   3793     mov_r1,r0
   3794     ld_r3,sp,40                      ## r3 = code id
   3795     epilogue_n4
   3796     li_br &prim_dispatch
   3797     b
   3798 
   3799 
   3800 ## ---- marshal_argv(r1 = args list) -> r0 = argc, r2 = &prim_argv ---
   3801 ## Walks the tagged-pair list, storing each car into prim_argv[i*8].
   3802 ## Overflow beyond PRIM_ARGV_SLOTS is an error (err_too_many_args).
   3803 :marshal_argv
   3804     prologue
   3805     li_r2 &prim_argv  ## r2 = cursor
   3806     li_r3 %0  ## r3 = count
   3807 
   3808 :marshal_argv_loop
   3809     li_r0 %7
   3810     li_br &marshal_argv_done
   3811     beq_r1,r0                        ## list == nil → done
   3812 
   3813     li_r0 %32  ## PRIM_ARGV_SLOTS = 32
   3814     li_br &err_too_many_args
   3815     beq_r3,r0
   3816 
   3817     addi_r0,r1,neg2                  ## r0 = raw pair ptr
   3818     ld_r1,r0,0                       ## r1 = car (we'll load cdr next)
   3819     st_r1,r2,0                       ## *cursor = car
   3820     ld_r1,r0,8                       ## r1 = cdr → next list
   3821     addi_r2,r2,8                     ## cursor += 8
   3822     addi_r3,r3,1                     ## count++
   3823     li_br &marshal_argv_loop
   3824     b
   3825 
   3826 :marshal_argv_done
   3827     ## Publish the count so the GC root walker only marks the live
   3828     ## prefix of prim_argv. Stale slots beyond the current call would
   3829     ## otherwise look like roots and pin freed objects across GCs.
   3830     li_r0 &prim_argc
   3831     st_r3,r0,0
   3832     mov_r0,r3                        ## r0 = count
   3833     li_r2 &prim_argv  ## r2 = buffer base
   3834     epilogue
   3835     ret
   3836 
   3837 
   3838 ## ---- prim_dispatch(r1=argc, r2=argv, r3=code_id) -------------------
   3839 ## Cascade dispatch — r3 is matched against each code id, and on
   3840 ## match we B to the primitive body. Bodies are leaf: r1/r2 are
   3841 ## already set up, and RET returns to apply's caller (since apply
   3842 ## already ran EPILOGUE_N4 before branching here).
   3843 :prim_dispatch
   3844     li_br &prim_add
   3845     beqz_r3
   3846     li_r0 %1
   3847     li_br &prim_sub
   3848     beq_r3,r0
   3849     li_r0 %2
   3850     li_br &prim_mul
   3851     beq_r3,r0
   3852     li_r0 %3
   3853     li_br &prim_div
   3854     beq_r3,r0
   3855     li_r0 %4
   3856     li_br &prim_mod
   3857     beq_r3,r0
   3858     li_r0 %5
   3859     li_br &prim_numeq
   3860     beq_r3,r0
   3861     li_r0 %6
   3862     li_br &prim_lt
   3863     beq_r3,r0
   3864     li_r0 %7
   3865     li_br &prim_gt
   3866     beq_r3,r0
   3867     li_r0 %8
   3868     li_br &prim_min
   3869     beq_r3,r0
   3870     li_r0 %9
   3871     li_br &prim_max
   3872     beq_r3,r0
   3873     li_r0 %10
   3874     li_br &prim_bitand
   3875     beq_r3,r0
   3876     li_r0 %11
   3877     li_br &prim_bitor
   3878     beq_r3,r0
   3879     li_r0 %12
   3880     li_br &prim_bitxor
   3881     beq_r3,r0
   3882     li_r0 %13
   3883     li_br &prim_bitnot
   3884     beq_r3,r0
   3885     li_r0 %14
   3886     li_br &prim_ashift
   3887     beq_r3,r0
   3888     li_r0 %15
   3889     li_br &prim_numberp
   3890     beq_r3,r0
   3891     li_r0 %16
   3892     li_br &prim_symbolp
   3893     beq_r3,r0
   3894     li_r0 %17
   3895     li_br &prim_stringp
   3896     beq_r3,r0
   3897     li_r0 %18
   3898     li_br &prim_vectorp
   3899     beq_r3,r0
   3900     li_r0 %19
   3901     li_br &prim_procp
   3902     beq_r3,r0
   3903     li_r0 %20
   3904     li_br &prim_eqp
   3905     beq_r3,r0
   3906     li_r0 %21
   3907     li_br &prim_cons
   3908     beq_r3,r0
   3909     li_r0 %22
   3910     li_br &prim_car
   3911     beq_r3,r0
   3912     li_r0 %23
   3913     li_br &prim_cdr
   3914     beq_r3,r0
   3915     li_r0 %24
   3916     li_br &prim_pairp
   3917     beq_r3,r0
   3918     li_r0 %25
   3919     li_br &prim_nullp
   3920     beq_r3,r0
   3921     li_r0 %26
   3922     li_br &prim_list
   3923     beq_r3,r0
   3924     li_r0 %27
   3925     li_br &prim_append
   3926     beq_r3,r0
   3927     li_r0 %28
   3928     li_br &prim_string_length
   3929     beq_r3,r0
   3930     li_r0 %29
   3931     li_br &prim_string_ref
   3932     beq_r3,r0
   3933     li_r0 %30
   3934     li_br &prim_substring
   3935     beq_r3,r0
   3936     li_r0 %31
   3937     li_br &prim_string_append
   3938     beq_r3,r0
   3939     li_r0 %32
   3940     li_br &prim_string_to_symbol
   3941     beq_r3,r0
   3942     li_r0 %33
   3943     li_br &prim_symbol_to_string
   3944     beq_r3,r0
   3945     li_r0 %34
   3946     li_br &prim_make_vector
   3947     beq_r3,r0
   3948     li_r0 %35
   3949     li_br &prim_vector_ref
   3950     beq_r3,r0
   3951     li_r0 %36
   3952     li_br &prim_vector_set
   3953     beq_r3,r0
   3954     li_r0 %37
   3955     li_br &prim_vector_length
   3956     beq_r3,r0
   3957     li_r0 %38
   3958     li_br &prim_display
   3959     beq_r3,r0
   3960     li_r0 %39
   3961     li_br &prim_write
   3962     beq_r3,r0
   3963     li_r0 %40
   3964     li_br &prim_newline
   3965     beq_r3,r0
   3966     li_r0 %41
   3967     li_br &prim_format
   3968     beq_r3,r0
   3969     li_r0 %42
   3970     li_br &prim_error
   3971     beq_r3,r0
   3972     li_r0 %43
   3973     li_br &prim_read_file
   3974     beq_r3,r0
   3975     li_r0 %44
   3976     li_br &prim_write_file
   3977     beq_r3,r0
   3978     li_r0 %45
   3979     li_br &prim_apply
   3980     beq_r3,r0
   3981 
   3982     li_br &err_bad_prim
   3983     b
   3984 
   3985 
   3986 ## ---- Primitive bodies ----------------------------------------------
   3987 ## Convention: entry with r1=argc (raw int), r2=argv (ptr into the
   3988 ## prim_argv buffer of tagged values). Leaf code: may use r0-r3
   3989 ## freely; must not touch r4-r7 (still hold apply's caller's values
   3990 ## since EPILOGUE_N4 ran before this branch). End with RET — that
   3991 ## returns to apply's caller, because lr was restored by EPILOGUE_N4.
   3992 
   3993 ## (+ ...) — variadic sum. Tagged fixnum (v<<3)|1: untag each, add
   3994 ## to decoded accumulator, retag at end.
   3995 :prim_add
   3996     li_r3 %0  ## acc = 0
   3997     shli_r1,r1,3
   3998     add_r1,r1,r2                     ## r1 = argv_end
   3999 :prim_add_loop
   4000     li_br &prim_add_done
   4001     beq_r2,r1
   4002     ld_r0,r2,0
   4003     sari_r0,r0,3
   4004     add_r3,r3,r0
   4005     addi_r2,r2,8
   4006     li_br &prim_add_loop
   4007     b
   4008 :prim_add_done
   4009     shli_r0,r3,3
   4010     ori_r0,r0,1
   4011     ret
   4012 
   4013 
   4014 ## (- x) negates; (- x y ...) folds subtraction from the left.
   4015 :prim_sub
   4016     li_r0 %1
   4017     li_br &prim_sub_negate
   4018     beq_r1,r0                        ## argc == 1 → unary negate
   4019 
   4020     ld_r3,r2,0
   4021     sari_r3,r3,3                     ## r3 = decoded first
   4022     addi_r2,r2,8
   4023     addi_r1,r1,neg1
   4024     shli_r1,r1,3
   4025     add_r1,r1,r2                     ## r1 = argv_end
   4026 :prim_sub_loop
   4027     li_br &prim_sub_done
   4028     beq_r2,r1
   4029     ld_r0,r2,0
   4030     sari_r0,r0,3
   4031     sub_r3,r3,r0
   4032     addi_r2,r2,8
   4033     li_br &prim_sub_loop
   4034     b
   4035 :prim_sub_done
   4036     shli_r0,r3,3
   4037     ori_r0,r0,1
   4038     ret
   4039 :prim_sub_negate
   4040     ld_r3,r2,0
   4041     sari_r3,r3,3
   4042     li_r0 %0
   4043     sub_r3,r0,r3
   4044     shli_r0,r3,3
   4045     ori_r0,r0,1
   4046     ret
   4047 
   4048 
   4049 ## (* ...) — variadic product. Identity 1.
   4050 :prim_mul
   4051     li_r3 %1  ## acc = 1
   4052     shli_r1,r1,3
   4053     add_r1,r1,r2
   4054 :prim_mul_loop
   4055     li_br &prim_mul_done
   4056     beq_r2,r1
   4057     ld_r0,r2,0
   4058     sari_r0,r0,3
   4059     mul_r3,r3,r0
   4060     addi_r2,r2,8
   4061     li_br &prim_mul_loop
   4062     b
   4063 :prim_mul_done
   4064     shli_r0,r3,3
   4065     ori_r0,r0,1
   4066     ret
   4067 
   4068 
   4069 ## (/ x y) — binary integer division (signed).
   4070 :prim_div
   4071     ld_r3,r2,0
   4072     sari_r3,r3,3
   4073     ld_r0,r2,8
   4074     sari_r0,r0,3
   4075     div_r3,r3,r0
   4076     shli_r0,r3,3
   4077     ori_r0,r0,1
   4078     ret
   4079 
   4080 
   4081 ## (% x y) — binary signed remainder. Scheme calls this `modulo`
   4082 ## / `remainder`; we bind the primitive as `%` to stay fixnum-only
   4083 ## and avoid the sign-convention split.
   4084 :prim_mod
   4085     ld_r3,r2,0
   4086     sari_r3,r3,3
   4087     ld_r0,r2,8
   4088     sari_r0,r0,3
   4089     rem_r3,r3,r0
   4090     shli_r0,r3,3
   4091     ori_r0,r0,1
   4092     ret
   4093 
   4094 
   4095 ## (= x y) — binary fixnum equality. Because tagged fixnums share
   4096 ## the same low-3-bit tag (001) and the payload is bit-identical
   4097 ## for equal values, we compare the tagged words directly.
   4098 :prim_numeq
   4099     ld_r3,r2,0
   4100     ld_r0,r2,8
   4101     li_br &prim_true
   4102     beq_r3,r0
   4103     li_r0 FALSE  ## #f
   4104     ret
   4105 
   4106 ## Shared "return #t" tail used by comparison/predicate primitives.
   4107 :prim_true
   4108     li_r0 TRUE
   4109     ret
   4110 
   4111 
   4112 ## (< x y). Tagged-fixnum order preserves decoded order (shift-by-3
   4113 ## is monotone for 61-bit signed values).
   4114 :prim_lt
   4115     ld_r3,r2,0
   4116     ld_r0,r2,8
   4117     li_br &prim_true
   4118     blt_r3,r0
   4119     li_r0 %23
   4120     ret
   4121 
   4122 
   4123 ## (> x y) ≡ (< y x).
   4124 :prim_gt
   4125     ld_r3,r2,0
   4126     ld_r0,r2,8
   4127     li_br &prim_true
   4128     blt_r0,r3
   4129     li_r0 %23
   4130     ret
   4131 
   4132 
   4133 ## (<=, >=, zero?, negative?, positive?, abs) live in the Scheme
   4134 ## prelude now — see src/prelude.scm.
   4135 
   4136 
   4137 ## (min ...) — variadic; first arg is seed, each later arg replaces
   4138 ## acc if strictly smaller. Comparing tagged fixnums directly keeps
   4139 ## the body untagging-free.
   4140 :prim_min
   4141     ld_r3,r2,0                       ## r3 = acc (tagged)
   4142     addi_r2,r2,8
   4143     addi_r1,r1,neg1
   4144     shli_r1,r1,3
   4145     add_r1,r1,r2
   4146 :prim_min_loop
   4147     li_br &prim_min_done
   4148     beq_r2,r1
   4149     ld_r0,r2,0
   4150     li_br &prim_min_skip
   4151     blt_r3,r0                        ## acc < x → keep acc
   4152     mov_r3,r0
   4153 :prim_min_skip
   4154     addi_r2,r2,8
   4155     li_br &prim_min_loop
   4156     b
   4157 :prim_min_done
   4158     mov_r0,r3
   4159     ret
   4160 
   4161 
   4162 ## (max ...) — acc replaces on strictly greater.
   4163 :prim_max
   4164     ld_r3,r2,0
   4165     addi_r2,r2,8
   4166     addi_r1,r1,neg1
   4167     shli_r1,r1,3
   4168     add_r1,r1,r2
   4169 :prim_max_loop
   4170     li_br &prim_max_done
   4171     beq_r2,r1
   4172     ld_r0,r2,0
   4173     li_br &prim_max_skip
   4174     blt_r0,r3                        ## x < acc → keep acc
   4175     mov_r3,r0
   4176 :prim_max_skip
   4177     addi_r2,r2,8
   4178     li_br &prim_max_loop
   4179     b
   4180 :prim_max_done
   4181     mov_r0,r3
   4182     ret
   4183 
   4184 
   4185 ## (bit-and ...) — variadic fold. Identity is all-ones; we seed the
   4186 ## decoded accumulator with -1 which ANDs as identity. Tag bits are
   4187 ## preserved through AND on tagged fixnums, but we still untag/retag
   4188 ## to match the shape of the other variadic bit ops.
   4189 :prim_bitand
   4190     li_r3 %0
   4191     addi_r3,r3,neg1                  ## r3 = -1
   4192     shli_r1,r1,3
   4193     add_r1,r1,r2
   4194 :prim_bitand_loop
   4195     li_br &prim_bitand_done
   4196     beq_r2,r1
   4197     ld_r0,r2,0
   4198     sari_r0,r0,3
   4199     and_r3,r3,r0
   4200     addi_r2,r2,8
   4201     li_br &prim_bitand_loop
   4202     b
   4203 :prim_bitand_done
   4204     shli_r0,r3,3
   4205     ori_r0,r0,1
   4206     ret
   4207 
   4208 
   4209 ## (bit-or ...) — identity 0.
   4210 :prim_bitor
   4211     li_r3 %0
   4212     shli_r1,r1,3
   4213     add_r1,r1,r2
   4214 :prim_bitor_loop
   4215     li_br &prim_bitor_done
   4216     beq_r2,r1
   4217     ld_r0,r2,0
   4218     sari_r0,r0,3
   4219     or_r3,r3,r0
   4220     addi_r2,r2,8
   4221     li_br &prim_bitor_loop
   4222     b
   4223 :prim_bitor_done
   4224     shli_r0,r3,3
   4225     ori_r0,r0,1
   4226     ret
   4227 
   4228 
   4229 ## (bit-xor ...) — identity 0.
   4230 :prim_bitxor
   4231     li_r3 %0
   4232     shli_r1,r1,3
   4233     add_r1,r1,r2
   4234 :prim_bitxor_loop
   4235     li_br &prim_bitxor_done
   4236     beq_r2,r1
   4237     ld_r0,r2,0
   4238     sari_r0,r0,3
   4239     xor_r3,r3,r0
   4240     addi_r2,r2,8
   4241     li_br &prim_bitxor_loop
   4242     b
   4243 :prim_bitxor_done
   4244     shli_r0,r3,3
   4245     ori_r0,r0,1
   4246     ret
   4247 
   4248 
   4249 ## (bit-not x) — ~x = -1 - x.
   4250 :prim_bitnot
   4251     ld_r3,r2,0
   4252     sari_r3,r3,3
   4253     li_r0 %0
   4254     addi_r0,r0,neg1                  ## r0 = -1
   4255     sub_r3,r0,r3                     ## r3 = -1 - r3
   4256     shli_r0,r3,3
   4257     ori_r0,r0,1
   4258     ret
   4259 
   4260 
   4261 ## (arithmetic-shift n k) — k>=0 left shift, k<0 arithmetic right.
   4262 :prim_ashift
   4263     ld_r3,r2,0
   4264     sari_r3,r3,3                     ## r3 = n (decoded)
   4265     ld_r0,r2,8
   4266     sari_r0,r0,3                     ## r0 = k (decoded, signed)
   4267     li_br &prim_ashift_neg
   4268     bltz_r0                        ## k < 0 → right shift by -k
   4269     shl_r3,r3,r0
   4270     li_br &prim_ashift_done
   4271     b
   4272 :prim_ashift_neg
   4273     li_r1 %0
   4274     sub_r0,r1,r0                     ## r0 = -k
   4275     sar_r3,r3,r0
   4276 :prim_ashift_done
   4277     shli_r0,r3,3
   4278     ori_r0,r0,1
   4279     ret
   4280 
   4281 
   4282 ## (number? x) — true iff tag is 1 (fixnum).
   4283 :prim_numberp
   4284     ld_r3,r2,0
   4285     andi_r3,r3,7
   4286     li_r0 TAG_FIXNUM
   4287     li_br &prim_true
   4288     beq_r3,r0
   4289     li_r0 FALSE
   4290     ret
   4291 
   4292 
   4293 ## (symbol? x) — tag is 5.
   4294 :prim_symbolp
   4295     ld_r3,r2,0
   4296     andi_r3,r3,7
   4297     li_r0 TAG_SYMBOL
   4298     li_br &prim_true
   4299     beq_r3,r0
   4300     li_r0 FALSE
   4301     ret
   4302 
   4303 
   4304 ## (string? x) — tag is 4.
   4305 :prim_stringp
   4306     ld_r3,r2,0
   4307     andi_r3,r3,7
   4308     li_r0 TAG_STRING
   4309     li_br &prim_true
   4310     beq_r3,r0
   4311     li_r0 FALSE
   4312     ret
   4313 
   4314 
   4315 ## (vector? x) — tag is 3.
   4316 :prim_vectorp
   4317     ld_r3,r2,0
   4318     andi_r3,r3,7
   4319     li_r0 TAG_VECTOR
   4320     li_br &prim_true
   4321     beq_r3,r0
   4322     li_r0 FALSE
   4323     ret
   4324 
   4325 
   4326 ## (procedure? x) — tag 6 AND header type ∈ {4,5,6}. Any of the three
   4327 ## heads (closure, prim-fixed, prim-variadic) count; the only other
   4328 ## tag-6 value shape (if we add one later) would need explicit
   4329 ## enumeration here.
   4330 :prim_procp
   4331     ld_r3,r2,0
   4332     mov_r0,r3
   4333     andi_r0,r0,7
   4334     li_r1 TAG_PROC
   4335     li_br &prim_procp_false
   4336     bne_r0,r1                        ## tag != 6 → #f
   4337 
   4338     addi_r3,r3,neg6                  ## strip tag
   4339     lb_r0,r3,7                       ## r0 = type byte
   4340     li_r1 TYPE_CLOSURE
   4341     li_br &prim_true
   4342     beq_r0,r1
   4343     li_r1 TYPE_PRIM_FIXED
   4344     li_br &prim_true
   4345     beq_r0,r1
   4346     li_r1 TYPE_PRIM_VARIADIC
   4347     li_br &prim_true
   4348     beq_r0,r1
   4349 :prim_procp_false
   4350     li_r0 FALSE
   4351     ret
   4352 
   4353 
   4354 ## (eq? x y) — pointer/bit identity. Fixnums, interned symbols, and
   4355 ## singletons all compare correctly this way; pairs/strings/vectors/
   4356 ## closures compare by heap address (distinct allocations are #f).
   4357 :prim_eqp
   4358     ld_r3,r2,0
   4359     ld_r0,r2,8
   4360     li_br &prim_true
   4361     beq_r3,r0
   4362     li_r0 FALSE
   4363     ret
   4364 
   4365 
   4366 ## ---- Step-10d list-core primitives ---------------------------------
   4367 ## (cons a d) — wraps the internal `cons` helper which calls alloc;
   4368 ## need PROLOGUE to save lr across the call.
   4369 :prim_cons
   4370     prologue
   4371     ld_r1,r2,0                       ## r1 = car arg
   4372     ld_r2,r2,8                       ## r2 = cdr arg (uses old r2 base)
   4373     li_br &cons
   4374     call
   4375     epilogue
   4376     ret
   4377 
   4378 
   4379 ## (car p) — unsafe (no tag check). Inlined: strip 0b010 tag, load +0.
   4380 :prim_car
   4381     ld_r1,r2,0
   4382     addi_r1,r1,neg2
   4383     ld_r0,r1,0
   4384     ret
   4385 
   4386 
   4387 ## (cdr p) — symmetric.
   4388 :prim_cdr
   4389     ld_r1,r2,0
   4390     addi_r1,r1,neg2
   4391     ld_r0,r1,8
   4392     ret
   4393 
   4394 
   4395 ## (pair? x) — tag is 2.
   4396 :prim_pairp
   4397     ld_r3,r2,0
   4398     andi_r3,r3,7
   4399     li_r0 TAG_PAIR
   4400     li_br &prim_true
   4401     beq_r3,r0
   4402     li_r0 %23
   4403     ret
   4404 
   4405 
   4406 ## (null? x) — equals nil singleton (0x07).
   4407 :prim_nullp
   4408     ld_r3,r2,0
   4409     li_r0 NIL
   4410     li_br &prim_true
   4411     beq_r3,r0
   4412     li_r0 %23
   4413     ret
   4414 
   4415 
   4416 ## (list ...) — variadic; build (a b c ...) by walking argv from the
   4417 ## tail and consing onto an accumulator (initially nil). Walking from
   4418 ## the tail avoids needing a reverse pass.
   4419 :prim_list
   4420     prologue_n3
   4421     mov_r3,sp
   4422     li_r0 NIL  ## nil
   4423     st_r0,sp,24                       ## slot1 = accumulator
   4424     st_r2,sp,40                      ## slot3 = base
   4425     shli_r1,r1,3                     ## r1 = argc * 8
   4426     add_r1,r1,r2                     ## r1 = end ptr (one past last)
   4427     st_r1,sp,32                      ## slot2 = cursor
   4428 :prim_list_loop
   4429     mov_r3,sp
   4430     ld_r0,sp,32                      ## cursor
   4431     ld_r1,sp,40                      ## base
   4432     li_br &prim_list_done
   4433     beq_r0,r1
   4434     addi_r0,r0,neg8                  ## cursor -= 8
   4435     st_r0,sp,32
   4436     ld_r1,r0,0                       ## r1 = arg
   4437     ld_r2,sp,24                       ## r2 = accumulator
   4438     li_br &cons
   4439     call
   4440     st_r0,sp,24                       ## accumulator = new pair
   4441     li_br &prim_list_loop
   4442     b
   4443 :prim_list_done
   4444     ld_r0,sp,24
   4445     epilogue_n3
   4446     ret
   4447 
   4448 
   4449 ## (length, list?) live in the Scheme prelude now — see
   4450 ## src/prelude.scm.
   4451 
   4452 
   4453 ## append_one(r1=xs, r2=ys) -> r0 = xs ++ ys. Copies xs's spine; ys
   4454 ## is shared by reference. Recursive — bounded by xs's length.
   4455 :append_one
   4456     prologue_n2
   4457     li_r0 %7
   4458     li_br &append_one_base
   4459     beq_r1,r0
   4460     st_r1,sp,24                       ## save xs
   4461     st_r2,sp,32                      ## save ys
   4462     addi_r0,r1,neg2
   4463     ld_r1,r0,8                       ## r1 = cdr(xs)
   4464     li_br &append_one
   4465     call                             ## r0 = appended tail
   4466     mov_r2,r0
   4467     ld_r0,sp,24
   4468     addi_r0,r0,neg2
   4469     ld_r1,r0,0                       ## r1 = car(xs)
   4470     li_br &cons
   4471     call
   4472     epilogue_n2
   4473     ret
   4474 :append_one_base
   4475     mov_r0,r2
   4476     epilogue_n2
   4477     ret
   4478 
   4479 
   4480 ## (append ...) — variadic; the last argument is shared (not copied),
   4481 ## all earlier arguments have their spines copied. Right-to-left fold.
   4482 :prim_append
   4483     prologue_n3
   4484     li_br &prim_append_zero
   4485     beqz_r1
   4486     mov_r3,sp
   4487     st_r2,sp,24                       ## slot1 = argv base
   4488     addi_r1,r1,neg1                  ## r1 = argc-1
   4489     shli_r1,r1,3                     ## r1 = (argc-1)*8
   4490     add_r1,r1,r2                     ## r1 = &argv[argc-1]
   4491     st_r1,sp,40                      ## slot3 = cursor
   4492     ld_r0,r1,0                       ## r0 = last arg
   4493     st_r0,sp,32                      ## slot2 = result
   4494 :prim_append_loop
   4495     mov_r3,sp
   4496     ld_r0,sp,40                      ## cursor
   4497     ld_r1,sp,24                       ## base
   4498     li_br &prim_append_done
   4499     beq_r0,r1
   4500     addi_r0,r0,neg8
   4501     st_r0,sp,40
   4502     ld_r1,r0,0                       ## r1 = arg at cursor
   4503     ld_r2,sp,32                      ## r2 = result
   4504     li_br &append_one
   4505     call
   4506     st_r0,sp,32
   4507     li_br &prim_append_loop
   4508     b
   4509 :prim_append_done
   4510     ld_r0,sp,32
   4511     epilogue_n3
   4512     ret
   4513 :prim_append_zero
   4514     li_r0 NIL
   4515     epilogue_n3
   4516     ret
   4517 
   4518 
   4519 ## (reverse, assoc, member) live in the Scheme prelude now — see
   4520 ## src/prelude.scm.
   4521 
   4522 
   4523 ## ---- (string-length s) — fixnum length out of header ---------------
   4524 :prim_string_length
   4525     ld_r1,r2,0
   4526     addi_r1,r1,neg4                  ## raw header
   4527     ld_r0,r1,0
   4528     shli_r0,r0,16
   4529     shri_r0,r0,16                    ## low 48b = length
   4530     shli_r0,r0,3
   4531     ori_r0,r0,1                      ## fixnum encode
   4532     ret
   4533 
   4534 
   4535 ## ---- (string-ref s i) — zero-extended byte as fixnum ---------------
   4536 :prim_string_ref
   4537     ld_r1,r2,0                       ## tagged string
   4538     ld_r0,r2,8                       ## tagged idx
   4539     sari_r0,r0,3                     ## raw idx
   4540     addi_r1,r1,neg4                  ## raw header
   4541     addi_r1,r1,8                     ## payload base
   4542     add_r1,r1,r0                     ## r1 = payload + idx
   4543     lb_r0,r1,0
   4544     shli_r0,r0,3
   4545     ori_r0,r0,1
   4546     ret
   4547 
   4548 
   4549 ## ---- (substring s start end) — copies s[start:end] -----------------
   4550 :prim_substring
   4551     prologue_n3
   4552     st_r6,sp,24
   4553     st_r7,sp,32
   4554 
   4555     ld_r1,r2,0                       ## tagged string
   4556     ld_r0,r2,8                       ## tagged start
   4557     ld_r3,r2,16                      ## tagged end
   4558     sari_r0,r0,3                     ## raw start
   4559     sari_r3,r3,3                     ## raw end
   4560 
   4561     addi_r1,r1,neg4                  ## raw header
   4562     addi_r1,r1,8                     ## payload base
   4563     sub_r3,r3,r0                     ## r3 = end - start (save len before r0 clobber)
   4564     mov_r7,r3                        ## r7 = len
   4565     mov_r2,r0                        ## r2 = start
   4566     add_r6,r1,r2                     ## src = payload + start
   4567 
   4568     mov_r1,r6
   4569     mov_r2,r7
   4570     li_br &make_string
   4571     call
   4572 
   4573     ld_r6,sp,24
   4574     ld_r7,sp,32
   4575     epilogue_n3
   4576     ret
   4577 
   4578 
   4579 ## ---- (string-append ...) — variadic concat, two-pass ---------------
   4580 ## Pass 1 walks each arg's header to total length; pass 2 alloc_string
   4581 ## then byte_copy each payload. argv is always &prim_argv so we don't
   4582 ## need to spill the r2 it came in on.
   4583 :prim_string_append
   4584     prologue_n4
   4585     st_r6,sp,24                       ## save r6 (cursor/total reuse)
   4586     st_r7,sp,32                      ## save r7 (i)
   4587     st_r1,sp,40                      ## slot3 = argc
   4588 
   4589     li_r6 %0  ## r6 = total length
   4590     li_r7 %0  ## r7 = i
   4591 :psa_lp1
   4592     mov_r3,sp
   4593     ld_r0,r3,40                      ## argc
   4594     li_br &psa_done1
   4595     beq_r7,r0
   4596 
   4597     li_r0 &prim_argv
   4598     shli_r3,r7,3
   4599     add_r0,r0,r3
   4600     ld_r0,r0,0                       ## tagged str
   4601     addi_r0,r0,neg4
   4602     ld_r0,r0,0                       ## header
   4603     shli_r0,r0,16
   4604     shri_r0,r0,16                    ## len
   4605     add_r6,r6,r0
   4606     addi_r7,r7,1
   4607     li_br &psa_lp1
   4608     b
   4609 
   4610 :psa_done1
   4611     mov_r1,r6                        ## total len
   4612     li_br &alloc_string
   4613     call                             ## r0 = tagged result
   4614     mov_r3,sp
   4615     st_r0,r3,48                      ## slot4 = tagged result
   4616 
   4617     ## r6 = payload cursor
   4618     addi_r6,r0,neg4
   4619     addi_r6,r6,8
   4620     li_r7 %0  ## i = 0
   4621 
   4622 :psa_lp2
   4623     mov_r3,sp
   4624     ld_r0,r3,40
   4625     li_br &psa_done2
   4626     beq_r7,r0
   4627 
   4628     li_r0 &prim_argv
   4629     shli_r3,r7,3
   4630     add_r0,r0,r3
   4631     ld_r2,r0,0                       ## tagged str
   4632     addi_r2,r2,neg4                  ## raw header
   4633     ld_r3,r2,0
   4634     shli_r3,r3,32
   4635     shri_r3,r3,32                    ## len
   4636     addi_r2,r2,8                     ## src payload
   4637     mov_r1,r6                        ## dst
   4638     li_br &byte_copy
   4639     call                             ## r0 = dst end
   4640     mov_r6,r0
   4641     addi_r7,r7,1
   4642     li_br &psa_lp2
   4643     b
   4644 
   4645 :psa_done2
   4646     mov_r3,sp
   4647     ld_r0,r3,48
   4648     ld_r6,sp,24
   4649     ld_r7,sp,32
   4650     epilogue_n4
   4651     ret
   4652 
   4653 
   4654 ## ---- (string->symbol s) — intern the string's bytes ----------------
   4655 :prim_string_to_symbol
   4656     prologue
   4657     ld_r0,r2,0
   4658     addi_r0,r0,neg4
   4659     ld_r2,r0,0
   4660     shli_r2,r2,16
   4661     shri_r2,r2,16                    ## len
   4662     addi_r1,r0,8                     ## payload
   4663     li_br &intern
   4664     call
   4665     epilogue
   4666     ret
   4667 
   4668 
   4669 ## ---- (symbol->string sym) — copy sym name into a fresh string -----
   4670 :prim_symbol_to_string
   4671     prologue
   4672     ld_r0,r2,0
   4673     addi_r0,r0,neg5                  ## raw sym ptr
   4674     ld_r2,r0,0
   4675     shli_r2,r2,16
   4676     shri_r2,r2,16                    ## len
   4677     addi_r1,r0,8                     ## src payload
   4678     li_br &make_string
   4679     call
   4680     epilogue
   4681     ret
   4682 
   4683 
   4684 ## ---- (make-vector n init) — n-slot vector filled with init --------
   4685 :prim_make_vector
   4686     prologue
   4687     ld_r1,r2,0                       ## tagged n
   4688     sari_r1,r1,3                     ## raw n
   4689     ld_r2,r2,8                       ## init (tagged)
   4690     li_br &make_vector
   4691     call
   4692     epilogue
   4693     ret
   4694 
   4695 
   4696 ## ---- (vector-ref v i) — unsafe slot read ---------------------------
   4697 :prim_vector_ref
   4698     ld_r1,r2,0
   4699     ld_r3,r2,8
   4700     sari_r3,r3,3                     ## raw idx
   4701     addi_r1,r1,neg3                  ## raw vec
   4702     addi_r1,r1,8                     ## payload base
   4703     shli_r3,r3,3                     ## idx*8
   4704     mov_r2,r3                        ## stage in r2 (no ADD_R1_R1_R3)
   4705     add_r1,r1,r2
   4706     ld_r0,r1,0
   4707     ret
   4708 
   4709 
   4710 ## ---- (vector-set! v i val) -> unspec --------------------------------
   4711 :prim_vector_set
   4712     ld_r1,r2,0
   4713     ld_r3,r2,8
   4714     ld_r0,r2,16
   4715     sari_r3,r3,3
   4716     addi_r1,r1,neg3
   4717     addi_r1,r1,8
   4718     shli_r3,r3,3
   4719     mov_r2,r3                        ## stage idx*8 in r2
   4720     add_r1,r1,r2
   4721     st_r0,r1,0
   4722     li_r0 UNSPEC
   4723     ret
   4724 
   4725 
   4726 ## ---- (vector-length v) — fixnum slot count -------------------------
   4727 :prim_vector_length
   4728     ld_r1,r2,0
   4729     addi_r1,r1,neg3
   4730     ld_r0,r1,0
   4731     shli_r0,r0,16
   4732     shri_r0,r0,16
   4733     shli_r0,r0,3
   4734     ori_r0,r0,1
   4735     ret
   4736 
   4737 
   4738 ## (vector->list, list->vector) live in the Scheme prelude now —
   4739 ## see src/prelude.scm.
   4740 
   4741 
   4742 ## ---- (display x) — runtime printer (unspec result) ------------------
   4743 :prim_display
   4744     prologue
   4745     ld_r1,r2,0
   4746     li_br &display
   4747     call
   4748     li_r0 UNSPEC
   4749     epilogue
   4750     ret
   4751 
   4752 
   4753 ## ---- (write x) — quoting printer ------------------------------------
   4754 :prim_write
   4755     prologue
   4756     ld_r1,r2,0
   4757     li_br &write
   4758     call
   4759     li_r0 UNSPEC
   4760     epilogue
   4761     ret
   4762 
   4763 
   4764 ## ---- (newline) — emit '\n' -----------------------------------------
   4765 :prim_newline
   4766     prologue
   4767     li_r1 %10
   4768     li_br &putc
   4769     call
   4770     li_r0 UNSPEC
   4771     epilogue
   4772     ret
   4773 
   4774 
   4775 ## ---- (format fmt args...) — minimal printf-ish ---------------------
   4776 ## Supported directives: ~a (display), ~s (write), ~d (decimal fixnum),
   4777 ## ~% (newline), ~~ (literal '~'). Unknown directives consume their char
   4778 ## silently. argv is &prim_argv; arg_index counts past the fmt string.
   4779 :prim_format
   4780     prologue_n4
   4781     st_r6,sp,24
   4782     st_r7,sp,32
   4783 
   4784     mov_r3,sp
   4785     st_r1,r3,40                      ## slot3 = argc
   4786     li_r0 %1
   4787     st_r0,r3,48                      ## slot4 = arg_index (skip fmt)
   4788 
   4789     ld_r0,r2,0                       ## tagged fmt string
   4790     addi_r0,r0,neg4
   4791     ld_r7,r0,0
   4792     shli_r7,r7,16
   4793     shri_r7,r7,16                    ## r7 = len
   4794     addi_r6,r0,8                     ## r6 = cursor
   4795 
   4796 :pf_loop
   4797     li_br &pf_done
   4798     beqz_r7
   4799 
   4800     lb_r0,r6,0
   4801     li_r1 %126  ## '~'
   4802     li_br &pf_directive
   4803     beq_r0,r1
   4804 
   4805     mov_r1,r0
   4806     li_br &putc
   4807     call
   4808     addi_r6,r6,1
   4809     addi_r7,r7,neg1
   4810     li_br &pf_loop
   4811     b
   4812 
   4813 :pf_directive
   4814     addi_r6,r6,1
   4815     addi_r7,r7,neg1
   4816     li_br &pf_done
   4817     beqz_r7
   4818 
   4819     lb_r0,r6,0
   4820     addi_r6,r6,1
   4821     addi_r7,r7,neg1
   4822 
   4823     li_r1 %37  ## '%'
   4824     li_br &pf_newline
   4825     beq_r0,r1
   4826 
   4827     li_r1 %126  ## '~'
   4828     li_br &pf_emit_tilde
   4829     beq_r0,r1
   4830 
   4831     ## a/s/d — fetch arg
   4832     li_r1 &prim_argv
   4833     mov_r3,sp
   4834     ld_r2,r3,48
   4835     shli_r2,r2,3
   4836     add_r1,r1,r2
   4837     ld_r1,r1,0                       ## r1 = tagged arg
   4838     ld_r2,r3,48
   4839     addi_r2,r2,1
   4840     st_r2,r3,48
   4841 
   4842     li_r2 %97  ## 'a'
   4843     li_br &pf_dir_a
   4844     beq_r0,r2
   4845     li_r2 %115  ## 's'
   4846     li_br &pf_dir_s
   4847     beq_r0,r2
   4848     li_r2 %100  ## 'd'
   4849     li_br &pf_dir_d
   4850     beq_r0,r2
   4851 
   4852     li_br &pf_loop
   4853     b
   4854 
   4855 :pf_dir_a
   4856     li_br &display
   4857     call
   4858     li_br &pf_loop
   4859     b
   4860 
   4861 :pf_dir_s
   4862     li_br &write
   4863     call
   4864     li_br &pf_loop
   4865     b
   4866 
   4867 :pf_dir_d
   4868     sari_r1,r1,3
   4869     li_r2 %1  ## fd = 1 (stdout)
   4870     li_br &display_uint
   4871     call
   4872     li_br &pf_loop
   4873     b
   4874 
   4875 :pf_newline
   4876     li_r1 %10
   4877     li_br &putc
   4878     call
   4879     li_br &pf_loop
   4880     b
   4881 
   4882 :pf_emit_tilde
   4883     li_r1 %126
   4884     li_br &putc
   4885     call
   4886     li_br &pf_loop
   4887     b
   4888 
   4889 :pf_done
   4890     li_r0 UNSPEC
   4891     ld_r6,sp,24
   4892     ld_r7,sp,32
   4893     epilogue_n4
   4894     ret
   4895 
   4896 
   4897 ## ---- (error msg) — print and exit(1) -------------------------------
   4898 :prim_error
   4899     ld_r0,r2,0
   4900     addi_r0,r0,neg4
   4901     ld_r2,r0,0
   4902     shli_r2,r2,16
   4903     shri_r2,r2,16
   4904     addi_r1,r0,8
   4905     li_br &error
   4906     b
   4907 
   4908 
   4909 ## ---- (read-file path) — slurp into a fresh string ------------------
   4910 ## Uses io_buf as scratch (512B); make_string copies into the heap.
   4911 :prim_read_file
   4912     prologue_n3
   4913     st_r6,sp,24
   4914     st_r7,sp,32
   4915 
   4916     ld_r1,r2,0
   4917     li_br &string_to_c_path
   4918     call                             ## r0 = &path_buf
   4919 
   4920     mov_r2,r0
   4921     li_r0 sys_openat
   4922     li_r1 %-100
   4923     li_r3 %0
   4924     li_r4 %0
   4925     syscall                          ## r0 = fd or err
   4926 
   4927     li_br &err_open
   4928     bltz_r0
   4929 
   4930     mov_r6,r0                        ## r6 = fd
   4931 
   4932     mov_r1,r6
   4933     li_r2 &io_buf
   4934     li_r3 %512
   4935     li_br &read_file_all
   4936     call                             ## r0 = bytes_read
   4937     mov_r7,r0
   4938 
   4939     li_r0 sys_close
   4940     mov_r1,r6
   4941     syscall
   4942 
   4943     li_r1 &io_buf
   4944     mov_r2,r7
   4945     li_br &make_string
   4946     call
   4947 
   4948     ld_r6,sp,24
   4949     ld_r7,sp,32
   4950     epilogue_n3
   4951     ret
   4952 
   4953 
   4954 ## ---- (write-file path data) — overwrite/truncate ------------------
   4955 :prim_write_file
   4956     prologue_n3
   4957     st_r6,sp,24
   4958     st_r7,sp,32
   4959 
   4960     ld_r7,r2,8                       ## r7 = tagged data string
   4961 
   4962     ld_r1,r2,0                       ## tagged path
   4963     li_br &string_to_c_path
   4964     call                             ## r0 = &path_buf
   4965 
   4966     mov_r2,r0
   4967     li_r0 sys_openat
   4968     li_r1 %-100
   4969     li_r3 %577  ## O_WRONLY|O_CREAT|O_TRUNC
   4970     li_r4 %420  ## mode 0644
   4971     syscall                          ## r0 = fd
   4972 
   4973     li_br &err_open
   4974     bltz_r0
   4975 
   4976     mov_r6,r0                        ## r6 = fd
   4977 
   4978     mov_r1,r7
   4979     addi_r1,r1,neg4                  ## raw header
   4980     ld_r3,r1,0
   4981     shli_r3,r3,32
   4982     shri_r3,r3,32                    ## len
   4983     addi_r2,r1,8                     ## payload
   4984     mov_r1,r6                        ## fd
   4985     li_br &write_file_all
   4986     call
   4987 
   4988     li_r0 sys_close
   4989     mov_r1,r6
   4990     syscall
   4991 
   4992     li_r0 UNSPEC
   4993     ld_r6,sp,24
   4994     ld_r7,sp,32
   4995     epilogue_n3
   4996     ret
   4997 
   4998 
   4999 ## (equal?) lives in the Scheme prelude now — see src/prelude.scm.
   5000 
   5001 
   5002 ## ---- (apply proc arg ... last-list) ---------------------------------
   5003 ## argc >= 2. Build args = (arg1 ... arg{n-1} ++ last-list), then call
   5004 ## the C-level apply with (callee, args).
   5005 :prim_apply
   5006     prologue_n4
   5007     st_r6,sp,24
   5008     st_r7,sp,32
   5009 
   5010     ld_r6,r2,0                       ## r6 = callee
   5011 
   5012     ## acc = argv[argc-1] (the trailing list)
   5013     addi_r3,r1,neg1
   5014     shli_r0,r3,3
   5015     li_r7 &prim_argv
   5016     add_r7,r7,r0
   5017     ld_r7,r7,0                       ## r7 = acc
   5018 
   5019     addi_r3,r1,neg2                  ## i = argc - 2
   5020 :papply_lp
   5021     li_r0 %1
   5022     li_br &papply_done
   5023     blt_r3,r0                        ## i < 1 → done
   5024 
   5025     shli_r0,r3,3
   5026     li_r1 &prim_argv
   5027     add_r1,r1,r0
   5028     ld_r1,r1,0                       ## r1 = arg
   5029 
   5030     st_r3,sp,40                      ## save i
   5031 
   5032     mov_r2,r7
   5033     li_br &cons
   5034     call                             ## r0 = (arg . acc)
   5035     mov_r7,r0
   5036 
   5037     mov_r3,sp
   5038     ld_r3,r3,40
   5039     addi_r3,r3,neg1
   5040     li_br &papply_lp
   5041     b
   5042 
   5043 :papply_done
   5044     mov_r1,r6
   5045     mov_r2,r7
   5046     li_br &apply
   5047     call
   5048 
   5049     ld_r6,sp,24
   5050     ld_r7,sp,32
   5051     epilogue_n4
   5052     ret
   5053 
   5054 
   5055 
   5056 ## ---- eval(r1=expr, r2=env) -> r0 = value ---------------------------
   5057 ## Self-evaluating: nil/fixnum/string/closure. Symbols → lookup. Pairs
   5058 ## → eval_pair (special-form or application dispatch).
   5059 :eval
   5060     prologue_n3
   5061     mov_r3,sp
   5062     st_r1,sp,24                       ## slot 1 = expr
   5063     st_r2,sp,32                      ## slot 2 = env
   5064 
   5065     li_r2 %7
   5066     li_br &eval_self_slot1
   5067     beq_r1,r2
   5068 
   5069     andi_r1,r1,7                     ## r1 = tag
   5070 
   5071     li_r2 %1
   5072     li_br &eval_self_slot1
   5073     beq_r1,r2                        ## fixnum
   5074 
   5075     li_r2 %5
   5076     li_br &eval_sym
   5077     beq_r1,r2
   5078 
   5079     li_r2 %2
   5080     li_br &eval_pair
   5081     beq_r1,r2
   5082 
   5083     ## Other tags (string, closure) self-evaluate.
   5084     li_br &eval_self_slot1
   5085     b
   5086 
   5087 :eval_self_slot1
   5088     ld_r0,sp,24
   5089     epilogue_n3
   5090     ret
   5091 
   5092 :eval_sym
   5093     ld_r1,sp,24
   5094     ld_r2,sp,32
   5095     li_br &lookup
   5096     tail_n3
   5097 
   5098 :eval_pair
   5099     ## Compound expression. Dispatch on car against cached sym_*
   5100     ## pointers; otherwise treat as function application.
   5101     ld_r1,sp,24
   5102     li_br &car
   5103     call                             ## r0 = callee-expr
   5104 
   5105     li_r1 &sym_quote
   5106     ld_r1,r1,0
   5107     li_br &eval_quote
   5108     beq_r0,r1
   5109 
   5110     li_r1 &sym_if
   5111     ld_r1,r1,0
   5112     li_br &eval_if
   5113     beq_r0,r1
   5114 
   5115     li_r1 &sym_begin
   5116     ld_r1,r1,0
   5117     li_br &eval_begin
   5118     beq_r0,r1
   5119 
   5120     li_r1 &sym_lambda
   5121     ld_r1,r1,0
   5122     li_br &eval_lambda
   5123     beq_r0,r1
   5124 
   5125     li_r1 &sym_define
   5126     ld_r1,r1,0
   5127     li_br &eval_define
   5128     beq_r0,r1
   5129 
   5130     li_r1 &sym_set
   5131     ld_r1,r1,0
   5132     li_br &eval_set
   5133     beq_r0,r1
   5134 
   5135     li_r1 &sym_let
   5136     ld_r1,r1,0
   5137     li_br &eval_let
   5138     beq_r0,r1
   5139 
   5140     li_r1 &sym_letstar
   5141     ld_r1,r1,0
   5142     li_br &eval_letstar
   5143     beq_r0,r1
   5144 
   5145     li_r1 &sym_letrec
   5146     ld_r1,r1,0
   5147     li_br &eval_letrec
   5148     beq_r0,r1
   5149 
   5150     li_r1 &sym_cond
   5151     ld_r1,r1,0
   5152     li_br &eval_cond
   5153     beq_r0,r1
   5154 
   5155     li_r1 &sym_quasiquote
   5156     ld_r1,r1,0
   5157     li_br &eval_quasiquote
   5158     beq_r0,r1
   5159 
   5160     ## Application: callee = eval(callee-expr, env)
   5161     mov_r1,r0
   5162     ld_r2,sp,32
   5163     li_br &eval
   5164     call                             ## r0 = callee value
   5165 
   5166     st_r0,sp,40                      ## slot 3 = callee
   5167 
   5168     ## args = eval_args(cdr(expr), env)
   5169     ld_r1,sp,24
   5170     li_br &cdr
   5171     call
   5172     mov_r1,r0
   5173     ld_r2,sp,32
   5174     li_br &eval_args
   5175     call                             ## r0 = args list
   5176 
   5177     mov_r2,r0
   5178     ld_r1,sp,40
   5179     li_br &apply
   5180     tail_n3                          ## Scheme tail call: application
   5181 
   5182 
   5183 ## ---- eval_quote / eval_if / eval_begin -----------------------------
   5184 ## All run inside eval's PROLOGUE_N3 frame: slot 1 = expr, slot 2 =
   5185 ## env, slot 3 = per-form scratch.
   5186 :eval_quote
   5187     ld_r1,sp,24
   5188     li_br &cdr
   5189     call                             ## r0 = (x)
   5190     mov_r1,r0
   5191     li_br &car
   5192     tail_n3                          ## Scheme tail: quote result = datum
   5193 
   5194 :eval_if
   5195     ## (if cond then else). Save (then else) tail into slot 3, eval
   5196     ## cond, branch to the correct arm.
   5197     ld_r1,sp,24
   5198     li_br &cdr
   5199     call                             ## r0 = (cond then else)
   5200     mov_r1,r0
   5201     li_br &cdr
   5202     call                             ## r0 = (then else)
   5203     st_r0,sp,40
   5204 
   5205     ld_r1,sp,24
   5206     li_br &cdr
   5207     call                             ## r0 = (cond then else)
   5208     mov_r1,r0
   5209     li_br &car
   5210     call                             ## r0 = cond expr
   5211     mov_r1,r0
   5212     ld_r2,sp,32
   5213     li_br &eval
   5214     call                             ## r0 = cond value
   5215 
   5216     li_r1 FALSE
   5217     li_br &eval_if_else
   5218     beq_r0,r1
   5219 
   5220     ## Then branch: eval car(slot3)
   5221     ld_r1,sp,40
   5222     li_br &car
   5223     call
   5224     mov_r1,r0
   5225     ld_r2,sp,32
   5226     li_br &eval  ## Scheme tail: then-branch
   5227     tail_n3
   5228 
   5229 :eval_if_else
   5230     ## Else branch: eval car(cdr(slot3))
   5231     ld_r1,sp,40
   5232     li_br &cdr
   5233     call
   5234     mov_r1,r0
   5235     li_br &car
   5236     call
   5237     mov_r1,r0
   5238     ld_r2,sp,32
   5239     li_br &eval  ## Scheme tail: else-branch
   5240     tail_n3
   5241 
   5242 :eval_begin
   5243     ## (begin e1 e2 ... en). Slot 1 = cursor over the body list. The
   5244     ## last form (en) is Scheme tail-position, so the loop peeks
   5245     ## cdr(cursor): nil next → TAIL eval the current head; otherwise
   5246     ## CALL eval and advance. Slot 3 bridges the saved next-cursor
   5247     ## across the CALL eval in the non-tail branch.
   5248     ld_r1,sp,24
   5249     li_br &cdr
   5250     call                             ## r0 = body list
   5251     mov_r3,sp
   5252     st_r0,sp,24                       ## slot 1 := cursor
   5253 
   5254 :eval_begin_loop
   5255     mov_r3,sp
   5256     ld_r1,sp,24
   5257     li_r2 %7
   5258     li_br &eval_begin_empty
   5259     beq_r1,r2                        ## cursor == nil → empty begin → nil
   5260 
   5261     ## next = cdr(cursor)
   5262     li_br &cdr
   5263     call                             ## r0 = next
   5264 
   5265     li_r1 %7
   5266     li_br &eval_begin_tail
   5267     beq_r0,r1                        ## next == nil → tail-eval last form
   5268 
   5269     ## Non-tail: stash next in slot 3, CALL eval(car(cursor), env),
   5270     ## discard result, advance cursor := next.
   5271     st_r0,sp,40                      ## slot 3 := next
   5272     ld_r1,sp,24
   5273     li_br &car
   5274     call
   5275     mov_r1,r0
   5276     ld_r2,sp,32
   5277     li_br &eval
   5278     call
   5279 
   5280     ld_r0,sp,40
   5281     st_r0,sp,24                       ## cursor := next
   5282     li_br &eval_begin_loop
   5283     b
   5284 
   5285 :eval_begin_tail
   5286     ## Scheme tail: TAIL eval(car(cursor), env). Frame torn down.
   5287     ld_r1,sp,24
   5288     li_br &car
   5289     call                             ## r0 = head expr
   5290     mov_r1,r0
   5291     ld_r2,sp,32
   5292     li_br &eval
   5293     tail_n3
   5294 
   5295 :eval_begin_empty
   5296     li_r0 %7
   5297     epilogue_n3
   5298     ret
   5299 
   5300 :eval_lambda
   5301     ## (lambda params body1 body2 ...). Collect body-list, rewrite any
   5302     ## leading (define …) forms into a letrec (step 12e), then stash
   5303     ## the resulting single expression as the closure's body.
   5304     ld_r1,sp,24
   5305     li_br &cdr
   5306     call                             ## r0 = (params body1 body2 ...)
   5307     st_r0,sp,40                      ## slot 3 = after-head
   5308 
   5309     mov_r1,r0
   5310     li_br &car
   5311     call                             ## r0 = params
   5312     st_r0,sp,24                       ## slot 1 := params
   5313 
   5314     ld_r1,sp,40
   5315     li_br &cdr
   5316     call                             ## r0 = body list
   5317     mov_r1,r0
   5318     li_br &rewrite_lambda_body
   5319     call                             ## r0 = rewritten single expr
   5320 
   5321     mov_r2,r0
   5322     ld_r1,sp,24                       ## r1 = params
   5323     ld_r3,sp,32                      ## r3 = env
   5324     li_br &make_closure
   5325     tail_n3
   5326 
   5327 :eval_define
   5328     ## (define sym val-expr). gset-binds the evaluated val into the
   5329     ## global alist, returns nil.
   5330     ld_r1,sp,24
   5331     li_br &cdr
   5332     call                             ## r0 = (sym val-expr)
   5333     st_r0,sp,40                      ## slot 3 := (sym val-expr)
   5334 
   5335     mov_r1,r0
   5336     li_br &car
   5337     call                             ## r0 = sym
   5338     st_r0,sp,24                       ## slot 1 := sym
   5339 
   5340     ld_r1,sp,40
   5341     li_br &cdr
   5342     call                             ## r0 = (val-expr)
   5343     mov_r1,r0
   5344     li_br &car
   5345     call                             ## r0 = val-expr
   5346     mov_r1,r0
   5347     ld_r2,sp,32
   5348     li_br &eval
   5349     call                             ## r0 = val
   5350 
   5351     mov_r2,r0
   5352     ld_r1,sp,24
   5353     li_br &gset
   5354     call
   5355 
   5356     li_r0 %7
   5357     epilogue_n3
   5358     ret
   5359 
   5360 
   5361 ## ---- eval_set (step 12a) -------------------------------------------
   5362 ## (set! sym val-expr). Evaluates val-expr, then mutates the first
   5363 ## binding of sym found in local→global search order.
   5364 :eval_set
   5365     ld_r1,sp,24
   5366     li_br &cdr
   5367     call                             ## r0 = (sym val-expr)
   5368     mov_r1,r0
   5369     li_br &car
   5370     call                             ## r0 = sym
   5371     st_r0,sp,40                      ## slot 3 = sym
   5372 
   5373     ld_r1,sp,24
   5374     li_br &cdr
   5375     call
   5376     mov_r1,r0
   5377     li_br &cdr
   5378     call
   5379     mov_r1,r0
   5380     li_br &car
   5381     call                             ## r0 = val-expr
   5382     mov_r1,r0
   5383     ld_r2,sp,32
   5384     li_br &eval
   5385     call                             ## r0 = val
   5386 
   5387     mov_r3,r0
   5388     ld_r1,sp,40
   5389     ld_r2,sp,32
   5390     li_br &set_binding
   5391     tail_n3
   5392 
   5393 
   5394 ## ---- set_binding(r1=sym, r2=env, r3=val) -> r0 = unspec -------------
   5395 ## Looks up the first (sym . v) cell in env (local alist first, then
   5396 ## global) and mutates the cdr to val. Errors out on unbound sym.
   5397 :set_binding
   5398     prologue_n3
   5399     st_r3,sp,40                      ## slot 3 = val
   5400     st_r1,sp,24                       ## slot 1 = sym (for global fallback)
   5401 
   5402     li_br &lookup_alist
   5403     call                             ## r0 = (sym . v) pair or nil
   5404     li_r1 NIL
   5405     li_br &set_binding_global
   5406     beq_r0,r1
   5407 
   5408     ld_r1,sp,40
   5409     addi_r0,r0,neg2                  ## raw pair ptr
   5410     st_r1,r0,8                       ## mutate cdr
   5411     li_r0 UNSPEC
   5412     epilogue_n3
   5413     ret
   5414 
   5415 :set_binding_global
   5416     ld_r1,sp,24
   5417     li_r2 &global_env_cell
   5418     ld_r2,r2,0
   5419     li_br &lookup_alist
   5420     call
   5421     li_r1 NIL
   5422     li_br &err_unbound
   5423     beq_r0,r1
   5424 
   5425     ld_r1,sp,40
   5426     addi_r0,r0,neg2
   5427     st_r1,r0,8
   5428     li_r0 UNSPEC
   5429     epilogue_n3
   5430     ret
   5431 
   5432 
   5433 ## ---- eval_let (step 12b) -------------------------------------------
   5434 ## (let ((n1 e1) …) body…). Each RHS is evaluated in the *outer* env;
   5435 ## then body evaluates in env extended with all new bindings.
   5436 :eval_let
   5437     ld_r1,sp,24
   5438     li_br &cdr
   5439     call                             ## r0 = (bindings body…)
   5440     st_r0,sp,40                      ## slot 3 = after-head
   5441 
   5442     mov_r1,r0
   5443     li_br &car
   5444     call                             ## r0 = bindings
   5445     mov_r1,r0
   5446     ld_r2,sp,32
   5447     li_br &build_let_env
   5448     call                             ## r0 = new env
   5449     st_r0,sp,32                      ## slot 2 = new env
   5450 
   5451     ld_r1,sp,40
   5452     li_br &cdr
   5453     call                             ## r0 = body list
   5454     mov_r2,r0
   5455     li_r1 &sym_begin
   5456     ld_r1,r1,0
   5457     li_br &cons
   5458     call                             ## r0 = (begin . body)
   5459     mov_r1,r0
   5460     ld_r2,sp,32
   5461     li_br &eval
   5462     tail_n3
   5463 
   5464 
   5465 ## ---- eval_letstar --------------------------------------------------
   5466 ## (let* ((n1 e1) …) body…). Each RHS is evaluated in the env built
   5467 ## from previous bindings, then body evaluates in the final env.
   5468 :eval_letstar
   5469     ld_r1,sp,24
   5470     li_br &cdr
   5471     call
   5472     st_r0,sp,40
   5473 
   5474     mov_r1,r0
   5475     li_br &car
   5476     call
   5477     mov_r1,r0
   5478     ld_r2,sp,32
   5479     li_br &build_letstar_env
   5480     call
   5481     st_r0,sp,32
   5482 
   5483     ld_r1,sp,40
   5484     li_br &cdr
   5485     call
   5486     mov_r2,r0
   5487     li_r1 &sym_begin
   5488     ld_r1,r1,0
   5489     li_br &cons
   5490     call
   5491     mov_r1,r0
   5492     ld_r2,sp,32
   5493     li_br &eval
   5494     tail_n3
   5495 
   5496 
   5497 ## ---- eval_letrec ---------------------------------------------------
   5498 ## (letrec ((n1 e1) …) body…). Pre-binds each name to UNSPEC, then
   5499 ## walks the binding list a second time evaluating each RHS in the
   5500 ## fully pre-bound env and mutating the binding cell.
   5501 :eval_letrec
   5502     ld_r1,sp,24
   5503     li_br &cdr
   5504     call
   5505     st_r0,sp,40
   5506 
   5507     mov_r1,r0
   5508     li_br &car
   5509     call
   5510     mov_r1,r0
   5511     ld_r2,sp,32
   5512     li_br &build_letrec_env
   5513     call
   5514     st_r0,sp,32
   5515 
   5516     ld_r1,sp,40
   5517     li_br &cdr
   5518     call
   5519     mov_r2,r0
   5520     li_r1 &sym_begin
   5521     ld_r1,r1,0
   5522     li_br &cons
   5523     call
   5524     mov_r1,r0
   5525     ld_r2,sp,32
   5526     li_br &eval
   5527     tail_n3
   5528 
   5529 
   5530 ## ---- eval_cond (step 12c) ------------------------------------------
   5531 ## (cond (test body…) … (else body…)). Walks clauses in order, evaluates
   5532 ## each test; on truthy test or literal `else`, evaluates body as a begin.
   5533 ## Returns unspec if no clause fires.
   5534 :eval_cond
   5535     ld_r1,sp,24
   5536     li_br &cdr
   5537     call                             ## r0 = clauses
   5538     st_r0,sp,24                       ## slot 1 = cursor
   5539 
   5540 :eval_cond_loop
   5541     ld_r1,sp,24
   5542     li_r0 NIL
   5543     li_br &eval_cond_done
   5544     beq_r1,r0
   5545 
   5546     li_br &car
   5547     call                             ## r0 = clause
   5548     st_r0,sp,40                      ## slot 3 = clause
   5549 
   5550     mov_r1,r0
   5551     li_br &car
   5552     call                             ## r0 = test
   5553 
   5554     li_r1 &sym_else
   5555     ld_r1,r1,0
   5556     li_br &eval_cond_body
   5557     beq_r0,r1
   5558 
   5559     mov_r1,r0
   5560     ld_r2,sp,32
   5561     li_br &eval
   5562     call                             ## r0 = test value
   5563 
   5564     li_r1 FALSE
   5565     li_br &eval_cond_next
   5566     beq_r0,r1
   5567 
   5568 :eval_cond_body
   5569     ld_r1,sp,40
   5570     li_br &cdr
   5571     call                             ## r0 = body list
   5572     mov_r2,r0
   5573     li_r1 &sym_begin
   5574     ld_r1,r1,0
   5575     li_br &cons
   5576     call
   5577     mov_r1,r0
   5578     ld_r2,sp,32
   5579     li_br &eval
   5580     tail_n3
   5581 
   5582 :eval_cond_next
   5583     ld_r1,sp,24
   5584     li_br &cdr
   5585     call
   5586     st_r0,sp,24
   5587     li_br &eval_cond_loop
   5588     b
   5589 
   5590 :eval_cond_done
   5591     li_r0 UNSPEC
   5592     epilogue_n3
   5593     ret
   5594 
   5595 
   5596 ## ---- eval_quasiquote (step 12d) -------------------------------------
   5597 ## (quasiquote template). Delegates to quasi_expand which walks the
   5598 ## template recursively, handling `,x` (unquote) and `,@x`
   5599 ## (unquote-splicing) forms.
   5600 :eval_quasiquote
   5601     ld_r1,sp,24
   5602     li_br &cdr
   5603     call
   5604     mov_r1,r0
   5605     li_br &car
   5606     call                             ## r0 = template
   5607     mov_r1,r0
   5608     ld_r2,sp,32
   5609     li_br &quasi_expand
   5610     tail_n3
   5611 
   5612 
   5613 ## ---- build_let_env(r1=bindings, r2=outer_env) -> r0 = new_env ------
   5614 ## Builds a fresh alist extending outer_env. Each RHS expression is
   5615 ## evaluated in the *outer* env — the let semantics.
   5616 :build_let_env
   5617     prologue_n4
   5618     st_r1,sp,24                       ## slot 1 = cursor
   5619     st_r2,sp,32                      ## slot 2 = outer env
   5620     st_r2,sp,40                      ## slot 3 = new env acc
   5621 
   5622 :ble_loop
   5623     ld_r1,sp,24
   5624     li_r0 NIL
   5625     li_br &ble_done
   5626     beq_r1,r0
   5627 
   5628     li_br &car
   5629     call                             ## r0 = (name expr)
   5630     st_r0,sp,48                      ## slot 4 = (name expr)
   5631 
   5632     mov_r1,r0
   5633     li_br &cdr
   5634     call
   5635     mov_r1,r0
   5636     li_br &car
   5637     call                             ## r0 = expr
   5638 
   5639     mov_r1,r0
   5640     ld_r2,sp,32                      ## outer env
   5641     li_br &eval
   5642     call                             ## r0 = val
   5643 
   5644     mov_r2,r0
   5645     ld_r1,sp,48
   5646     addi_r1,r1,neg2
   5647     ld_r1,r1,0                       ## r1 = name
   5648     li_br &cons
   5649     call                             ## r0 = (name . val)
   5650 
   5651     mov_r1,r0
   5652     ld_r2,sp,40
   5653     li_br &cons
   5654     call                             ## r0 = extended env
   5655     st_r0,sp,40
   5656 
   5657     ld_r1,sp,24
   5658     li_br &cdr
   5659     call
   5660     st_r0,sp,24
   5661     li_br &ble_loop
   5662     b
   5663 
   5664 :ble_done
   5665     ld_r0,sp,40
   5666     epilogue_n4
   5667     ret
   5668 
   5669 
   5670 ## ---- build_letstar_env(r1=bindings, r2=outer_env) -> r0 = new_env --
   5671 ## Like build_let_env, but each RHS is evaluated in the env accumulated
   5672 ## so far (not the fixed outer env).
   5673 :build_letstar_env
   5674     prologue_n4
   5675     st_r1,sp,24
   5676     st_r2,sp,40                      ## slot 3 = current env
   5677 
   5678 :bls_loop
   5679     ld_r1,sp,24
   5680     li_r0 NIL
   5681     li_br &bls_done
   5682     beq_r1,r0
   5683 
   5684     li_br &car
   5685     call
   5686     st_r0,sp,48
   5687 
   5688     mov_r1,r0
   5689     li_br &cdr
   5690     call
   5691     mov_r1,r0
   5692     li_br &car
   5693     call                             ## r0 = expr
   5694 
   5695     mov_r1,r0
   5696     ld_r2,sp,40                      ## current env
   5697     li_br &eval
   5698     call                             ## r0 = val
   5699 
   5700     mov_r2,r0
   5701     ld_r1,sp,48
   5702     addi_r1,r1,neg2
   5703     ld_r1,r1,0
   5704     li_br &cons
   5705     call
   5706 
   5707     mov_r1,r0
   5708     ld_r2,sp,40
   5709     li_br &cons
   5710     call
   5711     st_r0,sp,40
   5712 
   5713     ld_r1,sp,24
   5714     li_br &cdr
   5715     call
   5716     st_r0,sp,24
   5717     li_br &bls_loop
   5718     b
   5719 
   5720 :bls_done
   5721     ld_r0,sp,40
   5722     epilogue_n4
   5723     ret
   5724 
   5725 
   5726 ## ---- build_letrec_env(r1=bindings, r2=outer_env) -> r0 = new_env --
   5727 ## Two-pass: pass 1 seeds each name with UNSPEC; pass 2 evaluates each
   5728 ## RHS in the fully pre-bound env and mutates the binding's cdr via
   5729 ## set_binding. Supports mutual recursion.
   5730 :build_letrec_env
   5731     prologue_n4
   5732     st_r1,sp,24                       ## slot 1 = cursor
   5733     st_r1,sp,32                      ## slot 2 = original bindings (for pass 2)
   5734     st_r2,sp,40                      ## slot 3 = new env
   5735 
   5736 :blr_pre_loop
   5737     ld_r1,sp,24
   5738     li_r0 NIL
   5739     li_br &blr_pre_done
   5740     beq_r1,r0
   5741 
   5742     li_br &car
   5743     call                             ## r0 = (name expr)
   5744     mov_r1,r0
   5745     li_br &car
   5746     call                             ## r0 = name
   5747 
   5748     mov_r1,r0
   5749     li_r2 UNSPEC
   5750     li_br &cons
   5751     call                             ## r0 = (name . UNSPEC)
   5752 
   5753     mov_r1,r0
   5754     ld_r2,sp,40
   5755     li_br &cons
   5756     call
   5757     st_r0,sp,40
   5758 
   5759     ld_r1,sp,24
   5760     li_br &cdr
   5761     call
   5762     st_r0,sp,24
   5763     li_br &blr_pre_loop
   5764     b
   5765 
   5766 :blr_pre_done
   5767     ld_r1,sp,32
   5768     st_r1,sp,24                       ## reset cursor
   5769 
   5770 :blr_eval_loop
   5771     ld_r1,sp,24
   5772     li_r0 NIL
   5773     li_br &blr_eval_done
   5774     beq_r1,r0
   5775 
   5776     li_br &car
   5777     call
   5778     st_r0,sp,48                      ## slot 4 = (name expr)
   5779 
   5780     mov_r1,r0
   5781     li_br &cdr
   5782     call
   5783     mov_r1,r0
   5784     li_br &car
   5785     call                             ## r0 = expr
   5786 
   5787     mov_r1,r0
   5788     ld_r2,sp,40
   5789     li_br &eval
   5790     call                             ## r0 = val
   5791 
   5792     mov_r3,r0
   5793     ld_r1,sp,48
   5794     addi_r1,r1,neg2
   5795     ld_r1,r1,0                       ## r1 = name
   5796     ld_r2,sp,40
   5797     li_br &set_binding
   5798     call
   5799 
   5800     ld_r1,sp,24
   5801     li_br &cdr
   5802     call
   5803     st_r0,sp,24
   5804     li_br &blr_eval_loop
   5805     b
   5806 
   5807 :blr_eval_done
   5808     ld_r0,sp,40
   5809     epilogue_n4
   5810     ret
   5811 
   5812 
   5813 ## ---- rewrite_lambda_body(r1=body_list) -> r0 = single body expr ----
   5814 ## Collects leading (define name val) forms from a lambda body and
   5815 ## rewrites the body into (letrec ((n1 v1) …) body-tail…) if any are
   5816 ## present. Otherwise returns either the single body form or a
   5817 ## (begin . body) wrapper.
   5818 :rewrite_lambda_body
   5819     prologue_n4
   5820     st_r1,sp,24                       ## slot 1 = cursor
   5821     li_r0 NIL
   5822     st_r0,sp,32                      ## slot 2 = reversed bindings acc
   5823 
   5824 :rlb_loop
   5825     ld_r1,sp,24
   5826     li_r0 NIL
   5827     li_br &rlb_done
   5828     beq_r1,r0
   5829 
   5830     li_br &car
   5831     call                             ## r0 = body form
   5832     mov_r1,r0
   5833     andi_r0,r0,7
   5834     li_r2 TAG_PAIR
   5835     li_br &rlb_done
   5836     bne_r0,r2
   5837 
   5838     st_r1,sp,40                      ## slot 3 = form
   5839     li_br &car
   5840     call                             ## r0 = head
   5841     li_r1 &sym_define
   5842     ld_r1,r1,0
   5843     li_br &rlb_done
   5844     bne_r0,r1
   5845 
   5846     ld_r1,sp,40
   5847     li_br &cdr
   5848     call                             ## r0 = (name val)
   5849 
   5850     mov_r1,r0
   5851     ld_r2,sp,32
   5852     li_br &cons
   5853     call
   5854     st_r0,sp,32
   5855 
   5856     ld_r1,sp,24
   5857     li_br &cdr
   5858     call
   5859     st_r0,sp,24
   5860     li_br &rlb_loop
   5861     b
   5862 
   5863 :rlb_done
   5864     ld_r1,sp,32
   5865     li_r0 NIL
   5866     li_br &rlb_no_defs
   5867     beq_r1,r0
   5868 
   5869     ld_r1,sp,32
   5870     li_br &list_reverse
   5871     call
   5872     st_r0,sp,32
   5873 
   5874     ld_r1,sp,32
   5875     ld_r2,sp,24
   5876     li_br &cons
   5877     call                             ## r0 = (bindings . body-tail)
   5878     mov_r2,r0
   5879     li_r1 &sym_letrec
   5880     ld_r1,r1,0
   5881     li_br &cons
   5882     tail_n4
   5883 
   5884 :rlb_no_defs
   5885     ld_r1,sp,24
   5886     li_br &cdr
   5887     call
   5888     li_r1 NIL
   5889     li_br &rlb_single
   5890     beq_r0,r1
   5891 
   5892     ld_r2,sp,24
   5893     li_r1 &sym_begin
   5894     ld_r1,r1,0
   5895     li_br &cons
   5896     tail_n4
   5897 
   5898 :rlb_single
   5899     ld_r1,sp,24
   5900     li_br &car
   5901     tail_n4
   5902 
   5903 
   5904 ## ---- list_reverse(r1=list) -> r0 = reversed list --------------------
   5905 ## Simple tail-consing reverse; used by rewrite_lambda_body to restore
   5906 ## source-order bindings.
   5907 :list_reverse
   5908     prologue_n2
   5909     st_r1,sp,24
   5910     li_r0 NIL
   5911     st_r0,sp,32
   5912 
   5913 :lrv_loop
   5914     ld_r1,sp,24
   5915     li_r0 NIL
   5916     li_br &lrv_done
   5917     beq_r1,r0
   5918 
   5919     li_br &car
   5920     call
   5921     mov_r1,r0
   5922     ld_r2,sp,32
   5923     li_br &cons
   5924     call
   5925     st_r0,sp,32
   5926 
   5927     ld_r1,sp,24
   5928     li_br &cdr
   5929     call
   5930     st_r0,sp,24
   5931     li_br &lrv_loop
   5932     b
   5933 
   5934 :lrv_done
   5935     ld_r0,sp,32
   5936     epilogue_n2
   5937     ret
   5938 
   5939 
   5940 ## ---- quasi_expand(r1=template, r2=env) -> r0 = value ---------------
   5941 ## Top-level quasiquote walker. Non-pair templates self-evaluate. A pair
   5942 ## whose car is `unquote` evaluates the first element of its cdr.
   5943 ## Otherwise the pair is walked as a list via quasi_list.
   5944 :quasi_expand
   5945     prologue_n3
   5946     st_r1,sp,24
   5947     st_r2,sp,32
   5948 
   5949     mov_r0,r1
   5950     andi_r0,r0,7
   5951     li_r2 TAG_PAIR
   5952     li_br &qe_pair
   5953     beq_r0,r2
   5954     ld_r0,sp,24
   5955     epilogue_n3
   5956     ret
   5957 
   5958 :qe_pair
   5959     ld_r1,sp,24
   5960     li_br &car
   5961     call
   5962     li_r1 &sym_unquote
   5963     ld_r1,r1,0
   5964     li_br &qe_unquote
   5965     beq_r0,r1
   5966 
   5967     ld_r1,sp,24
   5968     ld_r2,sp,32
   5969     li_br &quasi_list
   5970     tail_n3
   5971 
   5972 :qe_unquote
   5973     ld_r1,sp,24
   5974     li_br &cdr
   5975     call
   5976     mov_r1,r0
   5977     li_br &car
   5978     call
   5979     mov_r1,r0
   5980     ld_r2,sp,32
   5981     li_br &eval
   5982     tail_n3
   5983 
   5984 
   5985 ## ---- quasi_list(r1=list, r2=env) -> r0 = expanded list --------------
   5986 ## Recursively expands each element. If an element is (unquote-splicing X),
   5987 ## evaluates X and appends the resulting list into the output; else it
   5988 ## recursively calls quasi_expand on the element.
   5989 :quasi_list
   5990     prologue_n4
   5991     st_r1,sp,24                       ## slot 1 = cursor
   5992     st_r2,sp,32                      ## slot 2 = env
   5993 
   5994     li_r0 NIL
   5995     li_br &ql_nil
   5996     beq_r1,r0
   5997 
   5998     mov_r0,r1
   5999     andi_r0,r0,7
   6000     li_r2 TAG_PAIR
   6001     li_br &ql_nonpair
   6002     bne_r0,r2
   6003 
   6004     li_br &car
   6005     call                             ## r0 = head
   6006     st_r0,sp,40                      ## slot 3 = head
   6007 
   6008     mov_r1,r0
   6009     andi_r0,r0,7
   6010     li_r2 TAG_PAIR
   6011     li_br &ql_head_reg
   6012     bne_r0,r2
   6013 
   6014     ld_r1,sp,40
   6015     li_br &car
   6016     call                             ## r0 = car(head)
   6017     li_r1 &sym_unquote_splicing
   6018     ld_r1,r1,0
   6019     li_br &ql_splice
   6020     beq_r0,r1
   6021 
   6022 :ql_head_reg
   6023     ld_r1,sp,40
   6024     ld_r2,sp,32
   6025     li_br &quasi_expand
   6026     call
   6027     st_r0,sp,40                      ## slot 3 = expanded head
   6028 
   6029     ld_r1,sp,24
   6030     li_br &cdr
   6031     call
   6032     mov_r1,r0
   6033     ld_r2,sp,32
   6034     li_br &quasi_list
   6035     call
   6036 
   6037     mov_r2,r0
   6038     ld_r1,sp,40
   6039     li_br &cons
   6040     tail_n4
   6041 
   6042 :ql_splice
   6043     ld_r1,sp,40
   6044     li_br &cdr
   6045     call
   6046     mov_r1,r0
   6047     li_br &car
   6048     call                             ## r0 = X (the spliced expression)
   6049     mov_r1,r0
   6050     ld_r2,sp,32
   6051     li_br &eval
   6052     call                             ## r0 = evaluated list
   6053     st_r0,sp,40
   6054 
   6055     ld_r1,sp,24
   6056     li_br &cdr
   6057     call
   6058     mov_r1,r0
   6059     ld_r2,sp,32
   6060     li_br &quasi_list
   6061     call
   6062 
   6063     mov_r2,r0
   6064     ld_r1,sp,40
   6065     li_br &append_one
   6066     tail_n4
   6067 
   6068 :ql_nil
   6069     li_r0 NIL
   6070     epilogue_n4
   6071     ret
   6072 
   6073 :ql_nonpair
   6074     ld_r0,sp,24
   6075     epilogue_n4
   6076     ret
   6077 
   6078 
   6079 ## ---- Step-6 error landing pads --------------------------------------
   6080 :err_unbound
   6081     li_r1 &msg_unbound
   6082     li_r2 %14  ## strlen("unbound symbol") == 14
   6083     li_br &error
   6084     b
   6085 
   6086 :err_arity
   6087     li_r1 &msg_arity
   6088     li_r2 %14  ## strlen("arity mismatch") == 14
   6089     li_br &error
   6090     b
   6091 
   6092 :err_not_callable
   6093     li_r1 &msg_not_callable
   6094     li_r2 %12  ## strlen("not callable") == 12
   6095     li_br &error
   6096     b
   6097 
   6098 :err_too_many_args
   6099     li_r1 &msg_too_many_args
   6100     li_r2 %21  ## strlen("primitive argc > 32") == 21
   6101     li_br &error
   6102     b
   6103 
   6104 :err_bad_prim
   6105     li_r1 &msg_bad_prim
   6106     li_r2 %20  ## strlen("unknown primitive id") == 20
   6107     li_br &error
   6108     b
   6109 
   6110 :err_path_too_long
   6111     li_r1 &msg_path_too_long
   6112     li_r2 %18
   6113     li_br &error
   6114     b
   6115 
   6116 
   6117 ## ---- Static strings -------------------------------------------------
   6118 :msg_newline
   6119 "
   6120 "
   6121 :msg_error_prefix "error: "
   6122 :msg_oom "heap exhausted"
   6123 :msg_intern_not_eq "intern: foo a != foo b (same name)"
   6124 :msg_intern_collision "intern: foo == bar (distinct names collided)"
   6125 :msg_reader_bad "reader: malformed input"
   6126 :msg_at " at "
   6127 :msg_colon ":"
   6128 :msg_unbound "unbound symbol"
   6129 :msg_arity "arity mismatch"
   6130 :msg_not_callable "not callable"
   6131 :msg_usage "usage: lisp <file.scm>"
   6132 :msg_open_fail "failed to open source file"
   6133 :msg_src_too_big "source file too large"
   6134 :msg_too_many_args "primitive argc > 32"
   6135 :msg_bad_prim "unknown primitive id"
   6136 :msg_path_too_long "file path too long"
   6137 
   6138 ## Interned name samples used by the self-test.
   6139 :str_foo "foo"
   6140 :str_bar "bar"
   6141 
   6142 ## Special-form name strings. Lengths are baked into _start's intern
   6143 ## calls (update both sides together).
   6144 :str_quote "quote"
   6145 :str_if "if"
   6146 :str_begin "begin"
   6147 :str_lambda "lambda"
   6148 :str_define "define"
   6149 :str_quasiquote "quasiquote"
   6150 :str_unquote "unquote"
   6151 :str_unquote_splicing "unquote-splicing"
   6152 :str_set "set!"
   6153 :str_let "let"
   6154 :str_letstar "let*"
   6155 :str_letrec "letrec"
   6156 :str_cond "cond"
   6157 :str_else "else"
   6158 
   6159 ## Primitive name strings (step 10b). The registration table below
   6160 ## holds (ptr, len, code_id, type, arity) for each. _start walks the
   6161 ## table, interns each name, and binds a freshly-built primitive into
   6162 ## global_env.
   6163 :str_prim_plus "+"
   6164 :str_prim_minus "-"
   6165 :str_prim_mul "*"
   6166 :str_prim_div "/"
   6167 :str_prim_mod "%"
   6168 :str_prim_numeq "="
   6169 :str_prim_lt "<"
   6170 :str_prim_gt ">"
   6171 :str_prim_min "min"
   6172 :str_prim_max "max"
   6173 :str_prim_bitand "bit-and"
   6174 :str_prim_bitor "bit-or"
   6175 :str_prim_bitxor "bit-xor"
   6176 :str_prim_bitnot "bit-not"
   6177 :str_prim_ashift "arithmetic-shift"
   6178 :str_prim_numberp "number?"
   6179 :str_prim_symbolp "symbol?"
   6180 :str_prim_stringp "string?"
   6181 :str_prim_vectorp "vector?"
   6182 :str_prim_procp "procedure?"
   6183 :str_prim_eqp "eq?"
   6184 
   6185 ## Step-10d list-core names.
   6186 :str_prim_cons "cons"
   6187 :str_prim_car "car"
   6188 :str_prim_cdr "cdr"
   6189 :str_prim_pairp "pair?"
   6190 :str_prim_nullp "null?"
   6191 :str_prim_list "list"
   6192 :str_prim_append "append"
   6193 
   6194 ## Step-10e string / vector / I/O / equal? / apply names.
   6195 :str_prim_string_length "string-length"
   6196 :str_prim_string_ref "string-ref"
   6197 :str_prim_substring "substring"
   6198 :str_prim_string_append "string-append"
   6199 :str_prim_string_to_symbol "string->symbol"
   6200 :str_prim_symbol_to_string "symbol->string"
   6201 :str_prim_make_vector "make-vector"
   6202 :str_prim_vector_ref "vector-ref"
   6203 :str_prim_vector_set "vector-set!"
   6204 :str_prim_vector_length "vector-length"
   6205 :str_prim_display "display"
   6206 :str_prim_write "write"
   6207 :str_prim_newline "newline"
   6208 :str_prim_format "format"
   6209 :str_prim_error "error"
   6210 :str_prim_read_file "read-file"
   6211 :str_prim_write_file "write-file"
   6212 :str_prim_apply "apply"
   6213 
   6214 
   6215 ## Registration table. 40-byte records: ptr(8) + len(8) + code_id(8) +
   6216 ## type(8) + arity(8). End-sentinel = zero name pointer. _start iterates
   6217 ## with ADDI +40.
   6218 :prim_table
   6219 ## Primitives promoted to the Scheme prelude (src/prelude.scm) and no
   6220 ## longer registered here: <=, >=, zero?, negative?, positive?, abs,
   6221 ## length, list?, reverse, assoc, member, vector->list, list->vector,
   6222 ## equal?. Code ids are contiguous 0..45 after that removal.
   6223 ## (+ ...) variadic — code 0
   6224 &str_prim_plus %0
   6225 %1 %0
   6226 %0 %0
   6227 %6 %0
   6228 %0 %0
   6229 ## (- x ...) variadic — code 1 (unary-negate branch in body)
   6230 &str_prim_minus %0
   6231 %1 %0
   6232 %1 %0
   6233 %6 %0
   6234 %0 %0
   6235 ## (* ...) variadic — code 2
   6236 &str_prim_mul %0
   6237 %1 %0
   6238 %2 %0
   6239 %6 %0
   6240 %0 %0
   6241 ## (/ x y) fixed 2 — code 3
   6242 &str_prim_div %0
   6243 %1 %0
   6244 %3 %0
   6245 %5 %0
   6246 %2 %0
   6247 ## (% x y) fixed 2 — code 4
   6248 &str_prim_mod %0
   6249 %1 %0
   6250 %4 %0
   6251 %5 %0
   6252 %2 %0
   6253 ## (= x y) fixed 2 — code 5
   6254 &str_prim_numeq %0
   6255 %1 %0
   6256 %5 %0
   6257 %5 %0
   6258 %2 %0
   6259 ## (< x y) fixed 2 — code 6
   6260 &str_prim_lt %0
   6261 %1 %0
   6262 %6 %0
   6263 %5 %0
   6264 %2 %0
   6265 ## (> x y) fixed 2 — code 7
   6266 &str_prim_gt %0
   6267 %1 %0
   6268 %7 %0
   6269 %5 %0
   6270 %2 %0
   6271 ## (min ...) variadic — code 8 (≥1 enforced by body load)
   6272 &str_prim_min %0
   6273 %3 %0
   6274 %8 %0
   6275 %6 %0
   6276 %0 %0
   6277 ## (max ...) variadic — code 9
   6278 &str_prim_max %0
   6279 %3 %0
   6280 %9 %0
   6281 %6 %0
   6282 %0 %0
   6283 ## (bit-and ...) variadic — code 10
   6284 &str_prim_bitand %0
   6285 %7 %0
   6286 %10 %0
   6287 %6 %0
   6288 %0 %0
   6289 ## (bit-or ...) variadic — code 11
   6290 &str_prim_bitor %0
   6291 %6 %0
   6292 %11 %0
   6293 %6 %0
   6294 %0 %0
   6295 ## (bit-xor ...) variadic — code 12
   6296 &str_prim_bitxor %0
   6297 %7 %0
   6298 %12 %0
   6299 %6 %0
   6300 %0 %0
   6301 ## (bit-not x) fixed 1 — code 13
   6302 &str_prim_bitnot %0
   6303 %7 %0
   6304 %13 %0
   6305 %5 %0
   6306 %1 %0
   6307 ## (arithmetic-shift n k) fixed 2 — code 14
   6308 &str_prim_ashift %0
   6309 %16 %0
   6310 %14 %0
   6311 %5 %0
   6312 %2 %0
   6313 ## (number? x) fixed 1 — code 15
   6314 &str_prim_numberp %0
   6315 %7 %0
   6316 %15 %0
   6317 %5 %0
   6318 %1 %0
   6319 ## (symbol? x) fixed 1 — code 16
   6320 &str_prim_symbolp %0
   6321 %7 %0
   6322 %16 %0
   6323 %5 %0
   6324 %1 %0
   6325 ## (string? x) fixed 1 — code 17
   6326 &str_prim_stringp %0
   6327 %7 %0
   6328 %17 %0
   6329 %5 %0
   6330 %1 %0
   6331 ## (vector? x) fixed 1 — code 18
   6332 &str_prim_vectorp %0
   6333 %7 %0
   6334 %18 %0
   6335 %5 %0
   6336 %1 %0
   6337 ## (procedure? x) fixed 1 — code 19
   6338 &str_prim_procp %0
   6339 %10 %0
   6340 %19 %0
   6341 %5 %0
   6342 %1 %0
   6343 ## (eq? x y) fixed 2 — code 20
   6344 &str_prim_eqp %0
   6345 %3 %0
   6346 %20 %0
   6347 %5 %0
   6348 %2 %0
   6349 ## (cons a d) fixed 2 — code 21
   6350 &str_prim_cons %0
   6351 %4 %0
   6352 %21 %0
   6353 %5 %0
   6354 %2 %0
   6355 ## (car p) fixed 1 — code 22
   6356 &str_prim_car %0
   6357 %3 %0
   6358 %22 %0
   6359 %5 %0
   6360 %1 %0
   6361 ## (cdr p) fixed 1 — code 23
   6362 &str_prim_cdr %0
   6363 %3 %0
   6364 %23 %0
   6365 %5 %0
   6366 %1 %0
   6367 ## (pair? x) fixed 1 — code 24
   6368 &str_prim_pairp %0
   6369 %5 %0
   6370 %24 %0
   6371 %5 %0
   6372 %1 %0
   6373 ## (null? x) fixed 1 — code 25
   6374 &str_prim_nullp %0
   6375 %5 %0
   6376 %25 %0
   6377 %5 %0
   6378 %1 %0
   6379 ## (list ...) variadic — code 26
   6380 &str_prim_list %0
   6381 %4 %0
   6382 %26 %0
   6383 %6 %0
   6384 %0 %0
   6385 ## (append ...) variadic — code 27
   6386 &str_prim_append %0
   6387 %6 %0
   6388 %27 %0
   6389 %6 %0
   6390 %0 %0
   6391 ## (string-length s) fixed 1 — code 28
   6392 &str_prim_string_length %0
   6393 %13 %0
   6394 %28 %0
   6395 %5 %0
   6396 %1 %0
   6397 ## (string-ref s i) fixed 2 — code 29
   6398 &str_prim_string_ref %0
   6399 %10 %0
   6400 %29 %0
   6401 %5 %0
   6402 %2 %0
   6403 ## (substring s start end) fixed 3 — code 30
   6404 &str_prim_substring %0
   6405 %9 %0
   6406 %30 %0
   6407 %5 %0
   6408 %3 %0
   6409 ## (string-append ...) variadic — code 31
   6410 &str_prim_string_append %0
   6411 %13 %0
   6412 %31 %0
   6413 %6 %0
   6414 %0 %0
   6415 ## (string->symbol s) fixed 1 — code 32
   6416 &str_prim_string_to_symbol %0
   6417 %14 %0
   6418 %32 %0
   6419 %5 %0
   6420 %1 %0
   6421 ## (symbol->string sym) fixed 1 — code 33
   6422 &str_prim_symbol_to_string %0
   6423 %14 %0
   6424 %33 %0
   6425 %5 %0
   6426 %1 %0
   6427 ## (make-vector n init) fixed 2 — code 34
   6428 &str_prim_make_vector %0
   6429 %11 %0
   6430 %34 %0
   6431 %5 %0
   6432 %2 %0
   6433 ## (vector-ref v i) fixed 2 — code 35
   6434 &str_prim_vector_ref %0
   6435 %10 %0
   6436 %35 %0
   6437 %5 %0
   6438 %2 %0
   6439 ## (vector-set! v i x) fixed 3 — code 36
   6440 &str_prim_vector_set %0
   6441 %11 %0
   6442 %36 %0
   6443 %5 %0
   6444 %3 %0
   6445 ## (vector-length v) fixed 1 — code 37
   6446 &str_prim_vector_length %0
   6447 %13 %0
   6448 %37 %0
   6449 %5 %0
   6450 %1 %0
   6451 ## (display x) fixed 1 — code 38
   6452 &str_prim_display %0
   6453 %7 %0
   6454 %38 %0
   6455 %5 %0
   6456 %1 %0
   6457 ## (write x) fixed 1 — code 39
   6458 &str_prim_write %0
   6459 %5 %0
   6460 %39 %0
   6461 %5 %0
   6462 %1 %0
   6463 ## (newline) fixed 0 — code 40
   6464 &str_prim_newline %0
   6465 %7 %0
   6466 %40 %0
   6467 %5 %0
   6468 %0 %0
   6469 ## (format fmt ...) variadic — code 41
   6470 &str_prim_format %0
   6471 %6 %0
   6472 %41 %0
   6473 %6 %0
   6474 %0 %0
   6475 ## (error msg) fixed 1 — code 42
   6476 &str_prim_error %0
   6477 %5 %0
   6478 %42 %0
   6479 %5 %0
   6480 %1 %0
   6481 ## (read-file path) fixed 1 — code 43
   6482 &str_prim_read_file %0
   6483 %9 %0
   6484 %43 %0
   6485 %5 %0
   6486 %1 %0
   6487 ## (write-file path data) fixed 2 — code 44
   6488 &str_prim_write_file %0
   6489 %10 %0
   6490 %44 %0
   6491 %5 %0
   6492 %2 %0
   6493 ## (apply proc arg ... last-list) variadic — code 45
   6494 &str_prim_apply %0
   6495 %5 %0
   6496 %45 %0
   6497 %6 %0
   6498 %0 %0
   6499 ## End sentinel: zero name pointer.
   6500 %0 %0
   6501 %0 %0
   6502 %0 %0
   6503 %0 %0
   6504 %0 %0
   6505 
   6506 
   6507 ## ---- Special-form symbol slots --------------------------------------
   6508 ## Zero-initialized; _start populates each slot with the interned
   6509 ## tagged-symbol pointer so eval_pair can dispatch by pointer identity.
   6510 :sym_quote            %0 %0
   6511 :sym_if               %0 %0
   6512 :sym_begin            %0 %0
   6513 :sym_lambda           %0 %0
   6514 :sym_define           %0 %0
   6515 :sym_quasiquote       %0 %0
   6516 :sym_unquote          %0 %0
   6517 :sym_unquote_splicing %0 %0
   6518 :sym_set              %0 %0
   6519 :sym_let              %0 %0
   6520 :sym_letstar          %0 %0
   6521 :sym_letrec           %0 %0
   6522 :sym_cond             %0 %0
   6523 :sym_else             %0 %0
   6524 
   6525 ## Global-binding alist head. Zero-initialized (which is a valid
   6526 ## untagged pair/nil sentinel? No — nil = 0x07. Seed to nil on entry
   6527 ## from _start; the zero here would be interpreted as a pair pointer
   6528 ## otherwise).
   6529 :global_env_cell  NIL %0
   6530 
   6531 
   6532 ## --------------------------------------------------------------------
   6533 ## :ELF_end marks the end of the initialised image — everything above
   6534 ## is code and data with real bytes, everything below is BSS: labels
   6535 ## only, no real content. The ELF header declares p_filesz ending
   6536 ## here and p_memsz ending at :ELF_bss_end, so the kernel zero-fills
   6537 ## the tail at load time and the on-disk file stops at :ELF_end (a
   6538 ## post-link truncate drops the trailing zero bytes). Labels here
   6539 ## still need `%0`/`ZERO32` placeholders so hex2's IP keeps advancing
   6540 ## and label addresses after them remain correct; the bytes those
   6541 ## emit get truncated away and replaced by kernel zeros.
   6542 ## --------------------------------------------------------------------
   6543 :ELF_end
   6544 
   6545 :stack_bottom_fp  %0 %0
   6546 :gc_root_fp       %0 %0
   6547 
   6548 ## Aligned mirrors of :pair_heap_start / :obj_heap_start. _start fills
   6549 ## these once, so every GC code path can derive the same canonical
   6550 ## start address that the bump pointer actually used for its first
   6551 ## allocation. See the alignment block in _start for the rationale.
   6552 :pair_heap_base   %0 %0
   6553 :obj_heap_base    %0 %0
   6554 
   6555 ## Live prefix length of :prim_argv, set by marshal_argv on every
   6556 ## primitive call. The GC root walker uses this as the upper bound
   6557 ## of the prim_argv scan. Initialised to 0 so a GC that fires before
   6558 ## the first primitive call walks zero slots.
   6559 :prim_argc        %0 %0
   6560 
   6561 :free_list_pair   %0 %0
   6562 :free_list_obj16  %0 %0
   6563 :free_list_obj24  %0 %0
   6564 :free_list_obj32  %0 %0
   6565 :free_list_obj40  %0 %0
   6566 :free_list_obj48  %0 %0
   6567 :free_list_obj56  %0 %0
   6568 :free_list_obj64  %0 %0
   6569 :free_list_obj80  %0 %0
   6570 :free_list_obj96  %0 %0
   6571 :free_list_obj128 %0 %0
   6572 
   6573 ## mark_stack_next lives in BSS: gc_mark_all re-seeds it to &mark_stack
   6574 ## at the top of every mark pass (see :gc_mark_all), so the initial
   6575 ## value is dead — zero is fine.
   6576 :mark_stack_next  %0 %0
   6577 :pair_mark_bitmap
   6578 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6579 
   6580 :mark_stack
   6581 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6582 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6583 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6584 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6585 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6586 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6587 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6588 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6589 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6590 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6591 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6592 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6593 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6594 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6595 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6596 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6597 :mark_stack_end
   6598 
   6599 
   6600 ## ---- Primitive argv scratch buffer (32 slots × 8B = 256B) -----------
   6601 ## apply fills this with tagged values before cascading to a primitive
   6602 ## body. 32 slots is generous for the step-10 surface; extend if later
   6603 ## primitives need more. Zeroed so a stray read sees the 0 tagged
   6604 ## sentinel (not a valid value — harmless).
   6605 :prim_argv
   6606 ZERO32 ZERO32
   6607 ZERO32 ZERO32
   6608 ZERO32 ZERO32
   6609 ZERO32 ZERO32
   6610 
   6611 ## Reader-state save area for eval_source().
   6612 :saved_src_base    %0 %0
   6613 :saved_src_len     %0 %0
   6614 :saved_src_cursor  %0 %0
   6615 :saved_src_line    %0 %0
   6616 :saved_src_col     %0 %0
   6617 
   6618 ## Shared buffers for pathname marshaling, string literal decoding, and
   6619 ## file I/O primitives.
   6620 :path_buf
   6621 ZERO32 ZERO32
   6622 ZERO32 ZERO32
   6623 ZERO32 ZERO32
   6624 ZERO32 ZERO32
   6625 
   6626 :reader_string_buf
   6627 ZERO32 ZERO32
   6628 ZERO32 ZERO32
   6629 ZERO32 ZERO32
   6630 ZERO32 ZERO32
   6631 ZERO32 ZERO32
   6632 ZERO32 ZERO32
   6633 ZERO32 ZERO32
   6634 ZERO32 ZERO32
   6635 
   6636 :io_buf
   6637 ZERO32 ZERO32
   6638 ZERO32 ZERO32
   6639 ZERO32 ZERO32
   6640 ZERO32 ZERO32
   6641 ZERO32 ZERO32
   6642 ZERO32 ZERO32
   6643 ZERO32 ZERO32
   6644 ZERO32 ZERO32
   6645 
   6646 
   6647 ## ---- Symbol table (4096 slots × 8 bytes = 32 KiB) -------------------
   6648 ## Open-addressing hash table. Empty slot = 0 (no valid tagged value
   6649 ## is 0). LISP.md §GC §Roots makes this a named BSS root.
   6650 :symbol_table
   6651 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6652 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6653 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6654 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6655 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6656 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6657 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6658 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6659 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6660 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6661 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6662 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6663 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6664 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6665 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6666 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6667 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6668 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6669 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6670 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6671 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6672 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6673 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6674 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6675 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6676 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6677 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6678 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6679 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6680 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6681 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6682 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6683 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6684 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6685 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6686 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6687 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6688 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6689 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6690 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6691 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6692 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6693 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6694 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6695 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6696 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6697 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6698 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6699 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6700 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6701 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6702 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6703 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6704 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6705 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6706 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6707 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6708 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6709 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6710 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6711 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6712 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6713 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6714 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6715 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6716 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6717 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6718 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6719 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6720 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6721 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6722 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6723 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6724 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6725 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6726 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6727 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6728 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6729 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6730 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6731 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6732 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6733 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6734 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6735 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6736 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6737 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6738 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6739 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6740 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6741 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6742 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6743 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6744 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6745 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6746 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6747 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6748 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6749 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6750 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6751 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6752 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6753 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6754 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6755 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6756 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6757 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6758 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6759 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6760 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6761 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6762 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6763 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6764 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6765 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6766 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6767 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6768 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6769 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6770 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6771 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6772 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6773 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6774 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6775 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6776 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6777 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6778 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6779 :symbol_table_end
   6780 
   6781 
   6782 ## ---- Heap arena (64 KiB split 32 KiB / 32 KiB for GC bring-up) -------
   6783 :heap_start
   6784 :pair_heap_start
   6785 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6786 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6787 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6788 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6789 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6790 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6791 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6792 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6793 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6794 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6795 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6796 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6797 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6798 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6799 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6800 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6801 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6802 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6803 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6804 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6805 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6806 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6807 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6808 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6809 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6810 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6811 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6812 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6813 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6814 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6815 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6816 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6817 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6818 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6819 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6820 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6821 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6822 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6823 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6824 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6825 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6826 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6827 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6828 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6829 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6830 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6831 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6832 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6833 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6834 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6835 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6836 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6837 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6838 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6839 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6840 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6841 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6842 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6843 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6844 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6845 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6846 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6847 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6848 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6849 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6850 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6851 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6852 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6853 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6854 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6855 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6856 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6857 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6858 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6859 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6860 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6861 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6862 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6863 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6864 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6865 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6866 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6867 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6868 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6869 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6870 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6871 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6872 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6873 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6874 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6875 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6876 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6877 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6878 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6879 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6880 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6881 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6882 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6883 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6884 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6885 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6886 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6887 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6888 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6889 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6890 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6891 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6892 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6893 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6894 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6895 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6896 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6897 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6898 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6899 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6900 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6901 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6902 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6903 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6904 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6905 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6906 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6907 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6908 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6909 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6910 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6911 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6912 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6913 :pair_heap_end
   6914 :obj_heap_start
   6915 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6916 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6917 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6918 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6919 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6920 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6921 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6922 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6923 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6924 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6925 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6926 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6927 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6928 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6929 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6930 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6931 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6932 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6933 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6934 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6935 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6936 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6937 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6938 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6939 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6940 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6941 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6942 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6943 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6944 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6945 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6946 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6947 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6948 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6949 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6950 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6951 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6952 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6953 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6954 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6955 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6956 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6957 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6958 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6959 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6960 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6961 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6962 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6963 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6964 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6965 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6966 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6967 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6968 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6969 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6970 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6971 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6972 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6973 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6974 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6975 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6976 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6977 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6978 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6979 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6980 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6981 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6982 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6983 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6984 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6985 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6986 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6987 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6988 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6989 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6990 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6991 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6992 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6993 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6994 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6995 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6996 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6997 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6998 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   6999 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7000 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7001 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7002 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7003 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7004 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7005 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7006 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7007 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7008 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7009 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7010 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7011 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7012 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7013 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7014 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7015 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7016 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7017 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7018 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7019 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7020 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7021 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7022 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7023 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7024 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7025 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7026 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7027 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7028 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7029 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7030 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7031 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7032 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7033 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7034 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7035 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7036 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7037 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7038 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7039 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7040 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7041 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7042 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7043 :obj_heap_end
   7044 :heap_tail
   7045 
   7046 
   7047 ## ---- Source buffer (16 KiB) -----------------------------------------
   7048 ## Receives the contents of the file named by argv[1]. Sized to cover
   7049 ## the step-9 test fixtures with headroom; widen once those exist.
   7050 ## Kept out of the Scheme heap so the reader's raw byte pointers don't
   7051 ## fight the bump allocator.
   7052 :src_buf
   7053 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7054 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7055 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7056 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7057 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7058 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7059 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7060 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7061 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7062 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7063 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7064 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7065 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7066 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7067 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7068 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7069 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7070 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7071 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7072 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7073 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7074 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7075 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7076 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7077 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7078 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7079 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7080 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7081 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7082 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7083 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7084 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7085 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7086 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7087 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7088 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7089 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7090 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7091 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7092 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7093 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7094 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7095 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7096 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7097 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7098 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7099 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7100 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7101 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7102 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7103 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7104 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7105 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7106 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7107 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7108 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7109 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7110 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7111 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7112 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7113 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7114 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7115 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7116 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32
   7117 :src_buf_end
   7118 
   7119 :ELF_bss_end