Skip to main content

ab_riscv_interpreter/
lib.rs

1//! Composable and generic RISC-V interpreter.
2//!
3//! This interpreter is designed to work with abstractions from [`ab-riscv-primitives`] crate and is
4//! similarly composable with a powerful macro system and trait abstractions over handling of
5//! memory, syscalls, etc.
6//!
7//! [`ab-riscv-primitives`]: ab_riscv_primitives
8//!
9//! The immediate needs dictate the current set of available instructions and extensions. Consider
10//! contributing if you need something not yet available.
11//!
12//! `ab-riscv-interpreter-compliance-tests` crate in the repository contains complementary
13//! compliance tests against <https://github.com/riscv-non-isa/riscv-arch-test> for many
14//! instructions from both base ISA and various extensions on top of the tests contained in this
15//! crate.
16//!
17//! Does not require a standard library (`no_std`) or an allocator.
18
19#![feature(widening_mul)]
20#![expect(incomplete_features, reason = "generic_const_exprs")]
21// TODO: This feature is not actually used in this crate, but is added as a workaround for
22//  https://github.com/rust-lang/rust/issues/141492
23#![feature(generic_const_exprs)]
24#![no_std]
25
26pub mod rv64;
27
28use ab_riscv_primitives::instructions::Instruction;
29use ab_riscv_primitives::registers::general_purpose::Register;
30use core::marker::PhantomData;
31use core::ops::ControlFlow;
32
33type Address<I> = <<I as Instruction>::Reg as Register>::Type;
34
35/// Errors for [`VirtualMemory`]
36#[derive(Debug, thiserror::Error)]
37pub enum VirtualMemoryError {
38    /// Out-of-bounds read
39    #[error("Out-of-bounds read at address {address}")]
40    OutOfBoundsRead {
41        /// Address of the out-of-bounds read
42        address: u64,
43    },
44    /// Out-of-bounds write
45    #[error("Out-of-bounds write at address {address}")]
46    OutOfBoundsWrite {
47        /// Address of the out-of-bounds write
48        address: u64,
49    },
50}
51
52mod private {
53    pub trait Sealed {}
54
55    impl Sealed for u8 {}
56    impl Sealed for u16 {}
57    impl Sealed for u32 {}
58    impl Sealed for u64 {}
59    impl Sealed for i8 {}
60    impl Sealed for i16 {}
61    impl Sealed for i32 {}
62    impl Sealed for i64 {}
63}
64
65/// Basic integer types that can be read and written to/from memory freely
66pub trait BasicInt: Sized + Copy + private::Sealed {}
67
68impl BasicInt for u8 {}
69impl BasicInt for u16 {}
70impl BasicInt for u32 {}
71impl BasicInt for u64 {}
72impl BasicInt for i8 {}
73impl BasicInt for i16 {}
74impl BasicInt for i32 {}
75impl BasicInt for i64 {}
76
77/// Virtual memory interface
78pub trait VirtualMemory {
79    /// Read a value from memory at the specified address
80    fn read<T>(&self, address: u64) -> Result<T, VirtualMemoryError>
81    where
82        T: BasicInt;
83
84    /// Unchecked read a value from memory at the specified address.
85    ///
86    /// # Safety
87    /// The address and value must be in-bounds.
88    unsafe fn read_unchecked<T>(&self, address: u64) -> T
89    where
90        T: BasicInt;
91
92    /// Write a value to memory at the specified address
93    fn write<T>(&mut self, address: u64, value: T) -> Result<(), VirtualMemoryError>
94    where
95        T: BasicInt;
96}
97
98/// Program counter errors
99#[derive(Debug, thiserror::Error)]
100pub enum ProgramCounterError<Address, Custom> {
101    /// Unaligned instruction
102    #[error("Unaligned instruction at address {address}")]
103    UnalignedInstruction {
104        /// Address of the unaligned instruction fetch
105        address: Address,
106    },
107    /// Memory access error
108    #[error("Memory access error: {0}")]
109    MemoryAccess(#[from] VirtualMemoryError),
110    /// Custom error
111    #[error("Custom error: {0}")]
112    Custom(Custom),
113}
114
115/// Generic program counter
116pub trait ProgramCounter<Address, Memory, CustomError> {
117    /// Get the current value of the program counter
118    fn get_pc(&self) -> Address;
119
120    /// Set the current value of the program counter
121    fn set_pc(
122        &mut self,
123        memory: &mut Memory,
124        pc: Address,
125    ) -> Result<ControlFlow<()>, ProgramCounterError<Address, CustomError>>;
126}
127
128/// Execution errors
129#[derive(Debug, thiserror::Error)]
130pub enum ExecutionError<Address, I, Custom> {
131    /// Unaligned instruction fetch
132    #[error("Unaligned instruction fetch at address {address}")]
133    UnalignedInstructionFetch {
134        /// Address of the unaligned instruction fetch
135        address: Address,
136    },
137    /// Program counter error
138    #[error("Program counter error: {0}")]
139    ProgramCounter(#[from] ProgramCounterError<Address, Custom>),
140    /// Memory access error
141    #[error("Memory access error: {0}")]
142    MemoryAccess(#[from] VirtualMemoryError),
143    /// Unsupported instruction
144    #[error("Unsupported instruction at address {address:#x}: {instruction}")]
145    UnsupportedInstruction {
146        /// Address of the unsupported instruction
147        address: Address,
148        /// Instruction that caused the error
149        instruction: I,
150    },
151    /// Unimplemented/illegal instruction
152    #[error("Unimplemented/illegal instruction at address {address:#x}")]
153    UnimpInstruction {
154        /// Address of the `unimp` instruction
155        address: Address,
156    },
157    /// Invalid instruction
158    #[error("Invalid instruction at address {address:#x}: {instruction:#010x}")]
159    InvalidInstruction {
160        /// Address of the invalid instruction
161        address: Address,
162        /// Instruction that caused the error
163        instruction: u32,
164    },
165    /// Custom error
166    #[error("Custom error: {0}")]
167    Custom(Custom),
168}
169
170impl<Address, BI, Custom> ExecutionError<Address, BI, Custom> {
171    /// Map instruction type
172    #[inline]
173    pub fn map_instruction<I>(self, map: fn(BI) -> I) -> ExecutionError<Address, I, Custom> {
174        match self {
175            Self::UnalignedInstructionFetch { address } => {
176                ExecutionError::UnalignedInstructionFetch { address }
177            }
178            Self::MemoryAccess(error) => ExecutionError::MemoryAccess(error),
179            Self::ProgramCounter(error) => ExecutionError::ProgramCounter(error),
180            Self::UnsupportedInstruction {
181                address,
182                instruction,
183            } => ExecutionError::UnsupportedInstruction {
184                address,
185                instruction: map(instruction),
186            },
187            Self::UnimpInstruction { address } => ExecutionError::UnimpInstruction { address },
188            Self::InvalidInstruction {
189                address,
190                instruction,
191            } => ExecutionError::InvalidInstruction {
192                address,
193                instruction,
194            },
195            Self::Custom(error) => ExecutionError::Custom(error),
196        }
197    }
198}
199
200/// Result of [`InstructionFetcher::fetch_instruction()`] call
201#[derive(Debug, Copy, Clone)]
202pub enum FetchInstructionResult<Instruction> {
203    /// Instruction fetched successfully
204    Instruction(Instruction),
205    /// Control flow instruction encountered
206    ControlFlow(ControlFlow<()>),
207}
208
209/// Generic instruction fetcher
210pub trait InstructionFetcher<I, Memory, CustomError>
211where
212    Self: ProgramCounter<Address<I>, Memory, CustomError>,
213    I: Instruction,
214{
215    /// Fetch a single instruction at a specified address and advance the program counter
216    fn fetch_instruction(
217        &mut self,
218        memory: &mut Memory,
219    ) -> Result<FetchInstructionResult<I>, ExecutionError<Address<I>, I, CustomError>>;
220}
221
222/// Basic instruction fetcher implementation
223#[derive(Debug, Copy, Clone)]
224pub struct BasicInstructionFetcher<I, CustomError>
225where
226    I: Instruction,
227{
228    return_trap_address: Address<I>,
229    pc: Address<I>,
230    _phantom: PhantomData<CustomError>,
231}
232
233impl<I, Memory, CustomError> ProgramCounter<Address<I>, Memory, CustomError>
234    for BasicInstructionFetcher<I, CustomError>
235where
236    I: Instruction,
237    Memory: VirtualMemory,
238{
239    #[inline(always)]
240    fn get_pc(&self) -> Address<I> {
241        self.pc
242    }
243
244    #[inline]
245    fn set_pc(
246        &mut self,
247        memory: &mut Memory,
248        pc: Address<I>,
249    ) -> Result<ControlFlow<()>, ProgramCounterError<Address<I>, CustomError>> {
250        if pc == self.return_trap_address {
251            return Ok(ControlFlow::Break(()));
252        }
253
254        if !pc.into().is_multiple_of(u64::from(I::alignment())) {
255            return Err(ProgramCounterError::UnalignedInstruction { address: pc });
256        }
257
258        memory.read::<u32>(pc.into())?;
259
260        self.pc = pc;
261
262        Ok(ControlFlow::Continue(()))
263    }
264}
265
266impl<I, Memory, CustomError> InstructionFetcher<I, Memory, CustomError>
267    for BasicInstructionFetcher<I, CustomError>
268where
269    I: Instruction,
270    Memory: VirtualMemory,
271{
272    #[inline]
273    fn fetch_instruction(
274        &mut self,
275        memory: &mut Memory,
276    ) -> Result<FetchInstructionResult<I>, ExecutionError<Address<I>, I, CustomError>> {
277        // SAFETY: Constructor guarantees that the last instruction is a jump, which means going
278        // through `Self::set_pc()` method that does bound check. Otherwise, advancing forward by
279        // one instruction can't result in out-of-bounds access.
280        let instruction = unsafe { memory.read_unchecked(self.pc.into()) };
281        // SAFETY: All instructions are valid, according to the constructor contract
282        let instruction = unsafe { I::try_decode(instruction).unwrap_unchecked() };
283        self.pc += instruction.size().into();
284
285        Ok(FetchInstructionResult::Instruction(instruction))
286    }
287}
288
289impl<I, CustomError> BasicInstructionFetcher<I, CustomError>
290where
291    I: Instruction,
292{
293    /// Create a new instance.
294    ///
295    /// `return_trap_address` is the address at which the interpreter will stop execution
296    /// (gracefully).
297    ///
298    /// # Safety
299    /// The program counter must be valid and aligned, the instructions processed must be valid and
300    /// end with a jump instruction.
301    #[inline(always)]
302    pub unsafe fn new(return_trap_address: Address<I>, pc: Address<I>) -> Self {
303        Self {
304            return_trap_address,
305            pc,
306            _phantom: PhantomData,
307        }
308    }
309}
310
311/// Trait for executable instructions
312pub trait ExecutableInstruction<State, CustomError>
313where
314    Self: Instruction,
315{
316    /// Execute instruction
317    fn execute(
318        self,
319        state: &mut State,
320    ) -> Result<ControlFlow<()>, ExecutionError<Address<Self>, Self, CustomError>>;
321}