boot2

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

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