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 with the I, M, A, F, D, Q and C extensions,
10//! Zicsr, Zifencei, and the bit-manipulation extensions Zba, Zbb, Zbc and
11//! Zbs. The text follows GNU objdump's for a raw binary
12//! (`objdump -D -b binary`), so branch targets are written as `0x1008`.
13//!
14//! ```
15//! use disc_v::{disassemble, Isa};
16//!
17//! let code = [0x13, 0x05, 0x10, 0x00, 0x82, 0x80]; // li a0,1; ret
18//! for ins in disassemble(Isa::Rv64, 0x1000, &code) {
19//! println!("{:x}: {ins}", ins.pc());
20//! }
21//! # let text: Vec<_> = disassemble(Isa::Rv64, 0x1000, &code).map(|i| i.to_string()).collect();
22//! # assert_eq!(text, ["li a0,1", "ret"]);
23//! ```
24#![no_std]
25#![warn(missing_docs)]
26
27mod csr;
28mod decode;
29mod format;
30mod instruction;
31mod opcodes;
32mod reg;
33
34pub use format::Operands;
35pub use instruction::Instruction;
36
37/// The base integer ISA, which determines the register width and which
38/// encodings are valid.
39#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
40pub enum Isa {
41 /// RV32: 32-bit registers.
42 Rv32,
43 /// RV64: 64-bit registers.
44 Rv64,
45 /// RV128: 128-bit registers. The RV128 encodings are not yet ratified.
46 Rv128,
47}
48
49impl Isa {
50 /// The width of the integer registers in bits.
51 pub fn xlen(self) -> u32 {
52 match self {
53 Isa::Rv32 => 32,
54 Isa::Rv64 => 64,
55 Isa::Rv128 => 128,
56 }
57 }
58}
59
60/// Decodes the instruction `inst`, located at address `pc`.
61///
62/// Only the low [`inst_length`] bytes of `inst` are part of the instruction; RISC-V encodings are little-endian, so this is the
63/// value of those bytes read as a little-endian integer.
64///
65/// ```
66/// use disc_v::{decode, Isa};
67///
68/// let ins = decode(Isa::Rv32, 0x1000, 0xfe050ee3);
69/// assert_eq!(ins.to_string(), "beqz a0,0xffc");
70/// ```
71pub fn decode(isa: Isa, pc: u64, inst: u64) -> Instruction {
72 decode::decode(isa, pc, inst)
73}
74
75/// Decodes the instruction at the start of `bytes`, located at address `pc`.
76///
77/// Returns `None` if `bytes` is shorter than the instruction.
78///
79/// ```
80/// use disc_v::{decode_bytes, Isa};
81///
82/// let ins = decode_bytes(Isa::Rv64, 0, &[0x93, 0x81, 0x01, 0x10]).unwrap();
83/// assert_eq!(ins.to_string(), "addi gp,gp,256");
84/// assert_eq!(ins.length(), 4);
85/// ```
86pub fn decode_bytes(isa: Isa, pc: u64, bytes: &[u8]) -> Option<Instruction> {
87 let first = u16::from_le_bytes([*bytes.first()?, *bytes.get(1)?]);
88 let len = inst_length(first).unwrap_or(2);
89 let mut word = [0; 8];
90 word[..len].copy_from_slice(bytes.get(..len)?);
91 Some(decode(isa, pc, u64::from_le_bytes(word)))
92}
93
94/// Disassembles `bytes`, the first of which is at address `pc`.
95///
96/// Iteration stops when the remaining bytes are too short for the next
97/// instruction; see [`Disassembler::remainder`].
98pub fn disassemble(isa: Isa, pc: u64, bytes: &[u8]) -> Disassembler<'_> {
99 Disassembler { isa, pc, bytes }
100}
101
102/// An iterator over the instructions in a byte slice. Create one with
103/// [`disassemble`].
104#[derive(Clone, Debug)]
105pub struct Disassembler<'a> {
106 isa: Isa,
107 pc: u64,
108 bytes: &'a [u8],
109}
110
111impl<'a> Disassembler<'a> {
112 /// The bytes not yet disassembled. After iteration ends, these are the
113 /// bytes of a truncated final instruction, if any.
114 pub fn remainder(&self) -> &'a [u8] {
115 self.bytes
116 }
117
118 /// The address of the next instruction.
119 pub fn pc(&self) -> u64 {
120 self.pc
121 }
122}
123
124impl Iterator for Disassembler<'_> {
125 type Item = Instruction;
126
127 fn next(&mut self) -> Option<Instruction> {
128 let ins = decode_bytes(self.isa, self.pc, self.bytes)?;
129 self.bytes = &self.bytes[ins.length()..];
130 self.pc = self.pc.wrapping_add(ins.length() as u64);
131 Some(ins)
132 }
133}
134
135impl core::iter::FusedIterator for Disassembler<'_> {}
136
137/// Returns the length in bytes of the instruction whose first 16-bit parcel
138/// is `parcel`, or `None` for the reserved lengths of 10 bytes or more.
139///
140/// ```
141/// use disc_v::inst_length;
142///
143/// assert_eq!(inst_length(0x4505), Some(2)); // li a0,1
144/// assert_eq!(inst_length(0x0513), Some(4)); // the low half of li a0,10
145/// assert_eq!(inst_length(0x007f), None);
146/// ```
147pub fn inst_length(parcel: u16) -> Option<usize> {
148 if parcel & 3 != 3 {
149 Some(2)
150 } else if parcel & 0x1c != 0x1c {
151 Some(4)
152 } else if parcel & 0x3f == 0x1f {
153 Some(6)
154 } else if parcel & 0x7f == 0x3f {
155 Some(8)
156 } else {
157 None
158 }
159}