boot2

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

hex2pp.P1 (66945B)


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