P1-riscv32.M1pp (14570B)
1 # P1-riscv32.M1pp -- P1 RV32IM backend expressed in m1macro. 2 # 3 # Mirrors p1/P1-aarch64.M1pp; same macro surface, different encodings. 4 # Native register picks follow docs/P1.md's RISC-V mapping table. 5 # 6 # Hidden backend regs: 7 # br = t6 (x31) -- dedicated branch-target mechanism 8 # scratch = t5 (x30) -- per-expansion scratch, never live across ops 9 # save0 = t4 (x29) -- transient across SYSCALL only 10 # save1 = t3 (x28) 11 # save2 = a6 (x16) 12 # saved_fp = fp (x8) -- used by ENTER/ERET to capture caller sp 13 # a7 = x17 -- Linux RISC-V syscall-number slot 14 # a4 = x14 -- syscall arg4 slot 15 # a5 = x15 -- syscall arg5 slot 16 17 # Width hooks consumed by the portable P1pp support library. 18 %macro p1_word_bytes() 19 4 20 %endm 21 %macro p1_word_bits() 22 32 23 %endm 24 %macro p1_word_shift() 25 2 26 %endm 27 %macro p1_scheme_heap_bytes() 28 0x00F00000 29 %endm 30 %macro p1_waitid_status_off() 31 20 32 %endm 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 32-bit signed immediate. Falls back to a 222 # word 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_lit32_prefix(scratch) 230 %(imm) 231 %rv_r_type(0x00000033, rd, ra, scratch)), 232 %rv_lit32_prefix(scratch) 233 %(imm) 234 %rv_r_type(0x00000033, rd, ra, scratch)) 235 %endm 236 237 %macro rv_lw(rd, ra, imm12) 238 %rv_i_type(0x00002003, rd, ra, imm12) 239 %endm 240 241 %macro rv_sw(rs, ra, imm12) 242 %rv_s_type(0x00002023, 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 # Load/store with arbitrary signed offset. The native I-type/S-type 254 # imm12 covers [-2048, 2047]; past that, materialize the offset in 255 # scratch (t5/x30), compute scratch = ra + scratch via R-type ADD, and 256 # issue the load/store with offset 0. Callers must not pass scratch as 257 # `ra` or `rs` — the materialize would clobber it before the address 258 # computation reads it. 259 %macro rv_lw_any(rd, ra, off) 260 %select((>= off -2048), 261 %select((<= off 2047), 262 %rv_lw(rd, ra, off), 263 %rv_lit32_prefix(scratch) 264 %(off) 265 %rv_r_type(0x00000033, scratch, ra, scratch) 266 %rv_lw(rd, scratch, 0)), 267 %rv_lit32_prefix(scratch) 268 %(off) 269 %rv_r_type(0x00000033, scratch, ra, scratch) 270 %rv_lw(rd, scratch, 0)) 271 %endm 272 273 %macro rv_sw_any(rs, ra, off) 274 %select((>= off -2048), 275 %select((<= off 2047), 276 %rv_sw(rs, ra, off), 277 %rv_lit32_prefix(scratch) 278 %(off) 279 %rv_r_type(0x00000033, scratch, ra, scratch) 280 %rv_sw(rs, scratch, 0)), 281 %rv_lit32_prefix(scratch) 282 %(off) 283 %rv_r_type(0x00000033, scratch, ra, scratch) 284 %rv_sw(rs, scratch, 0)) 285 %endm 286 287 %macro rv_lbu_any(rd, ra, off) 288 %select((>= off -2048), 289 %select((<= off 2047), 290 %rv_lbu(rd, ra, off), 291 %rv_lit32_prefix(scratch) 292 %(off) 293 %rv_r_type(0x00000033, scratch, ra, scratch) 294 %rv_lbu(rd, scratch, 0)), 295 %rv_lit32_prefix(scratch) 296 %(off) 297 %rv_r_type(0x00000033, scratch, ra, scratch) 298 %rv_lbu(rd, scratch, 0)) 299 %endm 300 301 %macro rv_sb_any(rs, ra, off) 302 %select((>= off -2048), 303 %select((<= off 2047), 304 %rv_sb(rs, ra, off), 305 %rv_lit32_prefix(scratch) 306 %(off) 307 %rv_r_type(0x00000033, scratch, ra, scratch) 308 %rv_sb(rs, scratch, 0)), 309 %rv_lit32_prefix(scratch) 310 %(off) 311 %rv_r_type(0x00000033, scratch, ra, scratch) 312 %rv_sb(rs, scratch, 0)) 313 %endm 314 315 %macro rv_mov_rr(dst, src) 316 %rv_addi(dst, src, 0) 317 %endm 318 319 %macro rv_slli(rd, ra, shamt) 320 %((| 0x00001013 (<< (& shamt 0x1F) 20) (<< %rv_reg(ra) 15) (<< %rv_reg(rd) 7))) 321 %endm 322 323 %macro rv_srli(rd, ra, shamt) 324 %((| 0x00005013 (<< (& shamt 0x1F) 20) (<< %rv_reg(ra) 15) (<< %rv_reg(rd) 7))) 325 %endm 326 327 %macro rv_srai(rd, ra, shamt) 328 %((| 0x40005013 (<< (& shamt 0x1F) 20) (<< %rv_reg(ra) 15) (<< %rv_reg(rd) 7))) 329 %endm 330 331 %macro rv_jalr(rd, rs, imm12) 332 %((| 0x00000067 (<< (& imm12 0xFFF) 20) (<< %rv_reg(rs) 15) (<< %rv_reg(rd) 7))) 333 %endm 334 335 %macro rv_ecall() 336 %(0x00000073) 337 %endm 338 339 # 32-bit word/address literal-pool prefix: 340 # auipc rd, 0 pc-relative base 341 # lw rd, 12(rd) load the 4-byte literal from pc+12 342 # jal x0, 8 skip the 4-byte literal. 343 %macro rv_lit32_prefix(rd) 344 %((| 0x00000017 (<< %rv_reg(rd) 7))) 345 %((| 0x00C02003 (<< %rv_reg(rd) 15) (<< %rv_reg(rd) 7))) 346 %(0x0080006F) 347 %endm 348 349 # Memory op fallback: offset outside signed 12-bit range. Load the 350 # offset into `scratch` via LUI+ADDI dance? For stage0 programs the 351 # curated offsets stay inside -2048..2047, so fall back is unused; 352 # still emit a defensive failure to flag any future overflow. 353 # (In practice none of the LD/ST off values in p1_gen.py exceed the 354 # signed 12-bit range, so no fallback path is wired in here.) 355 356 # ---- P1 register-register op lowering ----------------------------------- 357 358 %macro rv_rrr_ADD(rd, ra, rb) 359 %rv_r_type(0x00000033, rd, ra, rb) 360 %endm 361 %macro rv_rrr_SUB(rd, ra, rb) 362 %rv_r_type(0x40000033, rd, ra, rb) 363 %endm 364 %macro rv_rrr_AND(rd, ra, rb) 365 %rv_r_type(0x00007033, rd, ra, rb) 366 %endm 367 %macro rv_rrr_OR(rd, ra, rb) 368 %rv_r_type(0x00006033, rd, ra, rb) 369 %endm 370 %macro rv_rrr_XOR(rd, ra, rb) 371 %rv_r_type(0x00004033, rd, ra, rb) 372 %endm 373 %macro rv_rrr_SHL(rd, ra, rb) 374 %rv_r_type(0x00001033, rd, ra, rb) 375 %endm 376 %macro rv_rrr_SHR(rd, ra, rb) 377 %rv_r_type(0x00005033, rd, ra, rb) 378 %endm 379 %macro rv_rrr_SAR(rd, ra, rb) 380 %rv_r_type(0x40005033, rd, ra, rb) 381 %endm 382 %macro rv_rrr_MUL(rd, ra, rb) 383 %rv_r_type(0x02000033, rd, ra, rb) 384 %endm 385 %macro rv_rrr_DIV(rd, ra, rb) 386 %rv_r_type(0x02004033, rd, ra, rb) 387 %endm 388 %macro rv_rrr_UDIV(rd, ra, rb) 389 %rv_r_type(0x02005033, rd, ra, rb) 390 %endm 391 %macro rv_rrr_REM(rd, ra, rb) 392 %rv_r_type(0x02006033, rd, ra, rb) 393 %endm 394 %macro rv_rrr_UREM(rd, ra, rb) 395 %rv_r_type(0x02007033, rd, ra, rb) 396 %endm 397 398 %macro rv_rrr_op(op, rd, ra, rb) 399 %rv_rrr_##op(rd, ra, rb) 400 %endm 401 402 # ---- P1 operation lowering ----------------------------------------------- 403 404 %macro p1_li(rd, imm) 405 %rv_lit32_prefix(rd) 406 %(imm) 407 %endm 408 409 %macro p1_la(rd) 410 %rv_lit32_prefix(rd) 411 %endm 412 413 %macro p1_labr() 414 %rv_lit32_prefix(br) 415 %endm 416 417 %macro p1_mov(rd, rs) 418 %select((= %rv_is_sp(rs) 1), 419 %rv_addi(rd, sp, 8), 420 %rv_mov_rr(rd, rs)) 421 %endm 422 423 %macro p1_rrr(op, rd, ra, rb) 424 %rv_rrr_op(op, rd, ra, rb) 425 %endm 426 427 %macro p1_addi(rd, ra, imm) 428 %rv_addi_any(rd, ra, imm) 429 %endm 430 431 # Logical-immediate fallback: when imm fits the I-type's 12-bit signed 432 # field, emit the native ANDI/ORI; otherwise materialize the immediate 433 # in scratch (t5/x30) and use the R-type AND/OR. funct3=7 (AND) or 6 434 # (OR) is shared between the I-type (opcode 0x13) and R-type 435 # (opcode 0x33) encodings. 436 %macro rv_logi_any(rd, ra, imm, base_i, base_r) 437 %select((>= imm -2048), 438 %select((<= imm 2047), 439 %rv_i_type(base_i, rd, ra, imm), 440 %rv_lit32_prefix(scratch) 441 %(imm) 442 %rv_r_type(base_r, rd, ra, scratch)), 443 %rv_lit32_prefix(scratch) 444 %(imm) 445 %rv_r_type(base_r, rd, ra, scratch)) 446 %endm 447 448 %macro p1_logi_ANDI(rd, ra, imm) 449 %rv_logi_any(rd, ra, imm, 0x00007013, 0x00007033) 450 %endm 451 %macro p1_logi_ORI(rd, ra, imm) 452 %rv_logi_any(rd, ra, imm, 0x00006013, 0x00006033) 453 %endm 454 %macro p1_logi(op, rd, ra, imm) 455 %p1_logi_##op(rd, ra, imm) 456 %endm 457 458 %macro p1_shifti_SHLI(rd, ra, imm) 459 %rv_slli(rd, ra, imm) 460 %endm 461 %macro p1_shifti_SHRI(rd, ra, imm) 462 %rv_srli(rd, ra, imm) 463 %endm 464 %macro p1_shifti_SARI(rd, ra, imm) 465 %rv_srai(rd, ra, imm) 466 %endm 467 %macro p1_shifti(op, rd, ra, imm) 468 %p1_shifti_##op(rd, ra, imm) 469 %endm 470 471 %macro p1_mem_LD(rt, rn, off) 472 %rv_lw_any(rt, rn, off) 473 %endm 474 %macro p1_mem_ST(rt, rn, off) 475 %rv_sw_any(rt, rn, off) 476 %endm 477 %macro p1_mem_LB(rt, rn, off) 478 %rv_lbu_any(rt, rn, off) 479 %endm 480 %macro p1_mem_SB(rt, rn, off) 481 %rv_sb_any(rt, rn, off) 482 %endm 483 %macro p1_mem(op, rt, rn, off) 484 %select((= %rv_is_sp(rn) 1), 485 %p1_mem_##op(rt, rn, (+ off 8)), 486 %p1_mem_##op(rt, rn, off)) 487 %endm 488 489 %macro p1_ldarg(rd, slot) 490 %rv_lw(rd, sp, 4) 491 %rv_lw_any(rd, rd, (+ 8 (* 4 slot))) 492 %endm 493 494 %macro p1_b() 495 %rv_jalr(zero, br, 0) 496 %endm 497 498 %macro p1_br(rs) 499 %rv_jalr(zero, rs, 0) 500 %endm 501 502 %macro p1_call() 503 %rv_jalr(ra, br, 0) 504 %endm 505 506 %macro p1_callr(rs) 507 %rv_jalr(ra, rs, 0) 508 %endm 509 510 %macro p1_ret() 511 %rv_jalr(zero, ra, 0) 512 %endm 513 514 %macro p1_eret() 515 %rv_lw(ra, sp, 0) 516 %rv_lw(fp, sp, 4) 517 %rv_mov_rr(sp, fp) 518 %rv_jalr(zero, ra, 0) 519 %endm 520 521 %macro p1_tail() 522 %rv_lw(ra, sp, 0) 523 %rv_lw(fp, sp, 4) 524 %rv_mov_rr(sp, fp) 525 %rv_jalr(zero, br, 0) 526 %endm 527 528 %macro p1_tailr(rs) 529 %rv_lw(ra, sp, 0) 530 %rv_lw(fp, sp, 4) 531 %rv_mov_rr(sp, fp) 532 %rv_jalr(zero, rs, 0) 533 %endm 534 535 # Conditional branch: emit a skip-taken native branch over the `%p1_b` 536 # fall-through, then the jalr(br) that takes the P1 branch. Each native 537 # B-type here uses the inverted condition with a +8 offset so the `jalr` 538 # two insns below is the taken target. 539 %macro p1_condb_BEQ(ra, rb) 540 %rv_b_type_skip8(0x00001063, ra, rb) 541 %p1_b 542 %endm 543 %macro p1_condb_BNE(ra, rb) 544 %rv_b_type_skip8(0x00000063, ra, rb) 545 %p1_b 546 %endm 547 %macro p1_condb_BLT(ra, rb) 548 %rv_b_type_skip8(0x00005063, ra, rb) 549 %p1_b 550 %endm 551 %macro p1_condb_BLTU(ra, rb) 552 %rv_b_type_skip8(0x00007063, ra, rb) 553 %p1_b 554 %endm 555 %macro p1_condb(op, ra, rb) 556 %p1_condb_##op(ra, rb) 557 %endm 558 559 %macro p1_condbz_BEQZ(ra) 560 %rv_b_type_skip8(0x00001063, ra, zero) 561 %p1_b 562 %endm 563 %macro p1_condbz_BNEZ(ra) 564 %rv_b_type_skip8(0x00000063, ra, zero) 565 %p1_b 566 %endm 567 %macro p1_condbz_BLTZ(ra) 568 %rv_b_type_skip8(0x00005063, ra, zero) 569 %p1_b 570 %endm 571 %macro p1_condbz(op, ra) 572 %p1_condbz_##op(ra) 573 %endm 574 575 %macro p1_enter(size) 576 %rv_addi_any(sp, sp, (- 0 (& (+ (+ 8 size) 15) -16))) 577 %rv_sw(ra, sp, 0) 578 %rv_addi_any(fp, sp, (& (+ (+ 8 size) 15) -16)) 579 %rv_sw(fp, sp, 4) 580 %endm 581 582 %macro p1_entry() 583 # :_start stub per the P1 program-entry model. Linux RV32 puts argc 584 # at [sp] and argv starting at [sp+4], matching the generic SysV entry 585 # stack. Load argc into a0, compute &argv[0] into a1, call p1_main under 586 # the one-word direct-result convention, then issue sys_exit with the 587 # returned status. 588 :_start 589 %rv_lw(a0, sp, 0) 590 %rv_addi(a1, sp, 4) 591 %rv_lit32_prefix(br) 592 &p1_main 593 %rv_jalr(ra, br, 0) 594 %rv_addi(a7, zero, 93) 595 %rv_ecall 596 %endm 597 598 %macro p1_syscall() 599 # P1: a0=number, a1,a2,a3,t0,s0,s1 = args 0..5. 600 # Linux RISC-V: a7=number, a0..a5 = args 0..5, return in a0. 601 # SYSCALL clobbers only P1 a0; restore a1/a2/a3 after ecall. 602 # Native a4/a5 (x14/x15) aren't P1-exposed; we use them as syscall arg 603 # slots and don't need to save them. 604 %rv_mov_rr(save0, a1) 605 %rv_mov_rr(save1, a2) 606 %rv_mov_rr(save2, a3) 607 %rv_mov_rr(a7, a0) 608 %rv_mov_rr(a0, save0) 609 %rv_mov_rr(a1, save1) 610 %rv_mov_rr(a2, save2) 611 %rv_mov_rr(a3, t0) 612 %rv_mov_rr(a4, s0) 613 %rv_mov_rr(a5, s1) 614 %rv_ecall 615 %rv_mov_rr(a1, save0) 616 %rv_mov_rr(a2, save1) 617 %rv_mov_rr(a3, save2) 618 %endm 619 620 # ---- Linux RV32 syscall numbers ------------------------------------------ 621 # Each macro returns the syscall number as an integer atom so callers can 622 # use it inside expressions (e.g. `%li(a0, %sys_write)`). 623 624 %macro p1_sys_read() 625 63 626 %endm 627 %macro p1_sys_write() 628 64 629 %endm 630 %macro p1_sys_close() 631 57 632 %endm 633 %macro p1_sys_openat() 634 56 635 %endm 636 %macro p1_sys_exit() 637 93 638 %endm 639 %macro p1_sys_clone() 640 220 641 %endm 642 %macro p1_sys_execve() 643 221 644 %endm 645 %macro p1_sys_spawn() 646 1024 647 %endm 648 %macro p1_sys_waitid() 649 95 650 %endm 651 %macro p1_sys_lseek() 652 62 653 %endm 654 %macro p1_sys_lseek_wrapper() 655 # Linux RV32 exposes syscall 62 as _llseek(fd, hi, lo, result, whence), 656 # whereas the 64-bit ABIs expose lseek(fd, off, whence). Keep libp1pp's 657 # target-word API and adapt the signed 32-bit offset here. 658 %enter(12) 659 %st(s0, sp, 8) 660 %mov(s0, a2) 661 %mov(a3, a1) 662 %sari(a2, a1, 31) 663 %mov(t0, sp) 664 %mov(a1, a0) 665 %li(a0, %p1_sys_lseek) 666 %syscall 667 %ld(s0, sp, 8) 668 %bltz(a0, &@done) 669 %ld(a0, sp, 0) 670 :@done 671 %eret 672 %endm 673 %macro p1_sys_brk() 674 214 675 %endm 676 %macro p1_sys_unlinkat() 677 35 678 %endm