kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

isa.c (70022B)


      1 /* RV64 instruction descriptor table + operand print dispatch.
      2  *
      3  * Mirrors the aa64_isa.c pattern. Each row records (mnemonic, match,
      4  * mask, format, flags); rv64_disasm_find returns the first row whose
      5  * masked bits match the word, and rv64_print_operands renders the
      6  * operand text using the format's unpack helper.
      7  *
      8  * Row ordering: first-match wins. Aliases (rows with RV64_ASMFL_ALIAS)
      9  * use tighter masks placed BEFORE the canonical row they alias so the
     10  * disassembler renders the alias spelling. The assembler accepts both
     11  * forms via rv64_asm_find which prefers the canonical row. */
     12 
     13 #include "arch/riscv/isa.h"
     14 
     15 #include <string.h>
     16 
     17 #include "core/slice.h"
     18 #include "core/strbuf.h"
     19 
     20 /* True if `s` begins with the NUL-terminated literal `pfx` (length-explicit).
     21  */
     22 static bool slice_has_prefix_cstr(Slice s, const char* pfx, size_t n) {
     23   return s.len >= n && memcmp(s.s, pfx, n) == 0;
     24 }
     25 
     26 /* Family-match bit patterns. The opcode (bits 6:0) plus
     27  * funct3/funct7/funct5 selectors narrow each match. For aliases we pin
     28  * specific register fields (e.g. rs1=x0 for `li`, rd=x0 for `j`). */
     29 
     30 /* Helper: build a 32-bit match for R-type with fixed funct7/funct3/op. */
     31 #define MATCH_R(funct7, funct3, op) \
     32   (((u32)(funct7) << 25) | ((u32)(funct3) << 12) | (u32)(op))
     33 #define MASK_R (0xfe00707fu) /* funct7 + funct3 + opcode */
     34 
     35 #define MATCH_I(funct3, op) (((u32)(funct3) << 12) | (u32)(op))
     36 #define MASK_I (0x0000707fu) /* funct3 + opcode */
     37 
     38 #define MATCH_S(funct3, op) (((u32)(funct3) << 12) | (u32)(op))
     39 #define MASK_S (0x0000707fu)
     40 
     41 #define MATCH_B(funct3, op) (((u32)(funct3) << 12) | (u32)(op))
     42 #define MASK_B (0x0000707fu)
     43 
     44 #define MATCH_U(op) ((u32)(op))
     45 #define MASK_U (0x0000007fu)
     46 
     47 #define MATCH_J(op) ((u32)(op))
     48 #define MASK_J (0x0000007fu)
     49 
     50 /* FP fused multiply-add/sub: rs3(31:27) fmt(26:25) rs2 rs1 rm rd op. */
     51 #define MATCH_R4(fmt, op) (((u32)(fmt) << 25) | (u32)(op))
     52 #define MASK_R4 (0x0600007fu)
     53 
     54 /* I-type shift in RV64: funct6 (bits 31:26) is the selector + opcode +
     55  * funct3. shamt occupies bits 25:20. */
     56 #define MATCH_ISHIFT(funct6, funct3, op) \
     57   (((u32)(funct6) << 26) | ((u32)(funct3) << 12) | (u32)(op))
     58 #define MASK_ISHIFT (0xfc00707fu)
     59 
     60 /* I-type shift in 32-bit (W) form uses 7-bit funct7 + 5-bit shamt. */
     61 #define MATCH_ISHIFTW(funct7, funct3, op) \
     62   (((u32)(funct7) << 25) | ((u32)(funct3) << 12) | (u32)(op))
     63 #define MASK_ISHIFTW (0xfe00707fu)
     64 
     65 /* AMO: aq/rl bits 26/25 vary, so mask must exclude them. funct5 is
     66  * bits[31:27]. */
     67 #define MATCH_AMO(funct5, funct3, op) \
     68   (((u32)(funct5) << 27) | ((u32)(funct3) << 12) | (u32)(op))
     69 #define MASK_AMO (0xf800707fu)
     70 #define MATCH_AMO_ORDER(funct5, aq, rl, funct3, op)                \
     71   (((u32)(funct5) << 27) | ((u32)(aq) << 26) | ((u32)(rl) << 25) | \
     72    ((u32)(funct3) << 12) | (u32)(op))
     73 #define MASK_AMO_ORDER (MASK_AMO | (3u << 25))
     74 
     75 /* FP arithmetic with rm — rm field (funct3) is don't-care. funct7
     76  * encodes op-major and format. */
     77 #define MATCH_FP_RM(funct7, op) (((u32)(funct7) << 25) | (u32)(op))
     78 #define MASK_FP_RM (0xfe00007fu)
     79 
     80 /* FP R-type with fixed funct3 (compare or sign-injection variants). */
     81 #define MATCH_FP_R(funct7, funct3, op) MATCH_R((funct7), (funct3), (op))
     82 #define MASK_FP_R MASK_R
     83 
     84 /* FP conversion: funct7 + rs2 (type selector) + funct3-as-rm don't-care
     85  * + opcode. The rs2 field (bits 24:20) selects integer width / signedness. */
     86 #define MATCH_FP_CVT(funct7, rs2, op) \
     87   (((u32)(funct7) << 25) | ((u32)(rs2) << 20) | (u32)(op))
     88 #define MASK_FP_CVT (0xfff0007fu)
     89 
     90 /* SYSTEM (ECALL/EBREAK) — full 32-bit value matches a single instruction. */
     91 #define MATCH_FULL(w) ((u32)(w))
     92 #define MASK_FULL (0xffffffffu)
     93 
     94 /* CSR — Zicsr. csr (imm12) is don't-care, but funct3+opcode pin the op. */
     95 #define MATCH_CSR(funct3) (((u32)(funct3) << 12) | (u32)RV_SYSTEM)
     96 #define MASK_CSR (0x0000707fu)
     97 
     98 /* Compressed 16-bit instructions live in low 16 bits of the descriptor
     99  * word; the mask zeroes bits 16+ to ensure a match against the C-decode
    100  * path which presents the halfword in low 16 bits. */
    101 #define MATCH_C(w16) ((u32)(w16))
    102 
    103 /* Mnemonic Slice literal for a static table row (compile-time length). */
    104 #define MN(s) {{(s)}, sizeof(s) - 1}
    105 
    106 const Rv64InsnDesc rv64_insn_table[] = {
    107     /* =================================================================
    108      * RV64I base — integer register ops (R-type, OP=0x33)
    109      * ================================================================= */
    110     {MN("add"), MATCH_R(0x00, 0x0, RV_OP), MASK_R, RV64_FMT_R, 0, 0, {0}},
    111     {MN("sub"), MATCH_R(0x20, 0x0, RV_OP), MASK_R, RV64_FMT_R, 0, 0, {0}},
    112     {MN("sll"), MATCH_R(0x00, 0x1, RV_OP), MASK_R, RV64_FMT_R, 0, 0, {0}},
    113     {MN("slt"), MATCH_R(0x00, 0x2, RV_OP), MASK_R, RV64_FMT_R, 0, 0, {0}},
    114     {MN("sltu"), MATCH_R(0x00, 0x3, RV_OP), MASK_R, RV64_FMT_R, 0, 0, {0}},
    115     {MN("xor"), MATCH_R(0x00, 0x4, RV_OP), MASK_R, RV64_FMT_R, 0, 0, {0}},
    116     {MN("srl"), MATCH_R(0x00, 0x5, RV_OP), MASK_R, RV64_FMT_R, 0, 0, {0}},
    117     {MN("sra"), MATCH_R(0x20, 0x5, RV_OP), MASK_R, RV64_FMT_R, 0, 0, {0}},
    118     {MN("or"), MATCH_R(0x00, 0x6, RV_OP), MASK_R, RV64_FMT_R, 0, 0, {0}},
    119     {MN("and"), MATCH_R(0x00, 0x7, RV_OP), MASK_R, RV64_FMT_R, 0, 0, {0}},
    120 
    121     /* 32-bit (W) variants — OP_32 = 0x3b (RV64-only major opcode) */
    122     {MN("addw"), MATCH_R(0x00, 0x0, RV_OP_32), MASK_R, RV64_FMT_R, 0,
    123      RV_AV_RV64, {0}},
    124     {MN("subw"), MATCH_R(0x20, 0x0, RV_OP_32), MASK_R, RV64_FMT_R, 0,
    125      RV_AV_RV64, {0}},
    126     {MN("sllw"), MATCH_R(0x00, 0x1, RV_OP_32), MASK_R, RV64_FMT_R, 0,
    127      RV_AV_RV64, {0}},
    128     {MN("srlw"), MATCH_R(0x00, 0x5, RV_OP_32), MASK_R, RV64_FMT_R, 0,
    129      RV_AV_RV64, {0}},
    130     {MN("sraw"), MATCH_R(0x20, 0x5, RV_OP_32), MASK_R, RV64_FMT_R, 0,
    131      RV_AV_RV64, {0}},
    132 
    133     /* ---- I-type immediate ALU (OP_IMM=0x13) ----
    134      * Aliases: `li rd, imm` = ADDI rd, x0, imm (rs1=x0).
    135      *          `mv rd, rs1` = ADDI rd, rs1, 0 (imm=0).
    136      *          `nop`        = ADDI x0, x0, 0 (full word fixed). */
    137     {MN("nop"),
    138      0x00000013u,
    139      0xffffffffu,
    140      RV64_FMT_SYSTEM,
    141      RV64_ASMFL_ALIAS,
    142      0, {0}},
    143     {MN("li"), 0x00000013u, 0x000f807fu, RV64_FMT_I, RV64_ASMFL_ALIAS, 0, {0}},
    144     /* mv: ADDI with imm=0. mask requires imm12=0 + funct3=0 + op. */
    145     {MN("mv"), 0x00000013u, 0xfff0707fu, RV64_FMT_I, RV64_ASMFL_ALIAS, 0, {0}},
    146     /* seqz: SLTIU rd, rs, 1 — funct3=3, imm12=1, op=OP_IMM. */
    147     {MN("seqz"),
    148      0x00103013u,
    149      0xfff0707fu,
    150      RV64_FMT_I,
    151      RV64_ASMFL_ALIAS,
    152      0, {0}},
    153     /* snez: SLTU rd, x0, rs2 — rs1=x0, funct3=3, op=OP. */
    154     {MN("snez"),
    155      0x00003033u,
    156      0xfe0ff07fu,
    157      RV64_FMT_R,
    158      RV64_ASMFL_ALIAS,
    159      0, {0}},
    160     /* not: XORI rd, rs, -1 — imm12=0xfff, funct3=4, op=OP_IMM. */
    161     {MN("not"), 0xfff04013u, 0xfff0707fu, RV64_FMT_I, RV64_ASMFL_ALIAS, 0, {0}},
    162     /* neg: SUB rd, x0, rs2 — rs1=x0, funct7=0x20, funct3=0. */
    163     {MN("neg"), 0x40000033u, 0xfe0ff07fu, RV64_FMT_R, RV64_ASMFL_ALIAS, 0, {0}},
    164     /* negw: SUBW rd, x0, rs2 (RV64-only, SUBW major opcode). */
    165     {MN("negw"),
    166      0x4000003bu,
    167      0xfe0ff07fu,
    168      RV64_FMT_R,
    169      RV64_ASMFL_ALIAS,
    170      RV_AV_RV64, {0}},
    171     {MN("addi"), MATCH_I(0x0, RV_OP_IMM), MASK_I, RV64_FMT_I, 0, 0, {0}},
    172     {MN("slti"), MATCH_I(0x2, RV_OP_IMM), MASK_I, RV64_FMT_I, 0, 0, {0}},
    173     {MN("sltiu"), MATCH_I(0x3, RV_OP_IMM), MASK_I, RV64_FMT_I, 0, 0, {0}},
    174     {MN("xori"), MATCH_I(0x4, RV_OP_IMM), MASK_I, RV64_FMT_I, 0, 0, {0}},
    175     {MN("ori"), MATCH_I(0x6, RV_OP_IMM), MASK_I, RV64_FMT_I, 0, 0, {0}},
    176     {MN("andi"), MATCH_I(0x7, RV_OP_IMM), MASK_I, RV64_FMT_I, 0, 0, {0}},
    177 
    178     /* RV64I shift-imm: funct6 in bits 31:26, shamt in 25:20. */
    179     {MN("slli"),
    180      MATCH_ISHIFT(0x00, 0x1, RV_OP_IMM),
    181      MASK_ISHIFT,
    182      RV64_FMT_I_SHIFT,
    183      0,
    184      0, {0}},
    185     {MN("srli"),
    186      MATCH_ISHIFT(0x00, 0x5, RV_OP_IMM),
    187      MASK_ISHIFT,
    188      RV64_FMT_I_SHIFT,
    189      0,
    190      0, {0}},
    191     {MN("srai"),
    192      MATCH_ISHIFT(0x10, 0x5, RV_OP_IMM),
    193      MASK_ISHIFT,
    194      RV64_FMT_I_SHIFT,
    195      0,
    196      0, {0}},
    197 
    198     /* OP_IMM_32: ADDIW + word shifts. sext.w alias = ADDIW rd, rs, 0.
    199      * OP_IMM_32 major opcode (0x1b) is absent on rv32 — all RV64-only. */
    200     {MN("sext.w"),
    201      0x0000001bu,
    202      0xfff0707fu,
    203      RV64_FMT_I,
    204      RV64_ASMFL_ALIAS,
    205      RV_AV_RV64, {0}},
    206     {MN("addiw"), MATCH_I(0x0, RV_OP_IMM_32), MASK_I, RV64_FMT_I, 0,
    207      RV_AV_RV64, {0}},
    208     {MN("slliw"),
    209      MATCH_ISHIFTW(0x00, 0x1, RV_OP_IMM_32),
    210      MASK_ISHIFTW,
    211      RV64_FMT_I_SHIFTW,
    212      0,
    213      RV_AV_RV64, {0}},
    214     {MN("srliw"),
    215      MATCH_ISHIFTW(0x00, 0x5, RV_OP_IMM_32),
    216      MASK_ISHIFTW,
    217      RV64_FMT_I_SHIFTW,
    218      0,
    219      RV_AV_RV64, {0}},
    220     {MN("sraiw"),
    221      MATCH_ISHIFTW(0x20, 0x5, RV_OP_IMM_32),
    222      MASK_ISHIFTW,
    223      RV64_FMT_I_SHIFTW,
    224      0,
    225      RV_AV_RV64, {0}},
    226 
    227     /* ---- LUI / AUIPC ---- */
    228     {MN("lui"), MATCH_U(RV_LUI), MASK_U, RV64_FMT_U, 0, 0, {0}},
    229     {MN("auipc"), MATCH_U(RV_AUIPC), MASK_U, RV64_FMT_U, 0, 0, {0}},
    230 
    231     /* ---- Loads (I-type, op=LOAD=0x03) ---- */
    232     {MN("lb"), MATCH_I(0x0, RV_LOAD), MASK_I, RV64_FMT_LOAD, 0, 0, {0}},
    233     {MN("lh"), MATCH_I(0x1, RV_LOAD), MASK_I, RV64_FMT_LOAD, 0, 0, {0}},
    234     {MN("lw"), MATCH_I(0x2, RV_LOAD), MASK_I, RV64_FMT_LOAD, 0, 0, {0}},
    235     {MN("ld"), MATCH_I(0x3, RV_LOAD), MASK_I, RV64_FMT_LOAD, 0,
    236      RV_AV_RV64, {0}}, /* LD funct3=3 RV64-only */
    237     {MN("lbu"), MATCH_I(0x4, RV_LOAD), MASK_I, RV64_FMT_LOAD, 0, 0, {0}},
    238     {MN("lhu"), MATCH_I(0x5, RV_LOAD), MASK_I, RV64_FMT_LOAD, 0, 0, {0}},
    239     {MN("lwu"), MATCH_I(0x6, RV_LOAD), MASK_I, RV64_FMT_LOAD, 0,
    240      RV_AV_RV64, {0}}, /* LWU funct3=6 RV64-only */
    241 
    242     /* ---- Stores (S-type, op=STORE=0x23) ---- */
    243     {MN("sb"), MATCH_S(0x0, RV_STORE), MASK_S, RV64_FMT_STORE, 0, 0, {0}},
    244     {MN("sh"), MATCH_S(0x1, RV_STORE), MASK_S, RV64_FMT_STORE, 0, 0, {0}},
    245     {MN("sw"), MATCH_S(0x2, RV_STORE), MASK_S, RV64_FMT_STORE, 0, 0, {0}},
    246     {MN("sd"), MATCH_S(0x3, RV_STORE), MASK_S, RV64_FMT_STORE, 0,
    247      RV_AV_RV64, {0}}, /* SD funct3=3 RV64-only */
    248 
    249     /* ---- Branches (B-type, op=BRANCH=0x63) ----
    250      * Aliases: `beqz rs, off` = BEQ rs, x0, off; `bnez rs, off` = BNE. */
    251     {MN("beqz"),
    252      0x00000063u,
    253      0x01f0707fu,
    254      RV64_FMT_B,
    255      RV64_ASMFL_ALIAS,
    256      0, {0}},
    257     {MN("bnez"),
    258      0x00001063u,
    259      0x01f0707fu,
    260      RV64_FMT_B,
    261      RV64_ASMFL_ALIAS,
    262      0, {0}},
    263     {MN("beq"), MATCH_B(0x0, RV_BRANCH), MASK_B, RV64_FMT_B, 0, 0, {0}},
    264     {MN("bne"), MATCH_B(0x1, RV_BRANCH), MASK_B, RV64_FMT_B, 0, 0, {0}},
    265     {MN("blt"), MATCH_B(0x4, RV_BRANCH), MASK_B, RV64_FMT_B, 0, 0, {0}},
    266     {MN("bge"), MATCH_B(0x5, RV_BRANCH), MASK_B, RV64_FMT_B, 0, 0, {0}},
    267     {MN("bltu"), MATCH_B(0x6, RV_BRANCH), MASK_B, RV64_FMT_B, 0, 0, {0}},
    268     {MN("bgeu"), MATCH_B(0x7, RV_BRANCH), MASK_B, RV64_FMT_B, 0, 0, {0}},
    269 
    270     /* ---- JAL / JALR ----
    271      * `j off`   = JAL x0, off    (rd=x0).
    272      * `jal off` = JAL ra, off    (rd=ra, single-operand form).
    273      * `ret`     = JALR x0, 0(ra) (rd=x0 + rs1=ra + imm=0).
    274      * `jr rs`   = JALR x0, 0(rs) (rd=x0, imm=0).
    275      * `jalr rs` = JALR ra, 0(rs) (rd=ra, imm=0). */
    276     {MN("ret"),
    277      0x00008067u,
    278      0xffffffffu,
    279      RV64_FMT_SYSTEM,
    280      RV64_ASMFL_ALIAS,
    281      0, {0}},
    282     {MN("jr"),
    283      0x00000067u,
    284      0xfff07fffu,
    285      RV64_FMT_JALR,
    286      RV64_ASMFL_ALIAS,
    287      0, {0}},
    288     {MN("j"), 0x0000006fu, 0x00000fffu, RV64_FMT_J, RV64_ASMFL_ALIAS, 0, {0}},
    289     {MN("jal"), MATCH_J(RV_JAL), MASK_J, RV64_FMT_J, 0, 0, {0}},
    290     {MN("jalr"), MATCH_I(0x0, RV_JALR), MASK_I, RV64_FMT_JALR, 0, 0, {0}},
    291 
    292     /* ---- Multi-word pseudo-instructions ----
    293      * `call sym`  = AUIPC ra, %pcrel_hi(sym); JALR ra, %pcrel_lo(ra) — one
    294      *               R_RV_CALL reloc at the AUIPC; the linker patches both.
    295      * `tail sym`  = AUIPC t1, ...; JALR zero, t1 — same R_RV_CALL reloc.
    296      * `la rd,sym` / `lla rd,sym` = AUIPC rd, %pcrel_hi(sym); ADDI rd, rd,
    297      *               %pcrel_lo. kit's static Local-Exec model treats `la`
    298      *               and `lla` identically (no GOT indirection). The match
    299      *               column is unused: RV64_FMT_PSEUDO dispatches on the
    300      *               mnemonic and emits the expansion directly. */
    301     {MN("call"), 0u, 0u, RV64_FMT_PSEUDO, RV64_ASMFL_PSEUDO, 0, {0}},
    302     {MN("tail"), 0u, 0u, RV64_FMT_PSEUDO, RV64_ASMFL_PSEUDO, 0, {0}},
    303     {MN("la"), 0u, 0u, RV64_FMT_PSEUDO, RV64_ASMFL_PSEUDO, 0, {0}},
    304     {MN("lla"), 0u, 0u, RV64_FMT_PSEUDO, RV64_ASMFL_PSEUDO, 0, {0}},
    305 
    306     /* ---- FENCE ---- */
    307     {MN("fence"), MATCH_I(0x0, RV_FENCE), MASK_I, RV64_FMT_FENCE, 0, 0, {0}},
    308     {MN("fence.i"),
    309      MATCH_FULL(0x0000100fu),
    310      MASK_FULL,
    311      RV64_FMT_SYSTEM,
    312      0,
    313      0, {0}},
    314 
    315     /* ---- System (ECALL/EBREAK) ---- */
    316     {MN("ecall"),
    317      MATCH_FULL(0x00000073u),
    318      MASK_FULL,
    319      RV64_FMT_SYSTEM,
    320      0,
    321      0, {0}},
    322     {MN("ebreak"),
    323      MATCH_FULL(0x00100073u),
    324      MASK_FULL,
    325      RV64_FMT_SYSTEM,
    326      0,
    327      0, {0}},
    328 
    329     /* =================================================================
    330      * Zicsr (CSR access) — RV_SYSTEM with funct3 ∈ {1..3, 5..7}.
    331      * ================================================================= */
    332     {MN("csrrw"), MATCH_CSR(0x1), MASK_CSR, RV64_FMT_CSR, 0, 0, {0}},
    333     {MN("csrrs"), MATCH_CSR(0x2), MASK_CSR, RV64_FMT_CSR, 0, 0, {0}},
    334     {MN("csrrc"), MATCH_CSR(0x3), MASK_CSR, RV64_FMT_CSR, 0, 0, {0}},
    335     {MN("csrrwi"), MATCH_CSR(0x5), MASK_CSR, RV64_FMT_CSRI, 0, 0, {0}},
    336     {MN("csrrsi"), MATCH_CSR(0x6), MASK_CSR, RV64_FMT_CSRI, 0, 0, {0}},
    337     {MN("csrrci"), MATCH_CSR(0x7), MASK_CSR, RV64_FMT_CSRI, 0, 0, {0}},
    338 
    339     /* ---- 2-operand CSR pseudo-instructions (assembler-only) ----
    340      *   csrr  rd, csr   = csrrs  rd, csr, x0
    341      *   csrw  csr, rs   = csrrw  x0, csr, rs
    342      *   csrs  csr, rs   = csrrs  x0, csr, rs
    343      *   csrc  csr, rs   = csrrc  x0, csr, rs
    344      *   csrwi csr, uimm = csrrwi x0, csr, uimm
    345      *   csrsi csr, uimm = csrrsi x0, csr, uimm
    346      *   csrci csr, uimm = csrrci x0, csr, uimm
    347      * The match word carries funct3+opcode so rv_emit_csr_pseudo can build the
    348      * I-type directly; the parse shape is selected on the mnemonic. These never
    349      * reach the disassembler (the full-form rows above own the decode side). */
    350     {MN("csrr"), MATCH_CSR(0x2), MASK_CSR, RV64_FMT_CSR_PSEUDO, 0, 0, {0}},
    351     {MN("csrw"), MATCH_CSR(0x1), MASK_CSR, RV64_FMT_CSR_PSEUDO, 0, 0, {0}},
    352     {MN("csrs"), MATCH_CSR(0x2), MASK_CSR, RV64_FMT_CSR_PSEUDO, 0, 0, {0}},
    353     {MN("csrc"), MATCH_CSR(0x3), MASK_CSR, RV64_FMT_CSR_PSEUDO, 0, 0, {0}},
    354     {MN("csrwi"), MATCH_CSR(0x5), MASK_CSR, RV64_FMT_CSR_PSEUDO, 0, 0, {0}},
    355     {MN("csrsi"), MATCH_CSR(0x6), MASK_CSR, RV64_FMT_CSR_PSEUDO, 0, 0, {0}},
    356     {MN("csrci"), MATCH_CSR(0x7), MASK_CSR, RV64_FMT_CSR_PSEUDO, 0, 0, {0}},
    357 
    358     /* =================================================================
    359      * RV64M (multiply / divide) — funct7 = 0x01
    360      * ================================================================= */
    361     {MN("mul"), MATCH_R(0x01, 0x0, RV_OP), MASK_R, RV64_FMT_R, 0, 0, {0}},
    362     {MN("mulh"), MATCH_R(0x01, 0x1, RV_OP), MASK_R, RV64_FMT_R, 0, 0, {0}},
    363     {MN("mulhsu"), MATCH_R(0x01, 0x2, RV_OP), MASK_R, RV64_FMT_R, 0, 0, {0}},
    364     {MN("mulhu"), MATCH_R(0x01, 0x3, RV_OP), MASK_R, RV64_FMT_R, 0, 0, {0}},
    365     {MN("div"), MATCH_R(0x01, 0x4, RV_OP), MASK_R, RV64_FMT_R, 0, 0, {0}},
    366     {MN("divu"), MATCH_R(0x01, 0x5, RV_OP), MASK_R, RV64_FMT_R, 0, 0, {0}},
    367     {MN("rem"), MATCH_R(0x01, 0x6, RV_OP), MASK_R, RV64_FMT_R, 0, 0, {0}},
    368     {MN("remu"), MATCH_R(0x01, 0x7, RV_OP), MASK_R, RV64_FMT_R, 0, 0, {0}},
    369     /* W-form multiply/divide — OP_32 major opcode, RV64-only. */
    370     {MN("mulw"), MATCH_R(0x01, 0x0, RV_OP_32), MASK_R, RV64_FMT_R, 0,
    371      RV_AV_RV64, {0}},
    372     {MN("divw"), MATCH_R(0x01, 0x4, RV_OP_32), MASK_R, RV64_FMT_R, 0,
    373      RV_AV_RV64, {0}},
    374     {MN("divuw"), MATCH_R(0x01, 0x5, RV_OP_32), MASK_R, RV64_FMT_R, 0,
    375      RV_AV_RV64, {0}},
    376     {MN("remw"), MATCH_R(0x01, 0x6, RV_OP_32), MASK_R, RV64_FMT_R, 0,
    377      RV_AV_RV64, {0}},
    378     {MN("remuw"), MATCH_R(0x01, 0x7, RV_OP_32), MASK_R, RV64_FMT_R, 0,
    379      RV_AV_RV64, {0}},
    380 
    381     /* =================================================================
    382      * RV32F / RV32D — single and double precision FP
    383      * ================================================================= */
    384     /* FP fused multiply-add/subtract — rm defaults to dyn in the assembler. */
    385     {MN("fmadd.s"),
    386      MATCH_R4(RV_FMT_S, RV_MADD),
    387      MASK_R4,
    388      RV64_FMT_R4,
    389      RV64_ASMFL_FP,
    390      0, {0}},
    391     {MN("fmsub.s"),
    392      MATCH_R4(RV_FMT_S, RV_MSUB),
    393      MASK_R4,
    394      RV64_FMT_R4,
    395      RV64_ASMFL_FP,
    396      0, {0}},
    397     {MN("fnmsub.s"),
    398      MATCH_R4(RV_FMT_S, RV_NMSUB),
    399      MASK_R4,
    400      RV64_FMT_R4,
    401      RV64_ASMFL_FP,
    402      0, {0}},
    403     {MN("fnmadd.s"),
    404      MATCH_R4(RV_FMT_S, RV_NMADD),
    405      MASK_R4,
    406      RV64_FMT_R4,
    407      RV64_ASMFL_FP,
    408      0, {0}},
    409     {MN("fmadd.d"),
    410      MATCH_R4(RV_FMT_D, RV_MADD),
    411      MASK_R4,
    412      RV64_FMT_R4,
    413      RV64_ASMFL_FP,
    414      0, {0}},
    415     {MN("fmsub.d"),
    416      MATCH_R4(RV_FMT_D, RV_MSUB),
    417      MASK_R4,
    418      RV64_FMT_R4,
    419      RV64_ASMFL_FP,
    420      0, {0}},
    421     {MN("fnmsub.d"),
    422      MATCH_R4(RV_FMT_D, RV_NMSUB),
    423      MASK_R4,
    424      RV64_FMT_R4,
    425      RV64_ASMFL_FP,
    426      0, {0}},
    427     {MN("fnmadd.d"),
    428      MATCH_R4(RV_FMT_D, RV_NMADD),
    429      MASK_R4,
    430      RV64_FMT_R4,
    431      RV64_ASMFL_FP,
    432      0, {0}},
    433 
    434     /* FP arithmetic — rm field (funct3) is the rounding mode and prints
    435      * as the DYN(=7) default suppressed. funct7 low bits select fmt. */
    436     {MN("fadd.s"),
    437      MATCH_FP_RM(0x00, RV_OP_FP),
    438      MASK_FP_RM,
    439      RV64_FMT_FP_RM,
    440      RV64_ASMFL_FP,
    441      0, {0}},
    442     {MN("fsub.s"),
    443      MATCH_FP_RM(0x04, RV_OP_FP),
    444      MASK_FP_RM,
    445      RV64_FMT_FP_RM,
    446      RV64_ASMFL_FP,
    447      0, {0}},
    448     {MN("fmul.s"),
    449      MATCH_FP_RM(0x08, RV_OP_FP),
    450      MASK_FP_RM,
    451      RV64_FMT_FP_RM,
    452      RV64_ASMFL_FP,
    453      0, {0}},
    454     {MN("fdiv.s"),
    455      MATCH_FP_RM(0x0c, RV_OP_FP),
    456      MASK_FP_RM,
    457      RV64_FMT_FP_RM,
    458      RV64_ASMFL_FP,
    459      0, {0}},
    460     {MN("fadd.d"),
    461      MATCH_FP_RM(0x01, RV_OP_FP),
    462      MASK_FP_RM,
    463      RV64_FMT_FP_RM,
    464      RV64_ASMFL_FP,
    465      0, {0}},
    466     {MN("fsub.d"),
    467      MATCH_FP_RM(0x05, RV_OP_FP),
    468      MASK_FP_RM,
    469      RV64_FMT_FP_RM,
    470      RV64_ASMFL_FP,
    471      0, {0}},
    472     {MN("fmul.d"),
    473      MATCH_FP_RM(0x09, RV_OP_FP),
    474      MASK_FP_RM,
    475      RV64_FMT_FP_RM,
    476      RV64_ASMFL_FP,
    477      0, {0}},
    478     {MN("fdiv.d"),
    479      MATCH_FP_RM(0x0d, RV_OP_FP),
    480      MASK_FP_RM,
    481      RV64_FMT_FP_RM,
    482      RV64_ASMFL_FP,
    483      0, {0}},
    484 
    485     /* FP sqrt — funct7 = 0x2c (S) / 0x2d (D), rs2 must be 0. */
    486     {MN("fsqrt.s"),
    487      MATCH_FP_CVT(0x2c, 0x0, RV_OP_FP),
    488      MASK_FP_CVT,
    489      RV64_FMT_FP_CVT,
    490      RV64_ASMFL_FP,
    491      0, {0}},
    492     {MN("fsqrt.d"),
    493      MATCH_FP_CVT(0x2d, 0x0, RV_OP_FP),
    494      MASK_FP_CVT,
    495      RV64_FMT_FP_CVT,
    496      RV64_ASMFL_FP,
    497      0, {0}},
    498 
    499     /* FP min/max — funct7 = 0x14/0x15, funct3 = 0 (min) / 1 (max). */
    500     {MN("fmin.s"),
    501      MATCH_FP_R(0x14, 0x0, RV_OP_FP),
    502      MASK_FP_R,
    503      RV64_FMT_FP_R,
    504      RV64_ASMFL_FP | RV64_ASMFL_NORM,
    505      0, {0}},
    506     {MN("fmax.s"),
    507      MATCH_FP_R(0x14, 0x1, RV_OP_FP),
    508      MASK_FP_R,
    509      RV64_FMT_FP_R,
    510      RV64_ASMFL_FP | RV64_ASMFL_NORM,
    511      0, {0}},
    512     {MN("fmin.d"),
    513      MATCH_FP_R(0x15, 0x0, RV_OP_FP),
    514      MASK_FP_R,
    515      RV64_FMT_FP_R,
    516      RV64_ASMFL_FP | RV64_ASMFL_NORM,
    517      0, {0}},
    518     {MN("fmax.d"),
    519      MATCH_FP_R(0x15, 0x1, RV_OP_FP),
    520      MASK_FP_R,
    521      RV64_FMT_FP_R,
    522      RV64_ASMFL_FP | RV64_ASMFL_NORM,
    523      0, {0}},
    524 
    525     /* FP sign-injection — funct7 = 0x10/0x11, funct3 = 0/1/2 = J/JN/JX. */
    526     {MN("fsgnj.s"),
    527      MATCH_FP_R(0x10, 0x0, RV_OP_FP),
    528      MASK_FP_R,
    529      RV64_FMT_FP_R,
    530      RV64_ASMFL_FP | RV64_ASMFL_NORM,
    531      0, {0}},
    532     {MN("fsgnjn.s"),
    533      MATCH_FP_R(0x10, 0x1, RV_OP_FP),
    534      MASK_FP_R,
    535      RV64_FMT_FP_R,
    536      RV64_ASMFL_FP | RV64_ASMFL_NORM,
    537      0, {0}},
    538     {MN("fsgnjx.s"),
    539      MATCH_FP_R(0x10, 0x2, RV_OP_FP),
    540      MASK_FP_R,
    541      RV64_FMT_FP_R,
    542      RV64_ASMFL_FP | RV64_ASMFL_NORM,
    543      0, {0}},
    544     {MN("fsgnj.d"),
    545      MATCH_FP_R(0x11, 0x0, RV_OP_FP),
    546      MASK_FP_R,
    547      RV64_FMT_FP_R,
    548      RV64_ASMFL_FP | RV64_ASMFL_NORM,
    549      0, {0}},
    550     {MN("fsgnjn.d"),
    551      MATCH_FP_R(0x11, 0x1, RV_OP_FP),
    552      MASK_FP_R,
    553      RV64_FMT_FP_R,
    554      RV64_ASMFL_FP | RV64_ASMFL_NORM,
    555      0, {0}},
    556     {MN("fsgnjx.d"),
    557      MATCH_FP_R(0x11, 0x2, RV_OP_FP),
    558      MASK_FP_R,
    559      RV64_FMT_FP_R,
    560      RV64_ASMFL_FP | RV64_ASMFL_NORM,
    561      0, {0}},
    562 
    563     /* FP compare — funct7 = 0x50 (S) / 0x51 (D), funct3 = 0/1/2 = LE/LT/EQ.
    564      * rd is integer GPR (not FP). */
    565     {MN("fle.s"),
    566      MATCH_FP_R(0x50, 0x0, RV_OP_FP),
    567      MASK_FP_R,
    568      RV64_FMT_FP_R,
    569      RV64_ASMFL_NORM,
    570      0, {0}},
    571     {MN("flt.s"),
    572      MATCH_FP_R(0x50, 0x1, RV_OP_FP),
    573      MASK_FP_R,
    574      RV64_FMT_FP_R,
    575      RV64_ASMFL_NORM,
    576      0, {0}},
    577     {MN("feq.s"),
    578      MATCH_FP_R(0x50, 0x2, RV_OP_FP),
    579      MASK_FP_R,
    580      RV64_FMT_FP_R,
    581      RV64_ASMFL_NORM,
    582      0, {0}},
    583     {MN("fle.d"),
    584      MATCH_FP_R(0x51, 0x0, RV_OP_FP),
    585      MASK_FP_R,
    586      RV64_FMT_FP_R,
    587      RV64_ASMFL_NORM,
    588      0, {0}},
    589     {MN("flt.d"),
    590      MATCH_FP_R(0x51, 0x1, RV_OP_FP),
    591      MASK_FP_R,
    592      RV64_FMT_FP_R,
    593      RV64_ASMFL_NORM,
    594      0, {0}},
    595     {MN("feq.d"),
    596      MATCH_FP_R(0x51, 0x2, RV_OP_FP),
    597      MASK_FP_R,
    598      RV64_FMT_FP_R,
    599      RV64_ASMFL_NORM,
    600      0, {0}},
    601 
    602     /* FP classification — rd is GPR, rs1 is FPR, rs2=0, rm/funct3=1. */
    603     {MN("fclass.s"),
    604      MATCH_FP_R(0x70, 0x1, RV_OP_FP) | (0u << 20),
    605      MASK_FP_CVT | (7u << 12),
    606      RV64_FMT_FP_CVT,
    607      0,
    608      0, {0}},
    609     {MN("fclass.d"),
    610      MATCH_FP_R(0x71, 0x1, RV_OP_FP) | (0u << 20),
    611      MASK_FP_CVT | (7u << 12),
    612      RV64_FMT_FP_CVT,
    613      0,
    614      0, {0}},
    615 
    616     /* FP conversions — funct7 selects {direction, fmt}, rs2 selects
    617      * integer width/signedness. */
    618     {MN("fcvt.w.s"),
    619      MATCH_FP_CVT(0x60, 0x0, RV_OP_FP),
    620      MASK_FP_CVT,
    621      RV64_FMT_FP_CVT,
    622      0,
    623      0, {0}},
    624     {MN("fcvt.wu.s"),
    625      MATCH_FP_CVT(0x60, 0x1, RV_OP_FP),
    626      MASK_FP_CVT,
    627      RV64_FMT_FP_CVT,
    628      0,
    629      0, {0}},
    630     {MN("fcvt.l.s"),
    631      MATCH_FP_CVT(0x60, 0x2, RV_OP_FP),
    632      MASK_FP_CVT,
    633      RV64_FMT_FP_CVT,
    634      0,
    635      RV_AV_RV64, {0}}, /* 64-bit int dest needs 64-bit GPR */
    636     {MN("fcvt.lu.s"),
    637      MATCH_FP_CVT(0x60, 0x3, RV_OP_FP),
    638      MASK_FP_CVT,
    639      RV64_FMT_FP_CVT,
    640      0,
    641      RV_AV_RV64, {0}},
    642     {MN("fcvt.w.d"),
    643      MATCH_FP_CVT(0x61, 0x0, RV_OP_FP),
    644      MASK_FP_CVT,
    645      RV64_FMT_FP_CVT,
    646      0,
    647      0, {0}},
    648     {MN("fcvt.wu.d"),
    649      MATCH_FP_CVT(0x61, 0x1, RV_OP_FP),
    650      MASK_FP_CVT,
    651      RV64_FMT_FP_CVT,
    652      0,
    653      0, {0}},
    654     {MN("fcvt.l.d"),
    655      MATCH_FP_CVT(0x61, 0x2, RV_OP_FP),
    656      MASK_FP_CVT,
    657      RV64_FMT_FP_CVT,
    658      0,
    659      RV_AV_RV64, {0}},
    660     {MN("fcvt.lu.d"),
    661      MATCH_FP_CVT(0x61, 0x3, RV_OP_FP),
    662      MASK_FP_CVT,
    663      RV64_FMT_FP_CVT,
    664      0,
    665      RV_AV_RV64, {0}},
    666     {MN("fcvt.s.w"),
    667      MATCH_FP_CVT(0x68, 0x0, RV_OP_FP),
    668      MASK_FP_CVT,
    669      RV64_FMT_FP_CVT,
    670      RV64_ASMFL_FP,
    671      0, {0}},
    672     {MN("fcvt.s.wu"),
    673      MATCH_FP_CVT(0x68, 0x1, RV_OP_FP),
    674      MASK_FP_CVT,
    675      RV64_FMT_FP_CVT,
    676      RV64_ASMFL_FP,
    677      0, {0}},
    678     {MN("fcvt.s.l"),
    679      MATCH_FP_CVT(0x68, 0x2, RV_OP_FP),
    680      MASK_FP_CVT,
    681      RV64_FMT_FP_CVT,
    682      RV64_ASMFL_FP,
    683      RV_AV_RV64, {0}},
    684     {MN("fcvt.s.lu"),
    685      MATCH_FP_CVT(0x68, 0x3, RV_OP_FP),
    686      MASK_FP_CVT,
    687      RV64_FMT_FP_CVT,
    688      RV64_ASMFL_FP,
    689      RV_AV_RV64, {0}},
    690     {MN("fcvt.d.w"),
    691      MATCH_FP_CVT(0x69, 0x0, RV_OP_FP),
    692      MASK_FP_CVT,
    693      RV64_FMT_FP_CVT,
    694      RV64_ASMFL_FP,
    695      0, {0}},
    696     {MN("fcvt.d.wu"),
    697      MATCH_FP_CVT(0x69, 0x1, RV_OP_FP),
    698      MASK_FP_CVT,
    699      RV64_FMT_FP_CVT,
    700      RV64_ASMFL_FP,
    701      0, {0}},
    702     {MN("fcvt.d.l"),
    703      MATCH_FP_CVT(0x69, 0x2, RV_OP_FP),
    704      MASK_FP_CVT,
    705      RV64_FMT_FP_CVT,
    706      RV64_ASMFL_FP,
    707      RV_AV_RV64, {0}},
    708     {MN("fcvt.d.lu"),
    709      MATCH_FP_CVT(0x69, 0x3, RV_OP_FP),
    710      MASK_FP_CVT,
    711      RV64_FMT_FP_CVT,
    712      RV64_ASMFL_FP,
    713      RV_AV_RV64, {0}},
    714     {MN("fcvt.s.d"),
    715      MATCH_FP_CVT(0x20, 0x1, RV_OP_FP),
    716      MASK_FP_CVT,
    717      RV64_FMT_FP_CVT,
    718      RV64_ASMFL_FP,
    719      0, {0}},
    720     {MN("fcvt.d.s"),
    721      MATCH_FP_CVT(0x21, 0x0, RV_OP_FP),
    722      MASK_FP_CVT,
    723      RV64_FMT_FP_CVT,
    724      RV64_ASMFL_FP,
    725      0, {0}},
    726 
    727     /* FP bitcast moves — funct7 + rs2=0 + funct3=0 fixed. */
    728     {MN("fmv.x.w"),
    729      MATCH_FP_CVT(0x70, 0x0, RV_OP_FP),
    730      MASK_FP_CVT,
    731      RV64_FMT_FP_CVT,
    732      0,
    733      0, {0}},
    734     {MN("fmv.w.x"),
    735      MATCH_FP_CVT(0x78, 0x0, RV_OP_FP),
    736      MASK_FP_CVT,
    737      RV64_FMT_FP_CVT,
    738      RV64_ASMFL_FP,
    739      0, {0}},
    740     {MN("fmv.x.d"),
    741      MATCH_FP_CVT(0x71, 0x0, RV_OP_FP),
    742      MASK_FP_CVT,
    743      RV64_FMT_FP_CVT,
    744      0,
    745      RV_AV_RV64, {0}}, /* moves a 64-bit double through a GPR */
    746     {MN("fmv.d.x"),
    747      MATCH_FP_CVT(0x79, 0x0, RV_OP_FP),
    748      MASK_FP_CVT,
    749      RV64_FMT_FP_CVT,
    750      RV64_ASMFL_FP,
    751      RV_AV_RV64, {0}},
    752 
    753     /* FP load/store */
    754     {MN("flw"),
    755      MATCH_I(0x2, RV_LOAD_FP),
    756      MASK_I,
    757      RV64_FMT_FP_LOAD,
    758      RV64_ASMFL_FP,
    759      0, {0}},
    760     {MN("fld"),
    761      MATCH_I(0x3, RV_LOAD_FP),
    762      MASK_I,
    763      RV64_FMT_FP_LOAD,
    764      RV64_ASMFL_FP,
    765      0, {0}},
    766     {MN("fsw"),
    767      MATCH_S(0x2, RV_STORE_FP),
    768      MASK_S,
    769      RV64_FMT_FP_STORE,
    770      RV64_ASMFL_FP,
    771      0, {0}},
    772     {MN("fsd"),
    773      MATCH_S(0x3, RV_STORE_FP),
    774      MASK_S,
    775      RV64_FMT_FP_STORE,
    776      RV64_ASMFL_FP,
    777      0, {0}},
    778 
    779     /* =================================================================
    780      * RV64A (atomic) — AMO funct5 + funct3 (W=2, D=3). aq/rl vary, so
    781      * mask leaves bits 26:25 free. We expose the .aq/.rl ordering
    782      * suffixes via the disassembler's annotation, but the row mnemonic
    783      * itself is the bare form (e.g. "amoadd.w").
    784      * ================================================================= */
    785     {MN("lr.w.aq"),
    786      MATCH_AMO_ORDER(0x02, 1, 0, 0x2, RV_AMO),
    787      MASK_AMO_ORDER | (0x1fu << 20),
    788      RV64_FMT_LR,
    789      0,
    790      0, {0}},
    791     {MN("lr.w.rl"),
    792      MATCH_AMO_ORDER(0x02, 0, 1, 0x2, RV_AMO),
    793      MASK_AMO_ORDER | (0x1fu << 20),
    794      RV64_FMT_LR,
    795      0,
    796      0, {0}},
    797     {MN("lr.w.aqrl"),
    798      MATCH_AMO_ORDER(0x02, 1, 1, 0x2, RV_AMO),
    799      MASK_AMO_ORDER | (0x1fu << 20),
    800      RV64_FMT_LR,
    801      0,
    802      0, {0}},
    803     {MN("lr.d.aq"),
    804      MATCH_AMO_ORDER(0x02, 1, 0, 0x3, RV_AMO),
    805      MASK_AMO_ORDER | (0x1fu << 20),
    806      RV64_FMT_LR,
    807      0,
    808      RV_AV_RV64, {0}},
    809     {MN("lr.d.rl"),
    810      MATCH_AMO_ORDER(0x02, 0, 1, 0x3, RV_AMO),
    811      MASK_AMO_ORDER | (0x1fu << 20),
    812      RV64_FMT_LR,
    813      0,
    814      RV_AV_RV64, {0}},
    815     {MN("lr.d.aqrl"),
    816      MATCH_AMO_ORDER(0x02, 1, 1, 0x3, RV_AMO),
    817      MASK_AMO_ORDER | (0x1fu << 20),
    818      RV64_FMT_LR,
    819      0,
    820      RV_AV_RV64, {0}},
    821     {MN("sc.w.aq"),
    822      MATCH_AMO_ORDER(0x03, 1, 0, 0x2, RV_AMO),
    823      MASK_AMO_ORDER,
    824      RV64_FMT_AMO,
    825      0,
    826      0, {0}},
    827     {MN("sc.w.rl"),
    828      MATCH_AMO_ORDER(0x03, 0, 1, 0x2, RV_AMO),
    829      MASK_AMO_ORDER,
    830      RV64_FMT_AMO,
    831      0,
    832      0, {0}},
    833     {MN("sc.w.aqrl"),
    834      MATCH_AMO_ORDER(0x03, 1, 1, 0x2, RV_AMO),
    835      MASK_AMO_ORDER,
    836      RV64_FMT_AMO,
    837      0,
    838      0, {0}},
    839     {MN("sc.d.aq"),
    840      MATCH_AMO_ORDER(0x03, 1, 0, 0x3, RV_AMO),
    841      MASK_AMO_ORDER,
    842      RV64_FMT_AMO,
    843      0,
    844      RV_AV_RV64, {0}},
    845     {MN("sc.d.rl"),
    846      MATCH_AMO_ORDER(0x03, 0, 1, 0x3, RV_AMO),
    847      MASK_AMO_ORDER,
    848      RV64_FMT_AMO,
    849      0,
    850      RV_AV_RV64, {0}},
    851     {MN("sc.d.aqrl"),
    852      MATCH_AMO_ORDER(0x03, 1, 1, 0x3, RV_AMO),
    853      MASK_AMO_ORDER,
    854      RV64_FMT_AMO,
    855      0,
    856      RV_AV_RV64, {0}},
    857 /* `av` tags the doubleword (.d) atomics RV_AV_RV64 (RV64-only); word (.w)
    858  * forms pass 0 (BOTH). The av byte sits between flags and pad[1]. */
    859 #define RV64_AMO_ORDER_ROWS(mn, f5, f3, av)                                \
    860   {MN(mn ".aq"),                                                           \
    861    MATCH_AMO_ORDER(f5, 1, 0, f3, RV_AMO),                                  \
    862    MASK_AMO_ORDER,                                                         \
    863    RV64_FMT_AMO,                                                           \
    864    0,                                                                      \
    865    (av), {0}},                                                             \
    866       {MN(mn ".rl"),                                                       \
    867        MATCH_AMO_ORDER(f5, 0, 1, f3, RV_AMO),                              \
    868        MASK_AMO_ORDER,                                                     \
    869        RV64_FMT_AMO,                                                       \
    870        0,                                                                  \
    871        (av), {0}},                                                         \
    872   {                                                                        \
    873     MN(mn ".aqrl"), MATCH_AMO_ORDER(f5, 1, 1, f3, RV_AMO), MASK_AMO_ORDER, \
    874         RV64_FMT_AMO, 0, (av), {0}                                         \
    875   }
    876     RV64_AMO_ORDER_ROWS("amoswap.w", RV_AMO_SWAP, 0x2, 0),
    877     RV64_AMO_ORDER_ROWS("amoadd.w", RV_AMO_ADD, 0x2, 0),
    878     RV64_AMO_ORDER_ROWS("amoxor.w", RV_AMO_XOR, 0x2, 0),
    879     RV64_AMO_ORDER_ROWS("amoand.w", RV_AMO_AND, 0x2, 0),
    880     RV64_AMO_ORDER_ROWS("amoor.w", RV_AMO_OR, 0x2, 0),
    881     RV64_AMO_ORDER_ROWS("amomin.w", RV_AMO_MIN, 0x2, 0),
    882     RV64_AMO_ORDER_ROWS("amomax.w", RV_AMO_MAX, 0x2, 0),
    883     RV64_AMO_ORDER_ROWS("amominu.w", RV_AMO_MINU, 0x2, 0),
    884     RV64_AMO_ORDER_ROWS("amomaxu.w", RV_AMO_MAXU, 0x2, 0),
    885     RV64_AMO_ORDER_ROWS("amoswap.d", RV_AMO_SWAP, 0x3, RV_AV_RV64),
    886     RV64_AMO_ORDER_ROWS("amoadd.d", RV_AMO_ADD, 0x3, RV_AV_RV64),
    887     RV64_AMO_ORDER_ROWS("amoxor.d", RV_AMO_XOR, 0x3, RV_AV_RV64),
    888     RV64_AMO_ORDER_ROWS("amoand.d", RV_AMO_AND, 0x3, RV_AV_RV64),
    889     RV64_AMO_ORDER_ROWS("amoor.d", RV_AMO_OR, 0x3, RV_AV_RV64),
    890     RV64_AMO_ORDER_ROWS("amomin.d", RV_AMO_MIN, 0x3, RV_AV_RV64),
    891     RV64_AMO_ORDER_ROWS("amomax.d", RV_AMO_MAX, 0x3, RV_AV_RV64),
    892     RV64_AMO_ORDER_ROWS("amominu.d", RV_AMO_MINU, 0x3, RV_AV_RV64),
    893     RV64_AMO_ORDER_ROWS("amomaxu.d", RV_AMO_MAXU, 0x3, RV_AV_RV64),
    894     {MN("lr.w"),
    895      MATCH_AMO(0x02, 0x2, RV_AMO),
    896      MASK_AMO | (0x1fu << 20),
    897      RV64_FMT_LR,
    898      0,
    899      0, {0}},
    900     {MN("lr.d"),
    901      MATCH_AMO(0x02, 0x3, RV_AMO),
    902      MASK_AMO | (0x1fu << 20),
    903      RV64_FMT_LR,
    904      0,
    905      RV_AV_RV64, {0}},
    906     {MN("sc.w"),
    907      MATCH_AMO(0x03, 0x2, RV_AMO),
    908      MASK_AMO,
    909      RV64_FMT_AMO,
    910      0,
    911      0, {0}},
    912     {MN("sc.d"),
    913      MATCH_AMO(0x03, 0x3, RV_AMO),
    914      MASK_AMO,
    915      RV64_FMT_AMO,
    916      0,
    917      RV_AV_RV64, {0}},
    918     {MN("amoswap.w"),
    919      MATCH_AMO(RV_AMO_SWAP, 0x2, RV_AMO),
    920      MASK_AMO,
    921      RV64_FMT_AMO,
    922      0,
    923      0, {0}},
    924     {MN("amoadd.w"),
    925      MATCH_AMO(RV_AMO_ADD, 0x2, RV_AMO),
    926      MASK_AMO,
    927      RV64_FMT_AMO,
    928      0,
    929      0, {0}},
    930     {MN("amoxor.w"),
    931      MATCH_AMO(RV_AMO_XOR, 0x2, RV_AMO),
    932      MASK_AMO,
    933      RV64_FMT_AMO,
    934      0,
    935      0, {0}},
    936     {MN("amoand.w"),
    937      MATCH_AMO(RV_AMO_AND, 0x2, RV_AMO),
    938      MASK_AMO,
    939      RV64_FMT_AMO,
    940      0,
    941      0, {0}},
    942     {MN("amoor.w"),
    943      MATCH_AMO(RV_AMO_OR, 0x2, RV_AMO),
    944      MASK_AMO,
    945      RV64_FMT_AMO,
    946      0,
    947      0, {0}},
    948     {MN("amomin.w"),
    949      MATCH_AMO(RV_AMO_MIN, 0x2, RV_AMO),
    950      MASK_AMO,
    951      RV64_FMT_AMO,
    952      0,
    953      0, {0}},
    954     {MN("amomax.w"),
    955      MATCH_AMO(RV_AMO_MAX, 0x2, RV_AMO),
    956      MASK_AMO,
    957      RV64_FMT_AMO,
    958      0,
    959      0, {0}},
    960     {MN("amominu.w"),
    961      MATCH_AMO(RV_AMO_MINU, 0x2, RV_AMO),
    962      MASK_AMO,
    963      RV64_FMT_AMO,
    964      0,
    965      0, {0}},
    966     {MN("amomaxu.w"),
    967      MATCH_AMO(RV_AMO_MAXU, 0x2, RV_AMO),
    968      MASK_AMO,
    969      RV64_FMT_AMO,
    970      0,
    971      0, {0}},
    972     {MN("amoswap.d"),
    973      MATCH_AMO(RV_AMO_SWAP, 0x3, RV_AMO),
    974      MASK_AMO,
    975      RV64_FMT_AMO,
    976      0,
    977      RV_AV_RV64, {0}},
    978     {MN("amoadd.d"),
    979      MATCH_AMO(RV_AMO_ADD, 0x3, RV_AMO),
    980      MASK_AMO,
    981      RV64_FMT_AMO,
    982      0,
    983      RV_AV_RV64, {0}},
    984     {MN("amoxor.d"),
    985      MATCH_AMO(RV_AMO_XOR, 0x3, RV_AMO),
    986      MASK_AMO,
    987      RV64_FMT_AMO,
    988      0,
    989      RV_AV_RV64, {0}},
    990     {MN("amoand.d"),
    991      MATCH_AMO(RV_AMO_AND, 0x3, RV_AMO),
    992      MASK_AMO,
    993      RV64_FMT_AMO,
    994      0,
    995      RV_AV_RV64, {0}},
    996     {MN("amoor.d"),
    997      MATCH_AMO(RV_AMO_OR, 0x3, RV_AMO),
    998      MASK_AMO,
    999      RV64_FMT_AMO,
   1000      0,
   1001      RV_AV_RV64, {0}},
   1002     {MN("amomin.d"),
   1003      MATCH_AMO(RV_AMO_MIN, 0x3, RV_AMO),
   1004      MASK_AMO,
   1005      RV64_FMT_AMO,
   1006      0,
   1007      RV_AV_RV64, {0}},
   1008     {MN("amomax.d"),
   1009      MATCH_AMO(RV_AMO_MAX, 0x3, RV_AMO),
   1010      MASK_AMO,
   1011      RV64_FMT_AMO,
   1012      0,
   1013      RV_AV_RV64, {0}},
   1014     {MN("amominu.d"),
   1015      MATCH_AMO(RV_AMO_MINU, 0x3, RV_AMO),
   1016      MASK_AMO,
   1017      RV64_FMT_AMO,
   1018      0,
   1019      RV_AV_RV64, {0}},
   1020     {MN("amomaxu.d"),
   1021      MATCH_AMO(RV_AMO_MAXU, 0x3, RV_AMO),
   1022      MASK_AMO,
   1023      RV64_FMT_AMO,
   1024      0,
   1025      RV_AV_RV64, {0}},
   1026 
   1027     /* =================================================================
   1028      * RV64C compressed — assembler rows. The disassembler uses the
   1029      * dynamic C decoder below, so 32-bit decode skips these rows.
   1030      * ================================================================= */
   1031     {MN("c.nop"), 0x0001u, 0xffffu, RV64_FMT_C_NONE, RV64_ASMFL_C16, 0, {0}},
   1032     {MN("c.ebreak"), 0x9002u, 0xffffu, RV64_FMT_C_NONE, RV64_ASMFL_C16, 0, {0}},
   1033     {MN("c.jr"), 0x8002u, 0xf07fu, RV64_FMT_CR, RV64_ASMFL_C16, 0, {0}},
   1034     {MN("c.jalr"), 0x9002u, 0xf07fu, RV64_FMT_CR, RV64_ASMFL_C16, 0, {0}},
   1035     {MN("c.mv"), 0x8002u, 0xf003u, RV64_FMT_CR, RV64_ASMFL_C16, 0, {0}},
   1036     {MN("c.add"), 0x9002u, 0xf003u, RV64_FMT_CR, RV64_ASMFL_C16, 0, {0}},
   1037     {MN("c.li"), 0x4001u, 0xe003u, RV64_FMT_CI, RV64_ASMFL_C16, 0, {0}},
   1038     {MN("c.addi"), 0x0001u, 0xe003u, RV64_FMT_CI, RV64_ASMFL_C16, 0, {0}},
   1039     /* q1/f3=001: c.addiw on rv64, but the SAME encoding is c.jal on rv32. */
   1040     {MN("c.addiw"), 0x2001u, 0xe003u, RV64_FMT_CI, RV64_ASMFL_C16,
   1041      RV_AV_RV64, {0}},
   1042     {MN("c.jal"), 0x2001u, 0xe003u, RV64_FMT_CJ, RV64_ASMFL_C16,
   1043      RV_AV_RV32, {0}},
   1044     {MN("c.slli"), 0x0002u, 0xe003u, RV64_FMT_CI, RV64_ASMFL_C16, 0, {0}},
   1045     {MN("c.lui"), 0x6001u, 0xe003u, RV64_FMT_CI, RV64_ASMFL_C16, 0, {0}},
   1046     {MN("c.addi16sp"), 0x6101u, 0xef83u, RV64_FMT_CI, RV64_ASMFL_C16, 0, {0}},
   1047     {MN("c.lwsp"), 0x4002u, 0xe003u, RV64_FMT_CI, RV64_ASMFL_C16, 0, {0}},
   1048     /* q2/f3=011: c.ldsp on rv64, c.flwsp on rv32 (same encoding). */
   1049     {MN("c.ldsp"), 0x6002u, 0xe003u, RV64_FMT_CI, RV64_ASMFL_C16,
   1050      RV_AV_RV64, {0}},
   1051     {MN("c.flwsp"), 0x6002u, 0xe003u, RV64_FMT_CI,
   1052      RV64_ASMFL_C16 | RV64_ASMFL_FP, RV_AV_RV32, {0}},
   1053     {MN("c.fldsp"),
   1054      0x2002u,
   1055      0xe003u,
   1056      RV64_FMT_CI,
   1057      RV64_ASMFL_C16 | RV64_ASMFL_FP,
   1058      0, {0}},
   1059     {MN("c.swsp"), 0xc002u, 0xe003u, RV64_FMT_CSS, RV64_ASMFL_C16, 0, {0}},
   1060     /* q2/f3=111: c.sdsp on rv64, c.fswsp on rv32 (same encoding). */
   1061     {MN("c.sdsp"), 0xe002u, 0xe003u, RV64_FMT_CSS, RV64_ASMFL_C16,
   1062      RV_AV_RV64, {0}},
   1063     {MN("c.fswsp"), 0xe002u, 0xe003u, RV64_FMT_CSS,
   1064      RV64_ASMFL_C16 | RV64_ASMFL_FP, RV_AV_RV32, {0}},
   1065     {MN("c.fsdsp"),
   1066      0xa002u,
   1067      0xe003u,
   1068      RV64_FMT_CSS,
   1069      RV64_ASMFL_C16 | RV64_ASMFL_FP,
   1070      0, {0}},
   1071     {MN("c.addi4spn"), 0x0000u, 0xe003u, RV64_FMT_CIW, RV64_ASMFL_C16, 0, {0}},
   1072     {MN("c.lw"), 0x4000u, 0xe003u, RV64_FMT_CL, RV64_ASMFL_C16, 0, {0}},
   1073     /* q0/f3=011: c.ld on rv64, c.flw on rv32 (same encoding). */
   1074     {MN("c.ld"), 0x6000u, 0xe003u, RV64_FMT_CL, RV64_ASMFL_C16,
   1075      RV_AV_RV64, {0}},
   1076     {MN("c.flw"), 0x6000u, 0xe003u, RV64_FMT_CL,
   1077      RV64_ASMFL_C16 | RV64_ASMFL_FP, RV_AV_RV32, {0}},
   1078     {MN("c.fld"),
   1079      0x2000u,
   1080      0xe003u,
   1081      RV64_FMT_CL,
   1082      RV64_ASMFL_C16 | RV64_ASMFL_FP,
   1083      0, {0}},
   1084     {MN("c.sw"), 0xc000u, 0xe003u, RV64_FMT_CS, RV64_ASMFL_C16, 0, {0}},
   1085     /* q0/f3=111: c.sd on rv64, c.fsw on rv32 (same encoding). */
   1086     {MN("c.sd"), 0xe000u, 0xe003u, RV64_FMT_CS, RV64_ASMFL_C16,
   1087      RV_AV_RV64, {0}},
   1088     {MN("c.fsw"), 0xe000u, 0xe003u, RV64_FMT_CS,
   1089      RV64_ASMFL_C16 | RV64_ASMFL_FP, RV_AV_RV32, {0}},
   1090     {MN("c.fsd"),
   1091      0xa000u,
   1092      0xe003u,
   1093      RV64_FMT_CS,
   1094      RV64_ASMFL_C16 | RV64_ASMFL_FP,
   1095      0, {0}},
   1096     {MN("c.srli"), 0x8001u, 0xec03u, RV64_FMT_CB, RV64_ASMFL_C16, 0, {0}},
   1097     {MN("c.srai"), 0x8401u, 0xec03u, RV64_FMT_CB, RV64_ASMFL_C16, 0, {0}},
   1098     {MN("c.andi"), 0x8801u, 0xec03u, RV64_FMT_CB, RV64_ASMFL_C16, 0, {0}},
   1099     {MN("c.sub"), 0x8c01u, 0xfc63u, RV64_FMT_CA, RV64_ASMFL_C16, 0, {0}},
   1100     {MN("c.xor"), 0x8c21u, 0xfc63u, RV64_FMT_CA, RV64_ASMFL_C16, 0, {0}},
   1101     {MN("c.or"), 0x8c41u, 0xfc63u, RV64_FMT_CA, RV64_ASMFL_C16, 0, {0}},
   1102     {MN("c.and"), 0x8c61u, 0xfc63u, RV64_FMT_CA, RV64_ASMFL_C16, 0, {0}},
   1103     /* c.subw/c.addw are RV64-only (their CA slot is reserved on rv32). */
   1104     {MN("c.subw"), 0x9c01u, 0xfc63u, RV64_FMT_CA, RV64_ASMFL_C16,
   1105      RV_AV_RV64, {0}},
   1106     {MN("c.addw"), 0x9c21u, 0xfc63u, RV64_FMT_CA, RV64_ASMFL_C16,
   1107      RV_AV_RV64, {0}},
   1108     {MN("c.j"), 0xa001u, 0xe003u, RV64_FMT_CJ, RV64_ASMFL_C16, 0, {0}},
   1109     {MN("c.beqz"), 0xc001u, 0xe003u, RV64_FMT_CB, RV64_ASMFL_C16, 0, {0}},
   1110     {MN("c.bnez"), 0xe001u, 0xe003u, RV64_FMT_CB, RV64_ASMFL_C16, 0, {0}},
   1111 };
   1112 #undef RV64_AMO_ORDER_ROWS
   1113 
   1114 const u32 rv64_insn_table_n =
   1115     (u32)(sizeof rv64_insn_table / sizeof rv64_insn_table[0]);
   1116 
   1117 /* ---- Standard CSR name -> number table (shared by RV32 and RV64) ----
   1118  * Used by the assembler to accept symbolic CSR operands (a bare number is
   1119  * still accepted) and by the disassembler to print CSRs symbolically. The
   1120  * table round-trips: each number maps to exactly one canonical name. */
   1121 const Rv64CsrName rv64_csr_names[] = {
   1122     {"fflags", 0x001},   {"frm", 0x002},     {"fcsr", 0x003},
   1123     {"cycle", 0xC00},    {"time", 0xC01},    {"instret", 0xC02},
   1124     {"mstatus", 0x300},  {"misa", 0x301},    {"mie", 0x304},
   1125     {"mtvec", 0x305},    {"mscratch", 0x340}, {"mepc", 0x341},
   1126     {"mcause", 0x342},   {"mtval", 0x343},   {"mip", 0x344},
   1127     {"mvendorid", 0xF11}, {"marchid", 0xF12}, {"mimpid", 0xF13},
   1128     {"mhartid", 0xF14},
   1129 };
   1130 const u32 rv64_csr_names_n =
   1131     (u32)(sizeof rv64_csr_names / sizeof rv64_csr_names[0]);
   1132 
   1133 /* Look up a CSR by name. Returns 1 and writes *num_out on a hit, else 0. */
   1134 int rv64_csr_num_from_name(Slice name, u16* num_out) {
   1135   for (u32 i = 0; i < rv64_csr_names_n; ++i) {
   1136     if (slice_eq_cstr(name, rv64_csr_names[i].name)) {
   1137       if (num_out) *num_out = rv64_csr_names[i].num;
   1138       return 1;
   1139     }
   1140   }
   1141   return 0;
   1142 }
   1143 
   1144 /* Reverse lookup: canonical name for a CSR number, or NULL if unknown. */
   1145 const char* rv64_csr_name_from_num(u16 num) {
   1146   for (u32 i = 0; i < rv64_csr_names_n; ++i)
   1147     if (rv64_csr_names[i].num == num) return rv64_csr_names[i].name;
   1148   return NULL;
   1149 }
   1150 
   1151 /* A row is available for `av_wanted` when its av column is 0 (BOTH) or
   1152  * its av mask intersects the wanted arch. */
   1153 static bool rv_av_ok(u8 av, u8 av_wanted) {
   1154   return av == 0u || (av & av_wanted) != 0u;
   1155 }
   1156 
   1157 const Rv64InsnDesc* rv64_disasm_find(u32 word, u8 av_wanted) {
   1158   for (u32 i = 0; i < rv64_insn_table_n; ++i) {
   1159     const Rv64InsnDesc* d = &rv64_insn_table[i];
   1160     if ((d->flags & RV64_ASMFL_C16)) continue;    /* 32-bit decode path */
   1161     if ((d->flags & RV64_ASMFL_PSEUDO)) continue; /* assembler-only expansion */
   1162     if (!rv_av_ok(d->av, av_wanted)) continue;    /* wrong-XLEN row */
   1163     if ((word & d->mask) == d->match) return d;
   1164   }
   1165   return NULL;
   1166 }
   1167 
   1168 const Rv64InsnDesc* rv64_asm_find(Slice mnemonic, u8 av_wanted) {
   1169   /* Prefer canonical (non-alias) rows when both spellings exist; the
   1170    * caller can still write the alias and we'll match it on a second
   1171    * pass. Aliases share encoding with the canonical row so the choice
   1172    * is purely for diagnostics. Rows whose av excludes the target arch
   1173    * are skipped so e.g. `ld`/`addiw` are not assemblable under rv32. */
   1174   if (!mnemonic.s) return NULL;
   1175   for (u32 i = 0; i < rv64_insn_table_n; ++i) {
   1176     const Rv64InsnDesc* d = &rv64_insn_table[i];
   1177     if ((d->flags & RV64_ASMFL_ALIAS)) continue;
   1178     if (!rv_av_ok(d->av, av_wanted)) continue;
   1179     if (slice_eq(d->mnemonic, mnemonic)) return d;
   1180   }
   1181   for (u32 i = 0; i < rv64_insn_table_n; ++i) {
   1182     const Rv64InsnDesc* d = &rv64_insn_table[i];
   1183     if (!rv_av_ok(d->av, av_wanted)) continue;
   1184     if (slice_eq(d->mnemonic, mnemonic)) return d;
   1185   }
   1186   return NULL;
   1187 }
   1188 
   1189 /* =====================================================================
   1190  * Compressed-instruction decode.
   1191  *
   1192  * RV64C instructions are 16 bits; bits[1:0] (op-quadrant) is 00/01/10
   1193  * (11 means uncompressed/32-bit). bits[15:13] (funct3) further select.
   1194  *
   1195  * For the disassembler we expose a small set of the common encodings;
   1196  * less common ones decode as .hword. */
   1197 
   1198 static u32 rv64c_lookup_simple(u32 w) {
   1199   u32 op = w & 0x3u;
   1200   u32 f3 = (w >> 13) & 0x7u;
   1201   /* C.NOP: funct3=000, op=01, rd/rs1=x0, imm=0 → word=0x0001 */
   1202   if (w == 0x0001u) return 1; /* index in table-c below */
   1203   /* C.EBREAK: 0x9002 */
   1204   if (w == 0x9002u) return 2;
   1205   (void)op;
   1206   (void)f3;
   1207   return 0;
   1208 }
   1209 
   1210 /* The C-extension descriptors are stored in a private table indexed by
   1211  * an internal enum. They are minimal — most C-format instructions print
   1212  * with custom operand printers. */
   1213 static const Rv64InsnDesc rv64_c_table[] = {
   1214     /* index 0 reserved (no match). */
   1215     {MN("c.unknown"), 0, 0xffffu, RV64_FMT_C_NONE, RV64_ASMFL_C16, 0, {0}},
   1216     {MN("c.nop"), 0x0001u, 0xffffu, RV64_FMT_C_NONE, RV64_ASMFL_C16, 0, {0}},
   1217     {MN("c.ebreak"), 0x9002u, 0xffffu, RV64_FMT_C_NONE, RV64_ASMFL_C16, 0, {0}},
   1218 };
   1219 
   1220 #undef MN
   1221 
   1222 const Rv64InsnDesc* rv64_disasm_find_c(u32 word, u8 av_wanted) {
   1223   u32 hw = word & 0xffffu;
   1224   u32 idx = rv64c_lookup_simple(hw);
   1225   /* True when decoding for rv32: several RVC quadrant slots whose integer
   1226    * doubleword meaning is RV64-only carry an FP load/store meaning instead
   1227    * (RV32FC), and q1/f3=001 is c.jal not c.addiw. */
   1228   bool rv32 = (av_wanted & RV_AV_RV32) != 0u;
   1229   if (idx) return &rv64_c_table[idx];
   1230   /* Pattern-match remaining common C-instructions. We use a tiny static
   1231    * scratch descriptor that the printer interprets by funct3+op. */
   1232   static Rv64InsnDesc dyn;
   1233   u32 op = hw & 0x3u;
   1234   u32 f3 = (hw >> 13) & 0x7u;
   1235   if (op == 3u) return NULL; /* uncompressed */
   1236 
   1237   /* C.JR / C.JALR / C.MV / C.ADD — quadrant 2, funct3=100 */
   1238   if (op == 2u && f3 == 4u) {
   1239     u32 funct4 = (hw >> 12) & 0xfu;
   1240     u32 rd_rs1 = (hw >> 7) & 0x1fu;
   1241     u32 rs2 = (hw >> 2) & 0x1fu;
   1242     if (funct4 == 0x8u) {
   1243       dyn = (Rv64InsnDesc){slice_from_cstr(rs2 == 0 ? "c.jr" : "c.mv"),
   1244                            hw,
   1245                            0xffffu,
   1246                            RV64_FMT_CR,
   1247                            RV64_ASMFL_C16,
   1248                            0, {0}};
   1249       return rd_rs1 == 0 ? NULL : &dyn;
   1250     }
   1251     if (funct4 == 0x9u) {
   1252       if (rs2 == 0 && rd_rs1 == 0) {
   1253         dyn = rv64_c_table[2]; /* c.ebreak */
   1254         return &dyn;
   1255       }
   1256       dyn = (Rv64InsnDesc){slice_from_cstr(rs2 == 0 ? "c.jalr" : "c.add"),
   1257                            hw,
   1258                            0xffffu,
   1259                            RV64_FMT_CR,
   1260                            RV64_ASMFL_C16,
   1261                            0, {0}};
   1262       return &dyn;
   1263     }
   1264   }
   1265   /* C.LI / C.ADDI / C.LUI — quadrant 1 */
   1266   if (op == 1u && f3 == 2u) {
   1267     dyn = (Rv64InsnDesc){slice_from_cstr("c.li"), hw,    0xffffu, RV64_FMT_CI,
   1268                          RV64_ASMFL_C16,          0, {0}};
   1269     return &dyn;
   1270   }
   1271   if (op == 1u && f3 == 1u) {
   1272     /* q1/f3=001: c.addiw on rv64, c.jal on rv32 (same encoding). */
   1273     dyn = rv32 ? (Rv64InsnDesc){slice_from_cstr("c.jal"), hw,    0xffffu,
   1274                                 RV64_FMT_CJ,              RV64_ASMFL_C16,
   1275                                 0,                        {0}}
   1276                : (Rv64InsnDesc){slice_from_cstr("c.addiw"), hw,    0xffffu,
   1277                                 RV64_FMT_CI,                RV64_ASMFL_C16,
   1278                                 0,                          {0}};
   1279     return &dyn;
   1280   }
   1281   if (op == 1u && f3 == 0u) {
   1282     dyn = (Rv64InsnDesc){slice_from_cstr("c.addi"),
   1283                          hw,
   1284                          0xffffu,
   1285                          RV64_FMT_CI,
   1286                          RV64_ASMFL_C16,
   1287                          0, {0}};
   1288     return &dyn;
   1289   }
   1290   if (op == 1u && f3 == 3u) {
   1291     u32 rd = (hw >> 7) & 0x1fu;
   1292     dyn = (Rv64InsnDesc){slice_from_cstr(rd == 2u ? "c.addi16sp" : "c.lui"),
   1293                          hw,
   1294                          0xffffu,
   1295                          RV64_FMT_CI,
   1296                          RV64_ASMFL_C16,
   1297                          0, {0}};
   1298     return &dyn;
   1299   }
   1300   if (op == 1u && f3 == 4u) {
   1301     u32 top = (hw >> 10) & 0x3u;
   1302     if (top == 0u || top == 1u || top == 2u) {
   1303       static const char* const names[3] = {"c.srli", "c.srai", "c.andi"};
   1304       dyn = (Rv64InsnDesc){slice_from_cstr(names[top]),
   1305                            hw,
   1306                            0xffffu,
   1307                            RV64_FMT_CB,
   1308                            RV64_ASMFL_C16,
   1309                            0, {0}};
   1310       return &dyn;
   1311     }
   1312     {
   1313       u32 bit12 = (hw >> 12) & 1u;
   1314       u32 subop = (hw >> 5) & 0x3u;
   1315       static const char* const ca0[4] = {"c.sub", "c.xor", "c.or", "c.and"};
   1316       static const char* const ca1[4] = {"c.subw", "c.addw", NULL, NULL};
   1317       /* bit12==1 selects c.subw/c.addw — RV64-only; reserved on rv32. */
   1318       const char* name = bit12 ? (rv32 ? NULL : ca1[subop]) : ca0[subop];
   1319       if (!name) return NULL;
   1320       dyn = (Rv64InsnDesc){slice_from_cstr(name), hw,    0xffffu, RV64_FMT_CA,
   1321                            RV64_ASMFL_C16,        0, {0}};
   1322       return &dyn;
   1323     }
   1324   }
   1325   if (op == 1u && f3 == 5u) {
   1326     dyn = (Rv64InsnDesc){slice_from_cstr("c.j"), hw,    0xffffu, RV64_FMT_CJ,
   1327                          RV64_ASMFL_C16,         0, {0}};
   1328     return &dyn;
   1329   }
   1330   if (op == 1u && f3 == 6u) {
   1331     dyn = (Rv64InsnDesc){slice_from_cstr("c.beqz"),
   1332                          hw,
   1333                          0xffffu,
   1334                          RV64_FMT_CB,
   1335                          RV64_ASMFL_C16,
   1336                          0, {0}};
   1337     return &dyn;
   1338   }
   1339   if (op == 1u && f3 == 7u) {
   1340     dyn = (Rv64InsnDesc){slice_from_cstr("c.bnez"),
   1341                          hw,
   1342                          0xffffu,
   1343                          RV64_FMT_CB,
   1344                          RV64_ASMFL_C16,
   1345                          0, {0}};
   1346     return &dyn;
   1347   }
   1348   /* C.LWSP / C.LDSP — quadrant 2, funct3=010/011 */
   1349   if (op == 2u && f3 == 2u) {
   1350     dyn = (Rv64InsnDesc){slice_from_cstr("c.lwsp"),
   1351                          hw,
   1352                          0xffffu,
   1353                          RV64_FMT_CI,
   1354                          RV64_ASMFL_C16,
   1355                          0, {0}};
   1356     return &dyn;
   1357   }
   1358   if (op == 2u && f3 == 3u) {
   1359     /* q2/f3=011: c.ldsp on rv64, c.flwsp on rv32 (same encoding). */
   1360     if (rv32)
   1361       dyn = (Rv64InsnDesc){slice_from_cstr("c.flwsp"),
   1362                            hw,
   1363                            0xffffu,
   1364                            RV64_FMT_CI,
   1365                            RV64_ASMFL_C16 | RV64_ASMFL_FP,
   1366                            0,
   1367                            {0}};
   1368     else
   1369       dyn = (Rv64InsnDesc){slice_from_cstr("c.ldsp"), hw,    0xffffu,
   1370                            RV64_FMT_CI,                RV64_ASMFL_C16,
   1371                            0,                          {0}};
   1372     return &dyn;
   1373   }
   1374   if (op == 2u && f3 == 0u) {
   1375     dyn = (Rv64InsnDesc){slice_from_cstr("c.slli"),
   1376                          hw,
   1377                          0xffffu,
   1378                          RV64_FMT_CI,
   1379                          RV64_ASMFL_C16,
   1380                          0, {0}};
   1381     return &dyn;
   1382   }
   1383   if (op == 2u && f3 == 1u) {
   1384     dyn = (Rv64InsnDesc){
   1385         slice_from_cstr("c.fldsp"),     hw,    0xffffu, RV64_FMT_CI,
   1386         RV64_ASMFL_C16 | RV64_ASMFL_FP, 0, {0}};
   1387     return &dyn;
   1388   }
   1389   /* C.SWSP / C.SDSP — quadrant 2, funct3=110/111 */
   1390   if (op == 2u && f3 == 6u) {
   1391     dyn = (Rv64InsnDesc){slice_from_cstr("c.swsp"),
   1392                          hw,
   1393                          0xffffu,
   1394                          RV64_FMT_CSS,
   1395                          RV64_ASMFL_C16,
   1396                          0, {0}};
   1397     return &dyn;
   1398   }
   1399   if (op == 2u && f3 == 7u) {
   1400     /* q2/f3=111: c.sdsp on rv64, c.fswsp on rv32 (same encoding). */
   1401     if (rv32)
   1402       dyn = (Rv64InsnDesc){slice_from_cstr("c.fswsp"),
   1403                            hw,
   1404                            0xffffu,
   1405                            RV64_FMT_CSS,
   1406                            RV64_ASMFL_C16 | RV64_ASMFL_FP,
   1407                            0,
   1408                            {0}};
   1409     else
   1410       dyn = (Rv64InsnDesc){slice_from_cstr("c.sdsp"),
   1411                            hw,
   1412                            0xffffu,
   1413                            RV64_FMT_CSS,
   1414                            RV64_ASMFL_C16,
   1415                            0, {0}};
   1416     return &dyn;
   1417   }
   1418   if (op == 2u && f3 == 5u) {
   1419     dyn = (Rv64InsnDesc){
   1420         slice_from_cstr("c.fsdsp"),     hw,    0xffffu, RV64_FMT_CSS,
   1421         RV64_ASMFL_C16 | RV64_ASMFL_FP, 0, {0}};
   1422     return &dyn;
   1423   }
   1424   /* C.ADDI4SPN — quadrant 0, funct3=000 */
   1425   if (op == 0u && f3 == 0u) {
   1426     dyn = (Rv64InsnDesc){slice_from_cstr("c.addi4spn"),
   1427                          hw,
   1428                          0xffffu,
   1429                          RV64_FMT_CIW,
   1430                          RV64_ASMFL_C16,
   1431                          0, {0}};
   1432     return &dyn;
   1433   }
   1434   /* C.LW / C.LD — quadrant 0, funct3=010/011 */
   1435   if (op == 0u && f3 == 2u) {
   1436     dyn = (Rv64InsnDesc){slice_from_cstr("c.lw"), hw,    0xffffu, RV64_FMT_CL,
   1437                          RV64_ASMFL_C16,          0, {0}};
   1438     return &dyn;
   1439   }
   1440   if (op == 0u && f3 == 3u) {
   1441     /* q0/f3=011: c.ld on rv64, c.flw on rv32 (same encoding). */
   1442     if (rv32)
   1443       dyn = (Rv64InsnDesc){slice_from_cstr("c.flw"),
   1444                            hw,
   1445                            0xffffu,
   1446                            RV64_FMT_CL,
   1447                            RV64_ASMFL_C16 | RV64_ASMFL_FP,
   1448                            0,
   1449                            {0}};
   1450     else
   1451       dyn = (Rv64InsnDesc){slice_from_cstr("c.ld"), hw,    0xffffu, RV64_FMT_CL,
   1452                            RV64_ASMFL_C16,          0, {0}};
   1453     return &dyn;
   1454   }
   1455   if (op == 0u && f3 == 1u) {
   1456     dyn = (Rv64InsnDesc){
   1457         slice_from_cstr("c.fld"),       hw,    0xffffu, RV64_FMT_CL,
   1458         RV64_ASMFL_C16 | RV64_ASMFL_FP, 0, {0}};
   1459     return &dyn;
   1460   }
   1461   if (op == 0u && f3 == 6u) {
   1462     dyn = (Rv64InsnDesc){slice_from_cstr("c.sw"), hw,    0xffffu, RV64_FMT_CS,
   1463                          RV64_ASMFL_C16,          0, {0}};
   1464     return &dyn;
   1465   }
   1466   if (op == 0u && f3 == 7u) {
   1467     /* q0/f3=111: c.sd on rv64, c.fsw on rv32 (same encoding). */
   1468     if (rv32)
   1469       dyn = (Rv64InsnDesc){slice_from_cstr("c.fsw"),
   1470                            hw,
   1471                            0xffffu,
   1472                            RV64_FMT_CS,
   1473                            RV64_ASMFL_C16 | RV64_ASMFL_FP,
   1474                            0,
   1475                            {0}};
   1476     else
   1477       dyn = (Rv64InsnDesc){slice_from_cstr("c.sd"), hw,    0xffffu, RV64_FMT_CS,
   1478                            RV64_ASMFL_C16,          0, {0}};
   1479     return &dyn;
   1480   }
   1481   if (op == 0u && f3 == 5u) {
   1482     dyn = (Rv64InsnDesc){
   1483         slice_from_cstr("c.fsd"),       hw,    0xffffu, RV64_FMT_CS,
   1484         RV64_ASMFL_C16 | RV64_ASMFL_FP, 0, {0}};
   1485     return &dyn;
   1486   }
   1487   return NULL;
   1488 }
   1489 
   1490 /* =====================================================================
   1491  * Operand print — one helper per format. */
   1492 
   1493 static const char* const RV_XNAMES[32] = {
   1494     "zero", "ra", "sp", "gp", "tp",  "t0",  "t1", "t2", "s0", "s1", "a0",
   1495     "a1",   "a2", "a3", "a4", "a5",  "a6",  "a7", "s2", "s3", "s4", "s5",
   1496     "s6",   "s7", "s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6",
   1497 };
   1498 
   1499 static const char* const RV_FNAMES[32] = {
   1500     "ft0", "ft1", "ft2",  "ft3",  "ft4", "ft5", "ft6",  "ft7",
   1501     "fs0", "fs1", "fa0",  "fa1",  "fa2", "fa3", "fa4",  "fa5",
   1502     "fa6", "fa7", "fs2",  "fs3",  "fs4", "fs5", "fs6",  "fs7",
   1503     "fs8", "fs9", "fs10", "fs11", "ft8", "ft9", "ft10", "ft11",
   1504 };
   1505 
   1506 static void p_xreg(StrBuf* sb, u32 r) { strbuf_puts(sb, RV_XNAMES[r & 31u]); }
   1507 static void p_freg(StrBuf* sb, u32 r) { strbuf_puts(sb, RV_FNAMES[r & 31u]); }
   1508 static void p_sep(StrBuf* sb) { strbuf_puts(sb, ", "); }
   1509 static void p_mem(StrBuf* sb, i64 off, u32 base) {
   1510   strbuf_put_i64(sb, off);
   1511   strbuf_putc(sb, '(');
   1512   p_xreg(sb, base);
   1513   strbuf_putc(sb, ')');
   1514 }
   1515 static void p_rel(StrBuf* sb, u64 vaddr, i64 off) {
   1516   if (vaddr)
   1517     strbuf_put_hex_u64(sb, vaddr + (u64)off);
   1518   else {
   1519     strbuf_putc(sb, '#');
   1520     strbuf_put_i64(sb, off);
   1521   }
   1522 }
   1523 
   1524 static void print_r(StrBuf* sb, u32 w, const Rv64InsnDesc* d) {
   1525   Rv64R f = rv64_r_unpack(w);
   1526   /* Two-operand aliases (snez/neg/negw) drop rs1=x0 from the print. */
   1527   if (d->flags & RV64_ASMFL_ALIAS) {
   1528     p_xreg(sb, f.rd);
   1529     p_sep(sb);
   1530     p_xreg(sb, f.rs2);
   1531     return;
   1532   }
   1533   p_xreg(sb, f.rd);
   1534   p_sep(sb);
   1535   p_xreg(sb, f.rs1);
   1536   p_sep(sb);
   1537   p_xreg(sb, f.rs2);
   1538 }
   1539 
   1540 static void print_r4(StrBuf* sb, u32 w) {
   1541   u32 rd = (w >> 7) & 0x1fu;
   1542   u32 rs1 = (w >> 15) & 0x1fu;
   1543   u32 rs2 = (w >> 20) & 0x1fu;
   1544   u32 rs3 = (w >> 27) & 0x1fu;
   1545   p_freg(sb, rd);
   1546   p_sep(sb);
   1547   p_freg(sb, rs1);
   1548   p_sep(sb);
   1549   p_freg(sb, rs2);
   1550   p_sep(sb);
   1551   p_freg(sb, rs3);
   1552 }
   1553 
   1554 static void print_i(StrBuf* sb, u32 w, const Rv64InsnDesc* d) {
   1555   Rv64I f = rv64_i_unpack(w);
   1556   i64 imm = rv64_sext((u64)f.imm12, 12);
   1557   /* Alias: `li rd, imm` — print rd, imm. */
   1558   if ((d->flags & RV64_ASMFL_ALIAS) && slice_eq_cstr(d->mnemonic, "li")) {
   1559     p_xreg(sb, f.rd);
   1560     p_sep(sb);
   1561     strbuf_put_i64(sb, imm);
   1562     return;
   1563   }
   1564   /* Alias: `mv rd, rs1` — print rd, rs1. */
   1565   if ((d->flags & RV64_ASMFL_ALIAS) && slice_eq_cstr(d->mnemonic, "mv")) {
   1566     p_xreg(sb, f.rd);
   1567     p_sep(sb);
   1568     p_xreg(sb, f.rs1);
   1569     return;
   1570   }
   1571   /* Alias: `sext.w rd, rs1` — print rd, rs1. */
   1572   if ((d->flags & RV64_ASMFL_ALIAS) && slice_eq_cstr(d->mnemonic, "sext.w")) {
   1573     p_xreg(sb, f.rd);
   1574     p_sep(sb);
   1575     p_xreg(sb, f.rs1);
   1576     return;
   1577   }
   1578   /* Alias: `seqz rd, rs` / `not rd, rs` — print rd, rs (drop imm). */
   1579   if ((d->flags & RV64_ASMFL_ALIAS) && (slice_eq_cstr(d->mnemonic, "seqz") ||
   1580                                         slice_eq_cstr(d->mnemonic, "not"))) {
   1581     p_xreg(sb, f.rd);
   1582     p_sep(sb);
   1583     p_xreg(sb, f.rs1);
   1584     return;
   1585   }
   1586   p_xreg(sb, f.rd);
   1587   p_sep(sb);
   1588   p_xreg(sb, f.rs1);
   1589   p_sep(sb);
   1590   strbuf_put_i64(sb, imm);
   1591 }
   1592 
   1593 static void print_i_shift(StrBuf* sb, u32 w) {
   1594   /* shamt is 6 bits for RV64 shift-imm. */
   1595   u32 rd = (w >> 7) & 0x1fu;
   1596   u32 rs1 = (w >> 15) & 0x1fu;
   1597   u32 shamt = (w >> 20) & 0x3fu;
   1598   p_xreg(sb, rd);
   1599   p_sep(sb);
   1600   p_xreg(sb, rs1);
   1601   p_sep(sb);
   1602   strbuf_put_u64(sb, (u64)shamt);
   1603 }
   1604 
   1605 static void print_i_shiftw(StrBuf* sb, u32 w) {
   1606   u32 rd = (w >> 7) & 0x1fu;
   1607   u32 rs1 = (w >> 15) & 0x1fu;
   1608   u32 shamt = (w >> 20) & 0x1fu;
   1609   p_xreg(sb, rd);
   1610   p_sep(sb);
   1611   p_xreg(sb, rs1);
   1612   p_sep(sb);
   1613   strbuf_put_u64(sb, (u64)shamt);
   1614 }
   1615 
   1616 static void print_u(StrBuf* sb, u32 w) {
   1617   Rv64U f = rv64_u_unpack(w);
   1618   p_xreg(sb, f.rd);
   1619   p_sep(sb);
   1620   /* The immediate is the upper-20 already shifted into bits 31:12; print
   1621    * the raw 20-bit value the assembler expects. */
   1622   strbuf_put_hex_u64(sb, (u64)(f.imm32_hi20 >> 12));
   1623 }
   1624 
   1625 static void print_load(StrBuf* sb, u32 w, const Rv64InsnDesc* d) {
   1626   Rv64I f = rv64_i_unpack(w);
   1627   i64 imm = rv64_sext((u64)f.imm12, 12);
   1628   if (d->flags & RV64_ASMFL_FP)
   1629     p_freg(sb, f.rd);
   1630   else
   1631     p_xreg(sb, f.rd);
   1632   p_sep(sb);
   1633   p_mem(sb, imm, f.rs1);
   1634 }
   1635 
   1636 static void print_store(StrBuf* sb, u32 w, const Rv64InsnDesc* d) {
   1637   Rv64S f = rv64_s_unpack(w);
   1638   i64 imm = rv64_sext((u64)f.imm12, 12);
   1639   if (d->flags & RV64_ASMFL_FP)
   1640     p_freg(sb, f.rs2);
   1641   else
   1642     p_xreg(sb, f.rs2);
   1643   p_sep(sb);
   1644   p_mem(sb, imm, f.rs1);
   1645 }
   1646 
   1647 static void print_b(StrBuf* sb, u32 w, u64 vaddr, const Rv64InsnDesc* d) {
   1648   Rv64B f = rv64_b_unpack(w);
   1649   i64 off = rv64_sext((u64)f.imm13, 13);
   1650   if ((d->flags & RV64_ASMFL_ALIAS) && (slice_eq_cstr(d->mnemonic, "beqz") ||
   1651                                         slice_eq_cstr(d->mnemonic, "bnez"))) {
   1652     p_xreg(sb, f.rs1);
   1653     p_sep(sb);
   1654     p_rel(sb, vaddr, off);
   1655     return;
   1656   }
   1657   p_xreg(sb, f.rs1);
   1658   p_sep(sb);
   1659   p_xreg(sb, f.rs2);
   1660   p_sep(sb);
   1661   p_rel(sb, vaddr, off);
   1662 }
   1663 
   1664 static void print_j(StrBuf* sb, u32 w, u64 vaddr, const Rv64InsnDesc* d) {
   1665   Rv64J f = rv64_j_unpack(w);
   1666   i64 off = rv64_sext((u64)f.imm21, 21);
   1667   if ((d->flags & RV64_ASMFL_ALIAS) && slice_eq_cstr(d->mnemonic, "j")) {
   1668     p_rel(sb, vaddr, off);
   1669     return;
   1670   }
   1671   p_xreg(sb, f.rd);
   1672   p_sep(sb);
   1673   p_rel(sb, vaddr, off);
   1674 }
   1675 
   1676 static void print_jalr(StrBuf* sb, u32 w, const Rv64InsnDesc* d) {
   1677   Rv64I f = rv64_i_unpack(w);
   1678   i64 imm = rv64_sext((u64)f.imm12, 12);
   1679   if ((d->flags & RV64_ASMFL_ALIAS) && slice_eq_cstr(d->mnemonic, "jr")) {
   1680     p_xreg(sb, f.rs1);
   1681     return;
   1682   }
   1683   p_xreg(sb, f.rd);
   1684   p_sep(sb);
   1685   p_mem(sb, imm, f.rs1);
   1686 }
   1687 
   1688 static void print_fence(StrBuf* sb, u32 w) {
   1689   u32 pred = (w >> 24) & 0xfu;
   1690   u32 succ = (w >> 20) & 0xfu;
   1691   static const char order_chars[5] = {'w', 'r', 'o', 'i', '\0'};
   1692   /* pred/succ: bit3=i, bit2=o, bit1=r, bit0=w; print iorw left-to-right. */
   1693   char buf[8];
   1694   u32 k = 0;
   1695   if (pred & 8u) buf[k++] = 'i';
   1696   if (pred & 4u) buf[k++] = 'o';
   1697   if (pred & 2u) buf[k++] = 'r';
   1698   if (pred & 1u) buf[k++] = 'w';
   1699   if (!k) buf[k++] = '0';
   1700   buf[k] = '\0';
   1701   strbuf_puts(sb, buf);
   1702   p_sep(sb);
   1703   k = 0;
   1704   if (succ & 8u) buf[k++] = 'i';
   1705   if (succ & 4u) buf[k++] = 'o';
   1706   if (succ & 2u) buf[k++] = 'r';
   1707   if (succ & 1u) buf[k++] = 'w';
   1708   if (!k) buf[k++] = '0';
   1709   buf[k] = '\0';
   1710   strbuf_puts(sb, buf);
   1711   (void)order_chars;
   1712 }
   1713 
   1714 /* CSRs are disassembled numerically (as hex) so the disasm golden files
   1715  * round-trip through the assembler's numeric CSR parser. The assembler also
   1716  * accepts symbolic CSR names on input (see parse_csr / rv64_csr_names), but
   1717  * the printer stays numeric to keep a single canonical disassembly form. */
   1718 static void print_csr(StrBuf* sb, u32 w) {
   1719   Rv64I f = rv64_i_unpack(w);
   1720   p_xreg(sb, f.rd);
   1721   p_sep(sb);
   1722   strbuf_put_hex_u64(sb, (u64)f.imm12);
   1723   p_sep(sb);
   1724   p_xreg(sb, f.rs1);
   1725 }
   1726 
   1727 static void print_csri(StrBuf* sb, u32 w) {
   1728   Rv64I f = rv64_i_unpack(w);
   1729   p_xreg(sb, f.rd);
   1730   p_sep(sb);
   1731   strbuf_put_hex_u64(sb, (u64)f.imm12);
   1732   p_sep(sb);
   1733   strbuf_put_u64(sb, (u64)f.rs1);
   1734 }
   1735 
   1736 static void print_fp_rm(StrBuf* sb, u32 w) {
   1737   Rv64R f = rv64_r_unpack(w);
   1738   p_freg(sb, f.rd);
   1739   p_sep(sb);
   1740   p_freg(sb, f.rs1);
   1741   p_sep(sb);
   1742   p_freg(sb, f.rs2);
   1743 }
   1744 
   1745 static void print_fp_r(StrBuf* sb, u32 w, const Rv64InsnDesc* d) {
   1746   Rv64R f = rv64_r_unpack(w);
   1747   if (d->flags & RV64_ASMFL_FP) {
   1748     p_freg(sb, f.rd);
   1749     p_sep(sb);
   1750     p_freg(sb, f.rs1);
   1751     p_sep(sb);
   1752     p_freg(sb, f.rs2);
   1753   } else {
   1754     /* FP compare: rd is GPR. */
   1755     p_xreg(sb, f.rd);
   1756     p_sep(sb);
   1757     p_freg(sb, f.rs1);
   1758     p_sep(sb);
   1759     p_freg(sb, f.rs2);
   1760   }
   1761 }
   1762 
   1763 static void print_fp_cvt(StrBuf* sb, u32 w, const Rv64InsnDesc* d) {
   1764   Rv64R f = rv64_r_unpack(w);
   1765   /* rd is FP for: fcvt.s.*, fcvt.d.*, fmv.w.x, fmv.d.x, fsqrt.{s,d}.
   1766    *               GPR for: fcvt.w.*, fcvt.l.*, fmv.x.w, fmv.x.d. */
   1767   if (d->flags & RV64_ASMFL_FP)
   1768     p_freg(sb, f.rd);
   1769   else
   1770     p_xreg(sb, f.rd);
   1771   p_sep(sb);
   1772   /* rs1: FP if mnemonic is fcvt.X.{S,D} or fsqrt or fmv.x.{w,d};
   1773    *      GPR if mnemonic is fcvt.{S,D}.{w,wu,l,lu} or fmv.{w,d}.x. */
   1774   int rs1_is_fp = 1;
   1775   if (slice_eq_cstr(d->mnemonic, "fmv.w.x") ||
   1776       slice_eq_cstr(d->mnemonic, "fmv.d.x") ||
   1777       slice_has_prefix_cstr(d->mnemonic, "fcvt.s.", 7) ||
   1778       slice_has_prefix_cstr(d->mnemonic, "fcvt.d.", 7)) {
   1779     /* These have rs1 as integer GPR (source is integer). Exception:
   1780      * fcvt.s.d / fcvt.d.s have rs1 as FP. */
   1781     if (slice_eq_cstr(d->mnemonic, "fcvt.s.d") ||
   1782         slice_eq_cstr(d->mnemonic, "fcvt.d.s"))
   1783       rs1_is_fp = 1;
   1784     else
   1785       rs1_is_fp = 0;
   1786   }
   1787   if (rs1_is_fp)
   1788     p_freg(sb, f.rs1);
   1789   else
   1790     p_xreg(sb, f.rs1);
   1791   /* Explicit rounding mode for the rounding conversions (fcvt / fsqrt) when it
   1792    * isn't the default `dyn` — fmv and fclass carry no rounding mode. Matches
   1793    * the objdump/clang convention (an omitted suffix means dyn), so a third-
   1794    * party assembler re-encodes our fp->int truncation (rtz) exactly rather
   1795    * than substituting its own default. */
   1796   if (slice_has_prefix_cstr(d->mnemonic, "fcvt.", 5) ||
   1797       slice_has_prefix_cstr(d->mnemonic, "fsqrt.", 6)) {
   1798     u32 rm = (w >> 12) & 7u;
   1799     static const char* const RMN[8] = {"rne", "rtz", "rdn", "rup",
   1800                                        "rmm", 0,     0,     "dyn"};
   1801     if (rm != 7u && RMN[rm]) {
   1802       p_sep(sb);
   1803       strbuf_puts(sb, RMN[rm]);
   1804     }
   1805   }
   1806 }
   1807 
   1808 static void print_amo(StrBuf* sb, u32 w) {
   1809   Rv64R f = rv64_r_unpack(w);
   1810   p_xreg(sb, f.rd);
   1811   p_sep(sb);
   1812   p_xreg(sb, f.rs2);
   1813   p_sep(sb);
   1814   strbuf_putc(sb, '(');
   1815   p_xreg(sb, f.rs1);
   1816   strbuf_putc(sb, ')');
   1817 }
   1818 
   1819 static void print_lr(StrBuf* sb, u32 w) {
   1820   Rv64R f = rv64_r_unpack(w);
   1821   p_xreg(sb, f.rd);
   1822   p_sep(sb);
   1823   strbuf_putc(sb, '(');
   1824   p_xreg(sb, f.rs1);
   1825   strbuf_putc(sb, ')');
   1826 }
   1827 
   1828 /* ---- compressed printers ---- */
   1829 
   1830 static void print_cr(StrBuf* sb, u32 w, const Rv64InsnDesc* d) {
   1831   u32 hw = w & 0xffffu;
   1832   u32 rd_rs1 = (hw >> 7) & 0x1fu;
   1833   u32 rs2 = (hw >> 2) & 0x1fu;
   1834   if (slice_eq_cstr(d->mnemonic, "c.jr") ||
   1835       slice_eq_cstr(d->mnemonic, "c.jalr")) {
   1836     p_xreg(sb, rd_rs1);
   1837   } else {
   1838     /* c.mv / c.add */
   1839     p_xreg(sb, rd_rs1);
   1840     p_sep(sb);
   1841     p_xreg(sb, rs2);
   1842   }
   1843 }
   1844 
   1845 static void print_ci(StrBuf* sb, u32 w, const Rv64InsnDesc* d) {
   1846   u32 hw = w & 0xffffu;
   1847   u32 rd_rs1 = (hw >> 7) & 0x1fu;
   1848   /* immediate is split across bits 12 and 6:2 (signed 6-bit for most). */
   1849   u32 imm5 = (hw >> 12) & 1u;
   1850   u32 imm4_0 = (hw >> 2) & 0x1fu;
   1851   i64 imm;
   1852   if (slice_eq_cstr(d->mnemonic, "c.lui")) {
   1853     /* nzimm[17:12] = bits 12, 6:2 — signed extended to 18 bits. */
   1854     u64 raw = (u64)((imm5 << 5) | imm4_0);
   1855     imm = (i64)((u64)rv64_sext(raw, 6) << 12);
   1856     p_xreg(sb, rd_rs1);
   1857     p_sep(sb);
   1858     strbuf_put_hex_u64(sb, (u64)imm);
   1859     return;
   1860   }
   1861   if (slice_eq_cstr(d->mnemonic, "c.addi16sp")) {
   1862     /* nzimm[9|4|6|8:7|5] (scrambled). Just decode for print. */
   1863     u32 b9 = (hw >> 12) & 1u;
   1864     u32 b4 = (hw >> 6) & 1u;
   1865     u32 b6 = (hw >> 5) & 1u;
   1866     u32 b87 = (hw >> 3) & 3u;
   1867     u32 b5 = (hw >> 2) & 1u;
   1868     u64 raw = ((u64)b9 << 9) | ((u64)b87 << 7) | ((u64)b6 << 6) |
   1869               ((u64)b5 << 5) | ((u64)b4 << 4);
   1870     imm = rv64_sext(raw, 10);
   1871     p_xreg(sb, rd_rs1);
   1872     p_sep(sb);
   1873     strbuf_put_i64(sb, imm);
   1874     return;
   1875   }
   1876   if (slice_eq_cstr(d->mnemonic, "c.lwsp")) {
   1877     /* offset[5|4:2|7:6] scaled by 4. */
   1878     u32 b5 = imm5;
   1879     u32 b4_2 = (imm4_0 >> 2) & 7u;
   1880     u32 b7_6 = imm4_0 & 3u;
   1881     u32 off = (b7_6 << 6) | (b5 << 5) | (b4_2 << 2);
   1882     p_xreg(sb, rd_rs1);
   1883     p_sep(sb);
   1884     p_mem(sb, (i64)off, 2u);
   1885     return;
   1886   }
   1887   if (slice_eq_cstr(d->mnemonic, "c.ldsp") ||
   1888       slice_eq_cstr(d->mnemonic, "c.fldsp")) {
   1889     /* offset[5|4:3|8:6] scaled by 8. */
   1890     u32 b5 = imm5;
   1891     u32 b4_3 = (imm4_0 >> 3) & 3u;
   1892     u32 b8_6 = imm4_0 & 7u;
   1893     u32 off = (b8_6 << 6) | (b5 << 5) | (b4_3 << 3);
   1894     if (d->flags & RV64_ASMFL_FP)
   1895       p_freg(sb, rd_rs1);
   1896     else
   1897       p_xreg(sb, rd_rs1);
   1898     p_sep(sb);
   1899     p_mem(sb, (i64)off, 2u);
   1900     return;
   1901   }
   1902   if (slice_eq_cstr(d->mnemonic, "c.slli")) {
   1903     u32 shamt = (imm5 << 5) | imm4_0;
   1904     p_xreg(sb, rd_rs1);
   1905     p_sep(sb);
   1906     strbuf_put_u64(sb, (u64)shamt);
   1907     return;
   1908   }
   1909   /* c.li / c.addi — signed 6-bit immediate. */
   1910   imm = rv64_sext((u64)((imm5 << 5) | imm4_0), 6);
   1911   p_xreg(sb, rd_rs1);
   1912   p_sep(sb);
   1913   strbuf_put_i64(sb, imm);
   1914 }
   1915 
   1916 static void print_css(StrBuf* sb, u32 w, const Rv64InsnDesc* d) {
   1917   u32 hw = w & 0xffffu;
   1918   u32 rs2 = (hw >> 2) & 0x1fu;
   1919   u32 imm6 = (hw >> 7) & 0x3fu;
   1920   u32 off;
   1921   if (slice_eq_cstr(d->mnemonic, "c.swsp")) {
   1922     /* offset[5:2|7:6] scaled by 4. */
   1923     u32 b5_2 = (imm6 >> 2) & 0xfu;
   1924     u32 b7_6 = imm6 & 3u;
   1925     off = (b7_6 << 6) | (b5_2 << 2);
   1926     p_xreg(sb, rs2);
   1927     p_sep(sb);
   1928     p_mem(sb, (i64)off, 2u);
   1929     return;
   1930   }
   1931   /* c.sdsp / c.fsdsp — offset[5:3|8:6] scaled by 8. */
   1932   {
   1933     u32 b5_3 = (imm6 >> 3) & 7u;
   1934     u32 b8_6 = imm6 & 7u;
   1935     off = (b8_6 << 6) | (b5_3 << 3);
   1936     if (d->flags & RV64_ASMFL_FP)
   1937       p_freg(sb, rs2);
   1938     else
   1939       p_xreg(sb, rs2);
   1940     p_sep(sb);
   1941     p_mem(sb, (i64)off, 2u);
   1942   }
   1943 }
   1944 
   1945 static void print_ciw(StrBuf* sb, u32 w) {
   1946   u32 hw = w & 0xffffu;
   1947   u32 rd3 = (hw >> 2) & 7u;
   1948   /* nzuimm[5:4|9:6|2|3] scaled by 4 — encoded into bits 12:5. */
   1949   u32 imm = (hw >> 5) & 0xffu;
   1950   u32 b5_4 = (imm >> 6) & 3u;
   1951   u32 b9_6 = (imm >> 2) & 0xfu;
   1952   u32 b2 = (imm >> 1) & 1u;
   1953   u32 b3 = imm & 1u;
   1954   u32 off = (b9_6 << 6) | (b5_4 << 4) | (b3 << 3) | (b2 << 2);
   1955   p_xreg(sb, RVC_REG3(rd3));
   1956   p_sep(sb);
   1957   strbuf_puts(sb, "sp");
   1958   p_sep(sb);
   1959   strbuf_put_u64(sb, (u64)off);
   1960 }
   1961 
   1962 static void print_cl(StrBuf* sb, u32 w, const Rv64InsnDesc* d) {
   1963   u32 hw = w & 0xffffu;
   1964   u32 rd3 = (hw >> 2) & 7u;
   1965   u32 rs1_3 = (hw >> 7) & 7u;
   1966   u32 b5_3 = (hw >> 10) & 7u;
   1967   u32 lo = (hw >> 5) & 3u;
   1968   u32 off;
   1969   if (slice_eq_cstr(d->mnemonic, "c.lw")) {
   1970     /* offset[5:3|2|6] scaled by 4. */
   1971     u32 b2 = (lo >> 1) & 1u;
   1972     u32 b6 = lo & 1u;
   1973     off = (b6 << 6) | (b5_3 << 3) | (b2 << 2);
   1974   } else {
   1975     /* c.ld: offset[5:3|7:6] scaled by 8. */
   1976     off = (lo << 6) | (b5_3 << 3);
   1977   }
   1978   if (d->flags & RV64_ASMFL_FP)
   1979     p_freg(sb, RVC_REG3(rd3));
   1980   else
   1981     p_xreg(sb, RVC_REG3(rd3));
   1982   p_sep(sb);
   1983   p_mem(sb, (i64)off, RVC_REG3(rs1_3));
   1984 }
   1985 
   1986 static void print_cs(StrBuf* sb, u32 w, const Rv64InsnDesc* d) {
   1987   u32 hw = w & 0xffffu;
   1988   u32 rs2_3 = (hw >> 2) & 7u;
   1989   u32 rs1_3 = (hw >> 7) & 7u;
   1990   u32 b5_3 = (hw >> 10) & 7u;
   1991   u32 lo = (hw >> 5) & 3u;
   1992   u32 off;
   1993   if (slice_eq_cstr(d->mnemonic, "c.sw")) {
   1994     u32 b2 = (lo >> 1) & 1u;
   1995     u32 b6 = lo & 1u;
   1996     off = (b6 << 6) | (b5_3 << 3) | (b2 << 2);
   1997   } else {
   1998     off = (lo << 6) | (b5_3 << 3);
   1999   }
   2000   if (d->flags & RV64_ASMFL_FP)
   2001     p_freg(sb, RVC_REG3(rs2_3));
   2002   else
   2003     p_xreg(sb, RVC_REG3(rs2_3));
   2004   p_sep(sb);
   2005   p_mem(sb, (i64)off, RVC_REG3(rs1_3));
   2006 }
   2007 
   2008 static void print_ca(StrBuf* sb, u32 w) {
   2009   u32 hw = w & 0xffffu;
   2010   u32 rd3 = (hw >> 7) & 7u;
   2011   u32 rs2_3 = (hw >> 2) & 7u;
   2012   p_xreg(sb, RVC_REG3(rd3));
   2013   p_sep(sb);
   2014   p_xreg(sb, RVC_REG3(rs2_3));
   2015 }
   2016 
   2017 static void print_cb(StrBuf* sb, u32 w, u64 vaddr, const Rv64InsnDesc* d) {
   2018   u32 hw = w & 0xffffu;
   2019   u32 rs1_3 = (hw >> 7) & 7u;
   2020   if (slice_eq_cstr(d->mnemonic, "c.srli") ||
   2021       slice_eq_cstr(d->mnemonic, "c.srai") ||
   2022       slice_eq_cstr(d->mnemonic, "c.andi")) {
   2023     u32 imm = (((hw >> 12) & 1u) << 5) | ((hw >> 2) & 0x1fu);
   2024     p_xreg(sb, RVC_REG3(rs1_3));
   2025     p_sep(sb);
   2026     if (slice_eq_cstr(d->mnemonic, "c.andi"))
   2027       strbuf_put_i64(sb, rv64_sext((u64)imm, 6));
   2028     else
   2029       strbuf_put_u64(sb, (u64)imm);
   2030     return;
   2031   }
   2032   /* offset[8|4:3|7:6|2:1|5] scaled by 2. */
   2033   u32 b8 = (hw >> 12) & 1u;
   2034   u32 b4_3 = (hw >> 10) & 3u;
   2035   u32 b7_6 = (hw >> 5) & 3u;
   2036   u32 b2_1 = (hw >> 3) & 3u;
   2037   u32 b5 = (hw >> 2) & 1u;
   2038   u64 raw = ((u64)b8 << 8) | ((u64)b7_6 << 6) | ((u64)b5 << 5) |
   2039             ((u64)b4_3 << 3) | ((u64)b2_1 << 1);
   2040   i64 off = rv64_sext(raw, 9);
   2041   p_xreg(sb, RVC_REG3(rs1_3));
   2042   p_sep(sb);
   2043   p_rel(sb, vaddr, off);
   2044 }
   2045 
   2046 static void print_cj(StrBuf* sb, u32 w, u64 vaddr) {
   2047   u32 hw = w & 0xffffu;
   2048   /* offset[11|4|9:8|10|6|7|3:1|5] scaled by 2. */
   2049   u32 b11 = (hw >> 12) & 1u;
   2050   u32 b4 = (hw >> 11) & 1u;
   2051   u32 b9_8 = (hw >> 9) & 3u;
   2052   u32 b10 = (hw >> 8) & 1u;
   2053   u32 b6 = (hw >> 7) & 1u;
   2054   u32 b7 = (hw >> 6) & 1u;
   2055   u32 b3_1 = (hw >> 3) & 7u;
   2056   u32 b5 = (hw >> 2) & 1u;
   2057   u64 raw = ((u64)b11 << 11) | ((u64)b10 << 10) | ((u64)b9_8 << 8) |
   2058             ((u64)b7 << 7) | ((u64)b6 << 6) | ((u64)b5 << 5) | ((u64)b4 << 4) |
   2059             ((u64)b3_1 << 1);
   2060   i64 off = rv64_sext(raw, 12);
   2061   p_rel(sb, vaddr, off);
   2062 }
   2063 
   2064 void rv64_print_operands(StrBuf* sb, const Rv64InsnDesc* desc, u32 word,
   2065                          u64 vaddr) {
   2066   switch ((Rv64Format)desc->fmt) {
   2067     case RV64_FMT_R:
   2068       print_r(sb, word, desc);
   2069       break;
   2070     case RV64_FMT_R4:
   2071       print_r4(sb, word);
   2072       break;
   2073     case RV64_FMT_I:
   2074       print_i(sb, word, desc);
   2075       break;
   2076     case RV64_FMT_I_SHIFT:
   2077       print_i_shift(sb, word);
   2078       break;
   2079     case RV64_FMT_I_SHIFTW:
   2080       print_i_shiftw(sb, word);
   2081       break;
   2082     case RV64_FMT_S:
   2083       print_store(sb, word, desc);
   2084       break;
   2085     case RV64_FMT_B:
   2086       print_b(sb, word, vaddr, desc);
   2087       break;
   2088     case RV64_FMT_U:
   2089       print_u(sb, word);
   2090       break;
   2091     case RV64_FMT_J:
   2092       print_j(sb, word, vaddr, desc);
   2093       break;
   2094     case RV64_FMT_LOAD:
   2095       print_load(sb, word, desc);
   2096       break;
   2097     case RV64_FMT_STORE:
   2098       print_store(sb, word, desc);
   2099       break;
   2100     case RV64_FMT_JALR:
   2101       print_jalr(sb, word, desc);
   2102       break;
   2103     case RV64_FMT_FENCE:
   2104       print_fence(sb, word);
   2105       break;
   2106     case RV64_FMT_SYSTEM:
   2107       break; /* no operands */
   2108     case RV64_FMT_FP_RM:
   2109       print_fp_rm(sb, word);
   2110       break;
   2111     case RV64_FMT_FP_R:
   2112       print_fp_r(sb, word, desc);
   2113       break;
   2114     case RV64_FMT_FP_CVT:
   2115       print_fp_cvt(sb, word, desc);
   2116       break;
   2117     case RV64_FMT_FP_LOAD:
   2118       print_load(sb, word, desc);
   2119       break;
   2120     case RV64_FMT_FP_STORE:
   2121       print_store(sb, word, desc);
   2122       break;
   2123     case RV64_FMT_AMO:
   2124       print_amo(sb, word);
   2125       break;
   2126     case RV64_FMT_LR:
   2127       print_lr(sb, word);
   2128       break;
   2129     case RV64_FMT_CSR:
   2130       print_csr(sb, word);
   2131       break;
   2132     case RV64_FMT_CSRI:
   2133       print_csri(sb, word);
   2134       break;
   2135     case RV64_FMT_CSR_PSEUDO:
   2136       /* Encode-only alias rows; the disassembler always matches the canonical
   2137        * full-form csrr* row first, so this is never reached for real bytes. */
   2138       print_csr(sb, word);
   2139       break;
   2140     case RV64_FMT_CR:
   2141       print_cr(sb, word, desc);
   2142       break;
   2143     case RV64_FMT_CI:
   2144       print_ci(sb, word, desc);
   2145       break;
   2146     case RV64_FMT_CSS:
   2147       print_css(sb, word, desc);
   2148       break;
   2149     case RV64_FMT_CIW:
   2150       print_ciw(sb, word);
   2151       break;
   2152     case RV64_FMT_CL:
   2153       print_cl(sb, word, desc);
   2154       break;
   2155     case RV64_FMT_CS:
   2156       print_cs(sb, word, desc);
   2157       break;
   2158     case RV64_FMT_CA:
   2159       print_ca(sb, word);
   2160       break;
   2161     case RV64_FMT_CB:
   2162       print_cb(sb, word, vaddr, desc);
   2163       break;
   2164     case RV64_FMT_CJ:
   2165       print_cj(sb, word, vaddr);
   2166       break;
   2167     case RV64_FMT_C_NONE:
   2168       break;
   2169     case RV64_FMT_PSEUDO:
   2170       /* Assembler-only multi-word pseudo; rv64_disasm_find never returns
   2171        * these rows, so the printer is never reached for this format. */
   2172       break;
   2173   }
   2174 }