Skip to main content

ab_riscv_interpreter/
basic.rs

1//! Basic implementations of various interpreter traits
2
3#[cfg(test)]
4mod tests;
5
6use crate::{
7    Address, CustomErrorPlaceholder, ExecutionError, FetchInstructionResult, InstructionFetcher,
8    ProgramCounter, ProgramCounterError, RegisterFile, SystemInstructionHandler, VirtualMemory,
9};
10use ab_riscv_primitives::prelude::*;
11use core::hint::cold_path;
12use core::marker::PhantomData;
13use core::ops::ControlFlow;
14
15/// Basic general purpose register to be used with [`BasicRegisters`]
16///
17/// # Safety
18/// `Self::offset()` must return values in `0..Self::N` range. `Self::from_bits()` must return
19/// `Some()` for `0..=31` if `Self::RVE = false` and `0..=15` if `Self::RVE = true`.
20pub const unsafe trait BasicRegister
21where
22    Self: [const] Register,
23{
24    /// The number of general purpose registers.
25    ///
26    /// Canonically 32 unless E extension is used, in which case 16.
27    const N: usize;
28
29    /// Offset in a set of registers
30    fn offset(self) -> u8;
31}
32
33// SAFETY: `Self::offset()` returns values within `0..Self::N` range
34unsafe impl<Type> const BasicRegister for EReg<Type>
35where
36    Self: [const] Register,
37{
38    const N: usize = 16;
39
40    #[inline(always)]
41    fn offset(self) -> u8 {
42        // SAFETY: Enum is `#[repr(u8)]` and doesn't have any fields
43        unsafe { core::mem::transmute::<Self, u8>(self) }
44    }
45}
46
47// SAFETY: `Self::offset()` returns values within `0..Self::N` range
48unsafe impl<Type> const BasicRegister for Reg<Type>
49where
50    Self: [const] Register,
51{
52    const N: usize = 32;
53
54    #[inline(always)]
55    fn offset(self) -> u8 {
56        // SAFETY: Enum is `#[repr(u8)]` and doesn't have any fields
57        unsafe { core::mem::transmute::<Self, u8>(self) }
58    }
59}
60
61/// A basic set of RISC-V GPRs (General Purpose Registers)
62#[derive(Debug, Clone, Copy)]
63#[repr(align(16))]
64pub struct BasicRegisters<Reg>
65where
66    Reg: BasicRegister,
67    [(); Reg::N]:,
68{
69    regs: [Reg::Type; Reg::N],
70}
71
72impl<Reg> Default for BasicRegisters<Reg>
73where
74    Reg: BasicRegister,
75    [(); Reg::N]:,
76{
77    #[inline(always)]
78    fn default() -> Self {
79        Self {
80            regs: [Reg::Type::default(); Reg::N],
81        }
82    }
83}
84
85impl<Reg> const RegisterFile<Reg> for BasicRegisters<Reg>
86where
87    Reg: [const] BasicRegister,
88    [(); Reg::N]:,
89{
90    #[inline(always)]
91    fn read(&self, reg: Reg) -> Reg::Type {
92        if reg == Reg::ZERO {
93            // Always zero
94            return Reg::Type::default();
95        }
96
97        // SAFETY: register offset is always within bounds
98        *unsafe { self.regs.get_unchecked(usize::from(reg.offset())) }
99    }
100
101    #[inline(always)]
102    fn write(&mut self, reg: Reg, value: Reg::Type) {
103        // SAFETY: register offset is always within bounds
104        *unsafe { self.regs.get_unchecked_mut(usize::from(reg.offset())) } = value;
105    }
106}
107
108/// Basic interpreter state
109#[derive(Debug)]
110pub struct BasicInterpreterState<Regs, ExtState, Memory, IF, InstructionHandler> {
111    /// General purpose registers
112    pub regs: Regs,
113    /// Extended state.
114    ///
115    /// Extensions might use this to place additional constraints on `ExtState` to require
116    /// additional registers or other resources. If no such extension is used, `()` can be used as
117    /// a placeholder.
118    pub ext_state: ExtState,
119    /// Memory
120    pub memory: Memory,
121    /// Instruction fetcher
122    pub instruction_fetcher: IF,
123    /// System instruction handler
124    pub system_instruction_handler: InstructionHandler,
125}
126
127/// Basic instruction fetcher implementation.
128///
129/// Note that it loads instructions from anywhere in memory. This works, but it is likely that you
130/// want to restrict this to a specific executable region of memory.
131#[derive(Debug, Copy, Clone)]
132pub struct BasicInstructionFetcher<I, CustomError = CustomErrorPlaceholder>
133where
134    I: Instruction,
135{
136    return_trap_address: Address<I>,
137    pc: Address<I>,
138    _phantom: PhantomData<CustomError>,
139}
140
141impl<I, Memory, CustomError> ProgramCounter<Address<I>, Memory, CustomError>
142    for BasicInstructionFetcher<I, CustomError>
143where
144    I: Instruction,
145    Memory: VirtualMemory,
146{
147    #[inline(always)]
148    fn get_pc(&self) -> Address<I> {
149        self.pc
150    }
151
152    #[inline]
153    fn set_pc(
154        &mut self,
155        _memory: &Memory,
156        pc: Address<I>,
157    ) -> Result<ControlFlow<()>, ProgramCounterError<Address<I>, CustomError>> {
158        if pc == self.return_trap_address {
159            cold_path();
160            return Ok(ControlFlow::Break(()));
161        }
162
163        if !pc.as_u64().is_multiple_of(u64::from(I::alignment())) {
164            cold_path();
165            return Err(ProgramCounterError::UnalignedInstruction { address: pc });
166        }
167
168        self.pc = pc;
169
170        Ok(ControlFlow::Continue(()))
171    }
172}
173
174impl<I, Memory, CustomError> InstructionFetcher<I, Memory, CustomError>
175    for BasicInstructionFetcher<I, CustomError>
176where
177    I: Instruction,
178    Memory: VirtualMemory,
179{
180    #[inline]
181    fn fetch_instruction(
182        &mut self,
183        memory: &Memory,
184    ) -> Result<FetchInstructionResult<I>, ExecutionError<Address<I>, CustomError>> {
185        let instruction = match memory.read(self.pc.as_u64()).or_else(|error| {
186            cold_path();
187            // Attempt to read a 16-bit compressed instruction
188            if let Ok(instruction) = memory.read::<u16>(self.pc.as_u64())
189                && (instruction & 0b11) != 0b11
190            {
191                return Ok(u32::from(instruction));
192            }
193            Err(error)
194        }) {
195            Ok(instruction) => instruction,
196            Err(error) => {
197                cold_path();
198                return Err(ExecutionError::MemoryAccess(error));
199            }
200        };
201
202        let Some(instruction) = I::try_decode(instruction) else {
203            cold_path();
204            return Err(ExecutionError::IllegalInstruction { address: self.pc });
205        };
206        self.pc += instruction.size().into();
207
208        Ok(FetchInstructionResult::Instruction(instruction))
209    }
210}
211
212impl<I, CustomError> BasicInstructionFetcher<I, CustomError>
213where
214    I: Instruction,
215{
216    /// Create a new instance.
217    ///
218    /// `return_trap_address` is the address at which the interpreter will stop execution
219    /// (gracefully).
220    #[inline(always)]
221    pub fn new(return_trap_address: Address<I>, pc: Address<I>) -> Self {
222        Self {
223            return_trap_address,
224            pc,
225            _phantom: PhantomData,
226        }
227    }
228}
229
230/// System instruction handler that ignores all system calls and does nothing for other system
231/// instructions
232#[derive(Debug, Default, Clone, Copy)]
233pub struct IgnoreEcallSystemInstructionHandler;
234
235impl<Reg, Regs, Memory, PC, CustomError>
236    SystemInstructionHandler<Reg, Regs, Memory, PC, CustomError>
237    for IgnoreEcallSystemInstructionHandler
238where
239    Reg: Register,
240    Regs: RegisterFile<Reg>,
241{
242    fn handle_ecall(
243        &mut self,
244        _regs: &mut Regs,
245        _memory: &mut Memory,
246        _program_counter: &mut PC,
247    ) -> Result<ControlFlow<()>, ExecutionError<Reg::Type, CustomError>> {
248        Ok(ControlFlow::Continue(()))
249    }
250}