P1-amd64.M1pp (22819B)
1 # P1-amd64.M1pp -- P1 amd64 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. Native register mapping is backend-private; 21 # see the amd_reg_* table below. amd64 is variable-length, so every op 22 # emits its prefix bytes (REX / opcode) directly via the m1pp `!(…)` 23 # single-byte builtin; 4-byte immediates still go through `%(…)`. 24 # 25 # Hidden backend regs: 26 # br = r15 -- branch-target mechanism 27 # scratch = r9 -- per-expansion scratch (e.g. rcx save slot for SHIFT) 28 # rax -- syscall number / return slot + retaddr spill 29 # rbp -- spill slot for rcx / rdx when SHIFT and DIV/REM need 30 # to preserve a3 / a2 31 32 # ---- Native register numbers -------------------------------------------- 33 # 34 # Macros emit the 4-bit native regnum 0..15. Callers use `(& N 7)` for the 35 # ModRM/SIB low 3 bits and `(>> N 3)` for the REX high bit. 36 37 %macro amd_reg_a0() 38 7 39 %endm 40 %macro amd_reg_a1() 41 6 42 %endm 43 %macro amd_reg_a2() 44 2 45 %endm 46 %macro amd_reg_a3() 47 1 48 %endm 49 %macro amd_reg_t0() 50 10 51 %endm 52 %macro amd_reg_t1() 53 11 54 %endm 55 %macro amd_reg_t2() 56 8 57 %endm 58 %macro amd_reg_s0() 59 3 60 %endm 61 %macro amd_reg_s1() 62 12 63 %endm 64 %macro amd_reg_s2() 65 13 66 %endm 67 %macro amd_reg_s3() 68 14 69 %endm 70 %macro amd_reg_sp() 71 4 72 %endm 73 %macro amd_reg_rax() 74 0 75 %endm 76 %macro amd_reg_rcx() 77 1 78 %endm 79 %macro amd_reg_rdx() 80 2 81 %endm 82 %macro amd_reg_rbx() 83 3 84 %endm 85 %macro amd_reg_rsp() 86 4 87 %endm 88 %macro amd_reg_rbp() 89 5 90 %endm 91 %macro amd_reg_rsi() 92 6 93 %endm 94 %macro amd_reg_rdi() 95 7 96 %endm 97 %macro amd_reg_r8() 98 8 99 %endm 100 %macro amd_reg_r9() 101 9 102 %endm 103 %macro amd_reg_r10() 104 10 105 %endm 106 %macro amd_reg_r11() 107 11 108 %endm 109 %macro amd_reg_r12() 110 12 111 %endm 112 %macro amd_reg_r13() 113 13 114 %endm 115 %macro amd_reg_r14() 116 14 117 %endm 118 %macro amd_reg_r15() 119 15 120 %endm 121 %macro amd_reg_br() 122 15 123 %endm 124 %macro amd_reg_scratch() 125 9 126 %endm 127 128 %macro amd_reg(r) 129 %amd_reg_##r 130 %endm 131 132 # Per-P1-name `is this sp?` predicate. Used by p1_mem to decide whether 133 # the supplied offset needs the +16 frame-header adjustment. 134 135 %macro amd_is_sp_a0() 136 0 137 %endm 138 %macro amd_is_sp_a1() 139 0 140 %endm 141 %macro amd_is_sp_a2() 142 0 143 %endm 144 %macro amd_is_sp_a3() 145 0 146 %endm 147 %macro amd_is_sp_t0() 148 0 149 %endm 150 %macro amd_is_sp_t1() 151 0 152 %endm 153 %macro amd_is_sp_t2() 154 0 155 %endm 156 %macro amd_is_sp_s0() 157 0 158 %endm 159 %macro amd_is_sp_s1() 160 0 161 %endm 162 %macro amd_is_sp_s2() 163 0 164 %endm 165 %macro amd_is_sp_s3() 166 0 167 %endm 168 %macro amd_is_sp_sp() 169 1 170 %endm 171 172 %macro amd_is_sp(r) 173 %amd_is_sp_##r 174 %endm 175 176 # ---- REX / ModRM helpers ------------------------------------------------ 177 178 # Short one-byte REX.B prefix (no W). Used by opcodes that don't need 64-bit 179 # width — push/pop/jmp r/call r/mov r,imm32 — when the target reg is r8-r15. 180 %macro amd_rex_b_short() 181 !(0x41) 182 %endm 183 184 # No-op sentinel for %select branches that shouldn't emit anything. 185 %macro amd_nobytes() 186 %endm 187 188 # Emit REX.B (0x41) iff r is r8-r15. Used by the short-prefix opcodes above. 189 %macro amd_maybe_rex_b(r) 190 %select((>= %amd_reg(r) 8), 191 %amd_rex_b_short, 192 %amd_nobytes) 193 %endm 194 195 # REX.WB: W=1 for 64-bit, B=(r>>3) to extend ModRM.rm / SIB.base. 196 %macro amd_rex_wb(r) 197 !((| 0x48 (& (>> %amd_reg(r) 3) 1))) 198 %endm 199 200 # REX.WRB: W=1, R=(rg>>3), B=(rm>>3). Used whenever a ModRM.reg field is 201 # in use together with a ModRM.rm field. 202 %macro amd_rex_wrb(rg, rm) 203 !((| 0x48 (| (<< (& (>> %amd_reg(rg) 3) 1) 2) (& (>> %amd_reg(rm) 3) 1)))) 204 %endm 205 206 # ModRM byte for register/register: mod=3, reg=rg low3, rm=rm low3. 207 %macro amd_modrm_rr(rg, rm) 208 !((| 0xC0 (| (<< (& %amd_reg(rg) 7) 3) (& %amd_reg(rm) 7)))) 209 %endm 210 211 # ModRM /ext, rm: mod=3, reg=ext, rm=low3(rm). ext is 0..7. 212 %macro amd_modrm_ext_r(ext, rm) 213 !((| 0xC0 (| (<< ext 3) (& %amd_reg(rm) 7)))) 214 %endm 215 216 # ---- Memory-addressing ModRM (+ SIB + disp) ---------------------------- 217 # 218 # [base + disp] with `reg` in ModRM.reg. Bases whose low 3 bits are 100 — 219 # rsp and r12 — must go through a SIB byte; all others use the plain 220 # encoding. disp selects mod=1 (disp8) when it fits in [-128,127], else 221 # mod=2 (disp32). We never emit mod=0 / no-disp; the extra byte is fine. 222 223 %macro amd_modrm_disp8_plain(reg, base, disp) 224 !((| 0x40 (| (<< (& %amd_reg(reg) 7) 3) (& %amd_reg(base) 7)))) 225 !((& disp 0xFF)) 226 %endm 227 228 %macro amd_modrm_disp32_plain(reg, base, disp) 229 !((| 0x80 (| (<< (& %amd_reg(reg) 7) 3) (& %amd_reg(base) 7)))) 230 %((& disp 0xFFFFFFFF)) 231 %endm 232 233 %macro amd_modrm_disp8_sib(reg, disp) 234 !((| 0x44 (<< (& %amd_reg(reg) 7) 3))) 235 !(0x24) 236 !((& disp 0xFF)) 237 %endm 238 239 %macro amd_modrm_disp32_sib(reg, disp) 240 !((| 0x84 (<< (& %amd_reg(reg) 7) 3))) 241 !(0x24) 242 %((& disp 0xFFFFFFFF)) 243 %endm 244 245 %macro amd_modrm_disp_plain(reg, base, disp) 246 %select((>= disp -128), 247 %select((<= disp 127), 248 %amd_modrm_disp8_plain(reg, base, disp), 249 %amd_modrm_disp32_plain(reg, base, disp)), 250 %amd_modrm_disp32_plain(reg, base, disp)) 251 %endm 252 253 %macro amd_modrm_disp_sib(reg, disp) 254 %select((>= disp -128), 255 %select((<= disp 127), 256 %amd_modrm_disp8_sib(reg, disp), 257 %amd_modrm_disp32_sib(reg, disp)), 258 %amd_modrm_disp32_sib(reg, disp)) 259 %endm 260 261 %macro amd_modrm_disp(reg, base, disp) 262 %select((= (& %amd_reg(base) 7) 4), 263 %amd_modrm_disp_sib(reg, disp), 264 %amd_modrm_disp_plain(reg, base, disp)) 265 %endm 266 267 # ---- Register / arithmetic primitives ---------------------------------- 268 269 # mov dst, src -- 48 89 /r (modrm form: source in reg, dest in rm). 270 %macro amd_mov_rr(dst, src) 271 %amd_rex_wrb(src, dst) 272 !(0x89) 273 %amd_modrm_rr(src, dst) 274 %endm 275 276 # op dst, src for ADD/SUB/AND/OR/XOR (same shape, different opcode byte). 277 %macro amd_alu_rr(opcode, dst, src) 278 %amd_rex_wrb(src, dst) 279 !(opcode) 280 %amd_modrm_rr(src, dst) 281 %endm 282 283 # op dst, imm8 -- 48 83 /ext ib. 284 %macro amd_alu_ri8(ext, dst, imm) 285 %amd_rex_wb(dst) 286 !(0x83) 287 %amd_modrm_ext_r(ext, dst) 288 !((& imm 0xFF)) 289 %endm 290 291 # op dst, imm32 -- 48 81 /ext id. 292 %macro amd_alu_ri32(ext, dst, imm) 293 %amd_rex_wb(dst) 294 !(0x81) 295 %amd_modrm_ext_r(ext, dst) 296 %((& imm 0xFFFFFFFF)) 297 %endm 298 299 # shift dst, imm8 -- 48 C1 /ext ib. (ext: SHL=4, SHR=5, SAR=7) 300 %macro amd_shift_ri8(ext, dst, imm) 301 %amd_rex_wb(dst) 302 !(0xC1) 303 %amd_modrm_ext_r(ext, dst) 304 !((& imm 0x3F)) 305 %endm 306 307 # shift dst, cl -- 48 D3 /ext. 308 %macro amd_shift_cl(ext, dst) 309 %amd_rex_wb(dst) 310 !(0xD3) 311 %amd_modrm_ext_r(ext, dst) 312 %endm 313 314 # imul dst, src -- 48 0F AF /r (load source into reg, dest in rm? actually 315 # the canonical form is IMUL r64, r/m64 — dest in reg, source in rm.) 316 %macro amd_imul_rr(dst, src) 317 %amd_rex_wrb(dst, src) 318 !(0x0F) 319 !(0xAF) 320 %amd_modrm_rr(dst, src) 321 %endm 322 323 # idiv src -- 48 F7 /7. 324 %macro amd_idiv_r(src) 325 %amd_rex_wb(src) 326 !(0xF7) 327 %amd_modrm_ext_r(7, src) 328 %endm 329 330 # div src -- 48 F7 /6. 331 %macro amd_div_r(src) 332 %amd_rex_wb(src) 333 !(0xF7) 334 %amd_modrm_ext_r(6, src) 335 %endm 336 337 # cqo -- 48 99 (sign-extend rax into rdx:rax). 338 %macro amd_cqo() 339 !(0x48) 340 !(0x99) 341 %endm 342 343 # push / pop r64. 50+r / 58+r; REX.B=0x41 if r8-r15. 344 %macro amd_push(r) 345 %amd_maybe_rex_b(r) 346 !((| 0x50 (& %amd_reg(r) 7))) 347 %endm 348 349 %macro amd_pop(r) 350 %amd_maybe_rex_b(r) 351 !((| 0x58 (& %amd_reg(r) 7))) 352 %endm 353 354 # mov r32, imm32 -- B8+r id. Low-register form skips REX; r8-r15 need 355 # REX.B=0x41. The 4-byte literal the caller emits is zero-extended into 356 # the full 64-bit register, matching the LA / LA_BR literal-pool contract. 357 %macro amd_mov_imm32_prefix(rd) 358 %amd_maybe_rex_b(rd) 359 !((| 0xB8 (& %amd_reg(rd) 7))) 360 %endm 361 362 # mov r64, imm64 -- REX.W [+ REX.B] B8+r followed by 8 bytes of literal. 363 %macro amd_mov_imm64_prefix(rd) 364 %amd_rex_wb(rd) 365 !((| 0xB8 (& %amd_reg(rd) 7))) 366 %endm 367 368 # ---- Memory ops --------------------------------------------------------- 369 370 # mov rT, [rN + off] 48 8B /r modrm-with-disp 371 %macro amd_mem_LD(rt, rn, off) 372 %amd_rex_wrb(rt, rn) 373 !(0x8B) 374 %amd_modrm_disp(rt, rn, off) 375 %endm 376 377 # mov [rN + off], rT 48 89 /r 378 %macro amd_mem_ST(rt, rn, off) 379 %amd_rex_wrb(rt, rn) 380 !(0x89) 381 %amd_modrm_disp(rt, rn, off) 382 %endm 383 384 # mov [rN + off], rT8 48 88 /r (REX.W forces the rD8 encoding of 385 # dil/sil/bpl/spl when the byte view of those regs is needed.) 386 %macro amd_mem_SB(rt, rn, off) 387 %amd_rex_wrb(rt, rn) 388 !(0x88) 389 %amd_modrm_disp(rt, rn, off) 390 %endm 391 392 # movzx rT, byte ptr [rN + off] -- 48 0F B6 /r 393 %macro amd_mem_LB(rt, rn, off) 394 %amd_rex_wrb(rt, rn) 395 !(0x0F) 396 !(0xB6) 397 %amd_modrm_disp(rt, rn, off) 398 %endm 399 400 # ---- Control flow primitives ------------------------------------------- 401 402 # jmp r/m64 -- FF /4 403 # call r/m64 -- FF /2 404 # ret -- C3 405 # syscall -- 0F 05 406 # cmp rA, rB -- 48 39 /r (modrm: rB in reg, rA in rm) 407 # test rA, rA -- 48 85 /r 408 # Jcc rel8 -- 7x ib 409 410 %macro amd_jmp_r(r) 411 %amd_maybe_rex_b(r) 412 !(0xFF) 413 !((| 0xE0 (& %amd_reg(r) 7))) 414 %endm 415 416 %macro amd_call_r(r) 417 %amd_maybe_rex_b(r) 418 !(0xFF) 419 !((| 0xD0 (& %amd_reg(r) 7))) 420 %endm 421 422 %macro amd_ret() 423 !(0xC3) 424 %endm 425 426 %macro amd_syscall() 427 !(0x0F) 428 !(0x05) 429 %endm 430 431 # cmp rA, rB -- 48 39 /r (modrm: rB in reg, rA in rm). 432 %macro amd_cmp_rr(ra, rb) 433 %amd_rex_wrb(rb, ra) 434 !(0x39) 435 %amd_modrm_rr(rb, ra) 436 %endm 437 438 %macro amd_test_rr(ra, rb) 439 %amd_rex_wrb(rb, ra) 440 !(0x85) 441 %amd_modrm_rr(rb, ra) 442 %endm 443 444 # ---- P1 register-register op lowering ---------------------------------- 445 # 446 # For ADD/SUB/AND/OR/XOR we honor rD=rB aliasing — the naive `mov rD,rA ; 447 # op rD,rB` would clobber rB before the op reads it. Route rB through the 448 # scratch reg when that aliasing shows up. 449 450 %macro amd_rrr_simple_ADD(rd, ra, rb) 451 %amd_rrr_simple(0x01, rd, ra, rb) 452 %endm 453 %macro amd_rrr_simple_SUB(rd, ra, rb) 454 %amd_rrr_simple(0x29, rd, ra, rb) 455 %endm 456 %macro amd_rrr_simple_AND(rd, ra, rb) 457 %amd_rrr_simple(0x21, rd, ra, rb) 458 %endm 459 %macro amd_rrr_simple_OR(rd, ra, rb) 460 %amd_rrr_simple(0x09, rd, ra, rb) 461 %endm 462 %macro amd_rrr_simple_XOR(rd, ra, rb) 463 %amd_rrr_simple(0x31, rd, ra, rb) 464 %endm 465 466 %macro amd_rrr_simple(opcode, rd, ra, rb) 467 %select((= %amd_reg(rd) %amd_reg(rb)), 468 %amd_rrr_simple_via_scratch(opcode, rd, ra, rb), 469 %amd_rrr_simple_direct(opcode, rd, ra, rb)) 470 %endm 471 472 %macro amd_rrr_simple_direct(opcode, rd, ra, rb) 473 %amd_mov_rr(rd, ra) 474 %amd_alu_rr(opcode, rd, rb) 475 %endm 476 477 %macro amd_rrr_simple_via_scratch(opcode, rd, ra, rb) 478 %amd_mov_rr(scratch, rb) 479 %amd_mov_rr(rd, ra) 480 %amd_alu_rr(opcode, rd, scratch) 481 %endm 482 483 %macro amd_rrr_MUL(rd, ra, rb) 484 %select((= %amd_reg(rd) %amd_reg(rb)), 485 %amd_rrr_MUL_via_scratch(rd, ra, rb), 486 %amd_rrr_MUL_direct(rd, ra, rb)) 487 %endm 488 %macro amd_rrr_MUL_direct(rd, ra, rb) 489 %amd_mov_rr(rd, ra) 490 %amd_imul_rr(rd, rb) 491 %endm 492 %macro amd_rrr_MUL_via_scratch(rd, ra, rb) 493 %amd_mov_rr(scratch, rb) 494 %amd_mov_rr(rd, ra) 495 %amd_imul_rr(rd, scratch) 496 %endm 497 498 # DIV / REM clobber rax and rdx natively. rax is not a P1 register, so 499 # we clobber it freely; rdx IS P1 a2, so we stash it to rbp (also outside 500 # the P1 mapping) for the lifetime of the op. 501 # 502 # Aliasing-safety plan, same for DIV and REM: 503 # 1. rbp = rdx -- saved a2, also serves as "original rb 504 # if rb == a2" via the scratch copy 505 # 2. scratch = rb -- read rb while rdx still holds its 506 # original value (in case rb == a2) 507 # 3. rax = ra -- ra == a2 reads original rdx for the 508 # same reason; cqo hasn't run yet 509 # 4. cqo ; idiv scratch -- divide 510 # 5. rdx = rbp (restore) BEFORE -- so `mov rd, rax/rdx` below can 511 # writing rd legitimately overwrite rdx when 512 # rd == a2 without losing the result 513 # 6. mov rd, rax -- DIV quotient 514 # or capture rdx -> rax first, 515 # then rd = rax -- REM remainder (capture dodges the 516 # restore overwriting the remainder) 517 518 %macro amd_rrr_DIV(rd, ra, rb) 519 %amd_mov_rr(rbp, rdx) 520 %amd_mov_rr(scratch, rb) 521 %amd_mov_rr(rax, ra) 522 %amd_cqo 523 %amd_idiv_r(scratch) 524 %amd_mov_rr(rdx, rbp) 525 %amd_mov_rr(rd, rax) 526 %endm 527 528 %macro amd_rrr_REM(rd, ra, rb) 529 %amd_mov_rr(rbp, rdx) 530 %amd_mov_rr(scratch, rb) 531 %amd_mov_rr(rax, ra) 532 %amd_cqo 533 %amd_idiv_r(scratch) 534 %amd_mov_rr(rax, rdx) 535 %amd_mov_rr(rdx, rbp) 536 %amd_mov_rr(rd, rax) 537 %endm 538 539 %macro amd_rrr_UDIV(rd, ra, rb) 540 %amd_mov_rr(rbp, rdx) 541 %amd_mov_rr(scratch, rb) 542 %amd_mov_rr(rax, ra) 543 %amd_alu_rr(0x31, rdx, rdx) 544 %amd_div_r(scratch) 545 %amd_mov_rr(rdx, rbp) 546 %amd_mov_rr(rd, rax) 547 %endm 548 549 %macro amd_rrr_UREM(rd, ra, rb) 550 %amd_mov_rr(rbp, rdx) 551 %amd_mov_rr(scratch, rb) 552 %amd_mov_rr(rax, ra) 553 %amd_alu_rr(0x31, rdx, rdx) 554 %amd_div_r(scratch) 555 %amd_mov_rr(rax, rdx) 556 %amd_mov_rr(rdx, rbp) 557 %amd_mov_rr(rd, rax) 558 %endm 559 560 # SHL / SHR / SAR with reg count. x86 reads the count from CL only, so 561 # staging goes through rcx — which IS P1 a3. Save rcx to rbp for the 562 # duration. 563 # 564 # Ordering is load-bearing: 565 # 1. rbp = rcx -- save a3 566 # 2. scratch = ra -- read ra BEFORE we overwrite rcx; 567 # otherwise `ra == a3` reads the count 568 # we just staged 569 # 3. rcx = rb -- count into cl 570 # 4. shift scratch, cl -- do the work 571 # 5. rcx = rbp (restore) BEFORE -- so `mov rd, scratch` below can 572 # writing rd legitimately overwrite rcx when 573 # rd == a3 without losing the result 574 # 6. mov rd, scratch 575 576 %macro amd_rrr_SHL(rd, ra, rb) 577 %amd_rrr_shift(4, rd, ra, rb) 578 %endm 579 %macro amd_rrr_SHR(rd, ra, rb) 580 %amd_rrr_shift(5, rd, ra, rb) 581 %endm 582 %macro amd_rrr_SAR(rd, ra, rb) 583 %amd_rrr_shift(7, rd, ra, rb) 584 %endm 585 586 %macro amd_rrr_shift(ext, rd, ra, rb) 587 %amd_mov_rr(rbp, rcx) 588 %amd_mov_rr(scratch, ra) 589 %amd_mov_rr(rcx, rb) 590 %amd_shift_cl(ext, scratch) 591 %amd_mov_rr(rcx, rbp) 592 %amd_mov_rr(rd, scratch) 593 %endm 594 595 %macro amd_rrr_op(op, rd, ra, rb) 596 %amd_rrr_##op(rd, ra, rb) 597 %endm 598 599 %macro amd_rrr_ADD(rd, ra, rb) 600 %amd_rrr_simple_ADD(rd, ra, rb) 601 %endm 602 %macro amd_rrr_SUB(rd, ra, rb) 603 %amd_rrr_simple_SUB(rd, ra, rb) 604 %endm 605 %macro amd_rrr_AND(rd, ra, rb) 606 %amd_rrr_simple_AND(rd, ra, rb) 607 %endm 608 %macro amd_rrr_OR(rd, ra, rb) 609 %amd_rrr_simple_OR(rd, ra, rb) 610 %endm 611 %macro amd_rrr_XOR(rd, ra, rb) 612 %amd_rrr_simple_XOR(rd, ra, rb) 613 %endm 614 615 # ---- P1 operation lowering --------------------------------------------- 616 617 %macro p1_li(rd, imm) 618 %amd_mov_imm64_prefix(rd) 619 $(imm) 620 %endm 621 622 %macro p1_la(rd) 623 %amd_mov_imm32_prefix(rd) 624 %endm 625 626 %macro p1_labr() 627 %amd_mov_imm32_prefix(br) 628 %endm 629 630 %macro p1_mov(rd, rs) 631 %p1_mov_##rs(rd) 632 %endm 633 634 # All non-sp sources: plain register copy. 635 %macro p1_mov_a0(rd) 636 %amd_mov_rr(rd, a0) 637 %endm 638 %macro p1_mov_a1(rd) 639 %amd_mov_rr(rd, a1) 640 %endm 641 %macro p1_mov_a2(rd) 642 %amd_mov_rr(rd, a2) 643 %endm 644 %macro p1_mov_a3(rd) 645 %amd_mov_rr(rd, a3) 646 %endm 647 %macro p1_mov_t0(rd) 648 %amd_mov_rr(rd, t0) 649 %endm 650 %macro p1_mov_t1(rd) 651 %amd_mov_rr(rd, t1) 652 %endm 653 %macro p1_mov_t2(rd) 654 %amd_mov_rr(rd, t2) 655 %endm 656 %macro p1_mov_s0(rd) 657 %amd_mov_rr(rd, s0) 658 %endm 659 %macro p1_mov_s1(rd) 660 %amd_mov_rr(rd, s1) 661 %endm 662 %macro p1_mov_s2(rd) 663 %amd_mov_rr(rd, s2) 664 %endm 665 %macro p1_mov_s3(rd) 666 %amd_mov_rr(rd, s3) 667 %endm 668 669 # sp-source: portable sp is the frame-local base, which is native rsp + 16 670 # (the 16-byte backend-private frame header sits at [rsp+0..rsp+15]). 671 # Emit `mov rd, rsp ; add rd, 16`. 672 %macro p1_mov_sp(rd) 673 %amd_mov_rr(rd, sp) 674 %amd_alu_ri8(0, rd, 16) 675 %endm 676 677 %macro p1_rrr(op, rd, ra, rb) 678 %amd_rrr_op(op, rd, ra, rb) 679 %endm 680 681 %macro p1_addi(rd, ra, imm) 682 %amd_mov_rr(rd, ra) 683 %select((>= imm -128), 684 %select((<= imm 127), 685 %amd_alu_ri8(0, rd, imm), 686 %select((<= imm 2147483647), 687 %amd_alu_ri32(0, rd, imm), 688 %p1_li(scratch, imm) 689 %amd_rrr_ADD(rd, rd, scratch))), 690 %select((>= imm -2147483648), 691 %amd_alu_ri32(0, rd, imm), 692 %p1_li(scratch, imm) 693 %amd_rrr_ADD(rd, rd, scratch))) 694 %endm 695 696 # AND/OR with imm. Three windows: 697 # imm in [-128, 127] -> 83 /ext ib (imm8 sign-extends) 698 # imm in [INT32_MIN, INT32_MAX] -> 81 /ext id (imm32 sign-extends) 699 # else -> materialise imm in scratch, R-type AND/OR. 700 # The third arm covers positive imms above 0x7FFFFFFF (e.g. 0xFFFFFFFF 701 # or 0xDEADBEEF) where the imm32 sign-extension would silently flip the 702 # upper word to all-ones. 703 %macro p1_logi_ANDI(rd, ra, imm) 704 %amd_mov_rr(rd, ra) 705 %select((>= imm -128), 706 %select((<= imm 127), 707 %amd_alu_ri8(4, rd, imm), 708 %select((<= imm 2147483647), 709 %amd_alu_ri32(4, rd, imm), 710 %p1_li(scratch, imm) 711 %amd_rrr_AND(rd, rd, scratch))), 712 %select((>= imm -2147483648), 713 %amd_alu_ri32(4, rd, imm), 714 %p1_li(scratch, imm) 715 %amd_rrr_AND(rd, rd, scratch))) 716 %endm 717 %macro p1_logi_ORI(rd, ra, imm) 718 %amd_mov_rr(rd, ra) 719 %select((>= imm -128), 720 %select((<= imm 127), 721 %amd_alu_ri8(1, rd, imm), 722 %select((<= imm 2147483647), 723 %amd_alu_ri32(1, rd, imm), 724 %p1_li(scratch, imm) 725 %amd_rrr_OR(rd, rd, scratch))), 726 %select((>= imm -2147483648), 727 %amd_alu_ri32(1, rd, imm), 728 %p1_li(scratch, imm) 729 %amd_rrr_OR(rd, rd, scratch))) 730 %endm 731 %macro p1_logi(op, rd, ra, imm) 732 %p1_logi_##op(rd, ra, imm) 733 %endm 734 735 %macro p1_shifti_SHLI(rd, ra, imm) 736 %amd_mov_rr(rd, ra) 737 %amd_shift_ri8(4, rd, imm) 738 %endm 739 %macro p1_shifti_SHRI(rd, ra, imm) 740 %amd_mov_rr(rd, ra) 741 %amd_shift_ri8(5, rd, imm) 742 %endm 743 %macro p1_shifti_SARI(rd, ra, imm) 744 %amd_mov_rr(rd, ra) 745 %amd_shift_ri8(7, rd, imm) 746 %endm 747 %macro p1_shifti(op, rd, ra, imm) 748 %p1_shifti_##op(rd, ra, imm) 749 %endm 750 751 # p1_mem -- portable-offset memory access. When the base is sp, portable 752 # sp is the frame-local base (16 bytes above native rsp), so the physical 753 # access needs the supplied offset plus 16. For any other base, portable 754 # and native offsets coincide. Internal backend callers that need raw 755 # native-rsp access (p1_enter, p1_eret, _start stub, p1_ldarg, p1_syscall) 756 # use amd_mem_LD/amd_mem_ST directly and bypass this translation. 757 758 %macro p1_mem(op, rt, rn, off) 759 %select((= %amd_is_sp(rn) 1), 760 %amd_mem_##op(rt, rn, (+ off 16)), 761 %amd_mem_##op(rt, rn, off)) 762 %endm 763 764 %macro p1_ldarg(rd, slot) 765 %amd_mem_LD(scratch, sp, 8) 766 %amd_mem_LD(rd, scratch, (+ 16 (* 8 slot))) 767 %endm 768 769 %macro p1_b() 770 %amd_jmp_r(br) 771 %endm 772 773 %macro p1_br(rs) 774 %amd_jmp_r(rs) 775 %endm 776 777 %macro p1_call() 778 %amd_call_r(br) 779 %endm 780 781 %macro p1_callr(rs) 782 %amd_call_r(rs) 783 %endm 784 785 %macro p1_ret() 786 %amd_ret 787 %endm 788 789 # ERET -- atomic frame epilogue + return from a framed function. 790 # r9 = [rsp + 0] -- retaddr into scratch (native rsp; backend-private) 791 # rax = [rsp + 8] -- saved caller sp into rax (an unused native reg) 792 # rsp = rax -- unwind to caller sp 793 # push r9 -- reinstall retaddr so the trailing ret returns 794 # correctly 795 # ret -- pop reinstated retaddr into rip 796 %macro p1_eret() 797 %amd_mem_LD(scratch, sp, 0) 798 %amd_mem_LD(rax, sp, 8) 799 %amd_mov_rr(sp, rax) 800 %amd_push(scratch) 801 %amd_ret 802 %endm 803 804 # TAIL / TAILR -- frame epilogue followed by an unconditional jump to the 805 # target. The epilogue is the same sequence as the first four steps of 806 # p1_eret (we omit the trailing ret because we jmp to a fresh target 807 # instead). 808 %macro p1_tail() 809 %amd_mem_LD(scratch, sp, 0) 810 %amd_mem_LD(rax, sp, 8) 811 %amd_mov_rr(sp, rax) 812 %amd_push(scratch) 813 %amd_jmp_r(br) 814 %endm 815 816 %macro p1_tailr(rs) 817 %amd_mem_LD(scratch, sp, 0) 818 %amd_mem_LD(rax, sp, 8) 819 %amd_mov_rr(sp, rax) 820 %amd_push(scratch) 821 %amd_jmp_r(rs) 822 %endm 823 824 # Conditional-branch lowering: 825 # compare / test 826 # Jcc_inverse +3 skip the 3-byte `jmp r15` 827 # jmp r15 P1 branch-taken path 828 # 829 # Invert codes: BEQ->JNE(75), BNE->JE(74), BLT->JGE(7D), BLTU->JAE(73), 830 # BLTZ->JGE(7D), BEQZ->JNE(75), BNEZ->JE(74). 831 832 %macro p1_condb_BEQ(ra, rb) 833 %amd_cmp_rr(ra, rb) 834 !(0x75) 835 !(0x03) 836 %amd_jmp_r(br) 837 %endm 838 %macro p1_condb_BNE(ra, rb) 839 %amd_cmp_rr(ra, rb) 840 !(0x74) 841 !(0x03) 842 %amd_jmp_r(br) 843 %endm 844 %macro p1_condb_BLT(ra, rb) 845 %amd_cmp_rr(ra, rb) 846 !(0x7D) 847 !(0x03) 848 %amd_jmp_r(br) 849 %endm 850 %macro p1_condb_BLTU(ra, rb) 851 %amd_cmp_rr(ra, rb) 852 !(0x73) 853 !(0x03) 854 %amd_jmp_r(br) 855 %endm 856 %macro p1_condb(op, ra, rb) 857 %p1_condb_##op(ra, rb) 858 %endm 859 860 %macro p1_condbz_BEQZ(ra) 861 %amd_test_rr(ra, ra) 862 !(0x75) 863 !(0x03) 864 %amd_jmp_r(br) 865 %endm 866 %macro p1_condbz_BNEZ(ra) 867 %amd_test_rr(ra, ra) 868 !(0x74) 869 !(0x03) 870 %amd_jmp_r(br) 871 %endm 872 %macro p1_condbz_BLTZ(ra) 873 %amd_test_rr(ra, ra) 874 !(0x7D) 875 !(0x03) 876 %amd_jmp_r(br) 877 %endm 878 %macro p1_condbz(op, ra) 879 %p1_condbz_##op(ra) 880 %endm 881 882 # ENTER size 883 # 884 # CALL on amd64 pushed the retaddr, so on entry: 885 # rsp = caller_sp - 8 886 # [rsp] = retaddr 887 # 888 # We want the standard frame: 889 # [sp + 0] = retaddr 890 # [sp + 8] = saved caller_sp 891 # [sp + 16 .. 16 + size - 1] = locals 892 # total frame = round_up(16, 16 + size) 893 # 894 # Pop retaddr into scratch, save caller_sp into rax (unused by P1), 895 # allocate frame, restore retaddr at [sp], store caller_sp at [sp+8]. 896 %macro p1_enter(size) 897 %amd_pop(scratch) 898 %amd_mov_rr(rax, sp) 899 %amd_alu_ri32(5, sp, (& (+ (+ 16 size) 15) -16)) 900 %amd_mem_ST(scratch, sp, 0) 901 %amd_mem_ST(rax, sp, 8) 902 %endm 903 904 %macro p1_entry() 905 # :_start stub per the P1 program-entry model. Linux amd64 puts argc 906 # at [rsp] and argv starting at [rsp+8]. Load argc into a0 (rdi), 907 # compute &argv[0] into a1 (rsi), call p1_main under the one-word 908 # direct-result convention, then issue sys_exit with p1_main's return 909 # value in a0. 910 :_start 911 %amd_mem_LD(a0, sp, 0) 912 %amd_mov_rr(a1, sp) 913 %amd_alu_ri8(0, a1, 8) 914 %amd_mov_imm32_prefix(br) 915 &p1_main 916 %amd_call_r(br) 917 # mov eax, 60 (sys_exit); syscall. P1 a0 (native rdi) already holds 918 # p1_main's return value. 919 !(0xB8) 920 %(60) 921 !(0x0F) 922 !(0x05) 923 %endm 924 925 %macro p1_syscall() 926 # P1: a0=num, a1..a3,t0,s0,s1 = args 0..5. Linux amd64: rax=num, 927 # rdi/rsi/rdx/r10/r8/r9 = args 0..5, return in rax; syscall also 928 # clobbers rcx and r11. 929 # 930 # Plan: push the P1 registers whose native slots get overwritten or 931 # syscall-clobbered — rsi (a1), rdx (a2), rcx (a3), r11 (t1), r8 (t2) — 932 # then shuffle into the native slots, issue syscall, restore, and move 933 # the return value (rax) into a0 (rdi). 934 %amd_push(rsi) 935 %amd_push(rdx) 936 %amd_push(rcx) 937 %amd_push(r11) 938 %amd_push(r8) 939 940 %amd_mov_rr(rax, rdi) 941 %amd_mem_LD(rdi, sp, 32) 942 %amd_mem_LD(rsi, sp, 24) 943 %amd_mem_LD(rdx, sp, 16) 944 %amd_mov_rr(r8, rbx) 945 %amd_mov_rr(r9, r12) 946 947 !(0x0F) 948 !(0x05) 949 950 %amd_pop(r8) 951 %amd_pop(r11) 952 %amd_pop(rcx) 953 %amd_pop(rdx) 954 %amd_pop(rsi) 955 956 %amd_mov_rr(rdi, rax) 957 %endm 958 959 # ---- Linux amd64 syscall numbers ---------------------------------------- 960 # Each macro returns the syscall number as an integer atom so callers can 961 # use it inside expressions (e.g. `%li(a0, %sys_write)`). 962 963 %macro p1_sys_read() 964 0 965 %endm 966 %macro p1_sys_write() 967 1 968 %endm 969 %macro p1_sys_close() 970 3 971 %endm 972 %macro p1_sys_openat() 973 257 974 %endm 975 %macro p1_sys_exit() 976 60 977 %endm 978 %macro p1_sys_clone() 979 56 980 %endm 981 %macro p1_sys_execve() 982 59 983 %endm 984 %macro p1_sys_spawn() 985 1024 986 %endm 987 %macro p1_sys_waitid() 988 247 989 %endm 990 %macro p1_sys_lseek() 991 8 992 %endm 993 %macro p1_sys_lseek_wrapper() 994 %mov(a3, a2) 995 %mov(a2, a1) 996 %mov(a1, a0) 997 %li(a0, %p1_sys_lseek) 998 %syscall 999 %ret 1000 %endm 1001 %macro p1_sys_brk() 1002 12 1003 %endm 1004 %macro p1_sys_unlinkat() 1005 263 1006 %endm