boot2

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

P1-riscv64.M1pp (14933B)


      1 # P1-riscv64.M1pp -- P1 riscv64 backend expressed in m1macro.
      2 
      3 # Width hooks consumed by the portable P1pp support library.
      4 %macro p1_word_bytes()
      5 8
      6 %endm
      7 %macro p1_word_bits()
      8 64
      9 %endm
     10 %macro p1_word_shift()
     11 3
     12 %endm
     13 %macro p1_scheme_heap_bytes()
     14 0x10000000
     15 %endm
     16 %macro p1_waitid_status_off()
     17 24
     18 %endm
     19 #
     20 # Mirrors p1/P1-aarch64.M1pp; same macro surface, different encodings.
     21 # Native register picks follow docs/P1.md's 64-bit mapping table.
     22 #
     23 # Hidden backend regs:
     24 #   br      = t6 (x31)  -- dedicated branch-target mechanism
     25 #   scratch = t5 (x30)  -- per-expansion scratch, never live across ops
     26 #   save0   = t4 (x29)  -- transient across SYSCALL only
     27 #   save1   = t3 (x28)
     28 #   save2   = a6 (x16)
     29 #   saved_fp = fp (x8)  -- used by ENTER/ERET to capture caller sp
     30 #   a7      = x17       -- Linux riscv64 syscall-number slot
     31 #   a4      = x14       -- syscall arg4 slot
     32 #   a5      = x15       -- syscall arg5 slot
     33 
     34 # ---- Native register numbers --------------------------------------------
     35 
     36 %macro rv_reg_a0()
     37 10
     38 %endm
     39 %macro rv_reg_a1()
     40 11
     41 %endm
     42 %macro rv_reg_a2()
     43 12
     44 %endm
     45 %macro rv_reg_a3()
     46 13
     47 %endm
     48 %macro rv_reg_a4()
     49 14
     50 %endm
     51 %macro rv_reg_a5()
     52 15
     53 %endm
     54 %macro rv_reg_a6()
     55 16
     56 %endm
     57 %macro rv_reg_a7()
     58 17
     59 %endm
     60 %macro rv_reg_t0()
     61 5
     62 %endm
     63 %macro rv_reg_t1()
     64 6
     65 %endm
     66 %macro rv_reg_t2()
     67 7
     68 %endm
     69 %macro rv_reg_s0()
     70 9
     71 %endm
     72 %macro rv_reg_s1()
     73 18
     74 %endm
     75 %macro rv_reg_s2()
     76 19
     77 %endm
     78 %macro rv_reg_s3()
     79 20
     80 %endm
     81 %macro rv_reg_sp()
     82 2
     83 %endm
     84 %macro rv_reg_zero()
     85 0
     86 %endm
     87 %macro rv_reg_ra()
     88 1
     89 %endm
     90 %macro rv_reg_fp()
     91 8
     92 %endm
     93 %macro rv_reg_br()
     94 31
     95 %endm
     96 %macro rv_reg_scratch()
     97 30
     98 %endm
     99 %macro rv_reg_save0()
    100 29
    101 %endm
    102 %macro rv_reg_save1()
    103 28
    104 %endm
    105 %macro rv_reg_save2()
    106 16
    107 %endm
    108 
    109 %macro rv_reg(r)
    110 %rv_reg_##r
    111 %endm
    112 
    113 %macro rv_is_sp_a0()
    114 0
    115 %endm
    116 %macro rv_is_sp_a1()
    117 0
    118 %endm
    119 %macro rv_is_sp_a2()
    120 0
    121 %endm
    122 %macro rv_is_sp_a3()
    123 0
    124 %endm
    125 %macro rv_is_sp_a4()
    126 0
    127 %endm
    128 %macro rv_is_sp_a5()
    129 0
    130 %endm
    131 %macro rv_is_sp_a6()
    132 0
    133 %endm
    134 %macro rv_is_sp_a7()
    135 0
    136 %endm
    137 %macro rv_is_sp_t0()
    138 0
    139 %endm
    140 %macro rv_is_sp_t1()
    141 0
    142 %endm
    143 %macro rv_is_sp_t2()
    144 0
    145 %endm
    146 %macro rv_is_sp_s0()
    147 0
    148 %endm
    149 %macro rv_is_sp_s1()
    150 0
    151 %endm
    152 %macro rv_is_sp_s2()
    153 0
    154 %endm
    155 %macro rv_is_sp_s3()
    156 0
    157 %endm
    158 %macro rv_is_sp_sp()
    159 1
    160 %endm
    161 %macro rv_is_sp_zero()
    162 0
    163 %endm
    164 %macro rv_is_sp_ra()
    165 0
    166 %endm
    167 %macro rv_is_sp_fp()
    168 0
    169 %endm
    170 %macro rv_is_sp_br()
    171 0
    172 %endm
    173 %macro rv_is_sp_scratch()
    174 0
    175 %endm
    176 %macro rv_is_sp_save0()
    177 0
    178 %endm
    179 %macro rv_is_sp_save1()
    180 0
    181 %endm
    182 %macro rv_is_sp_save2()
    183 0
    184 %endm
    185 
    186 %macro rv_is_sp(r)
    187 %rv_is_sp_##r
    188 %endm
    189 
    190 # ---- Low-level instruction encoders --------------------------------------
    191 
    192 # R-type: funct7[31:25] rs2[24:20] rs1[19:15] funct3[14:12] rd[11:7] opcode[6:0]
    193 %macro rv_r_type(base, rd, ra, rb)
    194 %((| base (<< %rv_reg(rb) 20) (<< %rv_reg(ra) 15) (<< %rv_reg(rd) 7)))
    195 %endm
    196 
    197 # I-type: imm[31:20] rs1[19:15] funct3[14:12] rd[11:7] opcode[6:0]
    198 %macro rv_i_type(base, rd, ra, imm12)
    199 %((| base (<< (& imm12 0xFFF) 20) (<< %rv_reg(ra) 15) (<< %rv_reg(rd) 7)))
    200 %endm
    201 
    202 # S-type: imm[31:25] rs2[24:20] rs1[19:15] funct3[14:12] imm[11:7] opcode[6:0]
    203 %macro rv_s_type(base, rs, ra, imm12)
    204 %((| base (<< (& (>> imm12 5) 0x7F) 25) (<< %rv_reg(rs) 20) (<< %rv_reg(ra) 15) (<< (& imm12 0x1F) 7)))
    205 %endm
    206 
    207 # B-type: imm[12|10:5] rs2 rs1 funct3 imm[4:1|11] opcode. 12-bit signed,
    208 # imm[0] always 0. For the hardcoded skip-over-jalr we only need a fixed
    209 # positive offset (8 bytes = 2 insns), so inline the resulting bit pattern.
    210 %macro rv_b_type_skip8(base, ra, rb)
    211 # imm value 8 -> imm[11:0] = 0000_0000_0100. Bits of encoded imm:
    212 #   imm[12]=0, imm[10:5]=0, imm[4:1]=0100 (=4), imm[11]=0.
    213 #   encoded bits: [31:25]=0, [11:7]= (imm[4:1] << 1) | imm[11] = (4<<1)|0 = 8.
    214 %((| base (<< %rv_reg(rb) 20) (<< %rv_reg(ra) 15) (<< 8 7)))
    215 %endm
    216 
    217 %macro rv_addi(rd, ra, imm12)
    218 %rv_i_type(0x00000013, rd, ra, imm12)
    219 %endm
    220 
    221 # rv_addi with arbitrary 64-bit signed immediate. Falls back to a
    222 # 64-bit literal load into `scratch` followed by an R-type ADD when the
    223 # immediate doesn't fit in ADDI's 12-bit signed field. `scratch` (t5/x30)
    224 # is per-expansion and never live across ops, so clobbering it is safe.
    225 %macro rv_addi_any(rd, ra, imm)
    226 %select((>= imm -2048),
    227     %select((<= imm 2047),
    228         %rv_addi(rd, ra, imm),
    229         %rv_lit64_prefix(scratch)
    230         $(imm)
    231         %rv_r_type(0x00000033, rd, ra, scratch)),
    232     %rv_lit64_prefix(scratch)
    233     $(imm)
    234     %rv_r_type(0x00000033, rd, ra, scratch))
    235 %endm
    236 
    237 %macro rv_ld(rd, ra, imm12)
    238 %rv_i_type(0x00003003, rd, ra, imm12)
    239 %endm
    240 
    241 %macro rv_sd(rs, ra, imm12)
    242 %rv_s_type(0x00003023, rs, ra, imm12)
    243 %endm
    244 
    245 %macro rv_lbu(rd, ra, imm12)
    246 %rv_i_type(0x00004003, rd, ra, imm12)
    247 %endm
    248 
    249 %macro rv_sb(rs, ra, imm12)
    250 %rv_s_type(0x00000023, rs, ra, imm12)
    251 %endm
    252 
    253 %macro rv_lwu(rd, ra, imm12)
    254 %rv_i_type(0x00006003, rd, ra, imm12)
    255 %endm
    256 
    257 # Load/store with arbitrary signed offset. The native I-type/S-type
    258 # imm12 covers [-2048, 2047]; past that, materialize the offset in
    259 # scratch (t5/x30), compute scratch = ra + scratch via R-type ADD, and
    260 # issue the load/store with offset 0. Callers must not pass scratch as
    261 # `ra` or `rs` — the materialize would clobber it before the address
    262 # computation reads it.
    263 %macro rv_ld_any(rd, ra, off)
    264 %select((>= off -2048),
    265     %select((<= off 2047),
    266         %rv_ld(rd, ra, off),
    267         %rv_lit64_prefix(scratch)
    268         $(off)
    269         %rv_r_type(0x00000033, scratch, ra, scratch)
    270         %rv_ld(rd, scratch, 0)),
    271     %rv_lit64_prefix(scratch)
    272     $(off)
    273     %rv_r_type(0x00000033, scratch, ra, scratch)
    274     %rv_ld(rd, scratch, 0))
    275 %endm
    276 
    277 %macro rv_sd_any(rs, ra, off)
    278 %select((>= off -2048),
    279     %select((<= off 2047),
    280         %rv_sd(rs, ra, off),
    281         %rv_lit64_prefix(scratch)
    282         $(off)
    283         %rv_r_type(0x00000033, scratch, ra, scratch)
    284         %rv_sd(rs, scratch, 0)),
    285     %rv_lit64_prefix(scratch)
    286     $(off)
    287     %rv_r_type(0x00000033, scratch, ra, scratch)
    288     %rv_sd(rs, scratch, 0))
    289 %endm
    290 
    291 %macro rv_lbu_any(rd, ra, off)
    292 %select((>= off -2048),
    293     %select((<= off 2047),
    294         %rv_lbu(rd, ra, off),
    295         %rv_lit64_prefix(scratch)
    296         $(off)
    297         %rv_r_type(0x00000033, scratch, ra, scratch)
    298         %rv_lbu(rd, scratch, 0)),
    299     %rv_lit64_prefix(scratch)
    300     $(off)
    301     %rv_r_type(0x00000033, scratch, ra, scratch)
    302     %rv_lbu(rd, scratch, 0))
    303 %endm
    304 
    305 %macro rv_sb_any(rs, ra, off)
    306 %select((>= off -2048),
    307     %select((<= off 2047),
    308         %rv_sb(rs, ra, off),
    309         %rv_lit64_prefix(scratch)
    310         $(off)
    311         %rv_r_type(0x00000033, scratch, ra, scratch)
    312         %rv_sb(rs, scratch, 0)),
    313     %rv_lit64_prefix(scratch)
    314     $(off)
    315     %rv_r_type(0x00000033, scratch, ra, scratch)
    316     %rv_sb(rs, scratch, 0))
    317 %endm
    318 
    319 %macro rv_mov_rr(dst, src)
    320 %rv_addi(dst, src, 0)
    321 %endm
    322 
    323 %macro rv_slli(rd, ra, shamt)
    324 %((| 0x00001013 (<< (& shamt 0x3F) 20) (<< %rv_reg(ra) 15) (<< %rv_reg(rd) 7)))
    325 %endm
    326 
    327 %macro rv_srli(rd, ra, shamt)
    328 %((| 0x00005013 (<< (& shamt 0x3F) 20) (<< %rv_reg(ra) 15) (<< %rv_reg(rd) 7)))
    329 %endm
    330 
    331 %macro rv_srai(rd, ra, shamt)
    332 %((| 0x40005013 (<< (& shamt 0x3F) 20) (<< %rv_reg(ra) 15) (<< %rv_reg(rd) 7)))
    333 %endm
    334 
    335 %macro rv_jalr(rd, rs, imm12)
    336 %((| 0x00000067 (<< (& imm12 0xFFF) 20) (<< %rv_reg(rs) 15) (<< %rv_reg(rd) 7)))
    337 %endm
    338 
    339 %macro rv_ecall()
    340 %(0x00000073)
    341 %endm
    342 
    343 # 64-bit literal-pool prefix for LI:
    344 #   auipc rd, 0          pc-relative base
    345 #   ld    rd, 12(rd)     load 8-byte literal from pc+12
    346 #   jal   x0, 12         skip 12 bytes (literal + pad, =8 bytes of literal).
    347 # The 8 bytes that follow in source become the literal.
    348 %macro rv_lit64_prefix(rd)
    349 %((| 0x00000017 (<< %rv_reg(rd) 7)))
    350 %((| 0x00C03003 (<< %rv_reg(rd) 15) (<< %rv_reg(rd) 7)))
    351 %(0x00C0006F)
    352 %endm
    353 
    354 # 32-bit literal-pool prefix for LA / LA_BR:
    355 #   auipc rd, 0
    356 #   lwu   rd, 12(rd)     zero-extend 4-byte literal from pc+12
    357 #   jal   x0, 8          skip 8 bytes (=4 of insn slot? no, literal + align).
    358 # lwu zero-extends into the full 64-bit register, so 4 bytes is enough for
    359 # any address in the stage0 layout. Lets source use `&label` directly
    360 # without padding to 8 bytes.
    361 %macro rv_lit32_prefix(rd)
    362 %((| 0x00000017 (<< %rv_reg(rd) 7)))
    363 %((| 0x00C06003 (<< %rv_reg(rd) 15) (<< %rv_reg(rd) 7)))
    364 %(0x0080006F)
    365 %endm
    366 
    367 # Memory op fallback: offset outside signed 12-bit range. Load the
    368 # offset into `scratch` via LUI+ADDI dance? For stage0 programs the
    369 # curated offsets stay inside -2048..2047, so fall back is unused;
    370 # still emit a defensive failure to flag any future overflow.
    371 # (In practice none of the LD/ST off values in p1_gen.py exceed the
    372 # signed 12-bit range, so no fallback path is wired in here.)
    373 
    374 # ---- P1 register-register op lowering -----------------------------------
    375 
    376 %macro rv_rrr_ADD(rd, ra, rb)
    377 %rv_r_type(0x00000033, rd, ra, rb)
    378 %endm
    379 %macro rv_rrr_SUB(rd, ra, rb)
    380 %rv_r_type(0x40000033, rd, ra, rb)
    381 %endm
    382 %macro rv_rrr_AND(rd, ra, rb)
    383 %rv_r_type(0x00007033, rd, ra, rb)
    384 %endm
    385 %macro rv_rrr_OR(rd, ra, rb)
    386 %rv_r_type(0x00006033, rd, ra, rb)
    387 %endm
    388 %macro rv_rrr_XOR(rd, ra, rb)
    389 %rv_r_type(0x00004033, rd, ra, rb)
    390 %endm
    391 %macro rv_rrr_SHL(rd, ra, rb)
    392 %rv_r_type(0x00001033, rd, ra, rb)
    393 %endm
    394 %macro rv_rrr_SHR(rd, ra, rb)
    395 %rv_r_type(0x00005033, rd, ra, rb)
    396 %endm
    397 %macro rv_rrr_SAR(rd, ra, rb)
    398 %rv_r_type(0x40005033, rd, ra, rb)
    399 %endm
    400 %macro rv_rrr_MUL(rd, ra, rb)
    401 %rv_r_type(0x02000033, rd, ra, rb)
    402 %endm
    403 %macro rv_rrr_DIV(rd, ra, rb)
    404 %rv_r_type(0x02004033, rd, ra, rb)
    405 %endm
    406 %macro rv_rrr_UDIV(rd, ra, rb)
    407 %rv_r_type(0x02005033, rd, ra, rb)
    408 %endm
    409 %macro rv_rrr_REM(rd, ra, rb)
    410 %rv_r_type(0x02006033, rd, ra, rb)
    411 %endm
    412 %macro rv_rrr_UREM(rd, ra, rb)
    413 %rv_r_type(0x02007033, rd, ra, rb)
    414 %endm
    415 
    416 %macro rv_rrr_op(op, rd, ra, rb)
    417 %rv_rrr_##op(rd, ra, rb)
    418 %endm
    419 
    420 # ---- P1 operation lowering -----------------------------------------------
    421 
    422 %macro p1_li(rd, imm)
    423 %rv_lit64_prefix(rd)
    424 $(imm)
    425 %endm
    426 
    427 %macro p1_la(rd)
    428 %rv_lit32_prefix(rd)
    429 %endm
    430 
    431 %macro p1_labr()
    432 %rv_lit32_prefix(br)
    433 %endm
    434 
    435 %macro p1_mov(rd, rs)
    436 %select((= %rv_is_sp(rs) 1),
    437     %rv_addi(rd, sp, 16),
    438     %rv_mov_rr(rd, rs))
    439 %endm
    440 
    441 %macro p1_rrr(op, rd, ra, rb)
    442 %rv_rrr_op(op, rd, ra, rb)
    443 %endm
    444 
    445 %macro p1_addi(rd, ra, imm)
    446 %rv_addi_any(rd, ra, imm)
    447 %endm
    448 
    449 # Logical-immediate fallback: when imm fits the I-type's 12-bit signed
    450 # field, emit the native ANDI/ORI; otherwise materialize the immediate
    451 # in scratch (t5/x30) and use the R-type AND/OR. funct3=7 (AND) or 6
    452 # (OR) is shared between the I-type (opcode 0x13) and R-type
    453 # (opcode 0x33) encodings.
    454 %macro rv_logi_any(rd, ra, imm, base_i, base_r)
    455 %select((>= imm -2048),
    456     %select((<= imm 2047),
    457         %rv_i_type(base_i, rd, ra, imm),
    458         %rv_lit64_prefix(scratch)
    459         $(imm)
    460         %rv_r_type(base_r, rd, ra, scratch)),
    461     %rv_lit64_prefix(scratch)
    462     $(imm)
    463     %rv_r_type(base_r, rd, ra, scratch))
    464 %endm
    465 
    466 %macro p1_logi_ANDI(rd, ra, imm)
    467 %rv_logi_any(rd, ra, imm, 0x00007013, 0x00007033)
    468 %endm
    469 %macro p1_logi_ORI(rd, ra, imm)
    470 %rv_logi_any(rd, ra, imm, 0x00006013, 0x00006033)
    471 %endm
    472 %macro p1_logi(op, rd, ra, imm)
    473 %p1_logi_##op(rd, ra, imm)
    474 %endm
    475 
    476 %macro p1_shifti_SHLI(rd, ra, imm)
    477 %rv_slli(rd, ra, imm)
    478 %endm
    479 %macro p1_shifti_SHRI(rd, ra, imm)
    480 %rv_srli(rd, ra, imm)
    481 %endm
    482 %macro p1_shifti_SARI(rd, ra, imm)
    483 %rv_srai(rd, ra, imm)
    484 %endm
    485 %macro p1_shifti(op, rd, ra, imm)
    486 %p1_shifti_##op(rd, ra, imm)
    487 %endm
    488 
    489 %macro p1_mem_LD(rt, rn, off)
    490 %rv_ld_any(rt, rn, off)
    491 %endm
    492 %macro p1_mem_ST(rt, rn, off)
    493 %rv_sd_any(rt, rn, off)
    494 %endm
    495 %macro p1_mem_LB(rt, rn, off)
    496 %rv_lbu_any(rt, rn, off)
    497 %endm
    498 %macro p1_mem_SB(rt, rn, off)
    499 %rv_sb_any(rt, rn, off)
    500 %endm
    501 %macro p1_mem(op, rt, rn, off)
    502 %select((= %rv_is_sp(rn) 1),
    503     %p1_mem_##op(rt, rn, (+ off 16)),
    504     %p1_mem_##op(rt, rn, off))
    505 %endm
    506 
    507 %macro p1_ldarg(rd, slot)
    508 %rv_ld(rd, sp, 8)
    509 %rv_ld_any(rd, rd, (+ 16 (* 8 slot)))
    510 %endm
    511 
    512 %macro p1_b()
    513 %rv_jalr(zero, br, 0)
    514 %endm
    515 
    516 %macro p1_br(rs)
    517 %rv_jalr(zero, rs, 0)
    518 %endm
    519 
    520 %macro p1_call()
    521 %rv_jalr(ra, br, 0)
    522 %endm
    523 
    524 %macro p1_callr(rs)
    525 %rv_jalr(ra, rs, 0)
    526 %endm
    527 
    528 %macro p1_ret()
    529 %rv_jalr(zero, ra, 0)
    530 %endm
    531 
    532 %macro p1_eret()
    533 %rv_ld(ra, sp, 0)
    534 %rv_ld(fp, sp, 8)
    535 %rv_mov_rr(sp, fp)
    536 %rv_jalr(zero, ra, 0)
    537 %endm
    538 
    539 %macro p1_tail()
    540 %rv_ld(ra, sp, 0)
    541 %rv_ld(fp, sp, 8)
    542 %rv_mov_rr(sp, fp)
    543 %rv_jalr(zero, br, 0)
    544 %endm
    545 
    546 %macro p1_tailr(rs)
    547 %rv_ld(ra, sp, 0)
    548 %rv_ld(fp, sp, 8)
    549 %rv_mov_rr(sp, fp)
    550 %rv_jalr(zero, rs, 0)
    551 %endm
    552 
    553 # Conditional branch: emit a skip-taken native branch over the `%p1_b`
    554 # fall-through, then the jalr(br) that takes the P1 branch. Each native
    555 # B-type here uses the inverted condition with a +8 offset so the `jalr`
    556 # two insns below is the taken target.
    557 %macro p1_condb_BEQ(ra, rb)
    558 %rv_b_type_skip8(0x00001063, ra, rb)
    559 %p1_b
    560 %endm
    561 %macro p1_condb_BNE(ra, rb)
    562 %rv_b_type_skip8(0x00000063, ra, rb)
    563 %p1_b
    564 %endm
    565 %macro p1_condb_BLT(ra, rb)
    566 %rv_b_type_skip8(0x00005063, ra, rb)
    567 %p1_b
    568 %endm
    569 %macro p1_condb_BLTU(ra, rb)
    570 %rv_b_type_skip8(0x00007063, ra, rb)
    571 %p1_b
    572 %endm
    573 %macro p1_condb(op, ra, rb)
    574 %p1_condb_##op(ra, rb)
    575 %endm
    576 
    577 %macro p1_condbz_BEQZ(ra)
    578 %rv_b_type_skip8(0x00001063, ra, zero)
    579 %p1_b
    580 %endm
    581 %macro p1_condbz_BNEZ(ra)
    582 %rv_b_type_skip8(0x00000063, ra, zero)
    583 %p1_b
    584 %endm
    585 %macro p1_condbz_BLTZ(ra)
    586 %rv_b_type_skip8(0x00005063, ra, zero)
    587 %p1_b
    588 %endm
    589 %macro p1_condbz(op, ra)
    590 %p1_condbz_##op(ra)
    591 %endm
    592 
    593 %macro p1_enter(size)
    594 %rv_addi_any(sp, sp, (- 0 (& (+ (+ 16 size) 15) -16)))
    595 %rv_sd(ra, sp, 0)
    596 %rv_addi_any(fp, sp, (& (+ (+ 16 size) 15) -16))
    597 %rv_sd(fp, sp, 8)
    598 %endm
    599 
    600 %macro p1_entry()
    601 # :_start stub per the P1 program-entry model. Linux riscv64 puts argc
    602 # at [sp] and argv starting at [sp+8], matching the generic SysV entry
    603 # stack. Load argc into a0, compute &argv[0] into a1, call p1_main under
    604 # the one-word direct-result convention, then issue sys_exit with the
    605 # returned status.
    606 :_start
    607 %rv_ld(a0, sp, 0)
    608 %rv_addi(a1, sp, 8)
    609 %rv_lit32_prefix(br)
    610 &p1_main
    611 %rv_jalr(ra, br, 0)
    612 %rv_addi(a7, zero, 93)
    613 %rv_ecall
    614 %endm
    615 
    616 %macro p1_syscall()
    617 # P1: a0=number, a1,a2,a3,t0,s0,s1 = args 0..5.
    618 # Linux riscv64: a7=number, a0..a5 = args 0..5, return in a0.
    619 # SYSCALL clobbers only P1 a0; restore a1/a2/a3 after ecall.
    620 # Native a4/a5 (x14/x15) aren't P1-exposed; we use them as syscall arg
    621 # slots and don't need to save them.
    622 %rv_mov_rr(save0, a1)
    623 %rv_mov_rr(save1, a2)
    624 %rv_mov_rr(save2, a3)
    625 %rv_mov_rr(a7, a0)
    626 %rv_mov_rr(a0, save0)
    627 %rv_mov_rr(a1, save1)
    628 %rv_mov_rr(a2, save2)
    629 %rv_mov_rr(a3, t0)
    630 %rv_mov_rr(a4, s0)
    631 %rv_mov_rr(a5, s1)
    632 %rv_ecall
    633 %rv_mov_rr(a1, save0)
    634 %rv_mov_rr(a2, save1)
    635 %rv_mov_rr(a3, save2)
    636 %endm
    637 
    638 # ---- Linux riscv64 syscall numbers ---------------------------------------
    639 # Each macro returns the syscall number as an integer atom so callers can
    640 # use it inside expressions (e.g. `%li(a0, %sys_write)`).
    641 
    642 %macro p1_sys_read()
    643 63
    644 %endm
    645 %macro p1_sys_write()
    646 64
    647 %endm
    648 %macro p1_sys_close()
    649 57
    650 %endm
    651 %macro p1_sys_openat()
    652 56
    653 %endm
    654 %macro p1_sys_exit()
    655 93
    656 %endm
    657 %macro p1_sys_clone()
    658 220
    659 %endm
    660 %macro p1_sys_execve()
    661 221
    662 %endm
    663 %macro p1_sys_spawn()
    664 1024
    665 %endm
    666 %macro p1_sys_waitid()
    667 95
    668 %endm
    669 %macro p1_sys_lseek()
    670 62
    671 %endm
    672 %macro p1_sys_lseek_wrapper()
    673 %mov(a3, a2)
    674 %mov(a2, a1)
    675 %mov(a1, a0)
    676 %li(a0, %p1_sys_lseek)
    677 %syscall
    678 %ret
    679 %endm
    680 %macro p1_sys_brk()
    681 214
    682 %endm
    683 %macro p1_sys_unlinkat()
    684 35
    685 %endm