ab_riscv_interpreter/
lib.rs

1#![feature(bigint_helper_methods, const_convert, const_trait_impl)]
2#![expect(incomplete_features, reason = "generic_const_exprs")]
3// TODO: This feature is not actually used in this crate, but is added as a workaround for
4//  https://github.com/rust-lang/rust/issues/141492
5#![feature(generic_const_exprs)]
6#![no_std]
7
8pub mod b_64_ext;
9pub mod m_64_ext;
10pub mod rv64;
11
12use crate::rv64::Rv64SystemInstructionHandler;
13use ab_riscv_primitives::instruction::rv64::Rv64Instruction;
14use ab_riscv_primitives::instruction::{GenericBaseInstruction, GenericInstruction};
15use ab_riscv_primitives::registers::{GenericRegister, Registers};
16use core::fmt;
17use core::ops::ControlFlow;
18
19/// Errors for [`VirtualMemory`]
20#[derive(Debug, thiserror::Error)]
21pub enum VirtualMemoryError {
22    /// Out-of-bounds read
23    #[error("Out-of-bounds read at address {address}")]
24    OutOfBoundsRead {
25        /// Address of the out-of-bounds read
26        address: u64,
27    },
28    /// Out-of-bounds write
29    #[error("Out-of-bounds write at address {address}")]
30    OutOfBoundsWrite {
31        /// Address of the out-of-bounds write
32        address: u64,
33    },
34}
35
36mod private {
37    pub trait Sealed {}
38
39    impl Sealed for u8 {}
40    impl Sealed for u16 {}
41    impl Sealed for u32 {}
42    impl Sealed for u64 {}
43    impl Sealed for i8 {}
44    impl Sealed for i16 {}
45    impl Sealed for i32 {}
46    impl Sealed for i64 {}
47}
48
49/// Basic integer types that can be read and written to/from memory freely
50pub trait BasicInt: Sized + Copy + private::Sealed {}
51
52impl BasicInt for u8 {}
53impl BasicInt for u16 {}
54impl BasicInt for u32 {}
55impl BasicInt for u64 {}
56impl BasicInt for i8 {}
57impl BasicInt for i16 {}
58impl BasicInt for i32 {}
59impl BasicInt for i64 {}
60
61/// Virtual memory interface
62pub trait VirtualMemory {
63    /// Read a value from memory at the specified address
64    fn read<T>(&self, address: u64) -> Result<T, VirtualMemoryError>
65    where
66        T: BasicInt;
67
68    /// Write a value to memory at the specified address
69    fn write<T>(&mut self, address: u64, value: T) -> Result<(), VirtualMemoryError>
70    where
71        T: BasicInt;
72}
73
74/// Execution errors
75#[derive(Debug, thiserror::Error)]
76pub enum ExecuteError<Instruction, Custom>
77where
78    Instruction: fmt::Display,
79    Custom: fmt::Display,
80{
81    /// Unaligned instruction fetch
82    #[error("Unaligned instruction fetch at address {address}")]
83    UnalignedInstructionFetch {
84        /// Address of the unaligned instruction fetch
85        address: u64,
86    },
87    /// Memory access error
88    #[error("Memory access error: {0}")]
89    MemoryAccess(#[from] VirtualMemoryError),
90    /// Unsupported instruction
91    #[error("Unsupported instruction at address {address:#x}: {instruction}")]
92    UnsupportedInstruction {
93        /// Address of the unsupported instruction
94        address: u64,
95        /// Instruction that caused the error
96        instruction: Instruction,
97    },
98    /// Unimplemented/illegal instruction
99    #[error("Unimplemented/illegal instruction at address {address:#x}")]
100    UnimpInstruction {
101        /// Address of the `unimp` instruction
102        address: u64,
103    },
104    /// Invalid instruction
105    #[error("Invalid instruction at address {address:#x}: {instruction:#010x}")]
106    InvalidInstruction {
107        /// Address of the invalid instruction
108        address: u64,
109        /// Instruction that caused the error
110        instruction: u32,
111    },
112    /// Custom error
113    #[error("Custom error: {0}")]
114    Custom(Custom),
115}
116
117impl<BaseInstruction, Custom> ExecuteError<BaseInstruction, Custom>
118where
119    BaseInstruction: GenericBaseInstruction,
120    Custom: fmt::Display,
121{
122    /// Map instruction type from lower-level base instruction
123    #[inline]
124    pub fn map_from_base<Instruction>(self) -> ExecuteError<Instruction, Custom>
125    where
126        Instruction: GenericBaseInstruction<Base = BaseInstruction>,
127    {
128        match self {
129            Self::UnalignedInstructionFetch { address } => {
130                ExecuteError::UnalignedInstructionFetch { address }
131            }
132            Self::MemoryAccess(error) => ExecuteError::MemoryAccess(error),
133            Self::UnsupportedInstruction {
134                address,
135                instruction,
136            } => ExecuteError::UnsupportedInstruction {
137                address,
138                instruction: Instruction::from_base(instruction),
139            },
140            Self::UnimpInstruction { address } => ExecuteError::UnimpInstruction { address },
141            Self::InvalidInstruction {
142                address,
143                instruction,
144            } => ExecuteError::InvalidInstruction {
145                address,
146                instruction,
147            },
148            Self::Custom(error) => ExecuteError::Custom(error),
149        }
150    }
151}
152
153/// Result of [`GenericInstructionHandler::fetch_instruction()`] call
154#[derive(Debug, Copy, Clone)]
155pub enum FetchInstructionResult<Instruction> {
156    /// Instruction fetched successfully
157    Instruction(Instruction),
158    /// Control flow instruction encountered
159    ControlFlow(ControlFlow<()>),
160}
161
162/// Custom handlers for instructions `ecall` and `ebreak`
163pub trait GenericInstructionHandler<Instruction, Memory, CustomError>
164where
165    Instruction: GenericBaseInstruction,
166    [(); Instruction::Reg::N]:,
167    CustomError: fmt::Display,
168{
169    /// Fetch a single instruction at a specified address and advance the program counter
170    fn fetch_instruction(
171        &mut self,
172        _regs: &mut Registers<Instruction::Reg>,
173        memory: &mut Memory,
174        pc: &mut u64,
175    ) -> Result<FetchInstructionResult<Instruction>, ExecuteError<Instruction, CustomError>>;
176}
177
178/// Basic instruction handler implementation.
179///
180/// `RETURN_TRAP_ADDRESS` is the address at which the interpreter will stop execution (gracefully).
181#[derive(Debug, Default, Copy, Clone)]
182pub struct BasicInstructionHandler<const RETURN_TRAP_ADDRESS: u64>;
183
184impl<const RETURN_TRAP_ADDRESS: u64, Instruction, Memory>
185    GenericInstructionHandler<Instruction, Memory, &'static str>
186    for BasicInstructionHandler<RETURN_TRAP_ADDRESS>
187where
188    Instruction: GenericBaseInstruction,
189    [(); Instruction::Reg::N]:,
190    Memory: VirtualMemory,
191{
192    #[inline(always)]
193    fn fetch_instruction(
194        &mut self,
195        _regs: &mut Registers<Instruction::Reg>,
196        memory: &mut Memory,
197        pc: &mut u64,
198    ) -> Result<FetchInstructionResult<Instruction>, ExecuteError<Instruction, &'static str>> {
199        let address = *pc;
200
201        if address == RETURN_TRAP_ADDRESS {
202            return Ok(FetchInstructionResult::ControlFlow(ControlFlow::Break(())));
203        }
204
205        if !address.is_multiple_of(size_of::<u32>() as u64) {
206            return Err(ExecuteError::UnalignedInstructionFetch { address });
207        }
208
209        let instruction = memory.read(address)?;
210        let instruction = Instruction::decode(instruction);
211        *pc += instruction.size() as u64;
212
213        Ok(FetchInstructionResult::Instruction(instruction))
214    }
215}
216
217impl<const RETURN_TRAP_ADDRESS: u64, Reg, Memory>
218    Rv64SystemInstructionHandler<Reg, Memory, &'static str>
219    for BasicInstructionHandler<RETURN_TRAP_ADDRESS>
220where
221    Reg: GenericRegister<Type = u64>,
222    [(); Reg::N]:,
223    Memory: VirtualMemory,
224{
225    #[inline(always)]
226    fn handle_ecall(
227        &mut self,
228        _regs: &mut Registers<Reg>,
229        _memory: &mut Memory,
230        pc: &mut u64,
231        instruction: Rv64Instruction<Reg>,
232    ) -> Result<(), ExecuteError<Rv64Instruction<Reg>, &'static str>> {
233        Err(ExecuteError::UnsupportedInstruction {
234            address: *pc - instruction.size() as u64,
235            instruction,
236        })
237    }
238}