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)