kit

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

isa.c (67975B)


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