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//!
35//! The text follows GNU objdump's for a raw binary (`objdump -D -b binary`),
36//! so branch targets are written as `0x1008`.
37//!
38//! [`decode`](fn@decode), [`decode_bytes`] and [`disassemble`] decode every
39//! supported extension that does not conflict with another. A [`Decoder`]
40//! decodes for a particular target instead, such as [`Decoder::RVA23U64`] or
41//! one from an ISA string with [`Decoder::from_march`].
42//!
43//! ```
44//! use disc_v::{disassemble, Isa};
45//!
46//! let code = [0x13, 0x05, 0x10, 0x00, 0x82, 0x80]; // li a0,1; ret
47//! for ins in disassemble(Isa::Rv64, 0x1000, &code) {
48//! println!("{:x}: {ins}", ins.pc());
49//! }
50//! # let text: Vec<_> = disassemble(Isa::Rv64, 0x1000, &code).map(|i| i.to_string()).collect();
51//! # assert_eq!(text, ["li a0,1", "ret"]);
52//! ```
53#![no_std]
54#![warn(missing_docs)]
55
56mod csr;
57mod decode;
58mod decoder;
59mod extension;
60mod format;
61mod instruction;
62mod opcodes;
63mod reg;
64
65pub use decoder::{Conflict, Decoder, MarchError};
66pub use extension::{Extension, Extensions};
67pub use format::Operands;
68pub use instruction::Instruction;
69
70// Compiles and runs the README's examples as doc tests.
71#[cfg(doctest)]
72#[doc = include_str!("../README.md")]
73struct ReadmeDoctests;
74
75/// The base integer ISA, which determines the register width and which
76/// encodings are valid.
77#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
78pub enum Isa {
79 /// RV32: 32-bit registers.
80 Rv32,
81 /// RV64: 64-bit registers.
82 Rv64,
83 /// RV128: 128-bit registers. The RV128 encodings are not yet ratified.
84 Rv128,
85}
86
87impl Isa {
88 /// The width of the integer registers in bits.
89 pub fn xlen(self) -> u32 {
90 match self {
91 Isa::Rv32 => 32,
92 Isa::Rv64 => 64,
93 Isa::Rv128 => 128,
94 }
95 }
96}
97
98/// Decodes the instruction `inst`, located at address `pc`.
99///
100/// Only the low [`inst_length`] bytes of `inst` are part of the instruction; RISC-V encodings are little-endian, so this is the
101/// value of those bytes read as a little-endian integer.
102///
103/// ```
104/// use disc_v::{decode, Isa};
105///
106/// let ins = decode(Isa::Rv32, 0x1000, 0xfe050ee3);
107/// assert_eq!(ins.to_string(), "beqz a0,0xffc");
108/// ```
109pub fn decode(isa: Isa, pc: u64, inst: u64) -> Instruction {
110 Decoder::new(isa).decode(pc, inst)
111}
112
113/// Decodes the instruction at the start of `bytes`, located at address `pc`.
114///
115/// Returns `None` if `bytes` is shorter than the instruction.
116///
117/// ```
118/// use disc_v::{decode_bytes, Isa};
119///
120/// let ins = decode_bytes(Isa::Rv64, 0, &[0x93, 0x81, 0x01, 0x10]).unwrap();
121/// assert_eq!(ins.to_string(), "addi gp,gp,256");
122/// assert_eq!(ins.length(), 4);
123/// ```
124pub fn decode_bytes(isa: Isa, pc: u64, bytes: &[u8]) -> Option<Instruction> {
125 Decoder::new(isa).decode_bytes(pc, bytes)
126}
127
128/// Disassembles `bytes`, the first of which is at address `pc`.
129///
130/// Iteration stops when the remaining bytes are too short for the next
131/// instruction; see [`Disassembler::remainder`].
132pub fn disassemble(isa: Isa, pc: u64, bytes: &[u8]) -> Disassembler<'_> {
133 Decoder::new(isa).disassemble(pc, bytes)
134}
135
136/// An iterator over the instructions in a byte slice. Create one with
137/// [`disassemble`] or [`Decoder::disassemble`].
138#[derive(Clone, Debug)]
139pub struct Disassembler<'a> {
140 decoder: Decoder,
141 pc: u64,
142 bytes: &'a [u8],
143}
144
145impl<'a> Disassembler<'a> {
146 /// The bytes not yet disassembled. After iteration ends, these are the
147 /// bytes of a truncated final instruction, if any.
148 pub fn remainder(&self) -> &'a [u8] {
149 self.bytes
150 }
151
152 /// The address of the next instruction.
153 pub fn pc(&self) -> u64 {
154 self.pc
155 }
156}
157
158impl Iterator for Disassembler<'_> {
159 type Item = Instruction;
160
161 fn next(&mut self) -> Option<Instruction> {
162 let ins = self.decoder.decode_bytes(self.pc, self.bytes)?;
163 self.bytes = self.bytes.get(ins.length()..).unwrap_or(&[]);
164 self.pc = self.pc.wrapping_add(ins.length() as u64);
165 Some(ins)
166 }
167}
168
169impl core::iter::FusedIterator for Disassembler<'_> {}
170
171/// Returns the length in bytes of the instruction whose first 16-bit parcel
172/// is `parcel`, or `None` for the reserved lengths of 10 bytes or more.
173///
174/// The decoding functions show each 16-bit parcel of an encoding with a
175/// reserved length as a 2-byte `.2byte` directive, as objdump does, so that
176/// disassembly can continue.
177///
178/// ```
179/// use disc_v::inst_length;
180///
181/// assert_eq!(inst_length(0x4505), Some(2)); // li a0,1
182/// assert_eq!(inst_length(0x0513), Some(4)); // the low half of li a0,10
183/// assert_eq!(inst_length(0x007f), None);
184/// ```
185pub fn inst_length(parcel: u16) -> Option<usize> {
186 if parcel & 3 != 3 {
187 Some(2)
188 } else if parcel & 0x1c != 0x1c {
189 Some(4)
190 } else if parcel & 0x3f == 0x1f {
191 Some(6)
192 } else if parcel & 0x7f == 0x3f {
193 Some(8)
194 } else {
195 None
196 }
197}