boot2

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

P1pp.P1pp (51391B)


      1 # p1pp.P1pp -- libp1pp v1, portable utility library for P1pp programs.
      2 #
      3 # Concatenated after the P1 backend header and frontend, and before user
      4 # source:
      5 #
      6 #     catm P1-<arch>.M1pp P1.M1pp p1pp.P1pp usersrc.P1pp > program.M1
      7 #
      8 # Targets both P1-64 and P1-32. Data structures declared with `%struct`
      9 # deliberately retain their 8-byte padded field layout on both widths;
     10 # `%p1_word_*` describes target registers, pointers, and native arrays.
     11 # All internal labels use the `libp1pp__` prefix; public entry points are
     12 # unprefixed.
     13 #
     14 # See docs/LIBP1PP.md for the public contract.
     15 
     16 # =========================================================================
     17 # Compile-time helpers
     18 # =========================================================================
     19 
     20 # %alignup -- Round `rs` up to a multiple of `align` (a power of two) and
     21 # place the result in `rd`. `scratch` is clobbered. `align` must be a
     22 # constant integer expression. Two instructions plus an %li for the mask:
     23 #
     24 #     rd      = rs + (align - 1)
     25 #     scratch = -align          (i.e. ~(align-1) for power-of-two align)
     26 #     rd      = rd & scratch
     27 %macro alignup(rd, rs, align, scratch)
     28     %addi(rd, rs, (- align 1))
     29     %li(scratch, (- 0 align))
     30     %and(rd, rd, scratch)
     31 %endm
     32 
     33 # =========================================================================
     34 # Global memory access
     35 # =========================================================================
     36 #
     37 # Shorthand for the la + ld(_,0) / la + st(_,0) idiom that dereferences a
     38 # pointer slot at a labeled global. `name` is a label expression in the
     39 # `&foo` form that %la accepts.
     40 #
     41 # %ld_global  -- rd = *name. rd doubles as the address scratch.
     42 # %st_global  -- *name = rs. Needs a separate `scratch` since rs holds the
     43 #                value being written.
     44 # %lda_global -- rd = *name AND raddr = &name. Use when the address is
     45 #                also needed afterward (e.g. load then store back).
     46 
     47 %macro ld_global(rd, name)
     48     %la(rd, name)
     49     %ld(rd, rd, 0)
     50 %endm
     51 
     52 %macro st_global(rs, name, scratch)
     53     %la(scratch, name)
     54     %st(rs, scratch, 0)
     55 %endm
     56 
     57 %macro lda_global(rd, raddr, name)
     58     %la(raddr, name)
     59     %ld(rd, raddr, 0)
     60 %endm
     61 
     62 # =========================================================================
     63 # Array indexing
     64 # =========================================================================
     65 #
     66 # Compute *(base + idx * stride + off) for register `base`, register
     67 # `idx`, and constant `stride` and `off`. `stride` is materialized via
     68 # %li and folded into idx via %mul, so it does not need to be a power of
     69 # two. `off` is a constant byte offset within the element (use 0 for
     70 # plain element access; non-zero for field access).
     71 #
     72 # All three macros need a separate scratch to hold the computed address,
     73 # because both `base` and `idx` are register inputs that must survive the
     74 # multiply. `lda_array` reuses its `raddr` output as that scratch.
     75 #
     76 # %ld_array  -- rd = *(base + idx*stride + off).
     77 # %st_array  -- *(base + idx*stride + off) = rs.
     78 # %lda_array -- rd = *addr AND raddr = base + idx*stride; ld is at
     79 #               offset `off`. Use when subsequent code also needs the
     80 #               computed address (e.g. for additional field accesses).
     81 
     82 %macro ld_array(rd, base, stride, idx, off, scratch)
     83     %li(scratch, stride)
     84     %mul(scratch, idx, scratch)
     85     %add(scratch, base, scratch)
     86     %ld(rd, scratch, off)
     87 %endm
     88 
     89 %macro st_array(rs, base, stride, idx, off, scratch)
     90     %li(scratch, stride)
     91     %mul(scratch, idx, scratch)
     92     %add(scratch, base, scratch)
     93     %st(rs, scratch, off)
     94 %endm
     95 
     96 %macro lda_array(rd, raddr, base, stride, idx, off)
     97     %li(raddr, stride)
     98     %mul(raddr, idx, raddr)
     99     %add(raddr, base, raddr)
    100     %ld(rd, raddr, off)
    101 %endm
    102 
    103 # =========================================================================
    104 # Sub-word memory access
    105 # =========================================================================
    106 #
    107 # P1 has only 1-byte (%lb/%sb) and word-sized (%ld/%st) memory ops. For
    108 # struct fields and
    109 # packed data laid out at narrower widths, sub-word access is byte-
    110 # decomposed: %lb-gather + shli/or for loads, %sb-scatter + shri for
    111 # stores. These macros encapsulate that pattern so callers do not have
    112 # to open-code it (and so a backend can later substitute a single
    113 # native sub-word load/store when alignment is statically known).
    114 #
    115 # Conventions:
    116 #   `rd` is the destination (loads); `rs` is the source (stores).
    117 #   Stores preserve `rs`; loads clobber `rd`. `scratch` is a working
    118 #   register distinct from rd/rs and base. Bytes are little-endian:
    119 #   byte 0 (low) at off+0. The signed-load variants (%ld_sh, %ld_sw)
    120 #   sign-extend the gathered value to the canonical target-word form.
    121 #
    122 # %ld_h(rd, base, off, scratch)   — 2-byte zero-extending load
    123 # %ld_w(rd, base, off, scratch)   — 4-byte zero-extending load
    124 # %ld_sh(rd, base, off, scratch)  — 2-byte sign-extending load
    125 # %ld_sw(rd, base, off, scratch)  — 4-byte sign-extending load
    126 # %st_h(rs, base, off, scratch)   — 2-byte store (writes low 16 bits)
    127 # %st_w(rs, base, off, scratch)   — 4-byte store (writes low 32 bits)
    128 
    129 %macro ld_h(rd, base, off, scratch)
    130     %lb(rd, base, off)
    131     %lb(scratch, base, (+ off 1))
    132     %shli(scratch, scratch, 8)
    133     %or(rd, rd, scratch)
    134 %endm
    135 
    136 %macro ld_w(rd, base, off, scratch)
    137     %lb(rd, base, off)
    138     %lb(scratch, base, (+ off 1))
    139     %shli(scratch, scratch, 8)
    140     %or(rd, rd, scratch)
    141     %lb(scratch, base, (+ off 2))
    142     %shli(scratch, scratch, 16)
    143     %or(rd, rd, scratch)
    144     %lb(scratch, base, (+ off 3))
    145     %shli(scratch, scratch, 24)
    146     %or(rd, rd, scratch)
    147 %endm
    148 
    149 %macro ld_sh(rd, base, off, scratch)
    150     %ld_h(rd, base, off, scratch)
    151     %shli(rd, rd, (- %p1_word_bits 16))
    152     %sari(rd, rd, (- %p1_word_bits 16))
    153 %endm
    154 
    155 %macro ld_sw(rd, base, off, scratch)
    156     %ld_w(rd, base, off, scratch)
    157     %shli(rd, rd, (- %p1_word_bits 32))
    158     %sari(rd, rd, (- %p1_word_bits 32))
    159 %endm
    160 
    161 %macro st_h(rs, base, off, scratch)
    162     %sb(rs, base, off)
    163     %shri(scratch, rs, 8)
    164     %sb(scratch, base, (+ off 1))
    165 %endm
    166 
    167 %macro st_w(rs, base, off, scratch)
    168     %sb(rs, base, off)
    169     %shri(scratch, rs, 8)
    170     %sb(scratch, base, (+ off 1))
    171     %shri(scratch, rs, 16)
    172     %sb(scratch, base, (+ off 2))
    173     %shri(scratch, rs, 24)
    174     %sb(scratch, base, (+ off 3))
    175 %endm
    176 
    177 # =========================================================================
    178 # Sign and zero extension
    179 # =========================================================================
    180 #
    181 # %sextN(rd, ra)        truncate ra to N bits and sign-extend to a word.
    182 # %zextN(rd, ra)        truncate ra to N bits and zero-extend to a word.
    183 # %zext32(rd, ra, scratch)
    184 #                       like zextN but needs a scratch register because
    185 #                       0xFFFFFFFF does not fit a 16-bit movz immediate
    186 #                       (the path %andi takes when materializing the mask).
    187 #
    188 # rd may equal ra. The signed forms use shli/sari at the right amount;
    189 # zext8/zext16 ride on %andi (the mask fits movz so no caller scratch
    190 # needed); zext32 materializes the mask explicitly.
    191 
    192 %macro sext8(rd, ra)
    193     %shli(rd, ra, (- %p1_word_bits 8))
    194     %sari(rd, rd, (- %p1_word_bits 8))
    195 %endm
    196 
    197 %macro sext16(rd, ra)
    198     %shli(rd, ra, (- %p1_word_bits 16))
    199     %sari(rd, rd, (- %p1_word_bits 16))
    200 %endm
    201 
    202 %macro sext32(rd, ra)
    203     %shli(rd, ra, (- %p1_word_bits 32))
    204     %sari(rd, rd, (- %p1_word_bits 32))
    205 %endm
    206 
    207 %macro zext8(rd, ra)
    208     %andi(rd, ra, 255)
    209 %endm
    210 
    211 %macro zext16(rd, ra)
    212     %andi(rd, ra, 65535)
    213 %endm
    214 
    215 %macro zext32(rd, ra, scratch)
    216     %li(scratch, 4294967295)
    217     %and(rd, ra, scratch)
    218 %endm
    219 
    220 # =========================================================================
    221 # Frame-slot address
    222 # =========================================================================
    223 #
    224 # %lea_slot(rd, slot)   rd = address of the frame slot at byte offset
    225 #                       `slot`. Centralizes the "%mov(rd, sp) +
    226 #                       %addi(rd, rd, slot)" idiom — the backend folds
    227 #                       its hidden frame-header offset into %mov(rd, sp),
    228 #                       so callers must not bake a literal 16 into the
    229 #                       %addi. `slot` may be any M1pp integer expression
    230 #                       (a literal byte offset or a %fn__SO-relative
    231 #                       slot-expr).
    232 
    233 %macro lea_slot(rd, slot)
    234     %mov(rd, sp)
    235     %addi(rd, rd, slot)
    236 %endm
    237 
    238 # =========================================================================
    239 # Pointer scaling
    240 # =========================================================================
    241 #
    242 # %ptr_add(rd, ptr, idx, sz, scratch)   rd = ptr + idx*sz
    243 # %ptr_sub(rd, ptr, idx, sz, scratch)   rd = ptr - idx*sz
    244 # %ptr_diff(rd, p, q, sz, scratch)      rd = (p - q) / sz
    245 #
    246 # `sz` is an M1pp-time integer constant (the C pointee size). When
    247 # sz == 1 the multiply (or divide) collapses out at expansion time.
    248 #
    249 # %ptr_add and %ptr_sub clobber `scratch`. %ptr_diff clobbers `scratch`
    250 # (only when sz != 1) and computes through `rd`, so callers must not
    251 # alias `rd` with `p` or `q` in the sz != 1 path.
    252 
    253 # sz <= 1 takes the byte-stride fast path: char* (sz=1) and void*
    254 # (cc.scm uses sz=-1 for the void pointee, following GCC's byte-arith
    255 # extension) both want raw idx with no scaling.
    256 
    257 %macro ptr_add(rd, ptr, idx, sz, scratch)
    258 %select((< sz 2),
    259     %add(rd, ptr, idx),
    260     %li(scratch, sz)
    261     %mul(scratch, idx, scratch)
    262     %add(rd, ptr, scratch))
    263 %endm
    264 
    265 %macro ptr_sub(rd, ptr, idx, sz, scratch)
    266 %select((< sz 2),
    267     %sub(rd, ptr, idx),
    268     %li(scratch, sz)
    269     %mul(scratch, idx, scratch)
    270     %sub(rd, ptr, scratch))
    271 %endm
    272 
    273 %macro ptr_diff(rd, p, q, sz, scratch)
    274 %select((< sz 2),
    275     %sub(rd, p, q),
    276     %sub(rd, p, q)
    277     %li(scratch, sz)
    278     %div(rd, rd, scratch))
    279 %endm
    280 
    281 # =========================================================================
    282 # Memcpy-call shorthand
    283 # =========================================================================
    284 #
    285 # %memcpy_call(dst_reg, src_reg, n_imm)
    286 #   Marshal arguments into the libp1pp memcpy ABI and invoke it. Useful
    287 #   for fixed-size memory copies (e.g. struct copy in a code generator)
    288 #   where the size is known at expansion time. dst_reg and src_reg must
    289 #   not be a0 — the dst move would clobber a different live input.
    290 
    291 %macro memcpy_call(dst_reg, src_reg, n_imm)
    292     %li(a2, n_imm)
    293     %mov(a1, src_reg)
    294     %mov(a0, dst_reg)
    295     %call(&memcpy)
    296 %endm
    297 
    298 # =========================================================================
    299 # Compare-and-set-bool macros
    300 # =========================================================================
    301 #
    302 # %cmpset_<cc>(rd, ra[, rb])  rd = (ra <cc> rb) ? 1 : 0
    303 #
    304 # Two-operand: eq, ne, lt, ltu, le, leu, ge, geu (signed/unsigned).
    305 # Zero-operand (compare against zero): eqz, nez, ltz.
    306 #
    307 # le/ge/leu/geu lower through the same ifelse machinery with operands
    308 # swapped or condition flipped: a >= b iff !(a < b) iff (b <= a-1), and
    309 # we reach it as (b < a) ? 0 : 1 via ifelse_lt with swapped arms (and
    310 # the unsigned/signed pairing follows ltu/lt).
    311 #
    312 # Lower to %ifelse_<cc>(...) which itself works across all P1 backends.
    313 # A backend that supports a native conditional-set instruction can later
    314 # specialize these to a single op without touching callers.
    315 
    316 %macro cmpset_eq(rd, ra, rb)
    317     %ifelse_eq(ra, rb, { %li(rd, 1) }, { %li(rd, 0) })
    318 %endm
    319 
    320 %macro cmpset_ne(rd, ra, rb)
    321     %ifelse_ne(ra, rb, { %li(rd, 1) }, { %li(rd, 0) })
    322 %endm
    323 
    324 %macro cmpset_lt(rd, ra, rb)
    325     %ifelse_lt(ra, rb, { %li(rd, 1) }, { %li(rd, 0) })
    326 %endm
    327 
    328 %macro cmpset_ltu(rd, ra, rb)
    329     %ifelse_ltu(ra, rb, { %li(rd, 1) }, { %li(rd, 0) })
    330 %endm
    331 
    332 %macro cmpset_le(rd, ra, rb)
    333     %ifelse_lt(rb, ra, { %li(rd, 0) }, { %li(rd, 1) })
    334 %endm
    335 
    336 %macro cmpset_leu(rd, ra, rb)
    337     %ifelse_ltu(rb, ra, { %li(rd, 0) }, { %li(rd, 1) })
    338 %endm
    339 
    340 %macro cmpset_ge(rd, ra, rb)
    341     %ifelse_lt(ra, rb, { %li(rd, 0) }, { %li(rd, 1) })
    342 %endm
    343 
    344 %macro cmpset_geu(rd, ra, rb)
    345     %ifelse_ltu(ra, rb, { %li(rd, 0) }, { %li(rd, 1) })
    346 %endm
    347 
    348 %macro cmpset_eqz(rd, ra)
    349     %ifelse_eqz(ra, { %li(rd, 1) }, { %li(rd, 0) })
    350 %endm
    351 
    352 %macro cmpset_nez(rd, ra)
    353     %ifelse_nez(ra, { %li(rd, 1) }, { %li(rd, 0) })
    354 %endm
    355 
    356 %macro cmpset_ltz(rd, ra)
    357     %ifelse_ltz(ra, { %li(rd, 1) }, { %li(rd, 0) })
    358 %endm
    359 
    360 # =========================================================================
    361 # Tiny unops
    362 # =========================================================================
    363 #
    364 # %neg(rd, ra, scratch)   rd = -ra        (scratch holds the zero literal)
    365 # %bnot(rd, ra, scratch)  rd = ~ra        (scratch holds the all-ones literal)
    366 # %bool(rd, ra)           rd = (ra != 0) ? 1 : 0   (alias of cmpset_nez)
    367 
    368 %macro neg(rd, ra, scratch)
    369     %li(scratch, 0)
    370     %sub(rd, scratch, ra)
    371 %endm
    372 
    373 %macro bnot(rd, ra, scratch)
    374     %li(scratch, -1)
    375     %xor(rd, ra, scratch)
    376 %endm
    377 
    378 %macro bool(rd, ra)
    379     %cmpset_nez(rd, ra)
    380 %endm
    381 
    382 # =========================================================================
    383 # Two-word 64-bit integer helpers for P1-32
    384 # =========================================================================
    385 #
    386 # cc.scm uses these only when `%p1_word_bits == 32`. Values are little-
    387 # endian register pairs `(lo, hi)`. The helpers stay in the portable P1pp
    388 # layer because they lower entirely through one-word P1 operations.
    389 
    390 %macro i64_neg(rlo, rhi, lo, hi, scratch)
    391     %li(scratch, 0)
    392     %sub(rlo, scratch, lo)
    393     %cmpset_eqz(scratch, rlo)
    394     %li(rhi, -1)
    395     %xor(rhi, hi, rhi)
    396     %add(rhi, rhi, scratch)
    397 %endm
    398 
    399 %macro i64_add(rlo, rhi, alo, ahi, blo, bhi, scratch)
    400     %add(rlo, alo, blo)
    401     %cmpset_ltu(scratch, rlo, alo)
    402     %add(rhi, ahi, bhi)
    403     %add(rhi, rhi, scratch)
    404 %endm
    405 
    406 %macro i64_sub(rlo, rhi, alo, ahi, blo, bhi, scratch)
    407     %cmpset_ltu(scratch, alo, blo)
    408     %sub(rlo, alo, blo)
    409     %sub(rhi, ahi, bhi)
    410     %sub(rhi, rhi, scratch)
    411 %endm
    412 
    413 # Low 64 bits of a two-limb product. The cross terms contribute directly to
    414 # the high limb; the high half of alo*blo is recovered with 16-bit pieces so
    415 # this needs no target-specific multiply-high instruction. Inputs are
    416 # clobbered and therefore must be distinct from outputs and scratch.
    417 %macro i64_mul(rlo, rhi, alo, ahi, blo, bhi, scratch)
    418     %mul(rhi, alo, bhi)
    419     %mul(scratch, ahi, blo)
    420     %add(rhi, rhi, scratch)
    421     %mul(rlo, alo, blo)
    422 
    423     %andi(ahi, alo, 65535)
    424     %shri(alo, alo, 16)
    425     %andi(bhi, blo, 65535)
    426     %shri(blo, blo, 16)
    427 
    428     %mul(scratch, ahi, bhi)
    429     %shri(scratch, scratch, 16)
    430     %mul(bhi, alo, bhi)
    431     %add(scratch, scratch, bhi)
    432     %andi(bhi, scratch, 65535)
    433     %shri(scratch, scratch, 16)
    434     %mul(ahi, ahi, blo)
    435     %add(bhi, bhi, ahi)
    436     %shri(bhi, bhi, 16)
    437     %mul(alo, alo, blo)
    438     %add(scratch, scratch, bhi)
    439     %add(scratch, scratch, alo)
    440     %add(rhi, rhi, scratch)
    441 %endm
    442 
    443 %macro i64_cmpset_eq(rd, alo, ahi, blo, bhi, scratch)
    444     %xor(scratch, ahi, bhi)
    445     %xor(rd, alo, blo)
    446     %or(rd, rd, scratch)
    447     %cmpset_eqz(rd, rd)
    448 %endm
    449 
    450 %macro i64_cmpset_ne(rd, alo, ahi, blo, bhi, scratch)
    451     %xor(scratch, ahi, bhi)
    452     %xor(rd, alo, blo)
    453     %or(rd, rd, scratch)
    454     %cmpset_nez(rd, rd)
    455 %endm
    456 
    457 %macro i64_cmpset_lt(rd, alo, ahi, blo, bhi, scratch)
    458     .scope
    459     %beq(ahi, bhi, &.low)
    460     %cmpset_lt(rd, ahi, bhi)
    461     %b(&.done)
    462     :.low
    463     %cmpset_ltu(rd, alo, blo)
    464     :.done
    465     .endscope
    466 %endm
    467 
    468 %macro i64_cmpset_ltu(rd, alo, ahi, blo, bhi, scratch)
    469     .scope
    470     %beq(ahi, bhi, &.low)
    471     %cmpset_ltu(rd, ahi, bhi)
    472     %b(&.done)
    473     :.low
    474     %cmpset_ltu(rd, alo, blo)
    475     :.done
    476     .endscope
    477 %endm
    478 
    479 %macro i64_cmpset_gt(rd, alo, ahi, blo, bhi, scratch)
    480     %i64_cmpset_lt(rd, blo, bhi, alo, ahi, scratch)
    481 %endm
    482 
    483 %macro i64_cmpset_gtu(rd, alo, ahi, blo, bhi, scratch)
    484     %i64_cmpset_ltu(rd, blo, bhi, alo, ahi, scratch)
    485 %endm
    486 
    487 %macro i64_cmpset_le(rd, alo, ahi, blo, bhi, scratch)
    488     %i64_cmpset_gt(rd, alo, ahi, blo, bhi, scratch)
    489     %cmpset_eqz(rd, rd)
    490 %endm
    491 
    492 %macro i64_cmpset_leu(rd, alo, ahi, blo, bhi, scratch)
    493     %i64_cmpset_gtu(rd, alo, ahi, blo, bhi, scratch)
    494     %cmpset_eqz(rd, rd)
    495 %endm
    496 
    497 %macro i64_cmpset_ge(rd, alo, ahi, blo, bhi, scratch)
    498     %i64_cmpset_lt(rd, alo, ahi, blo, bhi, scratch)
    499     %cmpset_eqz(rd, rd)
    500 %endm
    501 
    502 %macro i64_cmpset_geu(rd, alo, ahi, blo, bhi, scratch)
    503     %i64_cmpset_ltu(rd, alo, ahi, blo, bhi, scratch)
    504     %cmpset_eqz(rd, rd)
    505 %endm
    506 
    507 %macro i64_shl(rlo, rhi, lo, hi, count, scratch)
    508     .scope
    509     %beqz(count, &.zero)
    510     %li(scratch, 32)
    511     %bltu(count, scratch, &.small)
    512     %sub(scratch, count, scratch)
    513     %shl(rhi, lo, scratch)
    514     %li(rlo, 0)
    515     %b(&.done)
    516     :.small
    517     %sub(scratch, scratch, count)
    518     %shr(rlo, lo, scratch)
    519     %shl(rhi, hi, count)
    520     %or(rhi, rhi, rlo)
    521     %shl(rlo, lo, count)
    522     %b(&.done)
    523     :.zero
    524     %mov(rlo, lo)
    525     %mov(rhi, hi)
    526     :.done
    527     .endscope
    528 %endm
    529 
    530 %macro i64_shr(rlo, rhi, lo, hi, count, scratch)
    531     .scope
    532     %beqz(count, &.zero)
    533     %li(scratch, 32)
    534     %bltu(count, scratch, &.small)
    535     %sub(scratch, count, scratch)
    536     %shr(rlo, hi, scratch)
    537     %li(rhi, 0)
    538     %b(&.done)
    539     :.small
    540     %sub(scratch, scratch, count)
    541     %shl(rlo, hi, scratch)
    542     %shr(scratch, lo, count)
    543     %or(rlo, rlo, scratch)
    544     %shr(rhi, hi, count)
    545     %b(&.done)
    546     :.zero
    547     %mov(rlo, lo)
    548     %mov(rhi, hi)
    549     :.done
    550     .endscope
    551 %endm
    552 
    553 %macro i64_sar(rlo, rhi, lo, hi, count, scratch)
    554     .scope
    555     %beqz(count, &.zero)
    556     %li(scratch, 32)
    557     %bltu(count, scratch, &.small)
    558     %sub(scratch, count, scratch)
    559     %sar(rlo, hi, scratch)
    560     %sari(rhi, hi, 31)
    561     %b(&.done)
    562     :.small
    563     %sub(scratch, scratch, count)
    564     %shl(rlo, hi, scratch)
    565     %shr(scratch, lo, count)
    566     %or(rlo, rlo, scratch)
    567     %sar(rhi, hi, count)
    568     %b(&.done)
    569     :.zero
    570     %mov(rlo, lo)
    571     %mov(rhi, hi)
    572     :.done
    573     .endscope
    574 %endm
    575 
    576 # =========================================================================
    577 # Switch dispatch
    578 # =========================================================================
    579 #
    580 # %switch_case(ctrl, scratch, key, target)
    581 #   If `ctrl == key`, branch to `target`. `scratch` is used to
    582 #   materialize the key as a register operand. `target` is the full
    583 #   branch target (e.g. `&.case_3`).
    584 #
    585 # A code generator emitting a switch dispatcher emits one
    586 # %switch_case per case, then an unconditional branch to the default.
    587 
    588 %macro switch_case(ctrl, scratch, key, target)
    589     %li(scratch, key)
    590     %beq(ctrl, scratch, target)
    591 %endm
    592 
    593 # =========================================================================
    594 # Control-flow macros
    595 # =========================================================================
    596 #
    597 # Every conditional block macro uses a uniform three-branch lowering that
    598 # works for all seven P1 conditions (including LT, LTU, LTZ which have no
    599 # inverted branch): load a "take the body" target, branch on cc, then
    600 # unconditionally skip past the body.
    601 
    602 # ---- %if_<cc> -----------------------------------------------------------
    603 
    604 %macro if_eq(ra, rb, body)
    605     %beq(ra, rb, &@body)
    606     %b(&@end)
    607     :@body
    608     body
    609     :@end
    610 %endm
    611 
    612 %macro if_ne(ra, rb, body)
    613     %bne(ra, rb, &@body)
    614     %b(&@end)
    615     :@body
    616     body
    617     :@end
    618 %endm
    619 
    620 %macro if_lt(ra, rb, body)
    621     %blt(ra, rb, &@body)
    622     %b(&@end)
    623     :@body
    624     body
    625     :@end
    626 %endm
    627 
    628 %macro if_ltu(ra, rb, body)
    629     %bltu(ra, rb, &@body)
    630     %b(&@end)
    631     :@body
    632     body
    633     :@end
    634 %endm
    635 
    636 %macro if_eqz(ra, body)
    637     %beqz(ra, &@body)
    638     %b(&@end)
    639     :@body
    640     body
    641     :@end
    642 %endm
    643 
    644 %macro if_nez(ra, body)
    645     %bnez(ra, &@body)
    646     %b(&@end)
    647     :@body
    648     body
    649     :@end
    650 %endm
    651 
    652 %macro if_ltz(ra, body)
    653     %bltz(ra, &@body)
    654     %b(&@end)
    655     :@body
    656     body
    657     :@end
    658 %endm
    659 
    660 # ---- %ifelse_<cc> -------------------------------------------------------
    661 
    662 %macro ifelse_eq(ra, rb, tblk, fblk)
    663     %beq(ra, rb, &@tblk)
    664     fblk
    665     %b(&@end)
    666     :@tblk
    667     tblk
    668     :@end
    669 %endm
    670 
    671 %macro ifelse_ne(ra, rb, tblk, fblk)
    672     %bne(ra, rb, &@tblk)
    673     fblk
    674     %b(&@end)
    675     :@tblk
    676     tblk
    677     :@end
    678 %endm
    679 
    680 %macro ifelse_lt(ra, rb, tblk, fblk)
    681     %blt(ra, rb, &@tblk)
    682     fblk
    683     %b(&@end)
    684     :@tblk
    685     tblk
    686     :@end
    687 %endm
    688 
    689 %macro ifelse_ltu(ra, rb, tblk, fblk)
    690     %bltu(ra, rb, &@tblk)
    691     fblk
    692     %b(&@end)
    693     :@tblk
    694     tblk
    695     :@end
    696 %endm
    697 
    698 %macro ifelse_eqz(ra, tblk, fblk)
    699     %beqz(ra, &@tblk)
    700     fblk
    701     %b(&@end)
    702     :@tblk
    703     tblk
    704     :@end
    705 %endm
    706 
    707 %macro ifelse_nez(ra, tblk, fblk)
    708     %bnez(ra, &@tblk)
    709     fblk
    710     %b(&@end)
    711     :@tblk
    712     tblk
    713     :@end
    714 %endm
    715 
    716 %macro ifelse_ltz(ra, tblk, fblk)
    717     %bltz(ra, &@tblk)
    718     fblk
    719     %b(&@end)
    720     :@tblk
    721     tblk
    722     :@end
    723 %endm
    724 
    725 # ---- %while_<cc> -------------------------------------------------------
    726 #
    727 # Jump-to-test layout: the body runs iff the positive-sense test holds,
    728 # and the test is compiled below the body so we only emit a forward
    729 # branch at entry.
    730 
    731 %macro while_eq(ra, rb, body)
    732     %b(&@test)
    733     :@body
    734     body
    735     :@test
    736     %beq(ra, rb, &@body)
    737 %endm
    738 
    739 %macro while_ne(ra, rb, body)
    740     %b(&@test)
    741     :@body
    742     body
    743     :@test
    744     %bne(ra, rb, &@body)
    745 %endm
    746 
    747 %macro while_lt(ra, rb, body)
    748     %b(&@test)
    749     :@body
    750     body
    751     :@test
    752     %blt(ra, rb, &@body)
    753 %endm
    754 
    755 %macro while_ltu(ra, rb, body)
    756     %b(&@test)
    757     :@body
    758     body
    759     :@test
    760     %bltu(ra, rb, &@body)
    761 %endm
    762 
    763 %macro while_eqz(ra, body)
    764     %b(&@test)
    765     :@body
    766     body
    767     :@test
    768     %beqz(ra, &@body)
    769 %endm
    770 
    771 %macro while_nez(ra, body)
    772     %b(&@test)
    773     :@body
    774     body
    775     :@test
    776     %bnez(ra, &@body)
    777 %endm
    778 
    779 %macro while_ltz(ra, body)
    780     %b(&@test)
    781     :@body
    782     body
    783     :@test
    784     %bltz(ra, &@body)
    785 %endm
    786 
    787 # ---- %do_while_<cc> ----------------------------------------------------
    788 
    789 %macro do_while_eq(ra, rb, body)
    790     :@body
    791     body
    792     %beq(ra, rb, &@body)
    793 %endm
    794 
    795 %macro do_while_ne(ra, rb, body)
    796     :@body
    797     body
    798     %bne(ra, rb, &@body)
    799 %endm
    800 
    801 %macro do_while_lt(ra, rb, body)
    802     :@body
    803     body
    804     %blt(ra, rb, &@body)
    805 %endm
    806 
    807 %macro do_while_ltu(ra, rb, body)
    808     :@body
    809     body
    810     %bltu(ra, rb, &@body)
    811 %endm
    812 
    813 %macro do_while_eqz(ra, body)
    814     :@body
    815     body
    816     %beqz(ra, &@body)
    817 %endm
    818 
    819 %macro do_while_nez(ra, body)
    820     :@body
    821     body
    822     %bnez(ra, &@body)
    823 %endm
    824 
    825 %macro do_while_ltz(ra, body)
    826     :@body
    827     body
    828     %bltz(ra, &@body)
    829 %endm
    830 
    831 # ---- %for_lt ------------------------------------------------------------
    832 
    833 %macro for_lt(i_reg, n_reg, body)
    834     %li(i_reg, 0)
    835     %b(&@test)
    836     :@body
    837     body
    838     %addi(i_reg, i_reg, 1)
    839     :@test
    840     %blt(i_reg, n_reg, &@body)
    841 %endm
    842 
    843 # ---- %loop --------------------------------------------------------------
    844 
    845 %macro loop(body)
    846     :@top
    847     body
    848     %b(&@top)
    849 %endm
    850 
    851 # ---- Scoped loops -------------------------------------------------------
    852 #
    853 # Each scoped form opens a hex2++ `.scope` and defines two dotted labels
    854 # inside it: `.top` (where `%continue` should land) and `.end`
    855 # (immediately after the loop, where `%break` should land). The generic
    856 # `%break` and `%continue` macros below emit branches to `&.end` /
    857 # `&.top`; hex2++'s innermost-out scope walk binds those references to
    858 # the nearest enclosing scoped loop.
    859 #
    860 # Nested scoped loops shadow each other: a `%break` inside an inner loop
    861 # targets the inner loop's `.end`. Non-loop control-flow macros
    862 # (`%if_<cc>`, `%ifelse_<cc>`) do not open a `.scope`, so `%break` /
    863 # `%continue` inside them passes through to the enclosing scoped loop.
    864 
    865 %macro loop_scoped(body)
    866     .scope
    867     :.top
    868     body
    869     %b(&.top)
    870     :.end
    871     .endscope
    872 %endm
    873 
    874 %macro while_scoped_eq(ra, rb, body)
    875     .scope
    876     %b(&.top)
    877     :.body
    878     body
    879     :.top
    880     %beq(ra, rb, &.body)
    881     :.end
    882     .endscope
    883 %endm
    884 
    885 %macro while_scoped_ne(ra, rb, body)
    886     .scope
    887     %b(&.top)
    888     :.body
    889     body
    890     :.top
    891     %bne(ra, rb, &.body)
    892     :.end
    893     .endscope
    894 %endm
    895 
    896 %macro while_scoped_lt(ra, rb, body)
    897     .scope
    898     %b(&.top)
    899     :.body
    900     body
    901     :.top
    902     %blt(ra, rb, &.body)
    903     :.end
    904     .endscope
    905 %endm
    906 
    907 %macro while_scoped_ltu(ra, rb, body)
    908     .scope
    909     %b(&.top)
    910     :.body
    911     body
    912     :.top
    913     %bltu(ra, rb, &.body)
    914     :.end
    915     .endscope
    916 %endm
    917 
    918 %macro while_scoped_eqz(ra, body)
    919     .scope
    920     %b(&.top)
    921     :.body
    922     body
    923     :.top
    924     %beqz(ra, &.body)
    925     :.end
    926     .endscope
    927 %endm
    928 
    929 %macro while_scoped_nez(ra, body)
    930     .scope
    931     %b(&.top)
    932     :.body
    933     body
    934     :.top
    935     %bnez(ra, &.body)
    936     :.end
    937     .endscope
    938 %endm
    939 
    940 %macro while_scoped_ltz(ra, body)
    941     .scope
    942     %b(&.top)
    943     :.body
    944     body
    945     :.top
    946     %bltz(ra, &.body)
    947     :.end
    948     .endscope
    949 %endm
    950 
    951 %macro for_lt_scoped(i_reg, n_reg, body)
    952     .scope
    953     %li(i_reg, 0)
    954     %b(&.test)
    955     :.body
    956     body
    957     :.top
    958     %addi(i_reg, i_reg, 1)
    959     :.test
    960     %blt(i_reg, n_reg, &.body)
    961     :.end
    962     .endscope
    963 %endm
    964 
    965 %macro break()
    966     %b(&.end)
    967 %endm
    968 
    969 %macro continue()
    970     %b(&.top)
    971 %endm
    972 
    973 # =========================================================================
    974 # %fn -- scope-introducing function definition
    975 # =========================================================================
    976 #
    977 # Opens a hex2++ `.scope` around the body so dotted local labels (`:.foo`,
    978 # `&.foo`) are private to this function. The body is bracketed by
    979 # %enter(size) and %eret, so functions defined with %fn always carry a
    980 # standard frame.
    981 
    982 %macro fn(name, size, body)
    983     : ## name
    984     .scope
    985     %enter(size)
    986     body
    987     %eret
    988     .endscope
    989 %endm
    990 
    991 # =========================================================================
    992 # %fn2 -- function with named locals
    993 # =========================================================================
    994 #
    995 # Like %fn, but the second argument is a braced list of local names
    996 # instead of a byte frame size. Synthesizes a `name_FRAME` %struct
    997 # (one 8-byte slot per local), opens both a hex2++ `.scope` and an
    998 # m1pp `%frame` named after the function, and sizes the stack frame
    999 # from %name_FRAME.SIZE.
   1000 #
   1001 # Inside the body these helpers resolve against the enclosing frame:
   1002 #   %local(slot)     byte offset of local `slot`
   1003 #   %stl(reg, slot)  store reg into local `slot`
   1004 #   %ldl(reg, slot)  load local `slot` into reg
   1005 #
   1006 # m1pp tracks the active frame in a single slot independent of hex2++
   1007 # scope nesting, so %local / %stl / %ldl keep resolving against the
   1008 # function even when the body opens nested `.scope` blocks (e.g. from
   1009 # a scoped control-flow macro).
   1010 #
   1011 # Locals follow the same braces convention as `body`: a multi-local
   1012 # list must be braced (`{a, b, c}`); a zero-local function uses `{}`.
   1013 
   1014 %macro fn2(name, locals, body)
   1015     %struct name ## _FRAME { locals }
   1016     : ## name
   1017     .scope
   1018     %frame name
   1019     %enter(% ## name ## _FRAME.SIZE)
   1020     body
   1021     %eret
   1022     %endframe
   1023     .endscope
   1024 %endm
   1025 
   1026 %macro stl(reg, slot) %st(reg, sp, %local(slot)) %endm
   1027 %macro ldl(reg, slot) %ld(reg, sp, %local(slot)) %endm
   1028 
   1029 # =========================================================================
   1030 # RV32 64-bit division helpers
   1031 # =========================================================================
   1032 #
   1033 # Both helpers use the P1 two-word direct-result convention for the quotient
   1034 # (a0=lo, a1=hi) and additionally return the remainder in a2/a3. They are
   1035 # emitted on every target but called only by cc.scm's P1-32 lowering.
   1036 
   1037 %fn(p1_i64_udivmod, (* 4 %p1_word_bytes), {
   1038     %st(s0, sp, 0)
   1039     %st(s1, sp, (* 1 %p1_word_bytes))
   1040     %st(s2, sp, (* 2 %p1_word_bytes))
   1041     %st(s3, sp, (* 3 %p1_word_bytes))
   1042 
   1043     %mov(s0, a2)
   1044     %mov(s1, a3)
   1045     %li(s2, 0)
   1046     %li(s3, 0)
   1047     %li(a2, 64)
   1048 
   1049     :.loop
   1050     %beqz(a2, &.done)
   1051 
   1052     # Shift the combined (remainder:quotient) 128-bit state left once.
   1053     %shri(t0, a1, 31)
   1054     %shri(t1, a0, 31)
   1055     %shli(a1, a1, 1)
   1056     %or(a1, a1, t1)
   1057     %shli(a0, a0, 1)
   1058     %shri(t1, s2, 31)
   1059     %shli(s3, s3, 1)
   1060     %or(s3, s3, t1)
   1061     %shli(s2, s2, 1)
   1062     %or(s2, s2, t0)
   1063 
   1064     # If remainder >= denominator, subtract it and set quotient bit 0.
   1065     %bltu(s3, s1, &.skip_sub)
   1066     %bltu(s1, s3, &.subtract)
   1067     %bltu(s2, s0, &.skip_sub)
   1068     :.subtract
   1069     %i64_sub(s2, s3, s2, s3, s0, s1, t0)
   1070     %ori(a0, a0, 1)
   1071     :.skip_sub
   1072 
   1073     %addi(a2, a2, -1)
   1074     %b(&.loop)
   1075 
   1076     :.done
   1077     %mov(a2, s2)
   1078     %mov(a3, s3)
   1079     %ld(s0, sp, 0)
   1080     %ld(s1, sp, (* 1 %p1_word_bytes))
   1081     %ld(s2, sp, (* 2 %p1_word_bytes))
   1082     %ld(s3, sp, (* 3 %p1_word_bytes))
   1083 })
   1084 
   1085 %fn(p1_i64_divmod, (* 2 %p1_word_bytes), {
   1086     %st(s0, sp, 0)
   1087     %st(s1, sp, (* 1 %p1_word_bytes))
   1088     %sari(s0, a1, 31)
   1089     %sari(s1, a3, 31)
   1090 
   1091     %if_ltz(s0, {
   1092         %i64_neg(t0, t1, a0, a1, t2)
   1093         %mov(a0, t0)
   1094         %mov(a1, t1)
   1095     })
   1096     %if_ltz(s1, {
   1097         %i64_neg(t0, t1, a2, a3, t2)
   1098         %mov(a2, t0)
   1099         %mov(a3, t1)
   1100     })
   1101 
   1102     %call(&p1_i64_udivmod)
   1103 
   1104     %xor(t0, s0, s1)
   1105     %if_ltz(t0, {
   1106         %i64_neg(t0, t1, a0, a1, t2)
   1107         %mov(a0, t0)
   1108         %mov(a1, t1)
   1109     })
   1110     %if_ltz(s0, {
   1111         %i64_neg(t0, t1, a2, a3, t2)
   1112         %mov(a2, t0)
   1113         %mov(a3, t1)
   1114     })
   1115 
   1116     %ld(s0, sp, 0)
   1117     %ld(s1, sp, (* 1 %p1_word_bytes))
   1118 })
   1119 
   1120 # =========================================================================
   1121 # %assert_<cc> macros
   1122 # =========================================================================
   1123 #
   1124 # Branch past the panic call when the condition holds; otherwise fall
   1125 # through to `LA a0, msg; LA_BR &panic; CALL`. Each assert requires the
   1126 # enclosing function to have an established frame.
   1127 
   1128 %macro assert_eq(ra, rb, msg)
   1129     %beq(ra, rb, &@done)
   1130     %la(a0, & ## msg)
   1131     %call(&panic)
   1132     :@done
   1133 %endm
   1134 
   1135 %macro assert_ne(ra, rb, msg)
   1136     %bne(ra, rb, &@done)
   1137     %la(a0, & ## msg)
   1138     %call(&panic)
   1139     :@done
   1140 %endm
   1141 
   1142 %macro assert_lt(ra, rb, msg)
   1143     %blt(ra, rb, &@done)
   1144     %la(a0, & ## msg)
   1145     %call(&panic)
   1146     :@done
   1147 %endm
   1148 
   1149 %macro assert_ltu(ra, rb, msg)
   1150     %bltu(ra, rb, &@done)
   1151     %la(a0, & ## msg)
   1152     %call(&panic)
   1153     :@done
   1154 %endm
   1155 
   1156 %macro assert_eqz(ra, msg)
   1157     %beqz(ra, &@done)
   1158     %la(a0, & ## msg)
   1159     %call(&panic)
   1160     :@done
   1161 %endm
   1162 
   1163 %macro assert_nez(ra, msg)
   1164     %bnez(ra, &@done)
   1165     %la(a0, & ## msg)
   1166     %call(&panic)
   1167     :@done
   1168 %endm
   1169 
   1170 %macro assert_ltz(ra, msg)
   1171     %bltz(ra, &@done)
   1172     %la(a0, & ## msg)
   1173     %call(&panic)
   1174     :@done
   1175 %endm
   1176 
   1177 # =========================================================================
   1178 # Memory and strings
   1179 # =========================================================================
   1180 
   1181 # memcpy(dst=a0, src=a1, n=a2) -> dst (a0)
   1182 # Leaf. Copies n bytes from src to dst. No overlap support where
   1183 # dst > src && dst < src + n; use memmove for that case. These mem*
   1184 # entries are the canonical compiler-builtin runtime — every build
   1185 # process in this tree (cc.scm + libp1pp + libc, tcc-cc, tcc-gcc)
   1186 # resolves bare `extern memcpy` against this implementation. The
   1187 # vendored mes-libc is flattened with its own memcpy/memmove/memset/
   1188 # memcmp omitted so the symbols are not duplicated at hex2++ time.
   1189 :memcpy
   1190 .scope
   1191     %mov(a3, a0)
   1192     %li(t0, 0)
   1193     :.loop
   1194     %beq(t0, a2, &.done)
   1195     %add(t1, a1, t0)
   1196     %lb(t1, t1, 0)
   1197     %add(t2, a3, t0)
   1198     %sb(t1, t2, 0)
   1199     %addi(t0, t0, 1)
   1200     %b(&.loop)
   1201     :.done
   1202     %mov(a0, a3)
   1203     %ret
   1204 .endscope
   1205 
   1206 # memmove(dst=a0, src=a1, n=a2) -> dst (a0)
   1207 # Leaf. Like memcpy but tolerates overlap by picking the safe direction.
   1208 :memmove
   1209 .scope
   1210     %mov(a3, a0)
   1211     %beq(a0, a1, &.done)
   1212     %beqz(a2, &.done)
   1213     %bltu(a0, a1, &.fwd)
   1214     # dst > src: copy from the high end down so an overlap that would
   1215     # clobber a yet-unread src byte is harmless.
   1216     %mov(t0, a2)
   1217     :.bwd_loop
   1218     %addi(t0, t0, -1)
   1219     %add(t1, a1, t0)
   1220     %lb(t1, t1, 0)
   1221     %add(t2, a3, t0)
   1222     %sb(t1, t2, 0)
   1223     %bnez(t0, &.bwd_loop)
   1224     %b(&.done)
   1225     :.fwd
   1226     # dst < src: forward copy is safe.
   1227     %li(t0, 0)
   1228     :.fwd_loop
   1229     %beq(t0, a2, &.done)
   1230     %add(t1, a1, t0)
   1231     %lb(t1, t1, 0)
   1232     %add(t2, a3, t0)
   1233     %sb(t1, t2, 0)
   1234     %addi(t0, t0, 1)
   1235     %b(&.fwd_loop)
   1236     :.done
   1237     %mov(a0, a3)
   1238     %ret
   1239 .endscope
   1240 
   1241 # memset(dst=a0, byte=a1, n=a2) -> dst (a0)
   1242 :memset
   1243 .scope
   1244     %mov(a3, a0)
   1245     %li(t0, 0)
   1246     :.loop
   1247     %beq(t0, a2, &.done)
   1248     %add(t1, a3, t0)
   1249     %sb(a1, t1, 0)
   1250     %addi(t0, t0, 1)
   1251     %b(&.loop)
   1252     :.done
   1253     %mov(a0, a3)
   1254     %ret
   1255 .endscope
   1256 
   1257 # memcmp(a=a0, b=a1, n=a2) -> -1/0/1 (a0)
   1258 :memcmp
   1259 .scope
   1260     %li(t0, 0)
   1261     :.loop
   1262     %beq(t0, a2, &.eq)
   1263     %add(t1, a0, t0)
   1264     %lb(t1, t1, 0)
   1265     %add(t2, a1, t0)
   1266     %lb(t2, t2, 0)
   1267     %bltu(t1, t2, &.lt)
   1268     %bltu(t2, t1, &.gt)
   1269     %addi(t0, t0, 1)
   1270     %b(&.loop)
   1271     :.lt
   1272     %li(a0, -1)
   1273     %ret
   1274     :.gt
   1275     %li(a0, 1)
   1276     %ret
   1277     :.eq
   1278     %li(a0, 0)
   1279     %ret
   1280 .endscope
   1281 
   1282 # libp1pp__strlen(cstr=a0) -> n (a0)
   1283 :libp1pp__strlen
   1284 .scope
   1285     %mov(a1, a0)
   1286     :.loop
   1287     %lb(t0, a1, 0)
   1288     %beqz(t0, &.done)
   1289     %addi(a1, a1, 1)
   1290     %b(&.loop)
   1291     :.done
   1292     %sub(a0, a1, a0)
   1293     %ret
   1294 .endscope
   1295 
   1296 # libp1pp__streq(a=a0, b=a1) -> 0 or 1
   1297 :libp1pp__streq
   1298 .scope
   1299     :.loop
   1300     %lb(t0, a0, 0)
   1301     %lb(t1, a1, 0)
   1302     %bne(t0, t1, &.ne)
   1303     %beqz(t0, &.eq)
   1304     %addi(a0, a0, 1)
   1305     %addi(a1, a1, 1)
   1306     %b(&.loop)
   1307     :.ne
   1308     %li(a0, 0)
   1309     %ret
   1310     :.eq
   1311     %li(a0, 1)
   1312     %ret
   1313 .endscope
   1314 
   1315 # libp1pp__strcmp(a=a0, b=a1) -> -1/0/1
   1316 :libp1pp__strcmp
   1317 .scope
   1318     :.loop
   1319     %lb(t0, a0, 0)
   1320     %lb(t1, a1, 0)
   1321     %bltu(t0, t1, &.lt)
   1322     %bltu(t1, t0, &.gt)
   1323     %beqz(t0, &.eq)
   1324     %addi(a0, a0, 1)
   1325     %addi(a1, a1, 1)
   1326     %b(&.loop)
   1327     :.lt
   1328     %li(a0, -1)
   1329     %ret
   1330     :.gt
   1331     %li(a0, 1)
   1332     %ret
   1333     :.eq
   1334     %li(a0, 0)
   1335     %ret
   1336 .endscope
   1337 
   1338 # =========================================================================
   1339 # Integer parsing and formatting
   1340 # =========================================================================
   1341 
   1342 # parse_dec(buf=a0, len=a1) -> (value=a0, consumed=a1)
   1343 # Uses an 8-byte frame slot to save buf_start; all hot-loop state lives
   1344 # in caller-saved registers.
   1345 :parse_dec
   1346 .scope
   1347     %enter(8)
   1348     %st(a0, sp, 0)
   1349     %add(a3, a0, a1)
   1350     %mov(a2, a0)
   1351     %li(t0, 0)
   1352     %li(t1, 0)
   1353 
   1354     %beq(a2, a3, &.after_sign)
   1355     %lb(t2, a2, 0)
   1356     %addi(t2, t2, -45)
   1357     %bnez(t2, &.after_sign)
   1358     %li(t0, 1)
   1359     %addi(a2, a2, 1)
   1360 
   1361     :.after_sign
   1362     %mov(a1, a2)
   1363 
   1364     :.digit_loop
   1365     %beq(a2, a3, &.digits_done)
   1366     %lb(t2, a2, 0)
   1367     %addi(t2, t2, -48)
   1368     %bltz(t2, &.digits_done)
   1369     %li(a0, 9)
   1370     %bltu(a0, t2, &.digits_done)
   1371     %li(a0, 10)
   1372     %mul(t1, t1, a0)
   1373     %add(t1, t1, t2)
   1374     %addi(a2, a2, 1)
   1375     %b(&.digit_loop)
   1376 
   1377     :.digits_done
   1378     %beq(a2, a1, &.no_digits)
   1379 
   1380     %bnez(t0, &.apply_sign)
   1381     %b(&.compute_return)
   1382     :.apply_sign
   1383     %li(a0, 0)
   1384     %sub(t1, a0, t1)
   1385 
   1386     :.compute_return
   1387     %ld(a0, sp, 0)
   1388     %sub(a1, a2, a0)
   1389     %mov(a0, t1)
   1390     %eret
   1391 
   1392     :.no_digits
   1393     %li(a0, 0)
   1394     %li(a1, 0)
   1395     %eret
   1396 .endscope
   1397 
   1398 # parse_hex(buf=a0, len=a1) -> (value=a0, consumed=a1)
   1399 :parse_hex
   1400 .scope
   1401     %enter(8)
   1402     %st(a0, sp, 0)
   1403     %add(a3, a0, a1)
   1404     %mov(a2, a0)
   1405     %li(t1, 0)
   1406     %mov(a1, a2)
   1407 
   1408     :.loop
   1409     %beq(a2, a3, &.done)
   1410     %lb(t2, a2, 0)
   1411 
   1412     %addi(t0, t2, -48)
   1413     %bltz(t0, &.check_lower)
   1414     %li(a0, 9)
   1415     %bltu(a0, t0, &.check_lower)
   1416     %b(&.accept)
   1417 
   1418     :.check_lower
   1419     %addi(t0, t2, -97)
   1420     %bltz(t0, &.check_upper)
   1421     %li(a0, 5)
   1422     %bltu(a0, t0, &.check_upper)
   1423     %addi(t0, t0, 10)
   1424     %b(&.accept)
   1425 
   1426     :.check_upper
   1427     %addi(t0, t2, -65)
   1428     %bltz(t0, &.done)
   1429     %li(a0, 5)
   1430     %bltu(a0, t0, &.done)
   1431     %addi(t0, t0, 10)
   1432 
   1433     :.accept
   1434     %shli(t1, t1, 4)
   1435     %or(t1, t1, t0)
   1436     %addi(a2, a2, 1)
   1437     %b(&.loop)
   1438 
   1439     :.done
   1440     %beq(a2, a1, &.no_digits)
   1441     %ld(a0, sp, 0)
   1442     %sub(a1, a2, a0)
   1443     %mov(a0, t1)
   1444     %eret
   1445 
   1446     :.no_digits
   1447     %li(a0, 0)
   1448     %li(a1, 0)
   1449     %eret
   1450 .endscope
   1451 
   1452 # fmt_dec(buf=a0, value=a1) -> n_bytes (a0)
   1453 #
   1454 # Unified signed formatting: digits are written from the per-iteration
   1455 # `value % 10`, negated when value is negative. This avoids the
   1456 # INT_MIN-overflow trap that `value = -value` would hit.
   1457 :fmt_dec
   1458 .scope
   1459     %enter(8)
   1460     %st(a0, sp, 0)
   1461 
   1462     %bltz(a1, &.is_neg)
   1463     %b(&.count)
   1464     :.is_neg
   1465     %li(t0, 45)
   1466     %sb(t0, a0, 0)
   1467     %addi(a0, a0, 1)
   1468 
   1469     :.count
   1470     %mov(t0, a1)
   1471     %li(a2, 1)
   1472     %li(t1, 10)
   1473     :.count_loop
   1474     %div(t0, t0, t1)
   1475     %beqz(t0, &.count_done)
   1476     %addi(a2, a2, 1)
   1477     %b(&.count_loop)
   1478     :.count_done
   1479 
   1480     %add(a3, a0, a2)
   1481 
   1482     :.dig_loop
   1483     %addi(a3, a3, -1)
   1484     %rem(t0, a1, t1)
   1485     %bltz(t0, &.neg_digit)
   1486     %b(&.write_digit)
   1487     :.neg_digit
   1488     %li(t2, 0)
   1489     %sub(t0, t2, t0)
   1490     :.write_digit
   1491     %addi(t0, t0, 48)
   1492     %sb(t0, a3, 0)
   1493     %div(a1, a1, t1)
   1494     %bnez(a1, &.dig_loop)
   1495 
   1496     %ld(t2, sp, 0)
   1497     %add(a0, a0, a2)
   1498     %sub(a0, a0, t2)
   1499     %eret
   1500 .endscope
   1501 
   1502 # fmt_hex(buf=a0, value=a1) -> n_bytes (a0)
   1503 :fmt_hex
   1504 .scope
   1505     %enter(8)
   1506     %st(a0, sp, 0)
   1507 
   1508     %bnez(a1, &.nonzero)
   1509     %li(t0, 48)
   1510     %sb(t0, a0, 0)
   1511     %li(a0, 1)
   1512     %eret
   1513 
   1514     :.nonzero
   1515     %mov(t0, a1)
   1516     %li(a2, 0)
   1517     :.count_loop
   1518     %addi(a2, a2, 1)
   1519     %shri(t0, t0, 4)
   1520     %bnez(t0, &.count_loop)
   1521 
   1522     %add(a3, a0, a2)
   1523 
   1524     :.dig_loop
   1525     %addi(a3, a3, -1)
   1526     %andi(t0, a1, 15)
   1527     %li(t1, 10)
   1528     %bltu(t0, t1, &.is_letter)
   1529     %addi(t0, t0, -10)
   1530     %addi(t0, t0, 97)
   1531     %b(&.write_digit)
   1532     :.is_letter
   1533     %addi(t0, t0, 48)
   1534     :.write_digit
   1535     %sb(t0, a3, 0)
   1536     %shri(a1, a1, 4)
   1537     %bnez(a1, &.dig_loop)
   1538 
   1539     %ld(t2, sp, 0)
   1540     %add(a0, a0, a2)
   1541     %sub(a0, a0, t2)
   1542     %eret
   1543 .endscope
   1544 
   1545 # =========================================================================
   1546 # Character predicates
   1547 # =========================================================================
   1548 
   1549 # is_digit(c=a0) -> 0 or 1
   1550 :is_digit
   1551 .scope
   1552     %addi(t0, a0, -48)
   1553     %li(t1, 10)
   1554     %li(a0, 1)
   1555     %bltu(t0, t1, &.done)
   1556     %li(a0, 0)
   1557     :.done
   1558     %ret
   1559 .endscope
   1560 
   1561 # is_hex_digit(c=a0) -> 0 or 1
   1562 :is_hex_digit
   1563 .scope
   1564     %li(t2, 1)
   1565     %addi(t0, a0, -48)
   1566     %li(t1, 10)
   1567     %bltu(t0, t1, &.done)
   1568     %addi(t0, a0, -97)
   1569     %li(t1, 6)
   1570     %bltu(t0, t1, &.done)
   1571     %addi(t0, a0, -65)
   1572     %bltu(t0, t1, &.done)
   1573     %li(t2, 0)
   1574     :.done
   1575     %mov(a0, t2)
   1576     %ret
   1577 .endscope
   1578 
   1579 # is_space(c=a0) -> 0 or 1
   1580 :is_space
   1581 .scope
   1582     %li(t2, 1)
   1583     %addi(t0, a0, -32)
   1584     %beqz(t0, &.done)
   1585     %addi(t0, a0, -9)
   1586     %li(t1, 5)
   1587     %bltu(t0, t1, &.done)
   1588     %li(t2, 0)
   1589     :.done
   1590     %mov(a0, t2)
   1591     %ret
   1592 .endscope
   1593 
   1594 # is_alpha(c=a0) -> 0 or 1
   1595 :is_alpha
   1596 .scope
   1597     %li(t2, 1)
   1598     %addi(t0, a0, -97)
   1599     %li(t1, 26)
   1600     %bltu(t0, t1, &.done)
   1601     %addi(t0, a0, -65)
   1602     %bltu(t0, t1, &.done)
   1603     %li(t2, 0)
   1604     :.done
   1605     %mov(a0, t2)
   1606     %ret
   1607 .endscope
   1608 
   1609 # is_alnum(c=a0) -> 0 or 1
   1610 :is_alnum
   1611 .scope
   1612     %li(t2, 1)
   1613     %addi(t0, a0, -48)
   1614     %li(t1, 10)
   1615     %bltu(t0, t1, &.done)
   1616     %addi(t0, a0, -97)
   1617     %li(t1, 26)
   1618     %bltu(t0, t1, &.done)
   1619     %addi(t0, a0, -65)
   1620     %bltu(t0, t1, &.done)
   1621     %li(t2, 0)
   1622     :.done
   1623     %mov(a0, t2)
   1624     %ret
   1625 .endscope
   1626 
   1627 # =========================================================================
   1628 # Raw syscall wrappers
   1629 # =========================================================================
   1630 #
   1631 # Each wrapper shifts arguments into the syscall convention
   1632 # (a0 = number, a1..a3/t0/s0/s1 = args 0..5), emits SYSCALL, and returns
   1633 # the raw kernel result. Syscall clobbers only a0, so t0/s0/s1 do not
   1634 # need saving.
   1635 
   1636 # sys_read(fd=a0, buf=a1, len=a2) -> n (a0)
   1637 :sys_read
   1638     %mov(a3, a2)
   1639     %mov(a2, a1)
   1640     %mov(a1, a0)
   1641     %li(a0, %p1_sys_read)
   1642     %syscall
   1643     %ret
   1644 
   1645 # sys_write(fd=a0, buf=a1, len=a2) -> n (a0)
   1646 :sys_write
   1647     %mov(a3, a2)
   1648     %mov(a2, a1)
   1649     %mov(a1, a0)
   1650     %li(a0, %p1_sys_write)
   1651     %syscall
   1652     %ret
   1653 
   1654 # sys_open(path=a0, flags=a1, mode=a2) -> fd (a0)
   1655 # Implemented as openat(AT_FDCWD, path, flags, mode). AT_FDCWD = -100.
   1656 :sys_open
   1657     %mov(t0, a2)
   1658     %mov(a3, a1)
   1659     %mov(a2, a0)
   1660     %li(a1, -100)
   1661     %li(a0, %p1_sys_openat)
   1662     %syscall
   1663     %ret
   1664 
   1665 # sys_close(fd=a0) -> r (a0)
   1666 :sys_close
   1667     %mov(a1, a0)
   1668     %li(a0, %p1_sys_close)
   1669     %syscall
   1670     %ret
   1671 
   1672 # sys_lseek(fd=a0, off=a1, whence=a2) -> off (a0)
   1673 :sys_lseek
   1674     %p1_sys_lseek_wrapper
   1675 
   1676 # sys_brk(addr=a0) -> new_break (a0). addr=0 returns the current break.
   1677 :sys_brk
   1678     %mov(a1, a0)
   1679     %li(a0, %p1_sys_brk)
   1680     %syscall
   1681     %ret
   1682 
   1683 # sys_unlink(path=a0) -> 0 / -errno (a0).
   1684 # Implemented as unlinkat(AT_FDCWD, path, 0). AT_FDCWD = -100.
   1685 :sys_unlink
   1686     %li(a3, 0)
   1687     %mov(a2, a0)
   1688     %li(a1, -100)
   1689     %li(a0, %p1_sys_unlinkat)
   1690     %syscall
   1691     %ret
   1692 
   1693 # sys_exit(code=a0) -> never returns
   1694 :sys_exit
   1695 .scope
   1696     %mov(a1, a0)
   1697     %li(a0, %p1_sys_exit)
   1698     %syscall
   1699     :.spin
   1700     %b(&.spin)
   1701 .endscope
   1702 
   1703 # =========================================================================
   1704 # Print helpers
   1705 # =========================================================================
   1706 #
   1707 # print(buf, len) and eprint(buf, len) loop on sys_write until all bytes
   1708 # are written or the kernel reports an error. All other print helpers
   1709 # compose on top of those two.
   1710 
   1711 %fn(print, 16, {
   1712     %st(s0, sp, 0)
   1713     %st(s1, sp, 8)
   1714     %mov(s0, a0)
   1715     %mov(s1, a1)
   1716 
   1717     :.loop
   1718     %beqz(s1, &.done_ok)
   1719     %li(a0, 1)
   1720     %mov(a1, s0)
   1721     %mov(a2, s1)
   1722     %call(&sys_write)
   1723     %bltz(a0, &.done)
   1724     %add(s0, s0, a0)
   1725     %sub(s1, s1, a0)
   1726     %b(&.loop)
   1727 
   1728     :.done_ok
   1729     %li(a0, 0)
   1730     :.done
   1731     %ld(s0, sp, 0)
   1732     %ld(s1, sp, 8)
   1733 })
   1734 
   1735 %fn(eprint, 16, {
   1736     %st(s0, sp, 0)
   1737     %st(s1, sp, 8)
   1738     %mov(s0, a0)
   1739     %mov(s1, a1)
   1740 
   1741     :.loop
   1742     %beqz(s1, &.done_ok)
   1743     %li(a0, 2)
   1744     %mov(a1, s0)
   1745     %mov(a2, s1)
   1746     %call(&sys_write)
   1747     %bltz(a0, &.done)
   1748     %add(s0, s0, a0)
   1749     %sub(s1, s1, a0)
   1750     %b(&.loop)
   1751 
   1752     :.done_ok
   1753     %li(a0, 0)
   1754     :.done
   1755     %ld(s0, sp, 0)
   1756     %ld(s1, sp, 8)
   1757 })
   1758 
   1759 %fn(println, 16, {
   1760     %st(s0, sp, 0)
   1761 
   1762     %call(&print)
   1763     %mov(s0, a0)
   1764     %bltz(s0, &.done)
   1765 
   1766     %la(a0, &libp1pp__newline)
   1767     %li(a1, 1)
   1768     %call(&print)
   1769     %mov(s0, a0)
   1770 
   1771     :.done
   1772     %mov(a0, s0)
   1773     %ld(s0, sp, 0)
   1774 })
   1775 
   1776 %fn(eprintln, 16, {
   1777     %st(s0, sp, 0)
   1778 
   1779     %call(&eprint)
   1780     %mov(s0, a0)
   1781     %bltz(s0, &.done)
   1782 
   1783     %la(a0, &libp1pp__newline)
   1784     %li(a1, 1)
   1785     %call(&eprint)
   1786     %mov(s0, a0)
   1787 
   1788     :.done
   1789     %mov(a0, s0)
   1790     %ld(s0, sp, 0)
   1791 })
   1792 
   1793 %fn(print_cstr, 16, {
   1794     %st(s0, sp, 0)
   1795     %mov(s0, a0)
   1796     %call(&libp1pp__strlen)
   1797     %mov(a1, a0)
   1798     %mov(a0, s0)
   1799     %call(&print)
   1800     %ld(s0, sp, 0)
   1801 })
   1802 
   1803 %fn(eprint_cstr, 16, {
   1804     %st(s0, sp, 0)
   1805     %mov(s0, a0)
   1806     %call(&libp1pp__strlen)
   1807     %mov(a1, a0)
   1808     %mov(a0, s0)
   1809     %call(&eprint)
   1810     %ld(s0, sp, 0)
   1811 })
   1812 
   1813 %fn(print_int, 0, {
   1814     %mov(a1, a0)
   1815     %la(a0, &libp1pp__num_buf)
   1816     %call(&fmt_dec)
   1817     %mov(a1, a0)
   1818     %la(a0, &libp1pp__num_buf)
   1819     %call(&print)
   1820 })
   1821 
   1822 %fn(print_hex, 0, {
   1823     %mov(a1, a0)
   1824     %la(a0, &libp1pp__num_buf)
   1825     %call(&fmt_hex)
   1826     %mov(a1, a0)
   1827     %la(a0, &libp1pp__num_buf)
   1828     %call(&print)
   1829 })
   1830 
   1831 # =========================================================================
   1832 # File helpers
   1833 # =========================================================================
   1834 
   1835 # read_file(path=a0, buf=a1, cap=a2) -> n or -1
   1836 %fn(read_file, 32, {
   1837     %st(s0, sp, 0)
   1838     %st(s1, sp, 8)
   1839     %st(s2, sp, 16)
   1840     %st(s3, sp, 24)
   1841 
   1842     %mov(s1, a1)
   1843     %mov(s2, a2)
   1844 
   1845     %li(a1, 0)
   1846     %li(a2, 0)
   1847     %call(&sys_open)
   1848     %bltz(a0, &.open_fail)
   1849     %mov(s3, a0)
   1850 
   1851     %mov(a0, s3)
   1852     %mov(a1, s1)
   1853     %mov(a2, s2)
   1854     %call(&sys_read)
   1855     %mov(s0, a0)
   1856 
   1857     %mov(a0, s3)
   1858     %call(&sys_close)
   1859 
   1860     %mov(a0, s0)
   1861     %bltz(a0, &.read_fail)
   1862     %b(&.done)
   1863 
   1864     :.read_fail
   1865     %li(a0, -1)
   1866     %b(&.done)
   1867 
   1868     :.open_fail
   1869     %li(a0, -1)
   1870 
   1871     :.done
   1872     %ld(s0, sp, 0)
   1873     %ld(s1, sp, 8)
   1874     %ld(s2, sp, 16)
   1875     %ld(s3, sp, 24)
   1876 })
   1877 
   1878 # libp1pp__write_all(fd=a0, buf=a1, len=a2) -> 0 or <0 on error
   1879 #
   1880 # Loop on sys_write until all bytes are written. Used by print / eprint
   1881 # / write_file. Retries partial writes but returns the first negative
   1882 # kernel return unchanged.
   1883 %fn(libp1pp__write_all, 24, {
   1884     %st(s0, sp, 0)
   1885     %st(s1, sp, 8)
   1886     %st(s2, sp, 16)
   1887 
   1888     %mov(s0, a0)
   1889     %mov(s1, a1)
   1890     %mov(s2, a2)
   1891 
   1892     :.loop
   1893     %beqz(s2, &.done_ok)
   1894     %mov(a0, s0)
   1895     %mov(a1, s1)
   1896     %mov(a2, s2)
   1897     %call(&sys_write)
   1898     %bltz(a0, &.done)
   1899     %add(s1, s1, a0)
   1900     %sub(s2, s2, a0)
   1901     %b(&.loop)
   1902 
   1903     :.done_ok
   1904     %li(a0, 0)
   1905     :.done
   1906     %ld(s0, sp, 0)
   1907     %ld(s1, sp, 8)
   1908     %ld(s2, sp, 16)
   1909 })
   1910 
   1911 # write_file(path=a0, buf=a1, len=a2) -> 0 or -1
   1912 #
   1913 # Flags: O_WRONLY|O_CREAT|O_TRUNC. On Linux these are 0x1 | 0x40 |
   1914 # 0x200 = 0x241. Mode 0644 octal = 0x1A4.
   1915 %fn(write_file, 24, {
   1916     %st(s0, sp, 0)
   1917     %st(s1, sp, 8)
   1918     %st(s2, sp, 16)
   1919 
   1920     %mov(s0, a1)
   1921     %mov(s1, a2)
   1922 
   1923     %li(a1, 0x241)
   1924     %li(a2, 0x1A4)
   1925     %call(&sys_open)
   1926     %bltz(a0, &.open_fail)
   1927     %mov(s2, a0)
   1928 
   1929     %mov(a0, s2)
   1930     %mov(a1, s0)
   1931     %mov(a2, s1)
   1932     %call(&libp1pp__write_all)
   1933 
   1934     %mov(s0, a0)
   1935     %mov(a0, s2)
   1936     %call(&sys_close)
   1937 
   1938     %mov(a0, s0)
   1939     %bltz(a0, &.fail_ret)
   1940     %li(a0, 0)
   1941     %b(&.done)
   1942 
   1943     :.fail_ret
   1944     %li(a0, -1)
   1945     %b(&.done)
   1946 
   1947     :.open_fail
   1948     %li(a0, -1)
   1949 
   1950     :.done
   1951     %ld(s0, sp, 0)
   1952     %ld(s1, sp, 8)
   1953     %ld(s2, sp, 16)
   1954 })
   1955 
   1956 # =========================================================================
   1957 # BSS arena pointer-init table
   1958 # =========================================================================
   1959 #
   1960 # Pattern: a program reserves a stretch of memory past :ELF_end (or any
   1961 # base) and wants to carve it into N fixed-size arenas, each anchored
   1962 # by a pointer slot in the data section. The table emits one
   1963 # (slot, size) row per arena via %arena_entry; init_arenas walks the
   1964 # table once at startup and writes base + sum of prior sizes into each
   1965 # slot, so arena[k] starts where arena[k-1] ended.
   1966 
   1967 # %arena_entry(slot, size) -- one 16-byte row: 4-byte label ref + 4
   1968 # bytes zero pad + 8-byte size. `slot` is passed as a label ref (`&foo`).
   1969 %macro arena_entry(slot, size) slot %(0) $(size) %endm
   1970 
   1971 # init_arenas(base=a0, tbl=a1, tbl_end=a2) -> 0
   1972 #
   1973 # Walks (slot, size) pairs from `tbl` to `tbl_end`, threading a running
   1974 # offset starting at 0. For each entry: *slot = base + offset, then
   1975 # offset += size. Leaf.
   1976 :init_arenas
   1977 .scope
   1978     %li(t0, 0)
   1979     :.loop
   1980         %beq(a1, a2, &.done)
   1981         %ld(t1, a1, 0)
   1982         %ld(t2, a1, 8)
   1983         %add(a3, a0, t0)
   1984         %st(a3, t1, 0)
   1985         %add(t0, t0, t2)
   1986         %addi(a1, a1, 16)
   1987         %b(&.loop)
   1988     :.done
   1989     %li(a0, 0)
   1990     %ret
   1991 .endscope
   1992 
   1993 # =========================================================================
   1994 # Bump allocator
   1995 # =========================================================================
   1996 #
   1997 # Single global arena, bytes carved by monotonic cursor with 8-byte
   1998 # alignment. bump_alloc returns 0 when the request would overflow.
   1999 
   2000 # bump_init(base=a0, cap=a1) -> 0
   2001 :bump_init
   2002     %la(t0, &libp1pp__bump_base)
   2003     %st(a0, t0, 0)
   2004     %la(t0, &libp1pp__bump_cursor)
   2005     %st(a0, t0, 0)
   2006     %la(t0, &libp1pp__bump_cap)
   2007     %st(a1, t0, 0)
   2008     %li(a0, 0)
   2009     %ret
   2010 
   2011 # bump_alloc(n=a0) -> ptr (0 on exhaustion)
   2012 #
   2013 # Round n up to a multiple of 8, then admit iff cursor + n_rounded does
   2014 # not exceed base + cap. On success, advance the cursor and return the
   2015 # pre-advance value; on failure, leave the cursor untouched and return 0.
   2016 :bump_alloc
   2017 .scope
   2018     %addi(a0, a0, 7)
   2019     %li(t0, -8)
   2020     %and(a0, a0, t0)
   2021     %la(t0, &libp1pp__bump_cursor)
   2022     %ld(t1, t0, 0)
   2023     %add(t2, t1, a0)
   2024     %la(a1, &libp1pp__bump_base)
   2025     %ld(a2, a1, 0)
   2026     %la(a1, &libp1pp__bump_cap)
   2027     %ld(a3, a1, 0)
   2028     %add(a3, a2, a3)
   2029     %bltu(a3, t2, &.fail)
   2030     %st(t2, t0, 0)
   2031     %mov(a0, t1)
   2032     %ret
   2033     :.fail
   2034     %li(a0, 0)
   2035     %ret
   2036 .endscope
   2037 
   2038 # bump_mark() -> saved
   2039 :bump_mark
   2040     %la(t0, &libp1pp__bump_cursor)
   2041     %ld(a0, t0, 0)
   2042     %ret
   2043 
   2044 # bump_release(saved=a0) -> 0
   2045 :bump_release
   2046     %la(t0, &libp1pp__bump_cursor)
   2047     %st(a0, t0, 0)
   2048     %li(a0, 0)
   2049     %ret
   2050 
   2051 # bump_reset() -> 0
   2052 :bump_reset
   2053     %la(t0, &libp1pp__bump_base)
   2054     %ld(t1, t0, 0)
   2055     %la(t0, &libp1pp__bump_cursor)
   2056     %st(t1, t0, 0)
   2057     %li(a0, 0)
   2058     %ret
   2059 
   2060 # =========================================================================
   2061 # Panic
   2062 # =========================================================================
   2063 
   2064 # panic(msg_cstr=a0) -> never returns
   2065 %fn(panic, 0, {
   2066     %call(&eprint_cstr)
   2067     %la(a0, &libp1pp__newline)
   2068     %li(a1, 1)
   2069     %call(&eprint)
   2070     %li(a0, 1)
   2071     %call(&sys_exit)
   2072     :.spin
   2073     %b(&.spin)
   2074 })
   2075 
   2076 # =========================================================================
   2077 # Tracepoint
   2078 # =========================================================================
   2079 #
   2080 # %trace(tag_addr, tag_len) — emit a runtime stderr probe at the call
   2081 # site. Prints `[trace @0xHEX TAG]\n` to stderr, where 0xHEX is the
   2082 # runtime address of this trace site (the address of `:@here` in this
   2083 # site's expansion) and TAG is the byte string at
   2084 # [tag_addr..tag_addr+tag_len).
   2085 #
   2086 # `tag_addr` is a label reference token (e.g. `&cc__str_3`) — the
   2087 # caller is responsible for emitting the bytes at that label. cc.scm's
   2088 # --cc-trace-emit interns the mangled function name through the
   2089 # regular string pool, which already pads each entry to an 8-byte
   2090 # multiple, so the next item past the tag stays aligned. `tag_len` is
   2091 # the *logical* byte count to print (without trailing NUL or pad).
   2092 #
   2093 # To map a printed address back to source, disassemble the ELF
   2094 # (`scripts/disasm-elf.sh`) and locate the printed address. cc.scm
   2095 # guarantees that each function's first instruction *is* a trace call,
   2096 # so the printed address falls on a known function-entry boundary.
   2097 #
   2098 # Preserves all exposed P1 registers (a0..a3, t0..t2, s0..s3) by
   2099 # borrowing 112 aligned bytes below the current stack pointer: 16 bytes
   2100 # for the backend frame prefix plus 88 bytes for saved registers. Use
   2101 # only inside an active %fn body, after %enter and before %eret.
   2102 %macro trace(tag_addr, tag_len)
   2103     :@here
   2104     %addi(sp, sp, -112)
   2105     %st(a0, sp, 0)
   2106     %st(a1, sp, 8)
   2107     %st(a2, sp, 16)
   2108     %st(a3, sp, 24)
   2109     %st(t0, sp, 32)
   2110     %st(t1, sp, 40)
   2111     %st(t2, sp, 48)
   2112     %st(s0, sp, 56)
   2113     %st(s1, sp, 64)
   2114     %st(s2, sp, 72)
   2115     %st(s3, sp, 80)
   2116     %la(a0, &@here)
   2117     %la(a1, tag_addr)
   2118     %li(a2, tag_len)
   2119     %call(&libp1pp__trace)
   2120     %ld(a0, sp, 0)
   2121     %ld(a1, sp, 8)
   2122     %ld(a2, sp, 16)
   2123     %ld(a3, sp, 24)
   2124     %ld(t0, sp, 32)
   2125     %ld(t1, sp, 40)
   2126     %ld(t2, sp, 48)
   2127     %ld(s0, sp, 56)
   2128     %ld(s1, sp, 64)
   2129     %ld(s2, sp, 72)
   2130     %ld(s3, sp, 80)
   2131     %addi(sp, sp, 112)
   2132 %endm
   2133 
   2134 # libp1pp__trace(addr=a0, tag_addr=a1, tag_len=a2) — print
   2135 # "[trace @0xHEX TAG]\n" to stderr.
   2136 %fn(libp1pp__trace, 32, {
   2137     %st(s0, sp, 0)
   2138     %st(s1, sp, 8)
   2139     %st(s2, sp, 16)
   2140     %st(s3, sp, 24)
   2141     %mov(s0, a0)
   2142     %mov(s1, a1)
   2143     %mov(s2, a2)
   2144 
   2145     %la(a0, &libp1pp__trace_pre)
   2146     %li(a1, 8)
   2147     %call(&eprint)
   2148 
   2149     %la(a0, &libp1pp__num_buf)
   2150     %mov(a1, s0)
   2151     %call(&fmt_hex)
   2152     %mov(s3, a0)
   2153     %la(a0, &libp1pp__num_buf)
   2154     %mov(a1, s3)
   2155     %call(&eprint)
   2156 
   2157     %la(a0, &libp1pp__trace_sep)
   2158     %li(a1, 1)
   2159     %call(&eprint)
   2160 
   2161     %mov(a0, s1)
   2162     %mov(a1, s2)
   2163     %call(&eprint)
   2164 
   2165     %la(a0, &libp1pp__trace_post)
   2166     %li(a1, 2)
   2167     %call(&eprint)
   2168 
   2169     %ld(s0, sp, 0)
   2170     %ld(s1, sp, 8)
   2171     %ld(s2, sp, 16)
   2172     %ld(s3, sp, 24)
   2173 })
   2174 
   2175 # Tracepoint message fragments. eprint reads only the leading
   2176 # visible-byte count (8, 1, 2); .align 8 keeps each fragment and the
   2177 # data labels that follow 8-byte aligned (aarch64 LDR / 4-byte
   2178 # inline-data loads fault otherwise).
   2179 :libp1pp__trace_pre  "[trace @"
   2180 .align 8
   2181 :libp1pp__trace_sep  " "
   2182 .align 8
   2183 :libp1pp__trace_post "]\n"
   2184 .align 8
   2185 
   2186 # =========================================================================
   2187 # Internal data
   2188 # =========================================================================
   2189 
   2190 # Single newline byte for println / eprintln / panic. Emitted as an
   2191 # 8-byte word (0x0A in the low byte, zeros above) so the following
   2192 # buffers and the user source that comes after libp1pp stay 8-byte
   2193 # aligned. sys_write reads only the one byte callers request.
   2194 :libp1pp__newline $(10)
   2195 
   2196 # Scratch buffer used by print_int / print_hex. fmt_dec writes at most
   2197 # 20 bytes, fmt_hex at most 16, so 32 bytes with word alignment is
   2198 # comfortably above both.
   2199 :libp1pp__num_buf $(0) $(0) $(0) $(0)
   2200 
   2201 # Bump-allocator state. Zero-initialized so bump_alloc returns 0 until
   2202 # bump_init installs an arena.
   2203 :libp1pp__bump_base $(0)
   2204 :libp1pp__bump_cursor $(0)
   2205 :libp1pp__bump_cap $(0)