kit

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

isa.h (30893B)


      1 /* RV64 instruction encoders + descriptor table — single source of truth
      2  * for every instruction the encoder, decoder, and disassembler need to
      3  * agree on. Mirrors the aa64_isa.[ch] pattern.
      4  *
      5  * The bottom of this header (after the `rv_*` inline encoders) declares
      6  * the format-kind enum and per-format pack/unpack helpers. The
      7  * descriptor table itself lives in isa.c. */
      8 
      9 #ifndef KIT_RV64_ISA_H
     10 #define KIT_RV64_ISA_H
     11 
     12 #include "core/core.h"
     13 #include "core/slice.h"
     14 #include "core/strbuf.h"
     15 
     16 /* ---- Named registers (DWARF / psABI numbering matches HW) ---- */
     17 enum {
     18   RV_X0 = 0,
     19   RV_ZERO = 0,
     20   RV_X1 = 1,
     21   RV_RA = 1,
     22   RV_X2 = 2,
     23   RV_SP = 2,
     24   RV_X3 = 3,
     25   RV_GP = 3,
     26   RV_X4 = 4,
     27   RV_TP = 4,
     28   RV_X5 = 5,
     29   RV_T0 = 5,
     30   RV_X6 = 6,
     31   RV_T1 = 6,
     32   RV_X7 = 7,
     33   RV_T2 = 7,
     34   RV_X8 = 8,
     35   RV_S0 = 8,
     36   RV_FP = 8,
     37   RV_X9 = 9,
     38   RV_S1 = 9,
     39   RV_X10 = 10,
     40   RV_A0 = 10,
     41   RV_X11 = 11,
     42   RV_A1 = 11,
     43   RV_X12 = 12,
     44   RV_A2 = 12,
     45   RV_X13 = 13,
     46   RV_A3 = 13,
     47   RV_X14 = 14,
     48   RV_A4 = 14,
     49   RV_X15 = 15,
     50   RV_A5 = 15,
     51   RV_X16 = 16,
     52   RV_A6 = 16,
     53   RV_X17 = 17,
     54   RV_A7 = 17,
     55   RV_X18 = 18,
     56   RV_S2 = 18,
     57   RV_X27 = 27,
     58   RV_S11 = 27,
     59   RV_X28 = 28,
     60   RV_T3 = 28,
     61   RV_X29 = 29,
     62   RV_T4 = 29,
     63   RV_X30 = 30,
     64   RV_T5 = 30,
     65   RV_X31 = 31,
     66   RV_T6 = 31,
     67 };
     68 
     69 #define RV_NOP 0x00000013u /* ADDI x0, x0, 0 */
     70 
     71 /* ---- Format helpers ----
     72  *
     73  * R-type: funct7(31:25) rs2(24:20) rs1(19:15) funct3(14:12) rd(11:7) op(6:0)
     74  * I-type: imm(31:20)    rs1(19:15) funct3(14:12) rd(11:7) op(6:0)
     75  * S-type: imm[11:5](31:25) rs2(24:20) rs1(19:15) funct3(14:12) imm[4:0](11:7)
     76  * op(6:0) B-type: imm[12](31) imm[10:5](30:25) rs2(24:20) rs1(19:15)
     77  * funct3(14:12) imm[4:1](11:8) imm[11](7) op(6:0) U-type: imm[31:12](31:12)
     78  * rd(11:7) op(6:0) J-type: imm[20](31) imm[10:1](30:21) imm[11](20)
     79  * imm[19:12](19:12) rd(11:7) op(6:0)
     80  */
     81 
     82 static inline u32 rv_r(u32 funct7, u32 rs2, u32 rs1, u32 funct3, u32 rd,
     83                        u32 op) {
     84   return ((funct7 & 0x7fu) << 25) | ((rs2 & 0x1fu) << 20) |
     85          ((rs1 & 0x1fu) << 15) | ((funct3 & 0x7u) << 12) | ((rd & 0x1fu) << 7) |
     86          (op & 0x7fu);
     87 }
     88 static inline u32 rv_i(i32 imm12, u32 rs1, u32 funct3, u32 rd, u32 op) {
     89   return (((u32)imm12 & 0xfffu) << 20) | ((rs1 & 0x1fu) << 15) |
     90          ((funct3 & 0x7u) << 12) | ((rd & 0x1fu) << 7) | (op & 0x7fu);
     91 }
     92 /* S/B/J immediate bit-scramble — the immediate-field bits only (no
     93  * register/funct/op). Single source of truth for both the codegen encoders
     94  * (rv_s/rv_b/rv_j below) and the assembler's match-word overlay encoders
     95  * (enc_s/enc_b/enc_j in asm.c), which were byte-identical reimplementations. */
     96 static inline u32 rv_imm_s(i32 imm12) {
     97   u32 ui = (u32)imm12 & 0xfffu;
     98   return ((ui >> 5) << 25) | ((ui & 0x1fu) << 7);
     99 }
    100 static inline u32 rv_imm_b(i32 imm13) {
    101   u32 ui = (u32)imm13;
    102   return (((ui >> 12) & 1u) << 31) | (((ui >> 5) & 0x3fu) << 25) |
    103          (((ui >> 1) & 0xfu) << 8) | (((ui >> 11) & 1u) << 7);
    104 }
    105 static inline u32 rv_imm_j(i32 imm21) {
    106   u32 ui = (u32)imm21;
    107   return (((ui >> 20) & 1u) << 31) | (((ui >> 1) & 0x3ffu) << 21) |
    108          (((ui >> 11) & 1u) << 20) | (((ui >> 12) & 0xffu) << 12);
    109 }
    110 
    111 static inline u32 rv_s(i32 imm12, u32 rs2, u32 rs1, u32 funct3, u32 op) {
    112   return rv_imm_s(imm12) | ((rs2 & 0x1fu) << 20) | ((rs1 & 0x1fu) << 15) |
    113          ((funct3 & 0x7u) << 12) | (op & 0x7fu);
    114 }
    115 static inline u32 rv_b(i32 imm13, u32 rs2, u32 rs1, u32 funct3, u32 op) {
    116   return rv_imm_b(imm13) | ((rs2 & 0x1fu) << 20) | ((rs1 & 0x1fu) << 15) |
    117          ((funct3 & 0x7u) << 12) | (op & 0x7fu);
    118 }
    119 static inline u32 rv_u(u32 imm32_hi20, u32 rd, u32 op) {
    120   return (imm32_hi20 & 0xfffff000u) | ((rd & 0x1fu) << 7) | (op & 0x7fu);
    121 }
    122 static inline u32 rv_j(i32 imm21, u32 rd, u32 op) {
    123   return rv_imm_j(imm21) | ((rd & 0x1fu) << 7) | (op & 0x7fu);
    124 }
    125 
    126 /* ---- Integer ops (RV32I/RV64I) ---- */
    127 
    128 #define RV_OP 0x33u
    129 #define RV_OP_IMM 0x13u
    130 #define RV_OP_32 0x3bu
    131 #define RV_OP_IMM_32 0x1bu
    132 #define RV_LUI 0x37u
    133 #define RV_AUIPC 0x17u
    134 #define RV_LOAD 0x03u
    135 #define RV_STORE 0x23u
    136 #define RV_BRANCH 0x63u
    137 #define RV_JAL 0x6fu
    138 #define RV_JALR 0x67u
    139 #define RV_LOAD_FP 0x07u
    140 #define RV_STORE_FP 0x27u
    141 #define RV_OP_FP 0x53u
    142 #define RV_MADD 0x43u
    143 #define RV_MSUB 0x47u
    144 #define RV_NMSUB 0x4bu
    145 #define RV_NMADD 0x4fu
    146 #define RV_AMO 0x2fu
    147 #define RV_FENCE 0x0fu
    148 #define RV_SYSTEM 0x73u
    149 
    150 static inline u32 rv_add(u32 rd, u32 rs1, u32 rs2) {
    151   return rv_r(0x00, rs2, rs1, 0x0, rd, RV_OP);
    152 }
    153 static inline u32 rv_sub(u32 rd, u32 rs1, u32 rs2) {
    154   return rv_r(0x20, rs2, rs1, 0x0, rd, RV_OP);
    155 }
    156 static inline u32 rv_sll(u32 rd, u32 rs1, u32 rs2) {
    157   return rv_r(0x00, rs2, rs1, 0x1, rd, RV_OP);
    158 }
    159 static inline u32 rv_slt(u32 rd, u32 rs1, u32 rs2) {
    160   return rv_r(0x00, rs2, rs1, 0x2, rd, RV_OP);
    161 }
    162 static inline u32 rv_sltu(u32 rd, u32 rs1, u32 rs2) {
    163   return rv_r(0x00, rs2, rs1, 0x3, rd, RV_OP);
    164 }
    165 static inline u32 rv_xor(u32 rd, u32 rs1, u32 rs2) {
    166   return rv_r(0x00, rs2, rs1, 0x4, rd, RV_OP);
    167 }
    168 static inline u32 rv_srl(u32 rd, u32 rs1, u32 rs2) {
    169   return rv_r(0x00, rs2, rs1, 0x5, rd, RV_OP);
    170 }
    171 static inline u32 rv_sra(u32 rd, u32 rs1, u32 rs2) {
    172   return rv_r(0x20, rs2, rs1, 0x5, rd, RV_OP);
    173 }
    174 static inline u32 rv_or(u32 rd, u32 rs1, u32 rs2) {
    175   return rv_r(0x00, rs2, rs1, 0x6, rd, RV_OP);
    176 }
    177 static inline u32 rv_and(u32 rd, u32 rs1, u32 rs2) {
    178   return rv_r(0x00, rs2, rs1, 0x7, rd, RV_OP);
    179 }
    180 
    181 static inline u32 rv_addw(u32 rd, u32 rs1, u32 rs2) {
    182   return rv_r(0x00, rs2, rs1, 0x0, rd, RV_OP_32);
    183 }
    184 static inline u32 rv_subw(u32 rd, u32 rs1, u32 rs2) {
    185   return rv_r(0x20, rs2, rs1, 0x0, rd, RV_OP_32);
    186 }
    187 static inline u32 rv_sllw(u32 rd, u32 rs1, u32 rs2) {
    188   return rv_r(0x00, rs2, rs1, 0x1, rd, RV_OP_32);
    189 }
    190 static inline u32 rv_srlw(u32 rd, u32 rs1, u32 rs2) {
    191   return rv_r(0x00, rs2, rs1, 0x5, rd, RV_OP_32);
    192 }
    193 static inline u32 rv_sraw(u32 rd, u32 rs1, u32 rs2) {
    194   return rv_r(0x20, rs2, rs1, 0x5, rd, RV_OP_32);
    195 }
    196 
    197 static inline u32 rv_addi(u32 rd, u32 rs1, i32 imm) {
    198   return rv_i(imm, rs1, 0x0, rd, RV_OP_IMM);
    199 }
    200 static inline u32 rv_slti(u32 rd, u32 rs1, i32 imm) {
    201   return rv_i(imm, rs1, 0x2, rd, RV_OP_IMM);
    202 }
    203 static inline u32 rv_sltiu(u32 rd, u32 rs1, i32 imm) {
    204   return rv_i(imm, rs1, 0x3, rd, RV_OP_IMM);
    205 }
    206 static inline u32 rv_xori(u32 rd, u32 rs1, i32 imm) {
    207   return rv_i(imm, rs1, 0x4, rd, RV_OP_IMM);
    208 }
    209 static inline u32 rv_ori(u32 rd, u32 rs1, i32 imm) {
    210   return rv_i(imm, rs1, 0x6, rd, RV_OP_IMM);
    211 }
    212 static inline u32 rv_andi(u32 rd, u32 rs1, i32 imm) {
    213   return rv_i(imm, rs1, 0x7, rd, RV_OP_IMM);
    214 }
    215 
    216 /* Shift-immediate forms. RV64I uses a 6-bit shamt in bits 25:20 and a
    217  * 6-bit funct6 in bits 31:26 (so the funct7-vs-shamt[5] split that
    218  * rv_r() does is wrong here — we hand-assemble these). */
    219 static inline u32 rv_slli(u32 rd, u32 rs1, u32 sh) {
    220   return (0x00u << 26) | ((sh & 0x3fu) << 20) | ((rs1 & 0x1fu) << 15) |
    221          (0x1u << 12) | ((rd & 0x1fu) << 7) | RV_OP_IMM;
    222 }
    223 static inline u32 rv_srli(u32 rd, u32 rs1, u32 sh) {
    224   return (0x00u << 26) | ((sh & 0x3fu) << 20) | ((rs1 & 0x1fu) << 15) |
    225          (0x5u << 12) | ((rd & 0x1fu) << 7) | RV_OP_IMM;
    226 }
    227 static inline u32 rv_srai(u32 rd, u32 rs1, u32 sh) {
    228   return (0x10u << 26) | ((sh & 0x3fu) << 20) | ((rs1 & 0x1fu) << 15) |
    229          (0x5u << 12) | ((rd & 0x1fu) << 7) | RV_OP_IMM;
    230 }
    231 
    232 static inline u32 rv_addiw(u32 rd, u32 rs1, i32 imm) {
    233   return rv_i(imm, rs1, 0x0, rd, RV_OP_IMM_32);
    234 }
    235 static inline u32 rv_slliw(u32 rd, u32 rs1, u32 sh) {
    236   return rv_r(0x00, sh & 0x1fu, rs1, 0x1, rd, RV_OP_IMM_32);
    237 }
    238 static inline u32 rv_srliw(u32 rd, u32 rs1, u32 sh) {
    239   return rv_r(0x00, sh & 0x1fu, rs1, 0x5, rd, RV_OP_IMM_32);
    240 }
    241 static inline u32 rv_sraiw(u32 rd, u32 rs1, u32 sh) {
    242   return rv_r(0x20, sh & 0x1fu, rs1, 0x5, rd, RV_OP_IMM_32);
    243 }
    244 
    245 static inline u32 rv_lui(u32 rd, u32 imm20) {
    246   return ((imm20 & 0xfffffu) << 12) | ((rd & 0x1fu) << 7) | RV_LUI;
    247 }
    248 static inline u32 rv_auipc(u32 rd, u32 imm20) {
    249   return ((imm20 & 0xfffffu) << 12) | ((rd & 0x1fu) << 7) | RV_AUIPC;
    250 }
    251 
    252 /* M extension */
    253 static inline u32 rv_mul(u32 rd, u32 rs1, u32 rs2) {
    254   return rv_r(0x01, rs2, rs1, 0x0, rd, RV_OP);
    255 }
    256 static inline u32 rv_mulh(u32 rd, u32 rs1, u32 rs2) {
    257   return rv_r(0x01, rs2, rs1, 0x1, rd, RV_OP);
    258 }
    259 static inline u32 rv_mulhsu(u32 rd, u32 rs1, u32 rs2) {
    260   return rv_r(0x01, rs2, rs1, 0x2, rd, RV_OP);
    261 }
    262 static inline u32 rv_mulhu(u32 rd, u32 rs1, u32 rs2) {
    263   return rv_r(0x01, rs2, rs1, 0x3, rd, RV_OP);
    264 }
    265 static inline u32 rv_div(u32 rd, u32 rs1, u32 rs2) {
    266   return rv_r(0x01, rs2, rs1, 0x4, rd, RV_OP);
    267 }
    268 static inline u32 rv_divu(u32 rd, u32 rs1, u32 rs2) {
    269   return rv_r(0x01, rs2, rs1, 0x5, rd, RV_OP);
    270 }
    271 static inline u32 rv_rem(u32 rd, u32 rs1, u32 rs2) {
    272   return rv_r(0x01, rs2, rs1, 0x6, rd, RV_OP);
    273 }
    274 static inline u32 rv_remu(u32 rd, u32 rs1, u32 rs2) {
    275   return rv_r(0x01, rs2, rs1, 0x7, rd, RV_OP);
    276 }
    277 static inline u32 rv_mulw(u32 rd, u32 rs1, u32 rs2) {
    278   return rv_r(0x01, rs2, rs1, 0x0, rd, RV_OP_32);
    279 }
    280 static inline u32 rv_divw(u32 rd, u32 rs1, u32 rs2) {
    281   return rv_r(0x01, rs2, rs1, 0x4, rd, RV_OP_32);
    282 }
    283 static inline u32 rv_divuw(u32 rd, u32 rs1, u32 rs2) {
    284   return rv_r(0x01, rs2, rs1, 0x5, rd, RV_OP_32);
    285 }
    286 static inline u32 rv_remw(u32 rd, u32 rs1, u32 rs2) {
    287   return rv_r(0x01, rs2, rs1, 0x6, rd, RV_OP_32);
    288 }
    289 static inline u32 rv_remuw(u32 rd, u32 rs1, u32 rs2) {
    290   return rv_r(0x01, rs2, rs1, 0x7, rd, RV_OP_32);
    291 }
    292 
    293 /* Zba (address-generation) subset — assumed available on rv64 targets.
    294  * SH{1,2,3}ADD rd, rs1, rs2 computes rd = (rs1 << {1,2,3}) + rs2 in one
    295  * instruction (funct7=0x10, opcode=OP). Used by load/store to fold an
    296  * indexed effective address `base + (index << log2_scale)` into a single
    297  * scratch register without an explicit shift+add pair. */
    298 static inline u32 rv_sh1add(u32 rd, u32 rs1, u32 rs2) {
    299   return rv_r(0x10, rs2, rs1, 0x2, rd, RV_OP);
    300 }
    301 static inline u32 rv_sh2add(u32 rd, u32 rs1, u32 rs2) {
    302   return rv_r(0x10, rs2, rs1, 0x4, rd, RV_OP);
    303 }
    304 static inline u32 rv_sh3add(u32 rd, u32 rs1, u32 rs2) {
    305   return rv_r(0x10, rs2, rs1, 0x6, rd, RV_OP);
    306 }
    307 
    308 /* Loads (funct3: 0=LB,1=LH,2=LW,3=LD,4=LBU,5=LHU,6=LWU) */
    309 static inline u32 rv_lb(u32 rd, u32 rs1, i32 imm) {
    310   return rv_i(imm, rs1, 0x0, rd, RV_LOAD);
    311 }
    312 static inline u32 rv_lh(u32 rd, u32 rs1, i32 imm) {
    313   return rv_i(imm, rs1, 0x1, rd, RV_LOAD);
    314 }
    315 static inline u32 rv_lw(u32 rd, u32 rs1, i32 imm) {
    316   return rv_i(imm, rs1, 0x2, rd, RV_LOAD);
    317 }
    318 static inline u32 rv_ld(u32 rd, u32 rs1, i32 imm) {
    319   return rv_i(imm, rs1, 0x3, rd, RV_LOAD);
    320 }
    321 static inline u32 rv_lbu(u32 rd, u32 rs1, i32 imm) {
    322   return rv_i(imm, rs1, 0x4, rd, RV_LOAD);
    323 }
    324 static inline u32 rv_lhu(u32 rd, u32 rs1, i32 imm) {
    325   return rv_i(imm, rs1, 0x5, rd, RV_LOAD);
    326 }
    327 static inline u32 rv_lwu(u32 rd, u32 rs1, i32 imm) {
    328   return rv_i(imm, rs1, 0x6, rd, RV_LOAD);
    329 }
    330 
    331 /* Stores (funct3: 0=SB,1=SH,2=SW,3=SD) */
    332 static inline u32 rv_sb(u32 rs2, u32 rs1, i32 imm) {
    333   return rv_s(imm, rs2, rs1, 0x0, RV_STORE);
    334 }
    335 static inline u32 rv_sh(u32 rs2, u32 rs1, i32 imm) {
    336   return rv_s(imm, rs2, rs1, 0x1, RV_STORE);
    337 }
    338 static inline u32 rv_sw(u32 rs2, u32 rs1, i32 imm) {
    339   return rv_s(imm, rs2, rs1, 0x2, RV_STORE);
    340 }
    341 static inline u32 rv_sd(u32 rs2, u32 rs1, i32 imm) {
    342   return rv_s(imm, rs2, rs1, 0x3, RV_STORE);
    343 }
    344 
    345 /* Branches */
    346 static inline u32 rv_beq(u32 rs1, u32 rs2, i32 imm) {
    347   return rv_b(imm, rs2, rs1, 0x0, RV_BRANCH);
    348 }
    349 static inline u32 rv_bne(u32 rs1, u32 rs2, i32 imm) {
    350   return rv_b(imm, rs2, rs1, 0x1, RV_BRANCH);
    351 }
    352 static inline u32 rv_blt(u32 rs1, u32 rs2, i32 imm) {
    353   return rv_b(imm, rs2, rs1, 0x4, RV_BRANCH);
    354 }
    355 static inline u32 rv_bge(u32 rs1, u32 rs2, i32 imm) {
    356   return rv_b(imm, rs2, rs1, 0x5, RV_BRANCH);
    357 }
    358 static inline u32 rv_bltu(u32 rs1, u32 rs2, i32 imm) {
    359   return rv_b(imm, rs2, rs1, 0x6, RV_BRANCH);
    360 }
    361 static inline u32 rv_bgeu(u32 rs1, u32 rs2, i32 imm) {
    362   return rv_b(imm, rs2, rs1, 0x7, RV_BRANCH);
    363 }
    364 
    365 /* Jumps */
    366 static inline u32 rv_jal(u32 rd, i32 imm21) { return rv_j(imm21, rd, RV_JAL); }
    367 static inline u32 rv_jalr(u32 rd, u32 rs1, i32 imm) {
    368   return rv_i(imm, rs1, 0x0, rd, RV_JALR);
    369 }
    370 
    371 /* Convenience: jr / ret / j / nop */
    372 static inline u32 rv_jr(u32 rs1) { return rv_jalr(RV_ZERO, rs1, 0); }
    373 static inline u32 rv_ret_(void) { return rv_jalr(RV_ZERO, RV_RA, 0); }
    374 static inline u32 rv_nop(void) { return RV_NOP; }
    375 
    376 /* System */
    377 static inline u32 rv_ecall(void) { return rv_i(0, 0, 0, 0, RV_SYSTEM); }
    378 static inline u32 rv_ebreak(void) { return rv_i(1, 0, 0, 0, RV_SYSTEM); }
    379 /* WFI: wait-for-interrupt, SYSTEM funct12=0x105 (privileged). */
    380 static inline u32 rv_wfi(void) { return 0x10500073u; }
    381 
    382 /* FENCE: pred/succ each 4 bits in imm[11:8]/imm[7:4]. fm bits 11:8 of imm */
    383 static inline u32 rv_fence_rw_rw(void) {
    384   return rv_i((i32)0x033, 0, 0, 0, RV_FENCE);
    385 }
    386 /* FENCE.I: instruction-stream sync (Zifencei). funct3=1 in the MISC-MEM major
    387  * opcode (0x0F). Used to lower the ISB intrinsic. */
    388 static inline u32 rv_fence_i(void) { return 0x0000100Fu; }
    389 /* PAUSE (Zihintpause): a FENCE with pred=W, succ=none. Used for cpu_yield;
    390  * decodes as a plain FENCE on hardware lacking the extension, which is a safe
    391  * (stronger) no-op hint. */
    392 static inline u32 rv_pause(void) { return 0x0100000Fu; }
    393 
    394 /* ---- FP (F + D extensions) ----
    395  * funct7 layout: bits[6:2] op-major (e.g. 0x00 FADD, 0x01 FSUB, ...);
    396  * bits[1:0] = fmt (00=S, 01=D). rm (rounding mode) in funct3; 0x7 = DYN. */
    397 
    398 #define RV_FMT_S 0u
    399 #define RV_FMT_D 1u
    400 
    401 static inline u32 rv_fadd(u32 fmt, u32 rd, u32 rs1, u32 rs2) {
    402   return rv_r((0x00u << 2) | fmt, rs2, rs1, 0x7, rd, RV_OP_FP);
    403 }
    404 static inline u32 rv_fsub(u32 fmt, u32 rd, u32 rs1, u32 rs2) {
    405   return rv_r((0x01u << 2) | fmt, rs2, rs1, 0x7, rd, RV_OP_FP);
    406 }
    407 static inline u32 rv_fmul(u32 fmt, u32 rd, u32 rs1, u32 rs2) {
    408   return rv_r((0x02u << 2) | fmt, rs2, rs1, 0x7, rd, RV_OP_FP);
    409 }
    410 static inline u32 rv_fdiv(u32 fmt, u32 rd, u32 rs1, u32 rs2) {
    411   return rv_r((0x03u << 2) | fmt, rs2, rs1, 0x7, rd, RV_OP_FP);
    412 }
    413 /* FSGNJ.fmt rd, rs1, rs2 — used to implement FMV.fmt rd, rs (sgnj rs, rs). */
    414 static inline u32 rv_fsgnj(u32 fmt, u32 rd, u32 rs1, u32 rs2) {
    415   return rv_r((0x04u << 2) | fmt, rs2, rs1, 0x0, rd, RV_OP_FP);
    416 }
    417 static inline u32 rv_fsgnjn(u32 fmt, u32 rd, u32 rs1, u32 rs2) {
    418   return rv_r((0x04u << 2) | fmt, rs2, rs1, 0x1, rd, RV_OP_FP);
    419 }
    420 /* FCVT — integer/FP conversions. funct7 = 0x18..0x1d depending on direction;
    421  * rs2 encodes the partner type:
    422  *   0x60(W <- S)  0x61(W <- D)
    423  *   0x68(S <- W)  0x69(D <- W) etc
    424  * We assemble explicitly via rv_r to be obvious.  */
    425 static inline u32 rv_fcvt(u32 funct7, u32 rs2_sel, u32 rd, u32 rs1, u32 rm) {
    426   return rv_r(funct7, rs2_sel, rs1, rm, rd, RV_OP_FP);
    427 }
    428 /* FCVT.W.S  rd, rs1   (signed i32 from f32, rtz=001)  : funct7=0x60 rs2=0 */
    429 static inline u32 rv_fcvt_w_s(u32 rd, u32 rs1) {
    430   return rv_fcvt(0x60, 0x0, rd, rs1, 0x1);
    431 }
    432 static inline u32 rv_fcvt_wu_s(u32 rd, u32 rs1) {
    433   return rv_fcvt(0x60, 0x1, rd, rs1, 0x1);
    434 }
    435 static inline u32 rv_fcvt_l_s(u32 rd, u32 rs1) {
    436   return rv_fcvt(0x60, 0x2, rd, rs1, 0x1);
    437 }
    438 static inline u32 rv_fcvt_lu_s(u32 rd, u32 rs1) {
    439   return rv_fcvt(0x60, 0x3, rd, rs1, 0x1);
    440 }
    441 static inline u32 rv_fcvt_w_d(u32 rd, u32 rs1) {
    442   return rv_fcvt(0x61, 0x0, rd, rs1, 0x1);
    443 }
    444 static inline u32 rv_fcvt_wu_d(u32 rd, u32 rs1) {
    445   return rv_fcvt(0x61, 0x1, rd, rs1, 0x1);
    446 }
    447 static inline u32 rv_fcvt_l_d(u32 rd, u32 rs1) {
    448   return rv_fcvt(0x61, 0x2, rd, rs1, 0x1);
    449 }
    450 static inline u32 rv_fcvt_lu_d(u32 rd, u32 rs1) {
    451   return rv_fcvt(0x61, 0x3, rd, rs1, 0x1);
    452 }
    453 static inline u32 rv_fcvt_s_w(u32 rd, u32 rs1) {
    454   return rv_fcvt(0x68, 0x0, rd, rs1, 0x7);
    455 }
    456 static inline u32 rv_fcvt_s_wu(u32 rd, u32 rs1) {
    457   return rv_fcvt(0x68, 0x1, rd, rs1, 0x7);
    458 }
    459 static inline u32 rv_fcvt_s_l(u32 rd, u32 rs1) {
    460   return rv_fcvt(0x68, 0x2, rd, rs1, 0x7);
    461 }
    462 static inline u32 rv_fcvt_s_lu(u32 rd, u32 rs1) {
    463   return rv_fcvt(0x68, 0x3, rd, rs1, 0x7);
    464 }
    465 static inline u32 rv_fcvt_d_w(u32 rd, u32 rs1) {
    466   return rv_fcvt(0x69, 0x0, rd, rs1, 0x7);
    467 }
    468 static inline u32 rv_fcvt_d_wu(u32 rd, u32 rs1) {
    469   return rv_fcvt(0x69, 0x1, rd, rs1, 0x7);
    470 }
    471 static inline u32 rv_fcvt_d_l(u32 rd, u32 rs1) {
    472   return rv_fcvt(0x69, 0x2, rd, rs1, 0x7);
    473 }
    474 static inline u32 rv_fcvt_d_lu(u32 rd, u32 rs1) {
    475   return rv_fcvt(0x69, 0x3, rd, rs1, 0x7);
    476 }
    477 /* FCVT.S.D / FCVT.D.S */
    478 static inline u32 rv_fcvt_s_d(u32 rd, u32 rs1) {
    479   return rv_fcvt(0x20, 0x1, rd, rs1, 0x7);
    480 }
    481 static inline u32 rv_fcvt_d_s(u32 rd, u32 rs1) {
    482   return rv_fcvt(0x21, 0x0, rd, rs1, 0x7);
    483 }
    484 
    485 /* FMV.X.W / FMV.W.X / FMV.X.D / FMV.D.X — bitcast between GPR and FPR. */
    486 static inline u32 rv_fmv_x_w(u32 rd, u32 rs1) {
    487   return rv_fcvt(0x70, 0x0, rd, rs1, 0x0);
    488 }
    489 static inline u32 rv_fmv_w_x(u32 rd, u32 rs1) {
    490   return rv_fcvt(0x78, 0x0, rd, rs1, 0x0);
    491 }
    492 static inline u32 rv_fmv_x_d(u32 rd, u32 rs1) {
    493   return rv_fcvt(0x71, 0x0, rd, rs1, 0x0);
    494 }
    495 static inline u32 rv_fmv_d_x(u32 rd, u32 rs1) {
    496   return rv_fcvt(0x79, 0x0, rd, rs1, 0x0);
    497 }
    498 
    499 /* FP compares — rd is integer GPR. funct7 = 0x50/0x51 (S/D). rm: 0=LE, 1=LT,
    500  * 2=EQ. */
    501 static inline u32 rv_feq_s(u32 rd, u32 rs1, u32 rs2) {
    502   return rv_r(0x50, rs2, rs1, 0x2, rd, RV_OP_FP);
    503 }
    504 static inline u32 rv_flt_s(u32 rd, u32 rs1, u32 rs2) {
    505   return rv_r(0x50, rs2, rs1, 0x1, rd, RV_OP_FP);
    506 }
    507 static inline u32 rv_fle_s(u32 rd, u32 rs1, u32 rs2) {
    508   return rv_r(0x50, rs2, rs1, 0x0, rd, RV_OP_FP);
    509 }
    510 static inline u32 rv_feq_d(u32 rd, u32 rs1, u32 rs2) {
    511   return rv_r(0x51, rs2, rs1, 0x2, rd, RV_OP_FP);
    512 }
    513 static inline u32 rv_flt_d(u32 rd, u32 rs1, u32 rs2) {
    514   return rv_r(0x51, rs2, rs1, 0x1, rd, RV_OP_FP);
    515 }
    516 static inline u32 rv_fle_d(u32 rd, u32 rs1, u32 rs2) {
    517   return rv_r(0x51, rs2, rs1, 0x0, rd, RV_OP_FP);
    518 }
    519 
    520 static inline u32 rv_flw(u32 rd, u32 rs1, i32 imm) {
    521   return rv_i(imm, rs1, 0x2, rd, RV_LOAD_FP);
    522 }
    523 static inline u32 rv_fld(u32 rd, u32 rs1, i32 imm) {
    524   return rv_i(imm, rs1, 0x3, rd, RV_LOAD_FP);
    525 }
    526 static inline u32 rv_fsw(u32 rs2, u32 rs1, i32 imm) {
    527   return rv_s(imm, rs2, rs1, 0x2, RV_STORE_FP);
    528 }
    529 static inline u32 rv_fsd(u32 rs2, u32 rs1, i32 imm) {
    530   return rv_s(imm, rs2, rs1, 0x3, RV_STORE_FP);
    531 }
    532 
    533 /* ---- A extension (LR/SC + AMO) ----
    534  * AMO funct7 layout: aq(26) rl(25) funct5(31:27) op-specific.
    535  * funct3 selects width: 0x2 = W (32-bit), 0x3 = D (64-bit). */
    536 static inline u32 rv_amo(u32 funct5, u32 aq, u32 rl, u32 rd, u32 rs1, u32 rs2,
    537                          u32 funct3) {
    538   u32 funct7 = (funct5 << 2) | ((aq & 1u) << 1) | (rl & 1u);
    539   return rv_r(funct7, rs2, rs1, funct3, rd, RV_AMO);
    540 }
    541 static inline u32 rv_lr_w(u32 rd, u32 rs1, u32 aq, u32 rl) {
    542   return rv_amo(0x02, aq, rl, rd, rs1, 0, 0x2);
    543 }
    544 static inline u32 rv_lr_d(u32 rd, u32 rs1, u32 aq, u32 rl) {
    545   return rv_amo(0x02, aq, rl, rd, rs1, 0, 0x3);
    546 }
    547 static inline u32 rv_sc_w(u32 rd, u32 rs1, u32 rs2, u32 aq, u32 rl) {
    548   return rv_amo(0x03, aq, rl, rd, rs1, rs2, 0x2);
    549 }
    550 static inline u32 rv_sc_d(u32 rd, u32 rs1, u32 rs2, u32 aq, u32 rl) {
    551   return rv_amo(0x03, aq, rl, rd, rs1, rs2, 0x3);
    552 }
    553 
    554 /* Other A-extension AMO funct5 codes (W and D widths via funct3). */
    555 #define RV_AMO_SWAP 0x01u
    556 #define RV_AMO_ADD 0x00u
    557 #define RV_AMO_XOR 0x04u
    558 #define RV_AMO_AND 0x0Cu
    559 #define RV_AMO_OR 0x08u
    560 #define RV_AMO_MIN 0x10u
    561 #define RV_AMO_MAX 0x14u
    562 #define RV_AMO_MINU 0x18u
    563 #define RV_AMO_MAXU 0x1Cu
    564 
    565 /* User-mode read-only performance CSRs (Zicntr). RDCYCLE rd is the canonical
    566  * `csrrs rd, cycle, x0`. The high halves (0xC80+) are rv32-only. */
    567 #define RV_CSR_CYCLE 0xC00u
    568 #define RV_CSR_TIME 0xC01u
    569 #define RV_CSR_INSTRET 0xC02u
    570 
    571 /* Zicsr — CSR instructions. csr in imm[11:0]; funct3 selects op.
    572  *   csrrw=1, csrrs=2, csrrc=3, csrrwi=5, csrrsi=6, csrrci=7 */
    573 static inline u32 rv_csrrw(u32 rd, u32 csr, u32 rs1) {
    574   return rv_i((i32)(csr & 0xfffu), rs1, 0x1, rd, RV_SYSTEM);
    575 }
    576 static inline u32 rv_csrrs(u32 rd, u32 csr, u32 rs1) {
    577   return rv_i((i32)(csr & 0xfffu), rs1, 0x2, rd, RV_SYSTEM);
    578 }
    579 static inline u32 rv_csrrc(u32 rd, u32 csr, u32 rs1) {
    580   return rv_i((i32)(csr & 0xfffu), rs1, 0x3, rd, RV_SYSTEM);
    581 }
    582 static inline u32 rv_csrrwi(u32 rd, u32 csr, u32 uimm) {
    583   return rv_i((i32)(csr & 0xfffu), uimm & 0x1fu, 0x5, rd, RV_SYSTEM);
    584 }
    585 static inline u32 rv_csrrsi(u32 rd, u32 csr, u32 uimm) {
    586   return rv_i((i32)(csr & 0xfffu), uimm & 0x1fu, 0x6, rd, RV_SYSTEM);
    587 }
    588 static inline u32 rv_csrrci(u32 rd, u32 csr, u32 uimm) {
    589   return rv_i((i32)(csr & 0xfffu), uimm & 0x1fu, 0x7, rd, RV_SYSTEM);
    590 }
    591 
    592 /* ===================================================================
    593  * Format kinds — one per encoding family the descriptor table dispatches
    594  * on. R-type splits by funct3/funct7 selectors; I/S/B/U/J each carry a
    595  * distinct immediate layout. The C-extension formats (CR/CI/CSS/CIW/CL/
    596  * CS/CB/CJ) are 16-bit; the disassembler picks 16 vs 32 by checking the
    597  * bottom two bits of the first halfword (00/01/10 → compressed, 11 → 32).
    598  * =================================================================== */
    599 typedef enum Rv64Format {
    600   RV64_FMT_R,        /* funct7 rs2 rs1 funct3 rd op — most ALU ops */
    601   RV64_FMT_R4,       /* fused FMA: rs3 funct2 rs2 rs1 funct3 rd op */
    602   RV64_FMT_I,        /* imm[11:0] rs1 funct3 rd op — ALU-imm, loads, jalr */
    603   RV64_FMT_I_SHIFT,  /* shift-imm (shamt6/funct6) — RV64 SLLI/SRLI/SRAI */
    604   RV64_FMT_I_SHIFTW, /* RV32 word-shift (shamt5/funct7) — SLLIW/SRLIW/SRAIW */
    605   RV64_FMT_S,        /* store */
    606   RV64_FMT_B,        /* branch */
    607   RV64_FMT_U,        /* LUI/AUIPC */
    608   RV64_FMT_J,        /* JAL */
    609   RV64_FMT_LOAD,    /* I-type load: rd, imm(rs1) — printer uses memory syntax */
    610   RV64_FMT_STORE,   /* S-type store: rs2, imm(rs1) */
    611   RV64_FMT_JALR,    /* JALR: rd, imm(rs1) — memory-style operand syntax */
    612   RV64_FMT_FENCE,   /* FENCE pred,succ */
    613   RV64_FMT_SYSTEM,  /* ECALL/EBREAK — no operands */
    614   RV64_FMT_FP_RM,   /* FP arithmetic with rm: funct7 rs2 rs1 rm rd op */
    615   RV64_FMT_FP_R,    /* FP R-type without rm-as-mnemonic-suffix (cmp/sgnj) */
    616   RV64_FMT_FP_CVT,  /* FP conversion: rs2 is type selector, rs1 is src */
    617   RV64_FMT_FP_LOAD, /* fld/flw — rd[FP], imm(rs1) */
    618   RV64_FMT_FP_STORE, /* fsd/fsw — rs2[FP], imm(rs1) */
    619   RV64_FMT_AMO,      /* atomic: rd, rs2, (rs1) */
    620   RV64_FMT_LR,       /* LR.W/D: rd, (rs1) — no rs2 */
    621   RV64_FMT_CSR,      /* csrr*: rd, csr, rs1 */
    622   RV64_FMT_CSRI,     /* csrr*i: rd, csr, uimm5 */
    623   /* ---- Compressed (16-bit) formats ---- */
    624   RV64_FMT_CR,     /* funct4 rd/rs1 rs2 op (e.g. C.MV, C.ADD, C.JR, C.JALR) */
    625   RV64_FMT_CI,     /* funct3 imm rd/rs1 imm op (e.g. C.ADDI, C.LI, C.LUI) */
    626   RV64_FMT_CSS,    /* funct3 imm rs2 op (stack store: C.SDSP, C.SWSP) */
    627   RV64_FMT_CIW,    /* funct3 imm rd' op (C.ADDI4SPN) */
    628   RV64_FMT_CL,     /* funct3 imm rs1' imm rd' op (C.LD, C.LW) */
    629   RV64_FMT_CS,     /* funct3 imm rs1' imm rs2' op (C.SD, C.SW) */
    630   RV64_FMT_CA,     /* funct6 rd'/rs1' funct2 rs2' op (C.AND, C.OR, ...) */
    631   RV64_FMT_CB,     /* branch: funct3 imm rs1' imm op (C.BEQZ, C.BNEZ) */
    632   RV64_FMT_CJ,     /* jump: funct3 imm op (C.J, C.JAL_unused on RV64) */
    633   RV64_FMT_C_NONE, /* known opcode with no operands (C.NOP, C.EBREAK) */
    634   /* Assembler-only multi-word pseudo-instruction (call/tail/la/lla). The
    635    * descriptor's `match` is unused; the assembler dispatches on mnemonic
    636    * and emits the AUIPC+JALR / AUIPC+ADDI expansion directly. */
    637   RV64_FMT_PSEUDO,
    638   /* Assembler-only 2-operand CSR pseudo-instructions. The descriptor's `match`
    639    * pins funct3+opcode (like the full-form csrr* rows); the assembler
    640    * dispatches on the mnemonic to decide the operand shape (which of rd/rs1 is
    641    * the implicit x0) and which operands are present. Reg form: csrr/csrw/csrs/
    642    * csrc; imm form: csrwi/csrsi/csrci. */
    643   RV64_FMT_CSR_PSEUDO,
    644 } Rv64Format;
    645 
    646 typedef enum Rv64DecodedOpcode {
    647   RV64_DEC_UNKNOWN = 0,
    648   RV64_DEC_ADDI,
    649   RV64_DEC_ADD,
    650   RV64_DEC_AUIPC,
    651   RV64_DEC_LD,
    652   RV64_DEC_SD,
    653   RV64_DEC_JALR,
    654   RV64_DEC_ECALL,
    655   RV64_DEC_EBREAK,
    656 } Rv64DecodedOpcode;
    657 
    658 /* ---- AsmFlags column on Rv64InsnDesc ---- */
    659 #define RV64_ASMFL_ALIAS 0x01u /* row is an alias (preferred print form) */
    660 #define RV64_ASMFL_FP 0x02u    /* operands take f-register prefix */
    661 #define RV64_ASMFL_NORM 0x04u  /* FP_RM row prints without rm suffix */
    662 #define RV64_ASMFL_C16 0x08u   /* 16-bit compressed instruction */
    663 /* Assembler-only multi-word pseudo (call/tail/la/lla). These expand to
    664  * several 32-bit words and never participate in disassembly — the decoder
    665  * sees the individual auipc/jalr/addi words instead. rv64_disasm_find
    666  * skips rows carrying this flag. */
    667 #define RV64_ASMFL_PSEUDO 0x10u
    668 
    669 /* ---- Availability column (`av`) on Rv64InsnDesc ----
    670  * Which XLEN variant(s) a row is valid on. A row with av==0 is implicitly
    671  * available on BOTH (this is how the ~200 untouched `{0, 0}` pad
    672  * initializers stay correct — av lands in the first pad byte and reads 0).
    673  * Rows whose encoding only exists / changes meaning per XLEN are tagged
    674  * explicitly; the find functions skip a row when its av is set and excludes
    675  * the wanted arch. */
    676 #define RV_AV_RV32 0x1u
    677 #define RV_AV_RV64 0x2u
    678 #define RV_AV_BOTH (RV_AV_RV32 | RV_AV_RV64)
    679 
    680 /* ===================================================================
    681  * Per-format field structs + pack/unpack pure functions.
    682  * =================================================================== */
    683 
    684 typedef struct Rv64R {
    685   u32 funct7, rs2, rs1, funct3, rd, op;
    686 } Rv64R;
    687 typedef struct Rv64I {
    688   u32 imm12, rs1, funct3, rd, op;
    689 } Rv64I;
    690 typedef struct Rv64S {
    691   u32 imm12, rs2, rs1, funct3, op;
    692 } Rv64S;
    693 typedef struct Rv64B {
    694   u32 imm13, rs2, rs1, funct3, op;
    695 } Rv64B;
    696 typedef struct Rv64U {
    697   u32 imm32_hi20, rd, op;
    698 } Rv64U;
    699 typedef struct Rv64J {
    700   u32 imm21, rd, op;
    701 } Rv64J;
    702 
    703 static inline Rv64R rv64_r_unpack(u32 w) {
    704   Rv64R f;
    705   f.funct7 = (w >> 25) & 0x7fu;
    706   f.rs2 = (w >> 20) & 0x1fu;
    707   f.rs1 = (w >> 15) & 0x1fu;
    708   f.funct3 = (w >> 12) & 0x7u;
    709   f.rd = (w >> 7) & 0x1fu;
    710   f.op = w & 0x7fu;
    711   return f;
    712 }
    713 static inline Rv64I rv64_i_unpack(u32 w) {
    714   Rv64I f;
    715   f.imm12 = (w >> 20) & 0xfffu;
    716   f.rs1 = (w >> 15) & 0x1fu;
    717   f.funct3 = (w >> 12) & 0x7u;
    718   f.rd = (w >> 7) & 0x1fu;
    719   f.op = w & 0x7fu;
    720   return f;
    721 }
    722 static inline Rv64S rv64_s_unpack(u32 w) {
    723   Rv64S f;
    724   f.imm12 = (((w >> 25) & 0x7fu) << 5) | ((w >> 7) & 0x1fu);
    725   f.rs2 = (w >> 20) & 0x1fu;
    726   f.rs1 = (w >> 15) & 0x1fu;
    727   f.funct3 = (w >> 12) & 0x7u;
    728   f.op = w & 0x7fu;
    729   return f;
    730 }
    731 static inline Rv64B rv64_b_unpack(u32 w) {
    732   Rv64B f;
    733   f.imm13 = (((w >> 31) & 1u) << 12) | (((w >> 7) & 1u) << 11) |
    734             (((w >> 25) & 0x3fu) << 5) | (((w >> 8) & 0xfu) << 1);
    735   f.rs2 = (w >> 20) & 0x1fu;
    736   f.rs1 = (w >> 15) & 0x1fu;
    737   f.funct3 = (w >> 12) & 0x7u;
    738   f.op = w & 0x7fu;
    739   return f;
    740 }
    741 static inline Rv64U rv64_u_unpack(u32 w) {
    742   Rv64U f;
    743   f.imm32_hi20 = w & 0xfffff000u;
    744   f.rd = (w >> 7) & 0x1fu;
    745   f.op = w & 0x7fu;
    746   return f;
    747 }
    748 static inline Rv64J rv64_j_unpack(u32 w) {
    749   Rv64J f;
    750   f.imm21 = (((w >> 31) & 1u) << 20) | (((w >> 12) & 0xffu) << 12) |
    751             (((w >> 20) & 1u) << 11) | (((w >> 21) & 0x3ffu) << 1);
    752   f.rd = (w >> 7) & 0x1fu;
    753   f.op = w & 0x7fu;
    754   return f;
    755 }
    756 
    757 /* Sign-extend an n-bit value held in the low bits of v to i64. */
    758 static inline i64 rv64_sext(u64 v, u32 nbits) {
    759   u64 mask = (nbits >= 64u) ? ~0ull : ((1ull << nbits) - 1ull);
    760   v &= mask;
    761   u64 sign = (nbits == 0u) ? 0ull : (1ull << (nbits - 1u));
    762   if (v & sign) v |= ~mask;
    763   return (i64)v;
    764 }
    765 
    766 /* ===================================================================
    767  * Compressed (RV64C) helpers — 16-bit instructions.
    768  *
    769  * Layout (per RVC quadrant): bits[1:0] (op) select the quadrant:
    770  *   00 → Q0 (stack-relative & load/store narrow),
    771  *   01 → Q1 (constant/branch),
    772  *   10 → Q2 (stack pointer access & jumps & MV/ADD).
    773  * 11 is reserved for 32-bit (uncompressed) instructions, so the
    774  * disassembler picks 16-bit when (halfword & 3) != 3.
    775  *
    776  * The "narrow" register fields rs1' / rs2' / rd' are 3-bit and encode
    777  * x8..x15; macro RVC_REG3 unfolds: r' → 8 + r'. */
    778 #define RVC_REG3(r3) ((u32)(8u + ((r3) & 7u)))
    779 
    780 typedef struct Rv64C {
    781   u32 word;
    782 } Rv64C; /* 16-bit halfword in low 16 bits */
    783 
    784 /* ===================================================================
    785  * Descriptor table.
    786  * =================================================================== */
    787 
    788 typedef struct Rv64InsnDesc {
    789   Slice mnemonic;
    790   u32 match;
    791   u32 mask;
    792   u8 fmt;   /* Rv64Format */
    793   u8 flags; /* RV64_ASMFL_* */
    794   u8 av;    /* RV_AV_* availability mask; 0 == available on BOTH */
    795   u8 pad[1];
    796 } Rv64InsnDesc;
    797 
    798 extern const Rv64InsnDesc rv64_insn_table[];
    799 extern const u32 rv64_insn_table_n;
    800 
    801 /* Standard CSR name <-> number table (shared by RV32 and RV64). */
    802 typedef struct Rv64CsrName {
    803   const char* name;
    804   u16 num;
    805 } Rv64CsrName;
    806 extern const Rv64CsrName rv64_csr_names[];
    807 extern const u32 rv64_csr_names_n;
    808 /* name -> number: returns 1 + writes *num_out on a hit, else 0. */
    809 int rv64_csr_num_from_name(Slice name, u16* num_out);
    810 /* number -> canonical name, or NULL if not in the table. */
    811 const char* rv64_csr_name_from_num(u16 num);
    812 
    813 /* Linear-scan lookup. Returns the matching descriptor or NULL. First
    814  * match wins; ordering puts more-specific entries (aliases, fixed-Rd
    815  * forms) before broader ones. `av_wanted` is the RV_AV_* mask of the
    816  * decoding arch (RV_AV_RV32 / RV_AV_RV64); rows whose av is set and
    817  * excludes it are skipped. Pass RV_AV_RV64 to reproduce the historical
    818  * rv64-only behavior exactly. */
    819 const Rv64InsnDesc* rv64_disasm_find(u32 word, u8 av_wanted);
    820 
    821 /* Compressed-instruction (16-bit) variant. Pass the halfword in the low
    822  * 16 bits of `word`. `av_wanted` branches the ambiguous quadrant slots
    823  * whose meaning differs between rv32 and rv64. Returns NULL if no
    824  * descriptor matches.
    825  *
    826  * Synthesized C-format descriptors are written into the caller-owned
    827  * `scratch` and that pointer is returned; fixed rows return a pointer into
    828  * the module's static table instead. `scratch` must outlive the returned
    829  * pointer's use. (Keeping the scratch caller-owned avoids a mutable
    830  * function-local static and keeps the decode path reentrant.) */
    831 const Rv64InsnDesc* rv64_disasm_find_c(u32 word, u8 av_wanted,
    832                                        Rv64InsnDesc* scratch);
    833 
    834 /* Mnemonic → descriptor for the assembler. Returns NULL if not found.
    835  * Ignores ALIAS-only rows when those would produce ambiguous parses
    836  * (the canonical form is always reachable). `av_wanted` filters rows by
    837  * target arch so e.g. `ld`/`addiw` are not assemblable under rv32. */
    838 const Rv64InsnDesc* rv64_asm_find(Slice mnemonic, u8 av_wanted);
    839 
    840 /* ===================================================================
    841  * Operand print / parse dispatch.
    842  *
    843  * rv64_print_operands renders the operand text (everything after the
    844  * mnemonic) for `word` into `sb`, using `desc->fmt` to dispatch.
    845  * Mnemonic itself is in `desc->mnemonic`; the caller writes it before
    846  * calling this helper. `vaddr` is the instruction's virtual address for
    847  * PC-relative formats; pass 0 if not known. */
    848 void rv64_print_operands(StrBuf* sb, const Rv64InsnDesc* desc, u32 word,
    849                          u64 vaddr);
    850 
    851 #endif /* KIT_RV64_ISA_H */