boot2

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

hex2pp.P1 (66900B)


      1 ## hex2pp.P1 -- P1 implementation of the hex2++ assembler/linker.
      2 ##
      3 ## Mirrors hex2pp/hex2pp.c. See docs/HEX2pp.md for the full spec; brief:
      4 ##
      5 ##   Input is scanned once. Label definitions are recorded into a table
      6 ##   on the fly; label references emit zero placeholders and append a
      7 ##   fixup record. After the scan, fixups are resolved against the
      8 ##   completed label table and patched into the output buffer.
      9 ##
     10 ##   Active syntax:
     11 ##     digits in current byte mode    -> raw bytes (HEX or BINARY)
     12 ##     :NAME                          -> label definition
     13 ##     SIGIL NAME [- OTHER]           -> label reference (! @ $ ~ % &)
     14 ##     .align N [PATTERN]             -> pad to N-byte boundary
     15 ##     .fill N B                      -> N copies of byte B
     16 ##     .scope / .endscope             -> nestable local-label scope
     17 ##     .ptrsize N                     -> width of '%' and '&' (4 or 8)
     18 ##     # ... / ; ...                  -> line comment
     19 ##
     20 ##   Multi-byte reference values are emitted little-endian by default.
     21 ##
     22 ## Invocation: hex2pp [-B ADDR] [-E|-e] [-b] [-N] IN OUT
     23 ##
     24 ## P1 ABI: a0..a3 arg/return, t0..t2 caller-saved temps. Non-leaf
     25 ## functions use enter_0 / eret. Entry is the portable p1_main
     26 ## (a0=argc, a1=argv); the backend-owned :_start stub captures argc/argv
     27 ## from the native entry state and sys_exits p1_main's return value.
     28 ##
     29 ## chmod note: the seed P1 mnemonic table only exposes openat/read/write/
     30 ## exit. We encode the desired final mode (0750 or 0640) directly in
     31 ## openat's mode argument at file-creation time, achieving the same
     32 ## resulting file permissions as the C reference's fopen+chmod.
     33 
     34 ## --- Caps ------------------------------------------------------------------
     35 ## Mirrors hex2pp.c constants. Stored as 8-byte little-endian.
     36 DEFINE H2_INPUT_CAP 0000000100000000
     37 DEFINE H2_OUTPUT_CAP 0000000800000000
     38 DEFINE H2_TEXT_CAP 0000800000000000
     39 DEFINE H2_LABEL_CAP 0000100000000000
     40 DEFINE H2_TOKEN_CAP 0010000000000000
     41 DEFINE H2_SCOPE_CAP 2000000000000000
     42 DEFINE H2_FIXUP_CAP 0000100000000000
     43 DEFINE H2_SCOPE_HISTORY_CAP 0000400000000000
     44 
     45 ## openat / mode constants (Linux generic).
     46 DEFINE H2_O_WRONLY_CREAT_TRUNC 4102000000000000
     47 DEFINE H2_O_RDONLY 0000000000000000
     48 DEFINE H2_MODE_0750 E801000000000000
     49 DEFINE H2_MODE_0640 A001000000000000
     50 DEFINE H2_AT_FDCWD 9CFFFFFFFFFFFFFF
     51 
     52 DEFINE ZERO8 '0000000000000000'
     53 DEFINE ZERO4 '00000000'
     54 
     55 ## --- BSS layout (offsets from ELF_end) -------------------------------------
     56 ## Each "_ptr" is a one-word slot in the executable's static data; p1_main's
     57 ## bss_init_loop initializes each to ELF_end + OFF_*. Arenas live past
     58 ## ELF_end (covered by the segment's memsz; the loader zero-initializes).
     59 ##
     60 ## Region sizes (matching the C caps):
     61 ##   scope_stack       32 *  8 =     256 B
     62 ##   line_scratch              =    64 B
     63 ##   name_buf                  =  4096 B   (used by read_directive_name)
     64 ##   label_buf                 =  4096 B   (unused since single-pass refactor)
     65 ##   other_buf                 =  4096 B   (unused since single-pass refactor)
     66 ##   pat_buf                   =  4096 B
     67 ##   ev_bytes                  =     8 B
     68 ##   df_byte                   =     8 B
     69 ##   input_buf                 =  16 MiB
     70 ##   output_buf                = 128 MiB
     71 ##   text_buf                  =   8 MiB
     72 ##   labels                    =  32 MiB    (1<<20 * 32 B)
     73 ##   fixups                    =  96 MiB    (1<<20 * 96 B)
     74 ##   scope_history             =  32 MiB    (1<<22 *  8 B)
     75 ##
     76 ## Compact cumulative offsets in 8-byte little-endian hex.
     77 DEFINE H2_OFF_scope_stack    0000000000000000
     78 DEFINE H2_OFF_line_scratch   0001000000000000
     79 DEFINE H2_OFF_name_buf       4001000000000000
     80 DEFINE H2_OFF_label_buf      4011000000000000
     81 DEFINE H2_OFF_other_buf      4021000000000000
     82 DEFINE H2_OFF_pat_buf        4031000000000000
     83 DEFINE H2_OFF_ev_bytes       4041000000000000
     84 DEFINE H2_OFF_df_byte        5041000000000000
     85 ## input_buf:        0x00004200 + 16 MiB
     86 ## output_buf:       0x01004200 + 128 MiB
     87 ## text_buf:         0x09004200 + 8 MiB
     88 ## labels:           0x09804200 + 32 MiB
     89 ## fixups:           0x0B804200 + 96 MiB
     90 ## scope_history:    0x11804200 + 32 MiB (end at ~313 MiB; ELF p_memsz is 512 MiB).
     91 DEFINE H2_OFF_input_buf      0042000000000000
     92 DEFINE H2_OFF_output_buf     0042000100000000
     93 DEFINE H2_OFF_text_buf       0042000900000000
     94 DEFINE H2_OFF_labels         0042800900000000
     95 DEFINE H2_OFF_fixups         0042800B00000000
     96 DEFINE H2_OFF_scope_history  0042801100000000
     97 
     98 ## --- p1_main: argv parse -> load input -> two passes -> write -> exit ------
     99 
    100 :p1_main
    101     enter_0
    102 
    103     # Save argc / argv before anything clobbers them.
    104     la_a2 &saved_argc
    105     st_a0,a2,0
    106     la_a2 &saved_argv
    107     st_a1,a2,0
    108 
    109     # Init BSS pointer slots from ELF_end via table walk.
    110     la_t0 &ELF_end
    111     la_t1 &bss_init_tbl
    112     la_t2 &bss_init_tbl_end
    113 :bss_init_loop
    114     la_br &bss_init_done
    115     beq_t1,t2
    116     ld_a2,t1,0
    117     ld_a3,t1,8
    118     add_a3,a3,t0
    119     st_a3,a2,0
    120     addi_t1,t1,16
    121     la_br &bss_init_loop
    122     b
    123 :bss_init_done
    124 
    125     # Default ptrsize = 4.
    126     li_t0 %4 %0
    127     la_a2 &ptrsize
    128     st_t0,a2,0
    129 
    130     # ---- Argv loop --------------------------------------------------------
    131     # i = 1
    132     li_t0 %1 %0
    133     la_a2 &arg_idx
    134     st_t0,a2,0
    135 :arg_loop
    136     la_a0 &arg_idx
    137     ld_t0,a0,0
    138     la_a1 &saved_argc
    139     ld_t1,a1,0
    140     la_br &arg_done
    141     beq_t0,t1
    142     la_br &arg_done
    143     blt_t1,t0
    144 
    145     # arg_ptr = argv[i]
    146     la_a0 &saved_argv
    147     ld_a1,a0,0
    148     shli_t2,t0,3
    149     add_a1,a1,t2
    150     ld_a0,a1,0
    151     la_a2 &arg_ptr
    152     st_a0,a2,0
    153 
    154     # Match against known short flags.
    155     la_a1 &opt_B
    156     li_a2 %2 %0
    157     la_br &str_eq
    158     call
    159     la_br &arg_is_B
    160     bnez_a0
    161 
    162     la_a0 &arg_ptr
    163     ld_a0,a0,0
    164     la_a1 &opt_E
    165     li_a2 %2 %0
    166     la_br &str_eq
    167     call
    168     la_br &arg_is_big
    169     bnez_a0
    170 
    171     la_a0 &arg_ptr
    172     ld_a0,a0,0
    173     la_a1 &opt_e
    174     li_a2 %2 %0
    175     la_br &str_eq
    176     call
    177     la_br &arg_is_little
    178     bnez_a0
    179 
    180     la_a0 &arg_ptr
    181     ld_a0,a0,0
    182     la_a1 &opt_b
    183     li_a2 %2 %0
    184     la_br &str_eq
    185     call
    186     la_br &arg_is_binary
    187     bnez_a0
    188 
    189     la_a0 &arg_ptr
    190     ld_a0,a0,0
    191     la_a1 &opt_N
    192     li_a2 %2 %0
    193     la_br &str_eq
    194     call
    195     la_br &arg_is_nonexec
    196     bnez_a0
    197 
    198     # Unrecognised. If first byte is '-' (and string len > 1), error.
    199     la_a0 &arg_ptr
    200     ld_a0,a0,0
    201     lb_t0,a0,0
    202     li_t1 %45 %0
    203     la_br &arg_is_positional
    204     bne_t0,t1
    205     lb_t0,a0,1
    206     la_br &arg_is_positional
    207     beqz_t0
    208     la_br &err_unknown_arg
    209     b
    210 
    211 :arg_is_positional
    212     # First positional = input path; second = output path; third = error.
    213     la_a0 &input_path
    214     ld_t0,a0,0
    215     la_br &arg_pos_set_out
    216     bnez_t0
    217     la_a1 &arg_ptr
    218     ld_t0,a1,0
    219     st_t0,a0,0
    220     la_br &arg_loop_next
    221     b
    222 :arg_pos_set_out
    223     la_a0 &output_path
    224     ld_t0,a0,0
    225     la_br &err_extra_positional
    226     bnez_t0
    227     la_a1 &arg_ptr
    228     ld_t0,a1,0
    229     st_t0,a0,0
    230     la_br &arg_loop_next
    231     b
    232 
    233 :arg_is_B
    234     # -B requires a value in the next argv slot.
    235     la_a0 &arg_idx
    236     ld_t0,a0,0
    237     addi_t0,t0,1
    238     st_t0,a0,0
    239     la_a1 &saved_argc
    240     ld_t1,a1,0
    241     la_br &err_missing_value
    242     beq_t0,t1
    243     la_br &err_missing_value
    244     blt_t1,t0
    245     la_a0 &saved_argv
    246     ld_a1,a0,0
    247     shli_t2,t0,3
    248     add_a1,a1,t2
    249     ld_a0,a1,0
    250     la_br &parse_long
    251     call
    252     la_a1 &base_address
    253     st_a0,a1,0
    254     la_br &arg_loop_next
    255     b
    256 
    257 :arg_is_big
    258     li_t0 %1 %0
    259     la_a1 &big_endian
    260     st_t0,a1,0
    261     la_br &arg_loop_next
    262     b
    263 
    264 :arg_is_little
    265     li_t0 %0 %0
    266     la_a1 &big_endian
    267     st_t0,a1,0
    268     la_br &arg_loop_next
    269     b
    270 
    271 :arg_is_binary
    272     li_t0 %1 %0
    273     la_a1 &byte_mode
    274     st_t0,a1,0
    275     la_br &arg_loop_next
    276     b
    277 
    278 :arg_is_nonexec
    279     li_t0 %1 %0
    280     la_a1 &non_executable
    281     st_t0,a1,0
    282     la_br &arg_loop_next
    283     b
    284 
    285 :arg_loop_next
    286     la_a0 &arg_idx
    287     ld_t0,a0,0
    288     addi_t0,t0,1
    289     st_t0,a0,0
    290     la_br &arg_loop
    291     b
    292 
    293 :arg_done
    294     la_a0 &input_path
    295     ld_t0,a0,0
    296     la_br &err_missing_positional
    297     beqz_t0
    298     la_a0 &output_path
    299     ld_t0,a0,0
    300     la_br &err_missing_positional
    301     beqz_t0
    302 
    303     # ---- Load input file -------------------------------------------------
    304     la_br &load_input
    305     call
    306 
    307     # ---- Single scan: builds labels + fixup list -------------------------
    308     li_t0 %0 %0
    309     la_a0 &ip
    310     st_t0,a0,0
    311     la_a0 &output_used
    312     st_t0,a0,0
    313     la_a0 &scope_depth
    314     st_t0,a0,0
    315     la_a0 &scope_seq
    316     st_t0,a0,0
    317     la_a0 &ptrsize_used
    318     st_t0,a0,0
    319     li_t0 %4 %0
    320     la_a0 &ptrsize
    321     st_t0,a0,0
    322     la_br &process_input
    323     call
    324     la_a0 &scope_depth
    325     ld_t0,a0,0
    326     la_br &err_scope_unclosed
    327     bnez_t0
    328 
    329     # ---- Resolve fixups --------------------------------------------------
    330     la_br &patch_fixups
    331     call
    332 
    333     # Clear cur_path so any post-fixup error reports without file:line.
    334     li_t0 %0 %0
    335     la_a0 &cur_path
    336     st_t0,a0,0
    337 
    338     la_br &write_output
    339     call
    340 
    341     li_a0 %0 %0
    342     eret
    343 
    344 ## --- Input loader ----------------------------------------------------------
    345 ## load_input(): openat(input_path), read into input_buf until EOF.
    346 :load_input
    347     enter_0
    348     # fd = openat(AT_FDCWD, input_path, O_RDONLY, 0)
    349     li_a0 sys_openat
    350     li_a1 H2_AT_FDCWD
    351     la_a2 &input_path
    352     ld_a2,a2,0
    353     li_a3 H2_O_RDONLY
    354     li_t0 %0 %0
    355     syscall
    356     la_br &err_open_input
    357     bltz_a0
    358     la_a1 &input_fd
    359     st_a0,a1,0
    360 
    361 :li_read_loop
    362     la_a0 &input_len
    363     ld_t0,a0,0
    364     li_t1 H2_INPUT_CAP
    365     la_br &err_input_too_big
    366     beq_t0,t1
    367     la_br &err_input_too_big
    368     blt_t1,t0
    369 
    370     # n = read(fd, input_buf + input_len, INPUT_CAP - input_len)
    371     la_a0 &input_fd
    372     ld_a1,a0,0
    373     la_a2 &input_buf_ptr
    374     ld_a2,a2,0
    375     add_a2,a2,t0
    376     sub_a3,t1,t0
    377     li_a0 sys_read
    378     syscall
    379     la_br &li_eof
    380     beqz_a0
    381     la_br &err_read
    382     bltz_a0
    383 
    384     la_a1 &input_len
    385     ld_t0,a1,0
    386     add_t0,t0,a0
    387     st_t0,a1,0
    388     la_br &li_read_loop
    389     b
    390 
    391 :li_eof
    392     eret
    393 
    394 ## --- Process input: scan one pass over input_buf ---------------------------
    395 :process_input
    396     enter_0
    397     # cur_path = input_path; cur_line = 1
    398     la_a0 &input_path
    399     ld_t0,a0,0
    400     la_a1 &cur_path
    401     st_t0,a1,0
    402     li_t0 %1 %0
    403     la_a1 &cur_line
    404     st_t0,a1,0
    405 
    406     # scan_pos = input_buf; scan_end = input_buf + input_len
    407     la_a0 &input_buf_ptr
    408     ld_t0,a0,0
    409     la_a1 &scan_pos
    410     st_t0,a1,0
    411     la_a0 &input_len
    412     ld_t1,a0,0
    413     add_t0,t0,t1
    414     la_a1 &scan_end
    415     st_t0,a1,0
    416 
    417 :scan_loop
    418     la_br &skip_ws_and_comments
    419     call
    420     la_a0 &scan_pos
    421     ld_t0,a0,0
    422     la_a1 &scan_end
    423     ld_t1,a1,0
    424     la_br &scan_done
    425     beq_t0,t1
    426     la_br &scan_done
    427     blt_t1,t0
    428     lb_a0,t0,0
    429 
    430     li_t1 %58 %0
    431     la_br &handle_label_def
    432     beq_a0,t1
    433     li_t1 %46 %0
    434     la_br &handle_directive
    435     beq_a0,t1
    436     li_t1 %33 %0
    437     la_br &handle_ref
    438     beq_a0,t1
    439     li_t1 %64 %0
    440     la_br &handle_ref
    441     beq_a0,t1
    442     li_t1 %36 %0
    443     la_br &handle_ref
    444     beq_a0,t1
    445     li_t1 %126 %0
    446     la_br &handle_ref
    447     beq_a0,t1
    448     li_t1 %37 %0
    449     la_br &handle_ref
    450     beq_a0,t1
    451     li_t1 %38 %0
    452     la_br &handle_ref
    453     beq_a0,t1
    454     la_br &is_byte_digit
    455     call
    456     la_br &handle_byte_stream
    457     bnez_a0
    458     la_br &err_unexpected_char
    459     b
    460 
    461 :handle_label_def
    462     # consume ':'
    463     la_a0 &scan_pos
    464     ld_t0,a0,0
    465     addi_t0,t0,1
    466     st_t0,a0,0
    467     # scan_name -> length in a0, pointer in sn_start (no copy).
    468     la_br &scan_name
    469     call
    470     la_a1 &name_len
    471     st_a0,a1,0
    472     # dotted = (name[0] == '.') AND scope_depth > 0
    473     la_a0 &sn_start
    474     ld_a0,a0,0
    475     lb_t0,a0,0
    476     li_t1 %46 %0
    477     la_br &handle_label_global
    478     bne_t0,t1
    479     la_a0 &scope_depth
    480     ld_t0,a0,0
    481     la_br &handle_label_global
    482     beqz_t0
    483     # scope_id = scope_stack[scope_depth-1]
    484     addi_t0,t0,neg1
    485     la_a1 &scope_stack_ptr
    486     ld_a1,a1,0
    487     shli_t2,t0,3
    488     add_a1,a1,t2
    489     ld_t0,a1,0
    490     la_a2 &name_scope
    491     st_t0,a2,0
    492     la_br &handle_label_dispatch
    493     b
    494 :handle_label_global
    495     li_t0 %0 %0
    496     la_a2 &name_scope
    497     st_t0,a2,0
    498 :handle_label_dispatch
    499     la_a0 &sn_start
    500     ld_a0,a0,0
    501     la_a1 &name_len
    502     ld_a1,a1,0
    503     la_a2 &name_scope
    504     ld_a2,a2,0
    505     la_br &define_label
    506     call
    507     la_br &scan_loop
    508     b
    509 
    510 :handle_directive
    511     # consume '.'
    512     la_a0 &scan_pos
    513     ld_t0,a0,0
    514     addi_t0,t0,1
    515     st_t0,a0,0
    516     la_a0 &name_buf_ptr
    517     ld_a0,a0,0
    518     li_a1 H2_TOKEN_CAP
    519     la_br &read_directive_name
    520     call
    521     la_a1 &name_len
    522     st_a0,a1,0
    523 
    524     # Dispatch by length, then memcmp.
    525     la_a0 &name_len
    526     ld_t0,a0,0
    527     li_t1 %5 %0
    528     la_br &dir_check_4
    529     bne_t0,t1
    530     # Could be "align" or "scope".
    531     la_a0 &name_buf_ptr
    532     ld_a0,a0,0
    533     la_a1 &kw_align
    534     li_a2 %5 %0
    535     la_br &mem_eq
    536     call
    537     la_br &dir_call_align
    538     bnez_a0
    539     la_a0 &name_buf_ptr
    540     ld_a0,a0,0
    541     la_a1 &kw_scope
    542     li_a2 %5 %0
    543     la_br &mem_eq
    544     call
    545     la_br &dir_call_scope
    546     bnez_a0
    547     la_br &err_unknown_directive
    548     b
    549 :dir_check_4
    550     li_t1 %4 %0
    551     la_br &dir_check_8
    552     bne_t0,t1
    553     la_a0 &name_buf_ptr
    554     ld_a0,a0,0
    555     la_a1 &kw_fill
    556     li_a2 %4 %0
    557     la_br &mem_eq
    558     call
    559     la_br &dir_call_fill
    560     bnez_a0
    561     la_br &err_unknown_directive
    562     b
    563 :dir_check_8
    564     li_t1 %8 %0
    565     la_br &dir_check_7
    566     bne_t0,t1
    567     la_a0 &name_buf_ptr
    568     ld_a0,a0,0
    569     la_a1 &kw_endscope
    570     li_a2 %8 %0
    571     la_br &mem_eq
    572     call
    573     la_br &dir_call_endscope
    574     bnez_a0
    575     la_br &err_unknown_directive
    576     b
    577 :dir_check_7
    578     li_t1 %7 %0
    579     la_br &err_unknown_directive
    580     bne_t0,t1
    581     la_a0 &name_buf_ptr
    582     ld_a0,a0,0
    583     la_a1 &kw_ptrsize
    584     li_a2 %7 %0
    585     la_br &mem_eq
    586     call
    587     la_br &dir_call_ptrsize
    588     bnez_a0
    589     la_br &err_unknown_directive
    590     b
    591 
    592 :dir_call_align
    593     la_br &do_align
    594     call
    595     la_br &scan_loop
    596     b
    597 :dir_call_fill
    598     la_br &do_fill
    599     call
    600     la_br &scan_loop
    601     b
    602 :dir_call_scope
    603     la_br &do_scope_open
    604     call
    605     la_br &scan_loop
    606     b
    607 :dir_call_endscope
    608     la_br &do_scope_close
    609     call
    610     la_br &scan_loop
    611     b
    612 :dir_call_ptrsize
    613     la_br &do_ptrsize
    614     call
    615     la_br &scan_loop
    616     b
    617 
    618 :handle_ref
    619     # a0 holds the sigil byte; advance past it then process_reference.
    620     la_a1 &cur_sigil
    621     st_a0,a1,0
    622     la_a0 &scan_pos
    623     ld_t0,a0,0
    624     addi_t0,t0,1
    625     st_t0,a0,0
    626     la_br &process_reference
    627     call
    628     la_br &scan_loop
    629     b
    630 
    631 :handle_byte_stream
    632     la_br &parse_byte_stream
    633     call
    634     la_br &scan_loop
    635     b
    636 
    637 :scan_done
    638     eret
    639 
    640 ## --- Lex helpers -----------------------------------------------------------
    641 
    642 ## skip_ws_and_comments(): advance scan_pos past whitespace and #/; comments.
    643 ## Updates cur_line on '\n'.
    644 :skip_ws_and_comments
    645     enter_0
    646 :swc_loop
    647     la_a0 &scan_pos
    648     ld_t0,a0,0
    649     la_a1 &scan_end
    650     ld_t1,a1,0
    651     la_br &swc_done
    652     beq_t0,t1
    653     la_br &swc_done
    654     blt_t1,t0
    655     lb_a0,t0,0
    656     la_br &is_space_any
    657     call
    658     la_br &swc_after_ws_check
    659     beqz_a0
    660     # whitespace: bump cur_line on '\n', advance scan_pos.
    661     la_a0 &scan_pos
    662     ld_t0,a0,0
    663     lb_a0,t0,0
    664     li_t1 %10 %0
    665     la_br &swc_advance
    666     bne_a0,t1
    667     la_a1 &cur_line
    668     ld_t2,a1,0
    669     addi_t2,t2,1
    670     st_t2,a1,0
    671 :swc_advance
    672     la_a0 &scan_pos
    673     ld_t0,a0,0
    674     addi_t0,t0,1
    675     st_t0,a0,0
    676     la_br &swc_loop
    677     b
    678 :swc_after_ws_check
    679     # is_space_any clobbered a0; re-read the byte at scan_pos.
    680     la_a0 &scan_pos
    681     ld_t0,a0,0
    682     lb_a0,t0,0
    683     li_t1 %35 %0
    684     la_br &swc_consume_comment
    685     beq_a0,t1
    686     li_t1 %59 %0
    687     la_br &swc_consume_comment
    688     beq_a0,t1
    689     la_br &swc_done
    690     b
    691 :swc_consume_comment
    692 :swc_cc_loop
    693     la_a0 &scan_pos
    694     ld_t0,a0,0
    695     la_a1 &scan_end
    696     ld_t1,a1,0
    697     la_br &swc_loop
    698     beq_t0,t1
    699     la_br &swc_loop
    700     blt_t1,t0
    701     lb_a0,t0,0
    702     li_t1 %10 %0
    703     la_br &swc_loop
    704     beq_a0,t1
    705     addi_t0,t0,1
    706     la_a1 &scan_pos
    707     st_t0,a1,0
    708     la_br &swc_cc_loop
    709     b
    710 :swc_done
    711     eret
    712 
    713 ## skip_inline_ws(): like skip_ws_and_comments but does NOT cross '\n'.
    714 :skip_inline_ws
    715     enter_0
    716 :siw_loop
    717     la_a0 &scan_pos
    718     ld_t0,a0,0
    719     la_a1 &scan_end
    720     ld_t1,a1,0
    721     la_br &siw_done
    722     beq_t0,t1
    723     la_br &siw_done
    724     blt_t1,t0
    725     lb_a0,t0,0
    726     li_t1 %32 %0
    727     la_br &siw_advance
    728     beq_a0,t1
    729     li_t1 %9 %0
    730     la_br &siw_advance
    731     beq_a0,t1
    732     li_t1 %13 %0
    733     la_br &siw_advance
    734     beq_a0,t1
    735     li_t1 %12 %0
    736     la_br &siw_advance
    737     beq_a0,t1
    738     li_t1 %11 %0
    739     la_br &siw_advance
    740     beq_a0,t1
    741     li_t1 %35 %0
    742     la_br &siw_consume_comment
    743     beq_a0,t1
    744     li_t1 %59 %0
    745     la_br &siw_consume_comment
    746     beq_a0,t1
    747     la_br &siw_done
    748     b
    749 :siw_advance
    750     la_a0 &scan_pos
    751     ld_t0,a0,0
    752     addi_t0,t0,1
    753     st_t0,a0,0
    754     la_br &siw_loop
    755     b
    756 :siw_consume_comment
    757 :siw_cc_loop
    758     la_a0 &scan_pos
    759     ld_t0,a0,0
    760     la_a1 &scan_end
    761     ld_t1,a1,0
    762     la_br &siw_done
    763     beq_t0,t1
    764     la_br &siw_done
    765     blt_t1,t0
    766     lb_a0,t0,0
    767     li_t1 %10 %0
    768     la_br &siw_done
    769     beq_a0,t1
    770     addi_t0,t0,1
    771     la_a1 &scan_pos
    772     st_t0,a1,0
    773     la_br &siw_cc_loop
    774     b
    775 :siw_done
    776     eret
    777 
    778 ## is_space_any(a0=c) -> a0=0/1. Whitespace = ' ' \t \n \r \f \v.
    779 :is_space_any
    780     li_t0 %32 %0
    781     la_br &isa_yes
    782     beq_a0,t0
    783     li_t0 %9 %0
    784     la_br &isa_yes
    785     beq_a0,t0
    786     li_t0 %10 %0
    787     la_br &isa_yes
    788     beq_a0,t0
    789     li_t0 %13 %0
    790     la_br &isa_yes
    791     beq_a0,t0
    792     li_t0 %12 %0
    793     la_br &isa_yes
    794     beq_a0,t0
    795     li_t0 %11 %0
    796     la_br &isa_yes
    797     beq_a0,t0
    798     li_a0 %0 %0
    799     ret
    800 :isa_yes
    801     li_a0 %1 %0
    802     ret
    803 
    804 ## is_name_terminator(a0=c) -> a0=0/1. Terminators: ws, '-', '>', '#', ';'.
    805 ## Spills c into a BSS slot since is_space_any clobbers a0.
    806 :is_name_terminator
    807     enter_0
    808     la_a1 &nt_c
    809     st_a0,a1,0
    810     la_br &is_space_any
    811     call
    812     la_br &nt_yes
    813     bnez_a0
    814     la_a0 &nt_c
    815     ld_a0,a0,0
    816     li_t0 %45 %0
    817     la_br &nt_yes
    818     beq_a0,t0
    819     li_t0 %62 %0
    820     la_br &nt_yes
    821     beq_a0,t0
    822     li_t0 %35 %0
    823     la_br &nt_yes
    824     beq_a0,t0
    825     li_t0 %59 %0
    826     la_br &nt_yes
    827     beq_a0,t0
    828     li_a0 %0 %0
    829     eret
    830 :nt_yes
    831     li_a0 %1 %0
    832     eret
    833 
    834 ## is_byte_digit(a0=c) -> a0=0/1. Mode-aware (HEX vs BINARY).
    835 :is_byte_digit
    836     la_a1 &byte_mode
    837     ld_t0,a1,0
    838     la_br &ibd_bin
    839     bnez_t0
    840     # HEX: 0-9, a-f, A-F
    841     li_t1 %48 %0
    842     la_br &ibd_no
    843     blt_a0,t1
    844     li_t1 %57 %0
    845     la_br &ibd_yes
    846     blt_a0,t1
    847     la_br &ibd_yes
    848     beq_a0,t1
    849     li_t1 %65 %0
    850     la_br &ibd_no
    851     blt_a0,t1
    852     li_t1 %70 %0
    853     la_br &ibd_yes
    854     blt_a0,t1
    855     la_br &ibd_yes
    856     beq_a0,t1
    857     li_t1 %97 %0
    858     la_br &ibd_no
    859     blt_a0,t1
    860     li_t1 %102 %0
    861     la_br &ibd_yes
    862     blt_a0,t1
    863     la_br &ibd_yes
    864     beq_a0,t1
    865     la_br &ibd_no
    866     b
    867 :ibd_bin
    868     li_t1 %48 %0
    869     la_br &ibd_yes
    870     beq_a0,t1
    871     li_t1 %49 %0
    872     la_br &ibd_yes
    873     beq_a0,t1
    874     la_br &ibd_no
    875     b
    876 :ibd_yes
    877     li_a0 %1 %0
    878     ret
    879 :ibd_no
    880     li_a0 %0 %0
    881     ret
    882 
    883 ## byte_digit_value(a0=c) -> a0=value (0..15). Caller guarantees c is a
    884 ## valid digit for the current byte mode.
    885 :byte_digit_value
    886     li_t0 %57 %0
    887     la_br &bdv_alpha
    888     blt_t0,a0
    889     li_t1 %48 %0
    890     sub_a0,a0,t1
    891     ret
    892 :bdv_alpha
    893     li_t0 %96 %0
    894     la_br &bdv_lower
    895     blt_t0,a0
    896     li_t1 %55 %0
    897     sub_a0,a0,t1
    898     ret
    899 :bdv_lower
    900     li_t1 %87 %0
    901     sub_a0,a0,t1
    902     ret
    903 
    904 ## byte_digit_count() -> a0. 2 for HEX, 8 for BINARY.
    905 :byte_digit_count
    906     la_a0 &byte_mode
    907     ld_t0,a0,0
    908     la_br &bdc_bin
    909     bnez_t0
    910     li_a0 %2 %0
    911     ret
    912 :bdc_bin
    913     li_a0 %8 %0
    914     ret
    915 
    916 ## scan_name() -> a0=length, sn_start=pointer into input buffer at name start.
    917 ## Advances scan_pos past the name. Fatal on empty.
    918 :scan_name
    919     enter_0
    920     la_a0 &scan_pos
    921     ld_t0,a0,0
    922     la_a1 &sn_start
    923     st_t0,a1,0
    924 :sn_loop
    925     la_a0 &scan_pos
    926     ld_t0,a0,0
    927     la_a1 &scan_end
    928     ld_t1,a1,0
    929     la_br &sn_done
    930     beq_t0,t1
    931     la_br &sn_done
    932     blt_t1,t0
    933     lb_a0,t0,0
    934     la_br &is_name_terminator
    935     call
    936     la_br &sn_done
    937     bnez_a0
    938     la_a0 &scan_pos
    939     ld_t0,a0,0
    940     addi_t0,t0,1
    941     st_t0,a0,0
    942     la_br &sn_loop
    943     b
    944 :sn_done
    945     la_a0 &scan_pos
    946     ld_t0,a0,0
    947     la_a1 &sn_start
    948     ld_t1,a1,0
    949     sub_a0,t0,t1
    950     la_br &err_empty_name
    951     beqz_a0
    952     eret
    953 
    954 ## read_name(a0=out_buf, a1=max) -> a0=length. Reads scan_pos into out_buf
    955 ## until is_name_terminator or scan_end. Fatal on overflow / empty.
    956 ## (Kept for reference; superseded by scan_name in single-pass refactor.)
    957 :read_name
    958     enter_0
    959     la_a2 &rn_out
    960     st_a0,a2,0
    961     la_a2 &rn_max
    962     st_a1,a2,0
    963     li_t0 %0 %0
    964     la_a2 &rn_n
    965     st_t0,a2,0
    966 :rn_loop
    967     la_a0 &scan_pos
    968     ld_t0,a0,0
    969     la_a1 &scan_end
    970     ld_t1,a1,0
    971     la_br &rn_done
    972     beq_t0,t1
    973     la_br &rn_done
    974     blt_t1,t0
    975     lb_a0,t0,0
    976     la_br &is_name_terminator
    977     call
    978     la_br &rn_done
    979     bnez_a0
    980     la_a1 &rn_n
    981     ld_t0,a1,0
    982     la_a0 &rn_max
    983     ld_t1,a0,0
    984     la_br &err_name_too_long
    985     beq_t0,t1
    986     # store char
    987     la_a0 &scan_pos
    988     ld_a0,a0,0
    989     lb_a3,a0,0
    990     la_a1 &rn_out
    991     ld_a1,a1,0
    992     add_a1,a1,t0
    993     sb_a3,a1,0
    994     addi_t0,t0,1
    995     la_a1 &rn_n
    996     st_t0,a1,0
    997     la_a0 &scan_pos
    998     ld_t0,a0,0
    999     addi_t0,t0,1
   1000     st_t0,a0,0
   1001     la_br &rn_loop
   1002     b
   1003 :rn_done
   1004     la_a1 &rn_n
   1005     ld_a0,a1,0
   1006     la_br &err_empty_name
   1007     beqz_a0
   1008     eret
   1009 
   1010 ## read_directive_name(a0=out_buf, a1=max) -> a0=length. Like read_name but
   1011 ## terminates on first non-alpha byte.
   1012 :read_directive_name
   1013     enter_0
   1014     la_a2 &rn_out
   1015     st_a0,a2,0
   1016     la_a2 &rn_max
   1017     st_a1,a2,0
   1018     li_t0 %0 %0
   1019     la_a2 &rn_n
   1020     st_t0,a2,0
   1021 :rdn_loop
   1022     la_a0 &scan_pos
   1023     ld_t0,a0,0
   1024     la_a1 &scan_end
   1025     ld_t1,a1,0
   1026     la_br &rdn_done
   1027     beq_t0,t1
   1028     la_br &rdn_done
   1029     blt_t1,t0
   1030     lb_a0,t0,0
   1031     # accept [A-Za-z]
   1032     li_t1 %65 %0
   1033     la_br &rdn_check_lower
   1034     blt_a0,t1
   1035     li_t1 %90 %0
   1036     la_br &rdn_consume
   1037     blt_a0,t1
   1038     la_br &rdn_consume
   1039     beq_a0,t1
   1040 :rdn_check_lower
   1041     li_t1 %97 %0
   1042     la_br &rdn_done
   1043     blt_a0,t1
   1044     li_t1 %122 %0
   1045     la_br &rdn_consume
   1046     blt_a0,t1
   1047     la_br &rdn_consume
   1048     beq_a0,t1
   1049     la_br &rdn_done
   1050     b
   1051 :rdn_consume
   1052     la_a1 &rn_n
   1053     ld_t0,a1,0
   1054     la_a2 &rn_max
   1055     ld_t1,a2,0
   1056     la_br &err_name_too_long
   1057     beq_t0,t1
   1058     la_a2 &rn_out
   1059     ld_a2,a2,0
   1060     add_a2,a2,t0
   1061     sb_a0,a2,0
   1062     addi_t0,t0,1
   1063     la_a1 &rn_n
   1064     st_t0,a1,0
   1065     la_a0 &scan_pos
   1066     ld_t0,a0,0
   1067     addi_t0,t0,1
   1068     st_t0,a0,0
   1069     la_br &rdn_loop
   1070     b
   1071 :rdn_done
   1072     la_a1 &rn_n
   1073     ld_a0,a1,0
   1074     la_br &err_empty_directive
   1075     beqz_a0
   1076     eret
   1077 
   1078 ## read_decimal() -> a0=value. Fatal on no digits.
   1079 :read_decimal
   1080     enter_0
   1081     li_t0 %0 %0
   1082     la_a0 &rd_val
   1083     st_t0,a0,0
   1084     la_a0 &rd_saw
   1085     st_t0,a0,0
   1086 :rd_loop
   1087     la_a0 &scan_pos
   1088     ld_t0,a0,0
   1089     la_a1 &scan_end
   1090     ld_t1,a1,0
   1091     la_br &rd_done
   1092     beq_t0,t1
   1093     la_br &rd_done
   1094     blt_t1,t0
   1095     lb_a0,t0,0
   1096     li_t1 %48 %0
   1097     la_br &rd_done
   1098     blt_a0,t1
   1099     li_t1 %57 %0
   1100     la_br &rd_done
   1101     blt_t1,a0
   1102     # acc = acc * 10 + (c - '0')
   1103     la_a1 &rd_val
   1104     ld_a2,a1,0
   1105     li_t1 %10 %0
   1106     mul_a2,a2,t1
   1107     li_t1 %48 %0
   1108     sub_a0,a0,t1
   1109     add_a2,a2,a0
   1110     st_a2,a1,0
   1111     li_t0 %1 %0
   1112     la_a1 &rd_saw
   1113     st_t0,a1,0
   1114     la_a0 &scan_pos
   1115     ld_t0,a0,0
   1116     addi_t0,t0,1
   1117     st_t0,a0,0
   1118     la_br &rd_loop
   1119     b
   1120 :rd_done
   1121     la_a1 &rd_saw
   1122     ld_t0,a1,0
   1123     la_br &err_expected_decimal
   1124     beqz_t0
   1125     la_a1 &rd_val
   1126     ld_a0,a1,0
   1127     eret
   1128 
   1129 ## --- Byte stream / single byte literal -------------------------------------
   1130 
   1131 ## parse_byte_stream(): consume free-flowing digits (intermixed with ws and
   1132 ## #/; comments) and emit_byte them. Stops at first non-digit non-ws
   1133 ## non-comment byte.
   1134 :parse_byte_stream
   1135     enter_0
   1136     li_t0 %0 %0
   1137     la_a0 &pbs_acc
   1138     st_t0,a0,0
   1139     la_a0 &pbs_have
   1140     st_t0,a0,0
   1141 :pbs_loop
   1142     la_a0 &scan_pos
   1143     ld_t0,a0,0
   1144     la_a1 &scan_end
   1145     ld_t1,a1,0
   1146     la_br &pbs_done
   1147     beq_t0,t1
   1148     la_br &pbs_done
   1149     blt_t1,t0
   1150     lb_a0,t0,0
   1151     la_br &is_space_any
   1152     call
   1153     la_br &pbs_consume_ws
   1154     bnez_a0
   1155     la_a0 &scan_pos
   1156     ld_t0,a0,0
   1157     lb_a0,t0,0
   1158     li_t1 %35 %0
   1159     la_br &pbs_consume_comment
   1160     beq_a0,t1
   1161     li_t1 %59 %0
   1162     la_br &pbs_consume_comment
   1163     beq_a0,t1
   1164     la_br &is_byte_digit
   1165     call
   1166     la_br &pbs_done
   1167     beqz_a0
   1168     # consume one digit
   1169     la_a0 &scan_pos
   1170     ld_t0,a0,0
   1171     lb_a0,t0,0
   1172     addi_t0,t0,1
   1173     la_a1 &scan_pos
   1174     st_t0,a1,0
   1175     la_a1 &pbs_c
   1176     st_a0,a1,0
   1177     # acc = (acc << shift) | digit
   1178     la_a1 &byte_mode
   1179     ld_t1,a1,0
   1180     la_br &pbs_bin
   1181     bnez_t1
   1182     # HEX: shift 4 + digit_value
   1183     la_a1 &pbs_acc
   1184     ld_t0,a1,0
   1185     shli_a3,t0,4
   1186     la_a1 &pbs_c
   1187     ld_a0,a1,0
   1188     la_br &byte_digit_value
   1189     call
   1190     add_a3,a3,a0
   1191     la_a1 &pbs_acc
   1192     st_a3,a1,0
   1193     la_br &pbs_bump
   1194     b
   1195 :pbs_bin
   1196     la_a1 &pbs_acc
   1197     ld_t0,a1,0
   1198     shli_a3,t0,1
   1199     la_a1 &pbs_c
   1200     ld_a0,a1,0
   1201     li_t1 %48 %0
   1202     sub_a0,a0,t1
   1203     add_a3,a3,a0
   1204     la_a1 &pbs_acc
   1205     st_a3,a1,0
   1206 :pbs_bump
   1207     la_a0 &pbs_have
   1208     ld_t0,a0,0
   1209     addi_t0,t0,1
   1210     st_t0,a0,0
   1211     la_br &byte_digit_count
   1212     call
   1213     la_a1 &pbs_have
   1214     ld_t0,a1,0
   1215     la_br &pbs_loop
   1216     bne_t0,a0
   1217     # full byte: emit
   1218     la_a1 &pbs_acc
   1219     ld_a0,a1,0
   1220     andi_a0,a0,255
   1221     la_br &emit_byte
   1222     call
   1223     li_t0 %0 %0
   1224     la_a0 &pbs_acc
   1225     st_t0,a0,0
   1226     la_a0 &pbs_have
   1227     st_t0,a0,0
   1228     la_br &pbs_loop
   1229     b
   1230 :pbs_consume_ws
   1231     la_a0 &scan_pos
   1232     ld_t0,a0,0
   1233     lb_a0,t0,0
   1234     li_t1 %10 %0
   1235     la_br &pbs_ws_advance
   1236     bne_a0,t1
   1237     la_a1 &cur_line
   1238     ld_t2,a1,0
   1239     addi_t2,t2,1
   1240     st_t2,a1,0
   1241 :pbs_ws_advance
   1242     la_a0 &scan_pos
   1243     ld_t0,a0,0
   1244     addi_t0,t0,1
   1245     st_t0,a0,0
   1246     la_br &pbs_loop
   1247     b
   1248 :pbs_consume_comment
   1249 :pbs_cc_loop
   1250     la_a0 &scan_pos
   1251     ld_t0,a0,0
   1252     la_a1 &scan_end
   1253     ld_t1,a1,0
   1254     la_br &pbs_loop
   1255     beq_t0,t1
   1256     la_br &pbs_loop
   1257     blt_t1,t0
   1258     lb_a0,t0,0
   1259     li_t1 %10 %0
   1260     la_br &pbs_loop
   1261     beq_a0,t1
   1262     addi_t0,t0,1
   1263     la_a1 &scan_pos
   1264     st_t0,a1,0
   1265     la_br &pbs_cc_loop
   1266     b
   1267 :pbs_done
   1268     la_a0 &pbs_have
   1269     ld_t0,a0,0
   1270     la_br &err_byte_stream_short
   1271     bnez_t0
   1272     eret
   1273 
   1274 ## parse_one_byte(a0=out_addr): read a single byte literal (exactly
   1275 ## byte_digit_count contiguous digits, no internal whitespace).
   1276 :parse_one_byte
   1277     enter_0
   1278     la_a1 &p1b_out
   1279     st_a0,a1,0
   1280     li_t0 %0 %0
   1281     la_a1 &p1b_acc
   1282     st_t0,a1,0
   1283     la_a1 &p1b_have
   1284     st_t0,a1,0
   1285 :p1b_loop
   1286     la_a0 &scan_pos
   1287     ld_t0,a0,0
   1288     la_a1 &scan_end
   1289     ld_t1,a1,0
   1290     la_br &p1b_done
   1291     beq_t0,t1
   1292     la_br &p1b_done
   1293     blt_t1,t0
   1294     lb_a0,t0,0
   1295     la_br &is_byte_digit
   1296     call
   1297     la_br &p1b_done
   1298     beqz_a0
   1299     la_a0 &scan_pos
   1300     ld_t0,a0,0
   1301     lb_a0,t0,0
   1302     addi_t0,t0,1
   1303     la_a1 &scan_pos
   1304     st_t0,a1,0
   1305     la_a1 &p1b_c
   1306     st_a0,a1,0
   1307     la_a1 &byte_mode
   1308     ld_t1,a1,0
   1309     la_br &p1b_bin
   1310     bnez_t1
   1311     la_a1 &p1b_acc
   1312     ld_t0,a1,0
   1313     shli_a3,t0,4
   1314     la_a1 &p1b_c
   1315     ld_a0,a1,0
   1316     la_br &byte_digit_value
   1317     call
   1318     add_a3,a3,a0
   1319     la_a1 &p1b_acc
   1320     st_a3,a1,0
   1321     la_br &p1b_bump
   1322     b
   1323 :p1b_bin
   1324     la_a1 &p1b_acc
   1325     ld_t0,a1,0
   1326     shli_a3,t0,1
   1327     la_a1 &p1b_c
   1328     ld_a0,a1,0
   1329     li_t1 %48 %0
   1330     sub_a0,a0,t1
   1331     add_a3,a3,a0
   1332     la_a1 &p1b_acc
   1333     st_a3,a1,0
   1334 :p1b_bump
   1335     la_a0 &p1b_have
   1336     ld_t0,a0,0
   1337     addi_t0,t0,1
   1338     st_t0,a0,0
   1339     la_br &byte_digit_count
   1340     call
   1341     la_a1 &p1b_have
   1342     ld_t0,a1,0
   1343     la_br &p1b_loop
   1344     bne_t0,a0
   1345     # got full byte: write to *out_addr and return
   1346     la_a1 &p1b_acc
   1347     ld_a0,a1,0
   1348     andi_a0,a0,255
   1349     la_a1 &p1b_out
   1350     ld_a1,a1,0
   1351     sb_a0,a1,0
   1352     eret
   1353 :p1b_done
   1354     la_a0 &p1b_have
   1355     ld_t0,a0,0
   1356     la_br &err_byte_lit_bad
   1357     bnez_t0
   1358     la_br &err_byte_lit_bad
   1359     b
   1360 
   1361 ## --- Label table -----------------------------------------------------------
   1362 
   1363 ## intern(a0=src, a1=len) -> a0=offset into text_buf. Copies bytes plus a
   1364 ## NUL terminator.
   1365 :intern
   1366     enter_0
   1367     la_a2 &intern_src
   1368     st_a0,a2,0
   1369     la_a2 &intern_len
   1370     st_a1,a2,0
   1371     la_a2 &text_used
   1372     ld_a3,a2,0
   1373     la_a2 &intern_orig
   1374     st_a3,a2,0
   1375     # if (text_used + len + 1 > TEXT_CAP) fatal
   1376     add_a2,a3,a1
   1377     addi_a2,a2,1
   1378     li_t0 H2_TEXT_CAP
   1379     la_br &err_text_overflow
   1380     blt_t0,a2
   1381     # dst = text_buf + text_used
   1382     la_a0 &text_buf_ptr
   1383     ld_a0,a0,0
   1384     add_a0,a0,a3
   1385     la_a2 &intern_dst
   1386     st_a0,a2,0
   1387     li_t0 %0 %0
   1388     la_a1 &intern_i
   1389     st_t0,a1,0
   1390 :intern_copy_loop
   1391     la_a0 &intern_i
   1392     ld_t0,a0,0
   1393     la_a1 &intern_len
   1394     ld_t1,a1,0
   1395     la_br &intern_copy_done
   1396     beq_t0,t1
   1397     la_a0 &intern_src
   1398     ld_a0,a0,0
   1399     add_a0,a0,t0
   1400     lb_a0,a0,0
   1401     la_a2 &intern_dst
   1402     ld_a2,a2,0
   1403     add_a2,a2,t0
   1404     sb_a0,a2,0
   1405     addi_t0,t0,1
   1406     la_a1 &intern_i
   1407     st_t0,a1,0
   1408     la_br &intern_copy_loop
   1409     b
   1410 :intern_copy_done
   1411     # NUL terminator
   1412     la_a2 &intern_dst
   1413     ld_a2,a2,0
   1414     la_a1 &intern_len
   1415     ld_t0,a1,0
   1416     add_a2,a2,t0
   1417     li_t1 %0 %0
   1418     sb_t1,a2,0
   1419     # text_used += len + 1
   1420     la_a2 &text_used
   1421     ld_a3,a2,0
   1422     la_a1 &intern_len
   1423     ld_a1,a1,0
   1424     add_a3,a3,a1
   1425     addi_a3,a3,1
   1426     st_a3,a2,0
   1427     la_a0 &intern_orig
   1428     ld_a0,a0,0
   1429     eret
   1430 
   1431 ## label_addr(a0=index) -> a0 = &labels[index]. Each label is 32 B.
   1432 :label_addr
   1433     li_t0 %32 %0
   1434     mul_a0,a0,t0
   1435     la_a1 &labels_ptr
   1436     ld_a1,a1,0
   1437     add_a0,a0,a1
   1438     ret
   1439 
   1440 ## name_eq(a0=label_ptr, a1=src, a2=len) -> a0=0/1.
   1441 :name_eq
   1442     enter_0
   1443     la_a3 &ne_label
   1444     st_a0,a3,0
   1445     la_a3 &ne_src
   1446     st_a1,a3,0
   1447     la_a3 &ne_len
   1448     st_a2,a3,0
   1449     # if (label->name_len != len) return 0
   1450     ld_t0,a0,8
   1451     la_br &ne_no
   1452     bne_t0,a2
   1453     # bytes equal?
   1454     la_a0 &ne_label
   1455     ld_a0,a0,0
   1456     ld_a3,a0,0
   1457     la_a0 &text_buf_ptr
   1458     ld_a0,a0,0
   1459     add_a0,a0,a3
   1460     la_a1 &ne_src
   1461     ld_a1,a1,0
   1462     la_a2 &ne_len
   1463     ld_a2,a2,0
   1464     la_br &mem_eq
   1465     call
   1466     eret
   1467 :ne_no
   1468     li_a0 %0 %0
   1469     eret
   1470 
   1471 ## define_label(a0=src, a1=len, a2=scope_id). First definition wins.
   1472 :define_label
   1473     enter_0
   1474     la_a3 &dl_src
   1475     st_a0,a3,0
   1476     la_a3 &dl_len
   1477     st_a1,a3,0
   1478     la_a3 &dl_scope
   1479     st_a2,a3,0
   1480     la_a0 &label_count
   1481     ld_t0,a0,0
   1482     li_t1 H2_LABEL_CAP
   1483     la_br &err_too_many_labels
   1484     beq_t0,t1
   1485     la_br &err_too_many_labels
   1486     blt_t1,t0
   1487     # name_off = intern(src, len)
   1488     la_a0 &dl_src
   1489     ld_a0,a0,0
   1490     la_a1 &dl_len
   1491     ld_a1,a1,0
   1492     la_br &intern
   1493     call
   1494     la_a3 &dl_name_off
   1495     st_a0,a3,0
   1496     # &labels[label_count]
   1497     la_a0 &label_count
   1498     ld_a0,a0,0
   1499     la_br &label_addr
   1500     call
   1501     la_a3 &dl_label
   1502     st_a0,a3,0
   1503     # name_off
   1504     la_a1 &dl_name_off
   1505     ld_t0,a1,0
   1506     st_t0,a0,0
   1507     # name_len
   1508     la_a1 &dl_len
   1509     ld_t0,a1,0
   1510     st_t0,a0,8
   1511     # target_ip
   1512     la_a1 &ip
   1513     ld_t0,a1,0
   1514     st_t0,a0,16
   1515     # scope_id
   1516     la_a1 &dl_scope
   1517     ld_t0,a1,0
   1518     st_t0,a0,24
   1519     # label_count++
   1520     la_a0 &label_count
   1521     ld_t0,a0,0
   1522     addi_t0,t0,1
   1523     st_t0,a0,0
   1524     eret
   1525 
   1526 ## lookup_label_in(a0=src, a1=len, a2=stack_ptr, a3=depth) -> a0=target_ip.
   1527 :lookup_label_in
   1528     enter_0
   1529     la_t1 &ll_src
   1530     st_a0,t1,0
   1531     la_t1 &ll_len
   1532     st_a1,t1,0
   1533     la_t1 &ll_stack
   1534     st_a2,t1,0
   1535     la_t1 &ll_depth
   1536     st_a3,t1,0
   1537     # dotted? (first byte == '.' AND depth > 0)
   1538     lb_t0,a0,0
   1539     li_t1 %46 %0
   1540     la_br &ll_undotted
   1541     bne_t0,t1
   1542     la_br &ll_undotted
   1543     beqz_a3
   1544     addi_t0,a3,neg1
   1545     la_a1 &ll_d
   1546     st_t0,a1,0
   1547 :ll_dot_outer
   1548     la_a0 &ll_d
   1549     ld_t0,a0,0
   1550     la_br &err_undefined_local
   1551     bltz_t0
   1552     la_a1 &ll_stack
   1553     ld_a1,a1,0
   1554     shli_t2,t0,3
   1555     add_a1,a1,t2
   1556     ld_t1,a1,0
   1557     la_a0 &ll_sid
   1558     st_t1,a0,0
   1559     li_t0 %0 %0
   1560     la_a0 &ll_i
   1561     st_t0,a0,0
   1562 :ll_dot_inner
   1563     la_a0 &ll_i
   1564     ld_t0,a0,0
   1565     la_a1 &label_count
   1566     ld_t1,a1,0
   1567     la_br &ll_dot_next_d
   1568     beq_t0,t1
   1569     mov_a0,t0
   1570     la_br &label_addr
   1571     call
   1572     la_a3 &ll_label
   1573     st_a0,a3,0
   1574     ld_t0,a0,24
   1575     la_a1 &ll_sid
   1576     ld_t1,a1,0
   1577     la_br &ll_dot_inner_next
   1578     bne_t0,t1
   1579     la_a0 &ll_label
   1580     ld_a0,a0,0
   1581     la_a1 &ll_src
   1582     ld_a1,a1,0
   1583     la_a2 &ll_len
   1584     ld_a2,a2,0
   1585     la_br &name_eq
   1586     call
   1587     la_br &ll_dot_inner_next
   1588     beqz_a0
   1589     la_a0 &ll_label
   1590     ld_a0,a0,0
   1591     ld_a0,a0,16
   1592     eret
   1593 :ll_dot_inner_next
   1594     la_a0 &ll_i
   1595     ld_t0,a0,0
   1596     addi_t0,t0,1
   1597     st_t0,a0,0
   1598     la_br &ll_dot_inner
   1599     b
   1600 :ll_dot_next_d
   1601     la_a0 &ll_d
   1602     ld_t0,a0,0
   1603     addi_t0,t0,neg1
   1604     st_t0,a0,0
   1605     la_br &ll_dot_outer
   1606     b
   1607 
   1608 :ll_undotted
   1609     li_t0 %0 %0
   1610     la_a0 &ll_i
   1611     st_t0,a0,0
   1612 :ll_undotted_loop
   1613     la_a0 &ll_i
   1614     ld_t0,a0,0
   1615     la_a1 &label_count
   1616     ld_t1,a1,0
   1617     la_br &err_undefined_label
   1618     beq_t0,t1
   1619     mov_a0,t0
   1620     la_br &label_addr
   1621     call
   1622     la_a3 &ll_label
   1623     st_a0,a3,0
   1624     ld_t0,a0,24
   1625     la_br &ll_undotted_next
   1626     bnez_t0
   1627     la_a0 &ll_label
   1628     ld_a0,a0,0
   1629     la_a1 &ll_src
   1630     ld_a1,a1,0
   1631     la_a2 &ll_len
   1632     ld_a2,a2,0
   1633     la_br &name_eq
   1634     call
   1635     la_br &ll_undotted_next
   1636     beqz_a0
   1637     la_a0 &ll_label
   1638     ld_a0,a0,0
   1639     ld_a0,a0,16
   1640     eret
   1641 :ll_undotted_next
   1642     la_a0 &ll_i
   1643     ld_t0,a0,0
   1644     addi_t0,t0,1
   1645     st_t0,a0,0
   1646     la_br &ll_undotted_loop
   1647     b
   1648 
   1649 ## --- Reference processor ---------------------------------------------------
   1650 ## process_reference(): cur_sigil already set; scan_pos already past sigil.
   1651 ## Captures label name span (and optional -OTHER), reserves placeholder bytes,
   1652 ## and appends a Fixup record. Resolution happens later in patch_fixups.
   1653 :process_reference
   1654     enter_0
   1655     la_br &set_sigil_info
   1656     call
   1657     # require non-terminator (label name follows)
   1658     la_a0 &scan_pos
   1659     ld_t0,a0,0
   1660     la_a1 &scan_end
   1661     ld_t1,a1,0
   1662     la_br &err_sigil_no_label
   1663     beq_t0,t1
   1664     la_br &err_sigil_no_label
   1665     blt_t1,t0
   1666     lb_a0,t0,0
   1667     la_br &is_name_terminator
   1668     call
   1669     la_br &err_sigil_no_label
   1670     bnez_a0
   1671     # scan_name -> length in a0, pointer in sn_start (no copy).
   1672     la_br &scan_name
   1673     call
   1674     la_a1 &pref_name_len
   1675     st_a0,a1,0
   1676     la_a0 &sn_start
   1677     ld_t0,a0,0
   1678     la_a1 &pref_name
   1679     st_t0,a1,0
   1680     # default: other = NULL, other_len = 0
   1681     li_t0 %0 %0
   1682     la_a1 &pref_other
   1683     st_t0,a1,0
   1684     la_a1 &pref_other_len
   1685     st_t0,a1,0
   1686     # optional '-' or '>' separator (tight, no whitespace)
   1687     la_a0 &scan_pos
   1688     ld_t0,a0,0
   1689     la_a1 &scan_end
   1690     ld_t1,a1,0
   1691     la_br &pref_after_other
   1692     beq_t0,t1
   1693     la_br &pref_after_other
   1694     blt_t1,t0
   1695     lb_a0,t0,0
   1696     li_t1 %45 %0
   1697     la_br &pref_consume_sep
   1698     beq_a0,t1
   1699     li_t1 %62 %0
   1700     la_br &pref_consume_sep
   1701     beq_a0,t1
   1702     la_br &pref_after_other
   1703     b
   1704 :pref_consume_sep
   1705     la_a0 &scan_pos
   1706     ld_t0,a0,0
   1707     addi_t0,t0,1
   1708     st_t0,a0,0
   1709     la_a1 &scan_end
   1710     ld_t1,a1,0
   1711     la_br &err_minus_no_label
   1712     beq_t0,t1
   1713     la_br &err_minus_no_label
   1714     blt_t1,t0
   1715     lb_a0,t0,0
   1716     la_br &is_name_terminator
   1717     call
   1718     la_br &err_minus_no_label
   1719     bnez_a0
   1720     la_br &scan_name
   1721     call
   1722     la_a1 &pref_other_len
   1723     st_a0,a1,0
   1724     la_a0 &sn_start
   1725     ld_t0,a0,0
   1726     la_a1 &pref_other
   1727     st_t0,a1,0
   1728 :pref_after_other
   1729     # Reserve placeholder bytes; emit_zeros returns starting offset.
   1730     la_a0 &pr_width
   1731     ld_a0,a0,0
   1732     la_br &emit_zeros
   1733     call
   1734     la_a1 &rf_out_off
   1735     st_a0,a1,0
   1736     # ip_after = ip (already advanced by emit_zeros).
   1737     la_a0 &ip
   1738     ld_t0,a0,0
   1739     la_a1 &rf_ip_after
   1740     st_t0,a1,0
   1741     la_br &record_fixup
   1742     call
   1743     eret
   1744 
   1745 ## record_fixup(): pref_name/pref_name_len/pref_other/pref_other_len already set
   1746 ## by caller. rf_out_off and rf_ip_after also set. Snapshots cur_sigil/cur_path/
   1747 ## cur_line and the current scope stack (if depth > 0).
   1748 :record_fixup
   1749     enter_0
   1750     la_a0 &fixup_count
   1751     ld_t0,a0,0
   1752     li_t1 H2_FIXUP_CAP
   1753     la_br &err_too_many_fixups
   1754     beq_t0,t1
   1755     la_br &err_too_many_fixups
   1756     blt_t1,t0
   1757     # fp = fixups_ptr + count * 96
   1758     li_t1 %96 %0
   1759     mul_a1,t0,t1
   1760     la_a2 &fixups_ptr
   1761     ld_a2,a2,0
   1762     add_a1,a1,a2
   1763     la_a3 &rf_fp
   1764     st_a1,a3,0
   1765     # offset 0:  out_off
   1766     la_a0 &rf_out_off
   1767     ld_t0,a0,0
   1768     st_t0,a1,0
   1769     # offset 8:  ip_at_ref
   1770     la_a0 &rf_ip_after
   1771     ld_t0,a0,0
   1772     st_t0,a1,8
   1773     # offset 16: name
   1774     la_a0 &pref_name
   1775     ld_t0,a0,0
   1776     st_t0,a1,16
   1777     # offset 24: other (0 if none)
   1778     la_a0 &pref_other
   1779     ld_t0,a0,0
   1780     st_t0,a1,24
   1781     # offset 32: src_path = cur_path
   1782     la_a0 &cur_path
   1783     ld_t0,a0,0
   1784     st_t0,a1,32
   1785     # offset 40: name_len
   1786     la_a0 &pref_name_len
   1787     ld_t0,a0,0
   1788     st_t0,a1,40
   1789     # offset 48: other_len
   1790     la_a0 &pref_other_len
   1791     ld_t0,a0,0
   1792     st_t0,a1,48
   1793     # offset 56: scope_hist_off (filled below if depth > 0)
   1794     li_t0 %0 %0
   1795     st_t0,a1,56
   1796     # offset 64: scope_depth (snapshot)
   1797     la_a0 &scope_depth
   1798     ld_t0,a0,0
   1799     st_t0,a1,64
   1800     # offset 72: src_line = cur_line  (offset > 64: indirect via a3)
   1801     la_a0 &cur_line
   1802     ld_t0,a0,0
   1803     li_t1 %72 %0
   1804     add_a3,a1,t1
   1805     st_t0,a3,0
   1806     # offset 80: sigil
   1807     la_a0 &cur_sigil
   1808     ld_t0,a0,0
   1809     li_t1 %80 %0
   1810     add_a3,a1,t1
   1811     st_t0,a3,0
   1812     # If depth > 0, snapshot scope_stack into scope_history.
   1813     la_a0 &scope_depth
   1814     ld_t0,a0,0
   1815     la_br &rf_no_scope
   1816     beqz_t0
   1817     la_a1 &scope_history_used
   1818     ld_t1,a1,0
   1819     add_t2,t1,t0
   1820     li_a3 H2_SCOPE_HISTORY_CAP
   1821     la_br &err_scope_history_overflow
   1822     blt_a3,t2
   1823     # Set scope_hist_off (offset 56) on the fixup.
   1824     la_a0 &rf_fp
   1825     ld_a0,a0,0
   1826     st_t1,a0,56
   1827     li_t2 %0 %0
   1828     la_a0 &rf_i
   1829     st_t2,a0,0
   1830 :rf_copy_loop
   1831     la_a0 &rf_i
   1832     ld_t0,a0,0
   1833     la_a1 &scope_depth
   1834     ld_t1,a1,0
   1835     la_br &rf_copy_done
   1836     beq_t0,t1
   1837     # src = scope_stack_ptr + i*8
   1838     la_a1 &scope_stack_ptr
   1839     ld_a1,a1,0
   1840     shli_t2,t0,3
   1841     add_a1,a1,t2
   1842     ld_a3,a1,0
   1843     # dst = scope_history_ptr + (scope_history_used + i)*8
   1844     la_a2 &scope_history_used
   1845     ld_a2,a2,0
   1846     add_a2,a2,t0
   1847     shli_a2,a2,3
   1848     la_a1 &scope_history_ptr
   1849     ld_a1,a1,0
   1850     add_a1,a1,a2
   1851     st_a3,a1,0
   1852     addi_t0,t0,1
   1853     la_a0 &rf_i
   1854     st_t0,a0,0
   1855     la_br &rf_copy_loop
   1856     b
   1857 :rf_copy_done
   1858     la_a1 &scope_history_used
   1859     ld_t0,a1,0
   1860     la_a2 &scope_depth
   1861     ld_t1,a2,0
   1862     add_t0,t0,t1
   1863     st_t0,a1,0
   1864 :rf_no_scope
   1865     la_a0 &fixup_count
   1866     ld_t0,a0,0
   1867     addi_t0,t0,1
   1868     st_t0,a0,0
   1869     eret
   1870 
   1871 ## set_sigil_info(): reads cur_sigil; populates pr_width / pr_is_rel /
   1872 ## pr_lo / pr_hi / pr_range_check.
   1873 :set_sigil_info
   1874     enter_0
   1875     la_a0 &cur_sigil
   1876     ld_a0,a0,0
   1877     li_t0 %33 %0
   1878     la_br &ssi_bang
   1879     beq_a0,t0
   1880     li_t0 %64 %0
   1881     la_br &ssi_at
   1882     beq_a0,t0
   1883     li_t0 %36 %0
   1884     la_br &ssi_dollar
   1885     beq_a0,t0
   1886     li_t0 %126 %0
   1887     la_br &ssi_tilde
   1888     beq_a0,t0
   1889     li_t0 %37 %0
   1890     la_br &ssi_pct
   1891     beq_a0,t0
   1892     li_t0 %38 %0
   1893     la_br &ssi_amp
   1894     beq_a0,t0
   1895     la_br &err_bad_sigil
   1896     b
   1897 :ssi_bang
   1898     li_t0 %1 %0
   1899     la_a1 &pr_width
   1900     st_t0,a1,0
   1901     la_a1 &pr_is_rel
   1902     st_t0,a1,0
   1903     la_a1 &pr_range_check
   1904     st_t0,a1,0
   1905     li_t0 %128 %0
   1906     li_t1 %0 %0
   1907     sub_t0,t1,t0
   1908     la_a1 &pr_lo
   1909     st_t0,a1,0
   1910     li_t0 %127 %0
   1911     la_a1 &pr_hi
   1912     st_t0,a1,0
   1913     eret
   1914 :ssi_at
   1915     li_t0 %2 %0
   1916     la_a1 &pr_width
   1917     st_t0,a1,0
   1918     li_t0 %1 %0
   1919     la_a1 &pr_is_rel
   1920     st_t0,a1,0
   1921     la_a1 &pr_range_check
   1922     st_t0,a1,0
   1923     # 32768 = 256 * 128
   1924     li_t0 %256 %0
   1925     li_t1 %128 %0
   1926     mul_t0,t0,t1
   1927     li_t1 %0 %0
   1928     sub_t1,t1,t0
   1929     la_a1 &pr_lo
   1930     st_t1,a1,0
   1931     addi_t0,t0,neg1
   1932     la_a1 &pr_hi
   1933     st_t0,a1,0
   1934     eret
   1935 :ssi_dollar
   1936     li_t0 %2 %0
   1937     la_a1 &pr_width
   1938     st_t0,a1,0
   1939     li_t0 %0 %0
   1940     la_a1 &pr_is_rel
   1941     st_t0,a1,0
   1942     li_t0 %1 %0
   1943     la_a1 &pr_range_check
   1944     st_t0,a1,0
   1945     li_t0 %0 %0
   1946     la_a1 &pr_lo
   1947     st_t0,a1,0
   1948     # 65536 = 256 * 256
   1949     li_t0 %256 %0
   1950     mov_t1,t0
   1951     mul_t0,t0,t1
   1952     addi_t0,t0,neg1
   1953     la_a1 &pr_hi
   1954     st_t0,a1,0
   1955     eret
   1956 :ssi_tilde
   1957     li_t0 %3 %0
   1958     la_a1 &pr_width
   1959     st_t0,a1,0
   1960     li_t0 %1 %0
   1961     la_a1 &pr_is_rel
   1962     st_t0,a1,0
   1963     la_a1 &pr_range_check
   1964     st_t0,a1,0
   1965     # 8388608 = 256 * 256 * 128
   1966     li_t0 %256 %0
   1967     mov_t1,t0
   1968     mul_t0,t0,t1
   1969     li_t1 %128 %0
   1970     mul_t0,t0,t1
   1971     li_t1 %0 %0
   1972     sub_t1,t1,t0
   1973     la_a1 &pr_lo
   1974     st_t1,a1,0
   1975     addi_t0,t0,neg1
   1976     la_a1 &pr_hi
   1977     st_t0,a1,0
   1978     eret
   1979 :ssi_pct
   1980     la_a0 &ptrsize
   1981     ld_t0,a0,0
   1982     la_a1 &pr_width
   1983     st_t0,a1,0
   1984     li_t0 %1 %0
   1985     la_a1 &pr_is_rel
   1986     st_t0,a1,0
   1987     la_a1 &ptrsize_used
   1988     st_t0,a1,0
   1989     li_t0 %0 %0
   1990     la_a1 &pr_range_check
   1991     st_t0,a1,0
   1992     la_a1 &pr_lo
   1993     st_t0,a1,0
   1994     la_a1 &pr_hi
   1995     st_t0,a1,0
   1996     eret
   1997 :ssi_amp
   1998     la_a0 &ptrsize
   1999     ld_t0,a0,0
   2000     la_a1 &pr_width
   2001     st_t0,a1,0
   2002     li_t0 %1 %0
   2003     la_a1 &ptrsize_used
   2004     st_t0,a1,0
   2005     li_t0 %0 %0
   2006     la_a1 &pr_is_rel
   2007     st_t0,a1,0
   2008     la_a1 &pr_range_check
   2009     st_t0,a1,0
   2010     la_a1 &pr_lo
   2011     st_t0,a1,0
   2012     la_a1 &pr_hi
   2013     st_t0,a1,0
   2014     eret
   2015 
   2016 ## --- Directives ------------------------------------------------------------
   2017 
   2018 ## do_align(): .align N [PATTERN]. N positive power of two; optional pattern.
   2019 :do_align
   2020     enter_0
   2021     la_br &skip_inline_ws
   2022     call
   2023     la_br &read_decimal
   2024     call
   2025     la_a1 &da_n
   2026     st_a0,a1,0
   2027     la_br &err_align_n
   2028     beqz_a0
   2029     la_br &err_align_n
   2030     bltz_a0
   2031     # power-of-two: N & (N-1) == 0
   2032     la_a0 &da_n
   2033     ld_a3,a0,0
   2034     addi_a3,a3,neg1
   2035     la_a0 &da_n
   2036     ld_a2,a0,0
   2037     and_a3,a3,a2
   2038     la_br &err_align_n
   2039     bnez_a3
   2040     li_t0 %0 %0
   2041     la_a0 &da_has_pat
   2042     st_t0,a0,0
   2043     la_a0 &da_patlen
   2044     st_t0,a0,0
   2045     la_br &skip_inline_ws
   2046     call
   2047     la_a0 &scan_pos
   2048     ld_t0,a0,0
   2049     la_a1 &scan_end
   2050     ld_t1,a1,0
   2051     la_br &da_compute
   2052     beq_t0,t1
   2053     la_br &da_compute
   2054     blt_t1,t0
   2055     lb_a0,t0,0
   2056     la_br &is_byte_digit
   2057     call
   2058     la_br &da_compute
   2059     beqz_a0
   2060     li_t0 %1 %0
   2061     la_a1 &da_has_pat
   2062     st_t0,a1,0
   2063 :da_pat_loop
   2064     la_a0 &scan_pos
   2065     ld_t0,a0,0
   2066     la_a1 &scan_end
   2067     ld_t1,a1,0
   2068     la_br &da_compute
   2069     beq_t0,t1
   2070     la_br &da_compute
   2071     blt_t1,t0
   2072     lb_a0,t0,0
   2073     la_br &is_byte_digit
   2074     call
   2075     la_br &da_compute
   2076     beqz_a0
   2077     la_a0 &da_patlen
   2078     ld_t0,a0,0
   2079     li_t1 H2_TOKEN_CAP
   2080     la_br &err_pattern_too_large
   2081     beq_t0,t1
   2082     la_a0 &pat_buf_ptr
   2083     ld_a0,a0,0
   2084     add_a0,a0,t0
   2085     la_br &parse_one_byte
   2086     call
   2087     la_a0 &da_patlen
   2088     ld_t0,a0,0
   2089     addi_t0,t0,1
   2090     st_t0,a0,0
   2091     la_br &da_pat_loop
   2092     b
   2093 :da_compute
   2094     # pad = (N - (ip mod N)) mod N
   2095     la_a0 &ip
   2096     ld_a0,a0,0
   2097     la_a1 &da_n
   2098     ld_a1,a1,0
   2099     rem_a2,a0,a1
   2100     li_t0 %0 %0
   2101     la_a3 &da_pad
   2102     st_t0,a3,0
   2103     la_br &da_emit
   2104     beqz_a2
   2105     sub_a3,a1,a2
   2106     la_a1 &da_pad
   2107     st_a3,a1,0
   2108 :da_emit
   2109     la_a0 &da_pad
   2110     ld_t0,a0,0
   2111     la_br &da_emit_done
   2112     beqz_t0
   2113     # No-pattern fast path: emit_zeros(pad).
   2114     la_a1 &da_has_pat
   2115     ld_t1,a1,0
   2116     la_br &da_emit_pattern
   2117     bnez_t1
   2118     la_a0 &da_pad
   2119     ld_a0,a0,0
   2120     la_br &emit_zeros
   2121     call
   2122     la_br &da_emit_done
   2123     b
   2124 :da_emit_pattern
   2125     li_t0 %0 %0
   2126     la_a0 &da_i
   2127     st_t0,a0,0
   2128 :da_pattern_loop
   2129     la_a0 &da_i
   2130     ld_t0,a0,0
   2131     la_a1 &da_pad
   2132     ld_t1,a1,0
   2133     la_br &da_emit_done
   2134     beq_t0,t1
   2135     # b = pat[i % patlen]
   2136     la_a1 &da_patlen
   2137     ld_a1,a1,0
   2138     rem_a2,t0,a1
   2139     la_a0 &pat_buf_ptr
   2140     ld_a0,a0,0
   2141     add_a0,a0,a2
   2142     lb_a0,a0,0
   2143     la_br &emit_byte
   2144     call
   2145     la_a0 &da_i
   2146     ld_t0,a0,0
   2147     addi_t0,t0,1
   2148     st_t0,a0,0
   2149     la_br &da_pattern_loop
   2150     b
   2151 :da_emit_done
   2152     eret
   2153 
   2154 ## do_fill(): .fill N B.
   2155 :do_fill
   2156     enter_0
   2157     la_br &skip_inline_ws
   2158     call
   2159     la_br &read_decimal
   2160     call
   2161     la_a1 &df_n
   2162     st_a0,a1,0
   2163     la_br &err_fill_n
   2164     bltz_a0
   2165     la_br &skip_inline_ws
   2166     call
   2167     la_a0 &df_byte_ptr
   2168     ld_a0,a0,0
   2169     la_br &parse_one_byte
   2170     call
   2171     la_a0 &df_n
   2172     ld_a0,a0,0
   2173     la_br &df_done
   2174     beqz_a0
   2175     la_a1 &df_byte_ptr
   2176     ld_a1,a1,0
   2177     lb_a1,a1,0
   2178     la_br &emit_fill
   2179     call
   2180 :df_done
   2181     eret
   2182 
   2183 ## do_scope_open(): scope_seq++; scope_stack[scope_depth++] = scope_seq.
   2184 :do_scope_open
   2185     enter_0
   2186     la_a0 &scope_depth
   2187     ld_t0,a0,0
   2188     li_t1 H2_SCOPE_CAP
   2189     la_br &err_scope_overflow
   2190     beq_t0,t1
   2191     la_br &err_scope_overflow
   2192     blt_t1,t0
   2193     la_a0 &scope_seq
   2194     ld_t1,a0,0
   2195     addi_t1,t1,1
   2196     st_t1,a0,0
   2197     la_a1 &scope_stack_ptr
   2198     ld_a1,a1,0
   2199     shli_t2,t0,3
   2200     add_a1,a1,t2
   2201     st_t1,a1,0
   2202     addi_t0,t0,1
   2203     la_a0 &scope_depth
   2204     st_t0,a0,0
   2205     eret
   2206 
   2207 ## do_scope_close(): scope_depth--.
   2208 :do_scope_close
   2209     enter_0
   2210     la_a0 &scope_depth
   2211     ld_t0,a0,0
   2212     la_br &err_scope_underflow
   2213     beqz_t0
   2214     addi_t0,t0,neg1
   2215     st_t0,a0,0
   2216     eret
   2217 
   2218 ## do_ptrsize(): .ptrsize N -- N must be 4 or 8.
   2219 :do_ptrsize
   2220     enter_0
   2221     la_br &skip_inline_ws
   2222     call
   2223     la_br &read_decimal
   2224     call
   2225     la_a1 &dp_n
   2226     st_a0,a1,0
   2227     li_t0 %4 %0
   2228     la_br &dp_ok
   2229     beq_a0,t0
   2230     li_t0 %8 %0
   2231     la_br &dp_ok
   2232     beq_a0,t0
   2233     la_br &err_ptrsize_bad
   2234     b
   2235 :dp_ok
   2236     la_a0 &ptrsize_used
   2237     ld_t0,a0,0
   2238     la_br &dp_set
   2239     beqz_t0
   2240     la_a0 &ptrsize
   2241     ld_t0,a0,0
   2242     la_a1 &dp_n
   2243     ld_t1,a1,0
   2244     la_br &err_ptrsize_conflict
   2245     bne_t0,t1
   2246     eret
   2247 :dp_set
   2248     la_a0 &dp_n
   2249     ld_t0,a0,0
   2250     la_a1 &ptrsize
   2251     st_t0,a1,0
   2252     eret
   2253 
   2254 ## --- Emit ------------------------------------------------------------------
   2255 
   2256 ## emit_byte(a0=byte): write one byte to output_buf and bump ip.
   2257 :emit_byte
   2258     la_a1 &output_used
   2259     ld_t0,a1,0
   2260     li_t1 H2_OUTPUT_CAP
   2261     la_br &err_output_overflow
   2262     beq_t0,t1
   2263     la_br &err_output_overflow
   2264     blt_t1,t0
   2265     la_a2 &output_buf_ptr
   2266     ld_a2,a2,0
   2267     add_a2,a2,t0
   2268     sb_a0,a2,0
   2269     addi_t0,t0,1
   2270     st_t0,a1,0
   2271     la_a0 &ip
   2272     ld_t0,a0,0
   2273     addi_t0,t0,1
   2274     st_t0,a0,0
   2275     ret
   2276 
   2277 ## emit_zeros(a0=n) -> a0 = starting offset (output_used before the call).
   2278 ## Writes n zero bytes to output_buf and advances output_used + ip by n.
   2279 :emit_zeros
   2280     enter_0
   2281     la_a3 &emz_n
   2282     st_a0,a3,0
   2283     # Capacity check: output_used + n must fit.
   2284     la_a1 &output_used
   2285     ld_t0,a1,0
   2286     add_t1,t0,a0
   2287     li_t2 H2_OUTPUT_CAP
   2288     la_br &err_output_overflow
   2289     blt_t2,t1
   2290     la_a2 &emz_off
   2291     st_t0,a2,0
   2292     la_a2 &output_buf_ptr
   2293     ld_a2,a2,0
   2294     add_a2,a2,t0
   2295     li_t1 %0 %0
   2296 :emz_loop
   2297     la_a3 &emz_n
   2298     ld_t2,a3,0
   2299     la_br &emz_done
   2300     beq_t1,t2
   2301     add_a3,a2,t1
   2302     li_t0 %0 %0
   2303     sb_t0,a3,0
   2304     addi_t1,t1,1
   2305     la_br &emz_loop
   2306     b
   2307 :emz_done
   2308     la_a3 &emz_n
   2309     ld_t0,a3,0
   2310     la_a1 &output_used
   2311     ld_t1,a1,0
   2312     add_t1,t1,t0
   2313     st_t1,a1,0
   2314     la_a1 &ip
   2315     ld_t1,a1,0
   2316     add_t1,t1,t0
   2317     st_t1,a1,0
   2318     la_a0 &emz_off
   2319     ld_a0,a0,0
   2320     eret
   2321 
   2322 ## emit_fill(a0=n, a1=byte): write n copies of byte; advances output_used + ip.
   2323 :emit_fill
   2324     enter_0
   2325     la_a3 &emf_n
   2326     st_a0,a3,0
   2327     la_a3 &emf_b
   2328     st_a1,a3,0
   2329     la_a2 &output_used
   2330     ld_t0,a2,0
   2331     add_t1,t0,a0
   2332     li_t2 H2_OUTPUT_CAP
   2333     la_br &err_output_overflow
   2334     blt_t2,t1
   2335     la_a2 &output_buf_ptr
   2336     ld_a2,a2,0
   2337     add_a2,a2,t0
   2338     li_t1 %0 %0
   2339 :emf_loop
   2340     la_a3 &emf_n
   2341     ld_t2,a3,0
   2342     la_br &emf_done
   2343     beq_t1,t2
   2344     la_a3 &emf_b
   2345     ld_t0,a3,0
   2346     add_a3,a2,t1
   2347     sb_t0,a3,0
   2348     addi_t1,t1,1
   2349     la_br &emf_loop
   2350     b
   2351 :emf_done
   2352     la_a3 &emf_n
   2353     ld_t0,a3,0
   2354     la_a1 &output_used
   2355     ld_t1,a1,0
   2356     add_t1,t1,t0
   2357     st_t1,a1,0
   2358     la_a1 &ip
   2359     ld_t1,a1,0
   2360     add_t1,t1,t0
   2361     st_t1,a1,0
   2362     eret
   2363 
   2364 ## write_value(a0=out_off): patch a value into output_buf at out_off.
   2365 ## Caller pre-populates ev_value/ev_width/ev_lo/ev_hi/ev_range_check.
   2366 :write_value
   2367     enter_0
   2368     la_t1 &ev_out_off
   2369     st_a0,t1,0
   2370 
   2371     la_a0 &ev_range_check
   2372     ld_t0,a0,0
   2373     la_br &wv_no_range
   2374     beqz_t0
   2375     la_a0 &ev_value
   2376     ld_a0,a0,0
   2377     la_a1 &ev_lo
   2378     ld_a1,a1,0
   2379     la_br &err_ref_out_of_range
   2380     blt_a0,a1
   2381     la_a0 &ev_value
   2382     ld_a0,a0,0
   2383     la_a1 &ev_hi
   2384     ld_a1,a1,0
   2385     la_br &err_ref_out_of_range
   2386     blt_a1,a0
   2387 :wv_no_range
   2388     la_a0 &ev_value
   2389     ld_a0,a0,0
   2390     la_a1 &ev_pack_v
   2391     st_a0,a1,0
   2392     li_t0 %0 %0
   2393     la_a0 &ev_i
   2394     st_t0,a0,0
   2395 :wv_pack_loop
   2396     la_a0 &ev_i
   2397     ld_t0,a0,0
   2398     la_a1 &ev_width
   2399     ld_t1,a1,0
   2400     la_br &wv_emit_dispatch
   2401     beq_t0,t1
   2402     la_a1 &ev_pack_v
   2403     ld_a3,a1,0
   2404     andi_a3,a3,255
   2405     la_a2 &ev_bytes_ptr
   2406     ld_a2,a2,0
   2407     add_a2,a2,t0
   2408     sb_a3,a2,0
   2409     la_a1 &ev_pack_v
   2410     ld_t2,a1,0
   2411     shri_t2,t2,8
   2412     st_t2,a1,0
   2413     addi_t0,t0,1
   2414     la_a0 &ev_i
   2415     st_t0,a0,0
   2416     la_br &wv_pack_loop
   2417     b
   2418 :wv_emit_dispatch
   2419     la_a0 &big_endian
   2420     ld_t0,a0,0
   2421     la_br &wv_emit_be
   2422     bnez_t0
   2423     li_t0 %0 %0
   2424     la_a0 &ev_i
   2425     st_t0,a0,0
   2426 :wv_emit_le_loop
   2427     la_a0 &ev_i
   2428     ld_t0,a0,0
   2429     la_a1 &ev_width
   2430     ld_t1,a1,0
   2431     la_br &wv_done
   2432     beq_t0,t1
   2433     la_a2 &ev_bytes_ptr
   2434     ld_a2,a2,0
   2435     add_a2,a2,t0
   2436     lb_a0,a2,0
   2437     la_a3 &output_buf_ptr
   2438     ld_a3,a3,0
   2439     la_a2 &ev_out_off
   2440     ld_a2,a2,0
   2441     add_a3,a3,a2
   2442     add_a3,a3,t0
   2443     sb_a0,a3,0
   2444     addi_t0,t0,1
   2445     la_a0 &ev_i
   2446     st_t0,a0,0
   2447     la_br &wv_emit_le_loop
   2448     b
   2449 :wv_emit_be
   2450     li_t0 %0 %0
   2451     la_a0 &ev_i
   2452     st_t0,a0,0
   2453 :wv_emit_be_loop
   2454     la_a0 &ev_i
   2455     ld_t0,a0,0
   2456     la_a1 &ev_width
   2457     ld_t1,a1,0
   2458     la_br &wv_done
   2459     beq_t0,t1
   2460     # src_idx = (width - 1) - i
   2461     addi_t1,t1,neg1
   2462     sub_t2,t1,t0
   2463     la_a2 &ev_bytes_ptr
   2464     ld_a2,a2,0
   2465     add_a2,a2,t2
   2466     lb_a0,a2,0
   2467     la_a3 &output_buf_ptr
   2468     ld_a3,a3,0
   2469     la_a2 &ev_out_off
   2470     ld_a2,a2,0
   2471     add_a3,a3,a2
   2472     add_a3,a3,t0
   2473     sb_a0,a3,0
   2474     addi_t0,t0,1
   2475     la_a0 &ev_i
   2476     st_t0,a0,0
   2477     la_br &wv_emit_be_loop
   2478     b
   2479 :wv_done
   2480     eret
   2481 
   2482 ## --- Fixup resolution ------------------------------------------------------
   2483 ## patch_fixups(): walk fixups[0..fixup_count), resolve labels, write values
   2484 ## into output_buf. Per-fixup error reports use the snapshotted src_path /
   2485 ## src_line via the cur_path / cur_line globals.
   2486 :patch_fixups
   2487     enter_0
   2488     li_t0 %0 %0
   2489     la_a0 &pf_i
   2490     st_t0,a0,0
   2491 :pf_loop
   2492     la_a0 &pf_i
   2493     ld_t0,a0,0
   2494     la_a1 &fixup_count
   2495     ld_t1,a1,0
   2496     la_br &pf_done
   2497     beq_t0,t1
   2498     # fp = fixups + i*96
   2499     li_t1 %96 %0
   2500     mul_a1,t0,t1
   2501     la_a2 &fixups_ptr
   2502     ld_a2,a2,0
   2503     add_a1,a1,a2
   2504     la_a3 &pf_fp
   2505     st_a1,a3,0
   2506     # cur_path = fp[32]; cur_line = fp[72]; cur_sigil = fp[80]
   2507     ld_t0,a1,32
   2508     la_a2 &cur_path
   2509     st_t0,a2,0
   2510     li_t1 %72 %0
   2511     add_a3,a1,t1
   2512     ld_t0,a3,0
   2513     la_a2 &cur_line
   2514     st_t0,a2,0
   2515     li_t1 %80 %0
   2516     add_a3,a1,t1
   2517     ld_t0,a3,0
   2518     la_a2 &cur_sigil
   2519     st_t0,a2,0
   2520     # pr_width / pr_is_rel / pr_lo / pr_hi / pr_range_check
   2521     la_br &set_sigil_info
   2522     call
   2523     # Determine stack pointer for the lookup (NULL if depth == 0).
   2524     la_a0 &pf_fp
   2525     ld_a1,a0,0
   2526     ld_t0,a1,64
   2527     la_a2 &pf_depth
   2528     st_t0,a2,0
   2529     li_t1 %0 %0
   2530     la_a2 &pf_stack
   2531     st_t1,a2,0
   2532     la_br &pf_lookup_label
   2533     beqz_t0
   2534     # stack = scope_history_ptr + scope_hist_off * 8
   2535     ld_t1,a1,56
   2536     shli_t1,t1,3
   2537     la_a2 &scope_history_ptr
   2538     ld_a2,a2,0
   2539     add_a2,a2,t1
   2540     la_a3 &pf_stack
   2541     st_a2,a3,0
   2542 :pf_lookup_label
   2543     la_a0 &pf_fp
   2544     ld_a1,a0,0
   2545     ld_a0,a1,16
   2546     ld_t0,a1,40
   2547     mov_a1,t0
   2548     la_a2 &pf_stack
   2549     ld_a2,a2,0
   2550     la_a3 &pf_depth
   2551     ld_a3,a3,0
   2552     la_br &lookup_label_in
   2553     call
   2554     la_a1 &pf_t_label
   2555     st_a0,a1,0
   2556     # If other != 0: value = t_label - t_other; emit.
   2557     la_a0 &pf_fp
   2558     ld_a1,a0,0
   2559     ld_t0,a1,24
   2560     la_br &pf_no_other
   2561     beqz_t0
   2562     mov_a0,t0
   2563     ld_t0,a1,48
   2564     mov_a1,t0
   2565     la_a2 &pf_stack
   2566     ld_a2,a2,0
   2567     la_a3 &pf_depth
   2568     ld_a3,a3,0
   2569     la_br &lookup_label_in
   2570     call
   2571     la_a1 &pf_t_label
   2572     ld_a1,a1,0
   2573     sub_a0,a1,a0
   2574     la_a1 &pf_value
   2575     st_a0,a1,0
   2576     la_br &pf_emit
   2577     b
   2578 :pf_no_other
   2579     la_a0 &pr_is_rel
   2580     ld_t0,a0,0
   2581     la_br &pf_abs
   2582     beqz_t0
   2583     # rel: value = t_label - ip_at_ref
   2584     la_a0 &pf_fp
   2585     ld_a1,a0,0
   2586     ld_t1,a1,8
   2587     la_a0 &pf_t_label
   2588     ld_a0,a0,0
   2589     sub_a0,a0,t1
   2590     la_a1 &pf_value
   2591     st_a0,a1,0
   2592     la_br &pf_emit
   2593     b
   2594 :pf_abs
   2595     la_a0 &pf_t_label
   2596     ld_a1,a0,0
   2597     la_a0 &base_address
   2598     ld_a0,a0,0
   2599     add_a1,a1,a0
   2600     la_a3 &pf_value
   2601     st_a1,a3,0
   2602 :pf_emit
   2603     # Populate ev_* state, then write_value(out_off).
   2604     la_a0 &pf_value
   2605     ld_t0,a0,0
   2606     la_a1 &ev_value
   2607     st_t0,a1,0
   2608     la_a0 &pr_width
   2609     ld_t0,a0,0
   2610     la_a1 &ev_width
   2611     st_t0,a1,0
   2612     la_a0 &pr_lo
   2613     ld_t0,a0,0
   2614     la_a1 &ev_lo
   2615     st_t0,a1,0
   2616     la_a0 &pr_hi
   2617     ld_t0,a0,0
   2618     la_a1 &ev_hi
   2619     st_t0,a1,0
   2620     la_a0 &pr_range_check
   2621     ld_t0,a0,0
   2622     la_a1 &ev_range_check
   2623     st_t0,a1,0
   2624     la_a0 &pf_fp
   2625     ld_a1,a0,0
   2626     ld_a0,a1,0
   2627     la_br &write_value
   2628     call
   2629     # i++
   2630     la_a0 &pf_i
   2631     ld_t0,a0,0
   2632     addi_t0,t0,1
   2633     st_t0,a0,0
   2634     la_br &pf_loop
   2635     b
   2636 :pf_done
   2637     eret
   2638 
   2639 ## --- Output writer ---------------------------------------------------------
   2640 :write_output
   2641     enter_0
   2642     la_a0 &output_path
   2643     ld_a2,a0,0
   2644     li_a0 sys_openat
   2645     li_a1 H2_AT_FDCWD
   2646     li_a3 H2_O_WRONLY_CREAT_TRUNC
   2647     la_t1 &non_executable
   2648     ld_t1,t1,0
   2649     la_br &wo_mode_nonexec
   2650     bnez_t1
   2651     li_t0 H2_MODE_0750
   2652     la_br &wo_after_mode
   2653     b
   2654 :wo_mode_nonexec
   2655     li_t0 H2_MODE_0640
   2656 :wo_after_mode
   2657     syscall
   2658     la_br &err_open_output
   2659     bltz_a0
   2660     la_a1 &output_fd
   2661     st_a0,a1,0
   2662     li_t0 %0 %0
   2663     la_a1 &output_written
   2664     st_t0,a1,0
   2665 :wo_loop
   2666     la_a0 &output_written
   2667     ld_t0,a0,0
   2668     la_a1 &output_used
   2669     ld_t1,a1,0
   2670     la_br &wo_done
   2671     beq_t0,t1
   2672     la_a0 &output_fd
   2673     ld_a1,a0,0
   2674     la_a2 &output_buf_ptr
   2675     ld_a2,a2,0
   2676     add_a2,a2,t0
   2677     sub_a3,t1,t0
   2678     li_a0 sys_write
   2679     syscall
   2680     la_br &err_write
   2681     bltz_a0
   2682     la_br &err_write
   2683     beqz_a0
   2684     la_a1 &output_written
   2685     ld_t0,a1,0
   2686     add_t0,t0,a0
   2687     st_t0,a1,0
   2688     la_br &wo_loop
   2689     b
   2690 :wo_done
   2691     eret
   2692 
   2693 ## --- Misc helpers ----------------------------------------------------------
   2694 
   2695 ## str_eq(a0=p, a1=q, a2=len) -> a0=0/1. len bytes equal AND p[len]==NUL.
   2696 :str_eq
   2697     enter_0
   2698     la_t0 &se_p
   2699     st_a0,t0,0
   2700     la_t0 &se_q
   2701     st_a1,t0,0
   2702     la_t0 &se_len
   2703     st_a2,t0,0
   2704     li_t1 %0 %0
   2705 :se_loop
   2706     la_a0 &se_len
   2707     ld_a1,a0,0
   2708     la_br &se_check_terminal
   2709     beq_t1,a1
   2710     la_a0 &se_p
   2711     ld_a0,a0,0
   2712     add_a0,a0,t1
   2713     lb_t0,a0,0
   2714     la_a2 &se_q
   2715     ld_a2,a2,0
   2716     add_a2,a2,t1
   2717     lb_t2,a2,0
   2718     la_br &se_no
   2719     bne_t0,t2
   2720     addi_t1,t1,1
   2721     la_br &se_loop
   2722     b
   2723 :se_check_terminal
   2724     la_a0 &se_p
   2725     ld_a0,a0,0
   2726     add_a0,a0,t1
   2727     lb_t0,a0,0
   2728     la_br &se_no
   2729     bnez_t0
   2730     li_a0 %1 %0
   2731     eret
   2732 :se_no
   2733     li_a0 %0 %0
   2734     eret
   2735 
   2736 ## mem_eq(a0=p, a1=q, a2=len) -> a0=0/1.
   2737 :mem_eq
   2738     enter_0
   2739     la_t0 &me_p
   2740     st_a0,t0,0
   2741     la_t0 &me_q
   2742     st_a1,t0,0
   2743     la_t0 &me_len
   2744     st_a2,t0,0
   2745     li_t1 %0 %0
   2746 :me_loop
   2747     la_a0 &me_len
   2748     ld_a1,a0,0
   2749     la_br &me_yes
   2750     beq_t1,a1
   2751     la_a0 &me_p
   2752     ld_a0,a0,0
   2753     add_a0,a0,t1
   2754     lb_t0,a0,0
   2755     la_a2 &me_q
   2756     ld_a2,a2,0
   2757     add_a2,a2,t1
   2758     lb_t2,a2,0
   2759     la_br &me_no
   2760     bne_t0,t2
   2761     addi_t1,t1,1
   2762     la_br &me_loop
   2763     b
   2764 :me_yes
   2765     li_a0 %1 %0
   2766     eret
   2767 :me_no
   2768     li_a0 %0 %0
   2769     eret
   2770 
   2771 ## parse_long(a0=cstr) -> a0=value. Decimal or 0x-prefixed hex i64.
   2772 :parse_long
   2773     enter_0
   2774     la_t0 &pl_p
   2775     st_a0,t0,0
   2776     li_t0 %0 %0
   2777     la_a1 &pl_val
   2778     st_t0,a1,0
   2779     la_a1 &pl_neg
   2780     st_t0,a1,0
   2781     # 0x / 0X prefix?
   2782     la_a0 &pl_p
   2783     ld_a0,a0,0
   2784     lb_t0,a0,0
   2785     li_t1 %48 %0
   2786     la_br &pl_dec_init
   2787     bne_t0,t1
   2788     lb_t0,a0,1
   2789     li_t1 %120 %0
   2790     la_br &pl_hex_init
   2791     beq_t0,t1
   2792     li_t1 %88 %0
   2793     la_br &pl_hex_init
   2794     beq_t0,t1
   2795     la_br &pl_dec_init
   2796     b
   2797 :pl_hex_init
   2798     la_a0 &pl_p
   2799     ld_t0,a0,0
   2800     addi_t0,t0,2
   2801     st_t0,a0,0
   2802 :pl_hex_loop
   2803     la_a0 &pl_p
   2804     ld_t0,a0,0
   2805     lb_t0,t0,0
   2806     la_br &pl_finish
   2807     beqz_t0
   2808     # accept '0'..'9' / 'A'..'F' / 'a'..'f'; reject anything else
   2809     li_t1 %48 %0
   2810     la_br &err_bad_long
   2811     blt_t0,t1
   2812     li_t1 %57 %0
   2813     la_br &pl_hex_acc
   2814     blt_t0,t1
   2815     la_br &pl_hex_acc
   2816     beq_t0,t1
   2817     li_t1 %65 %0
   2818     la_br &err_bad_long
   2819     blt_t0,t1
   2820     li_t1 %70 %0
   2821     la_br &pl_hex_acc
   2822     blt_t0,t1
   2823     la_br &pl_hex_acc
   2824     beq_t0,t1
   2825     li_t1 %97 %0
   2826     la_br &err_bad_long
   2827     blt_t0,t1
   2828     li_t1 %102 %0
   2829     la_br &pl_hex_acc
   2830     blt_t0,t1
   2831     la_br &pl_hex_acc
   2832     beq_t0,t1
   2833     la_br &err_bad_long
   2834     b
   2835 :pl_hex_acc
   2836     mov_a0,t0
   2837     la_br &byte_digit_value
   2838     call
   2839     la_a1 &pl_val
   2840     ld_t0,a1,0
   2841     shli_t0,t0,4
   2842     add_t0,t0,a0
   2843     st_t0,a1,0
   2844     la_a0 &pl_p
   2845     ld_t0,a0,0
   2846     addi_t0,t0,1
   2847     st_t0,a0,0
   2848     la_br &pl_hex_loop
   2849     b
   2850 :pl_dec_init
   2851     la_a0 &pl_p
   2852     ld_a0,a0,0
   2853     lb_t0,a0,0
   2854     li_t1 %45 %0
   2855     la_br &pl_dec_loop
   2856     bne_t0,t1
   2857     li_t0 %1 %0
   2858     la_a1 &pl_neg
   2859     st_t0,a1,0
   2860     la_a0 &pl_p
   2861     ld_t0,a0,0
   2862     addi_t0,t0,1
   2863     st_t0,a0,0
   2864 :pl_dec_loop
   2865     la_a0 &pl_p
   2866     ld_t0,a0,0
   2867     lb_t0,t0,0
   2868     la_br &pl_finish
   2869     beqz_t0
   2870     li_t1 %48 %0
   2871     la_br &err_bad_long
   2872     blt_t0,t1
   2873     li_t1 %57 %0
   2874     la_br &err_bad_long
   2875     blt_t1,t0
   2876     li_t1 %48 %0
   2877     sub_t0,t0,t1
   2878     la_a1 &pl_val
   2879     ld_a2,a1,0
   2880     li_t1 %10 %0
   2881     mul_a2,a2,t1
   2882     add_a2,a2,t0
   2883     st_a2,a1,0
   2884     la_a0 &pl_p
   2885     ld_t0,a0,0
   2886     addi_t0,t0,1
   2887     st_t0,a0,0
   2888     la_br &pl_dec_loop
   2889     b
   2890 :pl_finish
   2891     la_a1 &pl_neg
   2892     ld_t0,a1,0
   2893     la_br &pl_done
   2894     beqz_t0
   2895     la_a1 &pl_val
   2896     ld_a3,a1,0
   2897     li_t0 %0 %0
   2898     sub_a3,t0,a3
   2899     st_a3,a1,0
   2900 :pl_done
   2901     la_a1 &pl_val
   2902     ld_a0,a1,0
   2903     eret
   2904 
   2905 ## --- Errors ----------------------------------------------------------------
   2906 
   2907 ## fatal(a0=msg_ptr): writes "<path>:<line>: hex2pp: <msg>\n" to stderr if
   2908 ## cur_path is set, else "hex2pp: <msg>\n", then exits 1.
   2909 :fatal
   2910     la_a1 &fm_msg
   2911     st_a0,a1,0
   2912     la_a0 &cur_path
   2913     ld_t0,a0,0
   2914     la_br &fm_no_path
   2915     beqz_t0
   2916     # write path
   2917     mov_a0,t0
   2918     la_br &cstrlen
   2919     call
   2920     la_a3 &fm_tmp
   2921     st_a0,a3,0
   2922     la_a2 &cur_path
   2923     ld_a2,a2,0
   2924     la_a3 &fm_tmp
   2925     ld_a3,a3,0
   2926     li_a0 sys_write
   2927     li_a1 %2 %0
   2928     syscall
   2929     # ":"
   2930     li_a0 sys_write
   2931     li_a1 %2 %0
   2932     la_a2 &str_colon
   2933     li_a3 %1 %0
   2934     syscall
   2935     # decimal cur_line
   2936     la_a0 &cur_line
   2937     ld_a0,a0,0
   2938     la_br &write_decimal_stderr
   2939     call
   2940     # ": hex2pp: "
   2941     li_a0 sys_write
   2942     li_a1 %2 %0
   2943     la_a2 &str_colon_prog
   2944     li_a3 %10 %0
   2945     syscall
   2946     la_br &fm_emit_msg
   2947     b
   2948 :fm_no_path
   2949     li_a0 sys_write
   2950     li_a1 %2 %0
   2951     la_a2 &str_prog
   2952     li_a3 %8 %0
   2953     syscall
   2954 :fm_emit_msg
   2955     la_a0 &fm_msg
   2956     ld_a0,a0,0
   2957     la_br &cstrlen
   2958     call
   2959     la_a3 &fm_tmp
   2960     st_a0,a3,0
   2961     la_a2 &fm_msg
   2962     ld_a2,a2,0
   2963     la_a3 &fm_tmp
   2964     ld_a3,a3,0
   2965     li_a0 sys_write
   2966     li_a1 %2 %0
   2967     syscall
   2968     li_a0 sys_write
   2969     li_a1 %2 %0
   2970     la_a2 &str_newline
   2971     li_a3 %1 %0
   2972     syscall
   2973     li_a0 sys_exit
   2974     li_a1 %1 %0
   2975     syscall
   2976 
   2977 ## cstrlen(a0=p) -> a0=length. Walks until NUL.
   2978 :cstrlen
   2979     li_t0 %0 %0
   2980 :cs_loop
   2981     add_t1,a0,t0
   2982     lb_t1,t1,0
   2983     la_br &cs_done
   2984     beqz_t1
   2985     addi_t0,t0,1
   2986     la_br &cs_loop
   2987     b
   2988 :cs_done
   2989     mov_a0,t0
   2990     ret
   2991 
   2992 ## write_decimal_stderr(a0=value): write decimal of unsigned i64 to stderr.
   2993 :write_decimal_stderr
   2994     enter_0
   2995     la_a1 &wd_v
   2996     st_a0,a1,0
   2997     la_br &wd_nonzero
   2998     bnez_a0
   2999     li_a0 sys_write
   3000     li_a1 %2 %0
   3001     la_a2 &str_zero
   3002     li_a3 %1 %0
   3003     syscall
   3004     eret
   3005 :wd_nonzero
   3006     li_t0 %63 %0
   3007     la_a1 &wd_pos
   3008     st_t0,a1,0
   3009 :wd_loop
   3010     la_a0 &wd_v
   3011     ld_a0,a0,0
   3012     la_br &wd_emit
   3013     beqz_a0
   3014     li_t1 %10 %0
   3015     rem_a2,a0,t1
   3016     div_a0,a0,t1
   3017     la_a3 &wd_v
   3018     st_a0,a3,0
   3019     li_t1 %48 %0
   3020     add_a3,t1,a2
   3021     la_a0 &wd_pos
   3022     ld_t0,a0,0
   3023     la_a1 &line_scratch_ptr
   3024     ld_a1,a1,0
   3025     add_a1,a1,t0
   3026     sb_a3,a1,0
   3027     addi_t0,t0,neg1
   3028     la_a0 &wd_pos
   3029     st_t0,a0,0
   3030     la_br &wd_loop
   3031     b
   3032 :wd_emit
   3033     la_a0 &wd_pos
   3034     ld_t0,a0,0
   3035     addi_t0,t0,1
   3036     la_a1 &line_scratch_ptr
   3037     ld_a1,a1,0
   3038     add_a1,a1,t0
   3039     mov_a2,a1
   3040     li_t1 %64 %0
   3041     sub_a3,t1,t0
   3042     li_a0 sys_write
   3043     li_a1 %2 %0
   3044     syscall
   3045     eret
   3046 
   3047 ## --- Error stubs -----------------------------------------------------------
   3048 :err_unknown_arg
   3049     la_a0 &msg_unknown_arg
   3050     la_br &fatal
   3051     b
   3052 :err_extra_positional
   3053     la_a0 &msg_extra_positional
   3054     la_br &fatal
   3055     b
   3056 :err_missing_positional
   3057     la_a0 &msg_missing_positional
   3058     la_br &fatal
   3059     b
   3060 :err_missing_value
   3061     la_a0 &msg_missing_value
   3062     la_br &fatal
   3063     b
   3064 :err_open_input
   3065     la_a0 &msg_open_input
   3066     la_br &fatal
   3067     b
   3068 :err_read
   3069     la_a0 &msg_read
   3070     la_br &fatal
   3071     b
   3072 :err_input_too_big
   3073     la_a0 &msg_input_too_big
   3074     la_br &fatal
   3075     b
   3076 :err_open_output
   3077     la_a0 &msg_open_output
   3078     la_br &fatal
   3079     b
   3080 :err_write
   3081     la_a0 &msg_write
   3082     la_br &fatal
   3083     b
   3084 :err_text_overflow
   3085     la_a0 &msg_text_overflow
   3086     la_br &fatal
   3087     b
   3088 :err_too_many_labels
   3089     la_a0 &msg_too_many_labels
   3090     la_br &fatal
   3091     b
   3092 :err_duplicate_label
   3093     la_a0 &msg_duplicate_label
   3094     la_br &fatal
   3095     b
   3096 :err_undefined_label
   3097     la_a0 &msg_undefined_label
   3098     la_br &fatal
   3099     b
   3100 :err_undefined_local
   3101     la_a0 &msg_undefined_local
   3102     la_br &fatal
   3103     b
   3104 :err_unexpected_char
   3105     la_a0 &msg_unexpected_char
   3106     la_br &fatal
   3107     b
   3108 :err_unknown_directive
   3109     la_a0 &msg_unknown_directive
   3110     la_br &fatal
   3111     b
   3112 :err_scope_overflow
   3113     la_a0 &msg_scope_overflow
   3114     la_br &fatal
   3115     b
   3116 :err_scope_underflow
   3117     la_a0 &msg_scope_underflow
   3118     la_br &fatal
   3119     b
   3120 :err_scope_unclosed
   3121     la_a0 &msg_scope_unclosed
   3122     la_br &fatal
   3123     b
   3124 :err_align_n
   3125     la_a0 &msg_align_n
   3126     la_br &fatal
   3127     b
   3128 :err_fill_n
   3129     la_a0 &msg_fill_n
   3130     la_br &fatal
   3131     b
   3132 :err_pattern_too_large
   3133     la_a0 &msg_pattern_too_large
   3134     la_br &fatal
   3135     b
   3136 :err_byte_lit_bad
   3137     la_a0 &msg_byte_lit_bad
   3138     la_br &fatal
   3139     b
   3140 :err_byte_stream_short
   3141     la_a0 &msg_byte_stream_short
   3142     la_br &fatal
   3143     b
   3144 :err_sigil_no_label
   3145     la_a0 &msg_sigil_no_label
   3146     la_br &fatal
   3147     b
   3148 :err_minus_no_label
   3149     la_a0 &msg_minus_no_label
   3150     la_br &fatal
   3151     b
   3152 :err_bad_sigil
   3153     la_a0 &msg_bad_sigil
   3154     la_br &fatal
   3155     b
   3156 :err_ref_out_of_range
   3157     la_a0 &msg_ref_out_of_range
   3158     la_br &fatal
   3159     b
   3160 :err_name_too_long
   3161     la_a0 &msg_name_too_long
   3162     la_br &fatal
   3163     b
   3164 :err_empty_name
   3165     la_a0 &msg_empty_name
   3166     la_br &fatal
   3167     b
   3168 :err_empty_directive
   3169     la_a0 &msg_empty_directive
   3170     la_br &fatal
   3171     b
   3172 :err_expected_decimal
   3173     la_a0 &msg_expected_decimal
   3174     la_br &fatal
   3175     b
   3176 :err_output_overflow
   3177     la_a0 &msg_output_overflow
   3178     la_br &fatal
   3179     b
   3180 :err_bad_long
   3181     la_a0 &msg_bad_long
   3182     la_br &fatal
   3183     b
   3184 :err_too_many_fixups
   3185     la_a0 &msg_too_many_fixups
   3186     la_br &fatal
   3187     b
   3188 :err_scope_history_overflow
   3189     la_a0 &msg_scope_history_overflow
   3190     la_br &fatal
   3191     b
   3192 :err_ptrsize_bad
   3193     la_a0 &msg_ptrsize_bad
   3194     la_br &fatal
   3195     b
   3196 :err_ptrsize_conflict
   3197     la_a0 &msg_ptrsize_conflict
   3198     la_br &fatal
   3199     b
   3200 
   3201 ## Sentinel: end of executable text.
   3202 :_text_end
   3203 
   3204 ## --- Rodata ----------------------------------------------------------------
   3205 
   3206 :opt_B "-B" '00'
   3207 :opt_E "-E" '00'
   3208 :opt_e "-e" '00'
   3209 :opt_b "-b" '00'
   3210 :opt_N "-N" '00'
   3211 
   3212 :kw_align "align"
   3213 :kw_fill "fill"
   3214 :kw_scope "scope"
   3215 :kw_endscope "endscope"
   3216 :kw_ptrsize "ptrsize"
   3217 
   3218 :str_colon ":"
   3219 :str_colon_prog ": hex2pp: "
   3220 :str_prog "hex2pp: "
   3221 :str_newline "
   3222 "
   3223 :str_zero "0"
   3224 
   3225 :msg_unknown_arg "unknown argument" '00'
   3226 :msg_extra_positional "extra positional argument" '00'
   3227 :msg_missing_positional "missing IN or OUT positional argument" '00'
   3228 :msg_missing_value "missing value for option" '00'
   3229 :msg_open_input "failed to open input file" '00'
   3230 :msg_read "failed to read input" '00'
   3231 :msg_input_too_big "input too large" '00'
   3232 :msg_open_output "failed to open output file" '00'
   3233 :msg_write "failed to write output" '00'
   3234 :msg_text_overflow "text pool overflow" '00'
   3235 :msg_too_many_labels "too many labels" '00'
   3236 :msg_duplicate_label "duplicate label" '00'
   3237 :msg_undefined_label "undefined label" '00'
   3238 :msg_undefined_local "undefined local label" '00'
   3239 :msg_unexpected_char "unexpected character" '00'
   3240 :msg_unknown_directive "unknown directive" '00'
   3241 :msg_scope_overflow ".scope: depth overflow" '00'
   3242 :msg_scope_underflow ".endscope: not in a scope" '00'
   3243 :msg_scope_unclosed ".scope not closed at end of input" '00'
   3244 :msg_align_n ".align: N must be a positive power of two" '00'
   3245 :msg_fill_n ".fill: N must be non-negative" '00'
   3246 :msg_pattern_too_large "pattern too large" '00'
   3247 :msg_byte_lit_bad "byte literal: bad digit count" '00'
   3248 :msg_byte_stream_short "byte stream: incomplete digits at end of run" '00'
   3249 :msg_sigil_no_label "sigil not followed by label name" '00'
   3250 :msg_minus_no_label "'-' must be followed by label name" '00'
   3251 :msg_bad_sigil "internal: bad sigil" '00'
   3252 :msg_ref_out_of_range "reference out of range" '00'
   3253 :msg_name_too_long "name too long" '00'
   3254 :msg_empty_name "expected label name" '00'
   3255 :msg_empty_directive "expected directive name after '.'" '00'
   3256 :msg_expected_decimal "expected decimal integer" '00'
   3257 :msg_output_overflow "output overflow" '00'
   3258 :msg_bad_long "invalid integer argument" '00'
   3259 :msg_ptrsize_bad ".ptrsize: N must be 4 or 8" '00'
   3260 :msg_ptrsize_conflict ".ptrsize conflicts with already-used width" '00'
   3261 :msg_too_many_fixups "too many references" '00000000'
   3262 :msg_scope_history_overflow "scope history overflow" '00'
   3263 
   3264 ## BSS pointer-slot init table.
   3265 :bss_init_tbl
   3266 &scope_stack_ptr ZERO4 H2_OFF_scope_stack
   3267 &name_buf_ptr ZERO4 H2_OFF_name_buf
   3268 &label_buf_ptr ZERO4 H2_OFF_label_buf
   3269 &other_buf_ptr ZERO4 H2_OFF_other_buf
   3270 &pat_buf_ptr ZERO4 H2_OFF_pat_buf
   3271 &line_scratch_ptr ZERO4 H2_OFF_line_scratch
   3272 &ev_bytes_ptr ZERO4 H2_OFF_ev_bytes
   3273 &df_byte_ptr ZERO4 H2_OFF_df_byte
   3274 &input_buf_ptr ZERO4 H2_OFF_input_buf
   3275 &output_buf_ptr ZERO4 H2_OFF_output_buf
   3276 &text_buf_ptr ZERO4 H2_OFF_text_buf
   3277 &labels_ptr ZERO4 H2_OFF_labels
   3278 &fixups_ptr ZERO4 H2_OFF_fixups
   3279 &scope_history_ptr ZERO4 H2_OFF_scope_history
   3280 :bss_init_tbl_end
   3281 
   3282 ## --- BSS scalars ----------------------------------------------------------
   3283 
   3284 :saved_argc
   3285 ZERO8
   3286 :saved_argv
   3287 ZERO8
   3288 :arg_idx
   3289 ZERO8
   3290 :arg_ptr
   3291 ZERO8
   3292 :input_path
   3293 ZERO8
   3294 :input_fd
   3295 ZERO8
   3296 :input_len
   3297 ZERO8
   3298 :output_path
   3299 ZERO8
   3300 :output_fd
   3301 ZERO8
   3302 :output_used
   3303 ZERO8
   3304 :output_written
   3305 ZERO8
   3306 :base_address
   3307 ZERO8
   3308 :byte_mode
   3309 ZERO8
   3310 :big_endian
   3311 ZERO8
   3312 :non_executable
   3313 ZERO8
   3314 :ptrsize
   3315 ZERO8
   3316 :ptrsize_used
   3317 ZERO8
   3318 
   3319 :pass
   3320 ZERO8
   3321 :ip
   3322 ZERO8
   3323 :cur_path
   3324 ZERO8
   3325 :cur_line
   3326 ZERO8
   3327 :scan_pos
   3328 ZERO8
   3329 :scan_end
   3330 ZERO8
   3331 :text_used
   3332 ZERO8
   3333 :label_count
   3334 ZERO8
   3335 :fixup_count
   3336 ZERO8
   3337 :scope_history_used
   3338 ZERO8
   3339 :scope_depth
   3340 ZERO8
   3341 :scope_seq
   3342 ZERO8
   3343 
   3344 ## name read
   3345 :name_len
   3346 ZERO8
   3347 :name_scope
   3348 ZERO8
   3349 :nt_c
   3350 ZERO8
   3351 :rn_out
   3352 ZERO8
   3353 :rn_max
   3354 ZERO8
   3355 :rn_n
   3356 ZERO8
   3357 
   3358 ## scan_name (returns ptr+len; pointer goes here, length in a0).
   3359 :sn_start
   3360 ZERO8
   3361 
   3362 ## record_fixup scratch
   3363 :rf_name
   3364 ZERO8
   3365 :rf_name_len
   3366 ZERO8
   3367 :rf_other
   3368 ZERO8
   3369 :rf_other_len
   3370 ZERO8
   3371 :rf_out_off
   3372 ZERO8
   3373 :rf_ip_after
   3374 ZERO8
   3375 :rf_idx
   3376 ZERO8
   3377 :rf_fp
   3378 ZERO8
   3379 :rf_i
   3380 ZERO8
   3381 
   3382 ## patch_fixups scratch
   3383 :pf_i
   3384 ZERO8
   3385 :pf_fp
   3386 ZERO8
   3387 :pf_t_label
   3388 ZERO8
   3389 :pf_value
   3390 ZERO8
   3391 :pf_stack
   3392 ZERO8
   3393 :pf_depth
   3394 ZERO8
   3395 
   3396 ## process_reference scratch (single-pass record path)
   3397 :pref_name
   3398 ZERO8
   3399 :pref_name_len
   3400 ZERO8
   3401 :pref_other
   3402 ZERO8
   3403 :pref_other_len
   3404 ZERO8
   3405 
   3406 ## decimal read
   3407 :rd_val
   3408 ZERO8
   3409 :rd_saw
   3410 ZERO8
   3411 
   3412 ## byte stream
   3413 :pbs_acc
   3414 ZERO8
   3415 :pbs_have
   3416 ZERO8
   3417 :pbs_c
   3418 ZERO8
   3419 
   3420 ## one byte literal
   3421 :p1b_out
   3422 ZERO8
   3423 :p1b_acc
   3424 ZERO8
   3425 :p1b_have
   3426 ZERO8
   3427 :p1b_c
   3428 ZERO8
   3429 
   3430 ## intern
   3431 :intern_src
   3432 ZERO8
   3433 :intern_len
   3434 ZERO8
   3435 :intern_dst
   3436 ZERO8
   3437 :intern_orig
   3438 ZERO8
   3439 :intern_i
   3440 ZERO8
   3441 
   3442 ## name_eq
   3443 :ne_label
   3444 ZERO8
   3445 :ne_src
   3446 ZERO8
   3447 :ne_len
   3448 ZERO8
   3449 
   3450 ## define_label
   3451 :dl_src
   3452 ZERO8
   3453 :dl_len
   3454 ZERO8
   3455 :dl_scope
   3456 ZERO8
   3457 :dl_i
   3458 ZERO8
   3459 :dl_label
   3460 ZERO8
   3461 :dl_name_off
   3462 ZERO8
   3463 
   3464 ## lookup_label_in
   3465 :ll_src
   3466 ZERO8
   3467 :ll_len
   3468 ZERO8
   3469 :ll_stack
   3470 ZERO8
   3471 :ll_depth
   3472 ZERO8
   3473 :ll_d
   3474 ZERO8
   3475 :ll_sid
   3476 ZERO8
   3477 :ll_i
   3478 ZERO8
   3479 :ll_label
   3480 ZERO8
   3481 
   3482 ## process_reference / set_sigil_info
   3483 :cur_sigil
   3484 ZERO8
   3485 :pr_width
   3486 ZERO8
   3487 :pr_is_rel
   3488 ZERO8
   3489 :pr_lo
   3490 ZERO8
   3491 :pr_hi
   3492 ZERO8
   3493 :pr_range_check
   3494 ZERO8
   3495 :pr_llen
   3496 ZERO8
   3497 :pr_olen
   3498 ZERO8
   3499 :pr_has_other
   3500 ZERO8
   3501 :pr_t_label
   3502 ZERO8
   3503 :pr_value
   3504 ZERO8
   3505 
   3506 ## emit_value / write_value
   3507 :ev_value
   3508 ZERO8
   3509 :ev_width
   3510 ZERO8
   3511 :ev_lo
   3512 ZERO8
   3513 :ev_hi
   3514 ZERO8
   3515 :ev_range_check
   3516 ZERO8
   3517 :ev_pack_v
   3518 ZERO8
   3519 :ev_i
   3520 ZERO8
   3521 :ev_out_off
   3522 ZERO8
   3523 
   3524 ## emit_zeros / emit_fill scratch
   3525 :emz_n
   3526 ZERO8
   3527 :emz_off
   3528 ZERO8
   3529 :emf_n
   3530 ZERO8
   3531 :emf_b
   3532 ZERO8
   3533 
   3534 ## directive scratch
   3535 :da_n
   3536 ZERO8
   3537 :da_has_pat
   3538 ZERO8
   3539 :da_patlen
   3540 ZERO8
   3541 :da_pad
   3542 ZERO8
   3543 :da_i
   3544 ZERO8
   3545 :df_n
   3546 ZERO8
   3547 :df_i
   3548 ZERO8
   3549 :dp_n
   3550 ZERO8
   3551 
   3552 ## str/mem
   3553 :se_p
   3554 ZERO8
   3555 :se_q
   3556 ZERO8
   3557 :se_len
   3558 ZERO8
   3559 :me_p
   3560 ZERO8
   3561 :me_q
   3562 ZERO8
   3563 :me_len
   3564 ZERO8
   3565 
   3566 ## parse_long
   3567 :pl_p
   3568 ZERO8
   3569 :pl_val
   3570 ZERO8
   3571 :pl_neg
   3572 ZERO8
   3573 
   3574 ## fatal
   3575 :fm_msg
   3576 ZERO8
   3577 :fm_tmp
   3578 ZERO8
   3579 
   3580 ## write_decimal
   3581 :wd_v
   3582 ZERO8
   3583 :wd_pos
   3584 ZERO8
   3585 
   3586 ## --- BSS pointer slots ----------------------------------------------------
   3587 :scope_stack_ptr
   3588 ZERO8
   3589 :name_buf_ptr
   3590 ZERO8
   3591 :label_buf_ptr
   3592 ZERO8
   3593 :other_buf_ptr
   3594 ZERO8
   3595 :pat_buf_ptr
   3596 ZERO8
   3597 :line_scratch_ptr
   3598 ZERO8
   3599 :ev_bytes_ptr
   3600 ZERO8
   3601 :df_byte_ptr
   3602 ZERO8
   3603 :input_buf_ptr
   3604 ZERO8
   3605 :output_buf_ptr
   3606 ZERO8
   3607 :text_buf_ptr
   3608 ZERO8
   3609 :labels_ptr
   3610 ZERO8
   3611 :fixups_ptr
   3612 ZERO8
   3613 :scope_history_ptr
   3614 ZERO8
   3615 
   3616 :ELF_end