disc_v/lib.rs
1// Copyright (c) 2026 Christopher Swenson
2// Licensed under the Apache-2.0 license.
3//
4// Derived from riscv-disassembler, Copyright (c) 2016-2017 Michael Clark
5// and Copyright (c) 2017-2018 SiFive, Inc., under the MIT license; see NOTICE.
6
7//! A RISC-V disassembler.
8//!
9//! Supports RV32, RV64 and RV128 (and RV32E and RV64E, as the E extension)
10//! with these extensions:
11//!
12//! - M, A, F, D, Q and C
13//! - Zicsr and Zifencei
14//! - Zba, Zbb, Zbc and Zbs
15//! - Zicond
16//! - Zawrs
17//! - Zicbom, Zicboz and Zicbop; Zihintntl and Zihintpause
18//! - Zimop and Zcmop, with the Zicfiss and Zicfilp instructions
19//! - Zcb
20//! - Zfh and Zfhmin
21//! - Zfinx, Zdinx, Zhinx and Zhinxmin, which conflict with F and so are only
22//! decoded by a [`Decoder`] that enables them
23//! - Zfa
24//! - V (vectors)
25//! - Zvbb and Zvbc; Zvkg, Zvkned, Zvknha, Zvknhb, Zvksed and Zvksh
26//! - H (hypervisor), Svinval, Smrnmi and Ssctr
27//! - Zalasr, Zacas and Zabha
28//! - Zfbfmin, Zvfbfmin and Zvfbfwma (BFloat16)
29//! - Zbkb, Zbkc and Zbkx; Zknd, Zkne, Zknh, Zksed and Zksh (scalar cryptography)
30//! - Zca, Zcf and Zcd (the parts of C); Zcmp and Zcmt, which conflict with
31//! Zcd and so are only decoded by a [`Decoder`] that enables them
32//! - Zilsd; Zclsd, which conflicts with Zcf and so is only decoded by a
33//! [`Decoder`] that enables it
34//! - T-Head and CORE-V vendor extensions, which are only decoded by a
35//! [`Decoder`] that enables them
36//!
37//! The vector and vendor extensions need the `vector`, `vendor-thead` and
38//! `vendor-corev` Cargo features, which are on by default; without them,
39//! their instructions are shown as `.insn` and the binary is smaller.
40//!
41//! The text follows GNU objdump's for a raw binary (`objdump -D -b binary`),
42//! so branch targets are written as `0x1008`.
43//!
44//! [`decode`](fn@decode), [`decode_bytes`] and [`disassemble`] decode every
45//! supported extension that does not conflict with another. A [`Decoder`]
46//! decodes for a particular target instead, such as [`Decoder::RVA23U64`] or
47//! one from an ISA string with [`Decoder::from_march`].
48//!
49//! ```
50//! use disc_v::{disassemble, Isa};
51//!
52//! let code = [0x13, 0x05, 0x10, 0x00, 0x82, 0x80]; // li a0,1; ret
53//! for ins in disassemble(Isa::Rv64, 0x1000, &code) {
54//! println!("{:x}: {ins}", ins.pc());
55//! }
56//! # let text: Vec<_> = disassemble(Isa::Rv64, 0x1000, &code).map(|i| i.to_string()).collect();
57//! # assert_eq!(text, ["li a0,1", "ret"]);
58//! ```
59#![no_std]
60#![warn(missing_docs)]
61
62mod csr;
63mod decode;
64mod decoder;
65mod extension;
66mod format;
67mod instruction;
68mod opcodes;
69mod reg;
70
71pub use decoder::{Conflict, Decoder, MarchError};
72pub use extension::{Extension, Extensions};
73pub use format::Operands;
74pub use instruction::Instruction;
75
76// Compiles and runs the README's examples as doc tests. They use every
77// table.
78#[cfg(all(
79 doctest,
80 feature = "vector",
81 feature = "vendor-thead",
82 feature = "vendor-corev"
83))]
84#[doc = include_str!("../README.md")]
85struct ReadmeDoctests;
86
87/// The base integer ISA, which determines the register width and which
88/// encodings are valid.
89#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
90pub enum Isa {
91 /// RV32: 32-bit registers.
92 Rv32,
93 /// RV64: 64-bit registers.
94 Rv64,
95 /// RV128: 128-bit registers. The RV128 encodings are not yet ratified.
96 Rv128,
97}
98
99impl Isa {
100 /// The width of the integer registers in bits.
101 pub fn xlen(self) -> u32 {
102 match self {
103 Isa::Rv32 => 32,
104 Isa::Rv64 => 64,
105 Isa::Rv128 => 128,
106 }
107 }
108}
109
110/// Decodes the instruction `inst`, located at address `pc`.
111///
112/// Only the low [`inst_length`] bytes of `inst` are part of the instruction; RISC-V encodings are little-endian, so this is the
113/// value of those bytes read as a little-endian integer.
114///
115/// ```
116/// use disc_v::{decode, Isa};
117///
118/// let ins = decode(Isa::Rv32, 0x1000, 0xfe050ee3);
119/// assert_eq!(ins.to_string(), "beqz a0,0xffc");
120/// ```
121pub fn decode(isa: Isa, pc: u64, inst: u64) -> Instruction {
122 Decoder::new(isa).decode(pc, inst)
123}
124
125/// Decodes the instruction at the start of `bytes`, located at address `pc`.
126///
127/// Returns `None` if `bytes` is shorter than the instruction.
128///
129/// ```
130/// use disc_v::{decode_bytes, Isa};
131///
132/// let ins = decode_bytes(Isa::Rv64, 0, &[0x93, 0x81, 0x01, 0x10]).unwrap();
133/// assert_eq!(ins.to_string(), "addi gp,gp,256");
134/// assert_eq!(ins.length(), 4);
135/// ```
136pub fn decode_bytes(isa: Isa, pc: u64, bytes: &[u8]) -> Option<Instruction> {
137 Decoder::new(isa).decode_bytes(pc, bytes)
138}
139
140/// Disassembles `bytes`, the first of which is at address `pc`.
141///
142/// Iteration stops when the remaining bytes are too short for the next
143/// instruction; see [`Disassembler::remainder`].
144pub fn disassemble(isa: Isa, pc: u64, bytes: &[u8]) -> Disassembler<'_> {
145 Decoder::new(isa).disassemble(pc, bytes)
146}
147
148/// An iterator over the instructions in a byte slice. Create one with
149/// [`disassemble`] or [`Decoder::disassemble`].
150#[derive(Clone, Debug)]
151pub struct Disassembler<'a> {
152 decoder: Decoder,
153 pc: u64,
154 bytes: &'a [u8],
155}
156
157impl<'a> Disassembler<'a> {
158 /// The bytes not yet disassembled. After iteration ends, these are the
159 /// bytes of a truncated final instruction, if any.
160 pub fn remainder(&self) -> &'a [u8] {
161 self.bytes
162 }
163
164 /// The address of the next instruction.
165 pub fn pc(&self) -> u64 {
166 self.pc
167 }
168}
169
170impl Iterator for Disassembler<'_> {
171 type Item = Instruction;
172
173 fn next(&mut self) -> Option<Instruction> {
174 let ins = self.decoder.decode_bytes(self.pc, self.bytes)?;
175 self.bytes = self.bytes.get(ins.length()..).unwrap_or(&[]);
176 self.pc = self.pc.wrapping_add(ins.length() as u64);
177 Some(ins)
178 }
179}
180
181impl core::iter::FusedIterator for Disassembler<'_> {}
182
183/// Returns the length in bytes of the instruction whose first 16-bit parcel
184/// is `parcel`, or `None` for the reserved lengths of 10 bytes or more.
185///
186/// The decoding functions show each 16-bit parcel of an encoding with a
187/// reserved length as a 2-byte `.2byte` directive, as objdump does, so that
188/// disassembly can continue.
189///
190/// ```
191/// use disc_v::inst_length;
192///
193/// assert_eq!(inst_length(0x4505), Some(2)); // li a0,1
194/// assert_eq!(inst_length(0x0513), Some(4)); // the low half of li a0,10
195/// assert_eq!(inst_length(0x007f), None);
196/// ```
197pub fn inst_length(parcel: u16) -> Option<usize> {
198 if parcel & 3 != 3 {
199 Some(2)
200 } else if parcel & 0x1c != 0x1c {
201 Some(4)
202 } else if parcel & 0x3f == 0x1f {
203 Some(6)
204 } else if parcel & 0x7f == 0x3f {
205 Some(8)
206 } else {
207 None
208 }
209}