kit

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

isa.c (62102B)


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