disc-v 0.1.3

A RISC-V disassembler
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
// Copyright (c) 2026 Christopher Swenson
// Licensed under the Apache-2.0 license.
//
// Derived from riscv-disassembler, Copyright (c) 2016-2017 Michael Clark
// and Copyright (c) 2017-2018 SiFive, Inc., under the MIT license; see NOTICE.

//! Opcode tables: one static [`Opcode`] per instruction, grouped by extension.

pub(crate) mod a;
pub(crate) mod b;
pub(crate) mod c;
pub(crate) mod d;
pub(crate) mod f;
pub(crate) mod h;
pub(crate) mod i;
pub(crate) mod m;
pub(crate) mod pseudo;
pub(crate) mod q;
pub(crate) mod system;
#[cfg(feature = "vector")]
pub(crate) mod v;
#[cfg(feature = "vendor-corev")]
pub(crate) mod xcv;
#[cfg(feature = "vendor-thead")]
pub(crate) mod xthead;
pub(crate) mod zabha;
pub(crate) mod zalasr;
pub(crate) mod zawrs;
pub(crate) mod zcb;
pub(crate) mod zcmp;
pub(crate) mod zfa;
pub(crate) mod zfbfmin;
pub(crate) mod zfh;
pub(crate) mod zicbo;
pub(crate) mod zicond;
pub(crate) mod zilsd;
pub(crate) mod zimop;
pub(crate) mod zk;

use crate::{Extension, Extensions, Isa};

// Some codecs, constraints and table helpers are only used by the tables
// that the `vector` and vendor features add.
/// How an instruction's operands are laid out in its encoding.
///
/// The names follow the instruction formats in the RISC-V specification
/// (R, I, S, SB, U, UJ, and the compressed CR, CI, CSS, CIW, CL, CS, CB, CJ),
/// with a suffix where a format has several immediate layouts.
#[cfg_attr(
    not(all(feature = "vector", feature = "vendor-thead", feature = "vendor-corev")),
    allow(dead_code)
)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Codec {
    Li,
    CssSqsp,
    CssSdsp,
    CssSwsp,
    CsSq,
    CsSd,
    CsSw,
    Cs,
    CrJr,
    CrJalr,
    CrMv,
    Cr,
    ClLq,
    ClLd,
    ClLw,
    /// Zcb byte load: rd', rs1' and a 2-bit unsigned offset.
    ClB,
    /// Zcb halfword load: rd', rs1' and a 2-byte-aligned 2-bit offset.
    ClH,
    /// Zcb byte store: rs2', rs1' and a 2-bit unsigned offset.
    CsB,
    /// Zcb halfword store: rs2', rs1' and a 2-byte-aligned 2-bit offset.
    CsH,
    /// Zcb unary operation on rd' (which is also rs1').
    CuRd,
    /// `cm.push` and `cm.pop*`: the register list (in rs1) and the stack
    /// adjustment in bytes.
    CmPushPop,
    /// `cm.mvsa01` and `cm.mva01s`: two s-registers (in rs1 and rs2).
    CmMv,
    /// `cm.jt` and `cm.jalt`: the jump table index.
    CmJt,
    CjJal,
    Cj,
    Ciw4spn,
    CiNone,
    CiLui,
    CiLi,
    CiLqsp,
    CiLdsp,
    CiLwsp,
    Ci16sp,
    CiSh6,
    Ci,
    CbSh6,
    CbImm,
    Cb,
    RF,
    RL,
    RA,
    R4M,
    RM,
    R,
    SB,
    S,
    ICsr,
    ISh7,
    ISh6,
    ISh5,
    I,
    Uj,
    U,
    None,
    Illegal,
    /// Vector arithmetic, load or store: vd/rd, vs1/rs1, vs2/rs2, the `vm`
    /// mask bit, and a signed 5-bit immediate in the vs1 field.
    V,
    /// As [`Codec::V`], with an unsigned 5-bit immediate.
    Vu,
    /// As [`Codec::V`], with an unsigned 6-bit immediate in bit 26 and the
    /// vs1 field (`vror.vi`).
    Vu6,
    /// Scalar AES and SM4 (`aes32esi`, `sm4ed`): rd, rs1, rs2 and a byte
    /// select in bits 31:30.
    RBs,
    /// `aes64ks1i`: rd, rs1 and a round number in bits 23:20.
    Rnum,
    /// `vsetvli`: rd, rs1 and an 11-bit `vtype` immediate.
    VsetVli,
    /// `vsetivli`: rd, a 5-bit AVL immediate in the rs1 field, and a 10-bit
    /// `vtype` immediate.
    VsetIvli,
    /// rd, rs1 and an unsigned 6-bit immediate in bits 25:20 that, unlike a
    /// shift amount, is not limited by XLEN (`th.srri`).
    IUimm6,
    /// rd, rs1, rs2 and a 2-bit immediate in bits 26:25 (`th.addsl`,
    /// `th.lrw`).
    RImm2,
    /// rd, rs1, rs2 and a 5-bit immediate in bits 29:25 (`cv.addn`).
    RImm5,
    /// rd, rs1, and 6-bit immediates in bits 31:26 and 25:20 (`th.ext`).
    ImmPair6,
    /// rd, rs1, and 5-bit immediates in bits 29:25 and 24:20 (`cv.extract`).
    ImmPair5,
    /// rd, rs1, a signed 5-bit immediate in bits 24:20 and a 2-bit immediate
    /// in bits 26:25 (`th.lbia`).
    ImmUpdate,
    /// rd, rs1, rs2, a 2-bit immediate in bits 26:25, and the shift that
    /// scales it: 4 for doublewords (bits 31:27 all set), otherwise 3
    /// (`th.lwd`).
    Pair,
    /// A branch on rs1 and a signed 5-bit immediate in the rs2 field
    /// (`cv.beqimm`).
    BImm5,
    /// rd, rs1, and a signed 6-bit immediate with bits 5:1 in bits 24:20 and
    /// bit 0 in bit 25 (`cv.add.sci.h`).
    Simm6,
    /// As [`Codec::Simm6`], unsigned.
    Uimm6,
}

/// Operand format strings.
///
/// Each character expands to one piece of the operand text:
///
/// | Char | Output |
/// |------|--------|
/// | `0` `1` `2` | integer register `rd`, `rs1`, `rs2` |
/// | `3` `4` `5` `6` | floating-point register `rd`, `rs1`, `rs2`, `rs3` |
/// | `7` | `rs1` field as an unsigned immediate (CSR `zimm`) |
/// | `i` | immediate, in decimal |
/// | `j` | second immediate, in decimal |
/// | `>` | shift amount, in hex |
/// | `u` | upper immediate (bits 31:12), in hex |
/// | `o` | target address of a PC-relative offset |
/// | `c` | CSR name |
/// | `r` | `,` and the rounding mode, unless it is dynamic |
/// | `R` | `,` and the rounding mode, unless it is RNE (for exact conversions) |
/// | `p` `s` | fence predecessor and successor sets |
/// | `F` | the `fli` constant selected by the rs1 field |
/// | `D` `A` `B` | vector register `vd` (or `vs3`), `vs1`, `vs2` |
/// | `M` | `,v0.t` if the vector instruction is masked |
/// | `Z` | `v0` (the carry or merge operand) |
/// | `T` | the `vtype` immediate of `vsetvli`/`vsetivli` |
/// | `P` | prefetch offset: the immediate with its low five bits cleared |
/// | `l` | length of the encoding in bytes |
/// | `x` | the encoding, in hex, padded to a whole number of 16-bit parcels |
/// | `L` | `cm.push` register list, such as `{ra,s0-s1}` |
/// | `,` ` ` `(` `)` `-` | themselves |
pub(crate) mod fmt {
    pub(crate) const NONE: &str = "";
    pub(crate) const RS1: &str = "1";
    pub(crate) const OFFSET: &str = "o";
    pub(crate) const PRED_SUCC: &str = "p,s";
    pub(crate) const RS1_RS2: &str = "1,2";
    pub(crate) const RD_IMM: &str = "0,i";
    pub(crate) const RD_UIMM: &str = "0,u";
    pub(crate) const RD_OFFSET: &str = "0,o";
    pub(crate) const RD_RS1_RS2: &str = "0,1,2";
    pub(crate) const FRD_RS1: &str = "3,1";
    pub(crate) const FRD_FRS1: &str = "3,4";
    pub(crate) const RD_FRS1: &str = "0,4";
    pub(crate) const RD_FRS1_FRS2: &str = "0,4,5";
    pub(crate) const FRD_FRS1_FRS2: &str = "3,4,5";
    pub(crate) const RM_FRD_FRS1: &str = "3,4r";
    pub(crate) const RM_FRD_RS1: &str = "3,1r";
    pub(crate) const RM_RD_FRS1: &str = "0,4r";
    pub(crate) const RM_FRD_FRS1_FRS2: &str = "3,4,5r";
    pub(crate) const RM_FRD_FRS1_FRS2_FRS3: &str = "3,4,5,6r";
    pub(crate) const RD_RS1_IMM: &str = "0,1,i";
    pub(crate) const RD_RS1_SHAMT: &str = "0,1,>";
    pub(crate) const RD_OFFSET_RS1: &str = "0,i(1)";
    pub(crate) const FRD_OFFSET_RS1: &str = "3,i(1)";
    pub(crate) const RD_CSR_RS1: &str = "0,c,1";
    pub(crate) const RD_CSR_ZIMM: &str = "0,c,7";
    pub(crate) const RS2_OFFSET_RS1: &str = "2,i(1)";
    pub(crate) const FRS2_OFFSET_RS1: &str = "5,i(1)";
    pub(crate) const RS1_RS2_OFFSET: &str = "1,2,o";
    pub(crate) const RD_RS2_ADDR_RS1: &str = "0,2,(1)";
    pub(crate) const RD_ADDR_RS1: &str = "0,(1)";
    pub(crate) const RD: &str = "0";
    pub(crate) const RD_ZIMM: &str = "0,7";
    pub(crate) const RD_RS1: &str = "0,1";
    pub(crate) const RD_RS2: &str = "0,2";
    pub(crate) const RS1_OFFSET: &str = "1,o";
    pub(crate) const RS2_OFFSET: &str = "2,o";
    pub(crate) const OFFSET_RS1: &str = "i(1)";
    pub(crate) const RD_SHAMT: &str = "0,>";
    pub(crate) const RD_CSR: &str = "0,c";
    pub(crate) const CSR_RS1: &str = "c,1";
    pub(crate) const CSR_ZIMM: &str = "c,7";
    pub(crate) const IMM: &str = "i";
    pub(crate) const WIDEN_FRD_FRS1: &str = "3,4R";
    pub(crate) const WIDEN_FRD_RS1: &str = "3,1R";
    pub(crate) const INSN: &str = "l, x";
    pub(crate) const RAW: &str = "x";
    pub(crate) const ADDR_RS1: &str = "(1)";
    pub(crate) const RS2: &str = "2";
    pub(crate) const RS2_ADDR_RS1: &str = "2,(1)";
    pub(crate) const RD_RS1_RS2_BS: &str = "0,1,2,>";
    pub(crate) const RLIST_ADJ: &str = "L,i";
    pub(crate) const RLIST_NEG_ADJ: &str = "L,-i";
    pub(crate) const UIMM: &str = "u";
    pub(crate) const FRD_FLI: &str = "3,F";
    pub(crate) const FRD_RS1_RS2: &str = "3,1,2";
    pub(crate) const PREFETCH: &str = "P(1)";
}

/// A condition on decoded operands, used to pick a pseudoinstruction.
#[cfg_attr(
    not(all(feature = "vector", feature = "vendor-thead", feature = "vendor-corev")),
    allow(dead_code)
)]
#[derive(Clone, Copy, Debug)]
pub(crate) enum Constraint {
    RdEq(u8),
    Rs1Eq(u8),
    Rs2Eq(u8),
    Rs2EqRs1,
    RdEqRs1,
    /// rd is odd: not the first register of an even-odd pair.
    RdOdd,
    /// rs2 is odd: not the first register of an even-odd pair.
    Rs2Odd,
    RdEqRs2,
    ImmEq(i32),
    /// The immediate's bits in the mask (first) equal the value (second).
    ImmMaskEq(i32, i32),
    /// The CSR number, which is held in the immediate.
    CsrEq(i32),
}

/// An alternative way to show an instruction when all of `when` hold.
pub(crate) struct Pseudo {
    pub(crate) op: &'static Opcode,
    pub(crate) when: &'static [Constraint],
}

impl Pseudo {
    pub(crate) const fn new(op: &'static Opcode, when: &'static [Constraint]) -> Self {
        Pseudo { op, when }
    }
}

/// An opcode that is decoded by matching the encoding against a mask, as the
/// vector instructions are.
pub(crate) struct MaskedOpcode {
    pub(crate) bits: u32,
    pub(crate) mask: u32,
    pub(crate) op: Opcode,
}

/// Builds a [`MaskedOpcode`].
#[cfg_attr(
    not(all(feature = "vector", feature = "vendor-thead", feature = "vendor-corev")),
    allow(dead_code)
)]
pub(crate) const fn masked(bits: u32, mask: u32, op: Opcode) -> MaskedOpcode {
    MaskedOpcode { bits, mask, op }
}

/// Returns the first opcode in `table` that matches `inst`.
#[cfg_attr(
    not(all(feature = "vector", feature = "vendor-thead", feature = "vendor-corev")),
    allow(dead_code)
)]
pub(crate) fn lookup_masked(table: &'static [MaskedOpcode], inst: u64) -> Option<&'static Opcode> {
    let inst = inst as u32;
    table
        .iter()
        .find(|e| inst & e.mask == e.bits)
        .map(|e| &e.op)
}

/// One entry in the opcode tables.
pub(crate) struct Opcode {
    pub(crate) name: &'static str,
    /// Operand format string; see [`fmt`].
    pub(crate) format: &'static str,
    /// The extensions the instruction (or alias) needs, all of which must be
    /// enabled. Empty for the base ISA.
    pub(crate) requires: &'static [Extension],
    /// The fields that few opcodes use.
    pub(crate) extra: &'static Extra,
    pub(crate) codec: Codec,
    /// The base ISAs the instruction exists in, as a bit set indexed by
    /// [`Isa`].
    pub(crate) isas: u8,
    /// Whether the instruction loads, stores or moves to or from the
    /// floating-point registers, and so does not exist with Zfinx, where
    /// there are none.
    pub(crate) f_registers: bool,
}

/// The fields of an [`Opcode`] that few opcodes use, kept apart so that
/// the others take less space.
pub(crate) struct Extra {
    /// Pseudoinstructions to try, in order; the first match is shown instead.
    pub(crate) pseudo: &'static [Pseudo],
    /// For compressed instructions, the expansion for RV32, RV64 and RV128;
    /// `None` means the encoding is not valid for that ISA.
    pub(crate) decompress: [Option<&'static Opcode>; 3],
    /// Operand values that make the encoding reserved: it is illegal if any
    /// of these hold.
    pub(crate) illegal_if: &'static [Constraint],
    /// Operand values that make the encoding a HINT, which objdump shows as
    /// encoded rather than expanded: it is a HINT if any of these hold.
    pub(crate) hint_if: &'static [Constraint],
    /// Extensions that each provide the instruction (as Zbb and Zbkb both
    /// provide `rol`), at least one of which must be enabled. Empty if
    /// [`Opcode::requires`] alone decides.
    pub(crate) requires_any: &'static [Extension],
}

impl Extra {
    /// No aliases, expansions, reserved or HINT operands, or alternative
    /// extensions.
    pub(crate) const NONE: Extra = Extra {
        pseudo: &[],
        decompress: [None; 3],
        illegal_if: &[],
        hint_if: &[],
        requires_any: &[],
    };
}

impl Opcode {
    pub(crate) const fn new(name: &'static str, codec: Codec, format: &'static str) -> Self {
        Opcode {
            name,
            format,
            requires: &[],
            extra: &Extra::NONE,
            codec,
            isas: 0b111,
            f_registers: false,
        }
    }

    /// Marks an instruction as needing the floating-point registers.
    pub(crate) const fn f_registers(self) -> Self {
        Opcode {
            f_registers: true,
            ..self
        }
    }

    /// Marks an instruction as needing all of `exts`.
    pub(crate) const fn requires(self, exts: &'static [Extension]) -> Self {
        Opcode {
            requires: exts,
            ..self
        }
    }

    /// Marks an instruction as existing only in RV64 and RV128.
    pub(crate) const fn rv64(self) -> Self {
        Opcode {
            isas: 0b110,
            ..self
        }
    }

    /// Marks an instruction as existing only in RV32.
    pub(crate) const fn rv32_only(self) -> Self {
        Opcode {
            isas: 0b001,
            ..self
        }
    }

    /// Marks an instruction as existing only in RV64.
    pub(crate) const fn rv64_only(self) -> Self {
        Opcode {
            isas: 0b010,
            ..self
        }
    }

    /// Marks an instruction as existing only in RV128.
    pub(crate) const fn rv128(self) -> Self {
        Opcode {
            isas: 0b100,
            ..self
        }
    }

    /// Whether `exts` has the extensions the instruction (or alias) needs.
    /// With Zfinx, those of F, D, Zfh and Zfhmin are provided by Zfinx,
    /// Zdinx, Zhinx and Zhinxmin.
    pub(crate) fn provided_by(&self, exts: Extensions) -> bool {
        let has = |e: Extension| exts.contains(e) || exts.contains(e.in_x_registers());
        self.requires.iter().all(|&e| has(e))
            && (self.extra.requires_any.is_empty()
                || self.extra.requires_any.iter().any(|&e| has(e)))
    }

    /// Whether the instruction exists in `isa` with `exts`. A compressed
    /// instruction also needs the instruction it expands to.
    pub(crate) fn exists_in(&self, isa: Isa, exts: Extensions) -> bool {
        self.provided_by(exts)
            && (!exts.contains(Extension::Zfinx) || self.in_x_registers())
            && match self.extra.decompress {
                [None, None, None] => self.isas & (1 << isa as u8) != 0,
                expansions => expansions[isa as usize].is_some_and(|e| e.exists_in(isa, exts)),
            }
    }

    /// Whether the instruction can have its floating-point operands in the
    /// integer registers: it has none, or it is from F, D, Zfh or Zfhmin
    /// (rather than, say, a vector instruction with a scalar operand).
    fn in_x_registers(&self) -> bool {
        let fp_operands = self.format.contains(['3', '4', '5', '6']);
        !fp_operands
            || (!self.f_registers && self.requires.iter().any(|&e| e.in_x_registers() != e))
    }
}

// Each opcode is a unique static, so opcodes are compared by address (two
// statics with the same fields, such as an instruction and an alias with
// the same name, are different opcodes). `Debug` shows only the mnemonic
// rather than the alias and expansion tables.
impl PartialEq for Opcode {
    fn eq(&self, other: &Self) -> bool {
        core::ptr::eq(self, other)
    }
}

impl Eq for Opcode {}

impl core::fmt::Debug for Opcode {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.write_str(self.name)
    }
}

/// An encoding that is not a valid instruction, shown as objdump does.
pub(crate) static ILLEGAL: Opcode = Opcode::new(".insn", Codec::Illegal, fmt::INSN);
/// A 16-bit parcel of an encoding with a reserved length (10 bytes or more),
/// which `.insn` cannot express.
pub(crate) static RESERVED_PARCEL: Opcode = Opcode::new(".2byte", Codec::Illegal, fmt::RAW);