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::zawrs::WrsHandler;
7use crate::{
8    Address, BasicInt, ExecutableInstruction, ExecutionError, ExecutionResult,
9    FetchInstructionResult, InstructionFetcher, PackedAddress, ProgramCounter, RegisterFile,
10    Rs1Rs2OperandValues, Rs1Rs2Operands, SystemInstructionHandler, VirtualMemory,
11    VirtualMemoryError,
12};
13use ab_riscv_primitives::prelude::*;
14#[cfg(feature = "alloc")]
15use alloc::boxed::Box;
16use core::hint::cold_path;
17use core::ops::ControlFlow;
18use replace_with::replace_with_or_abort_and_return;
19
20/// Basic general purpose register to be used with [`BasicRegisters`]
21///
22/// # Safety
23/// `Self::offset()` must return values in `0..Self::N` range. `Self::from_bits()` must return
24/// `Some()` for `0..=31` if `Self::RVE = false` and `0..=15` if `Self::RVE = true`.
25pub const unsafe trait BasicRegister
26where
27    Self: [const] Register,
28{
29    /// The number of general purpose registers.
30    ///
31    /// Canonically 32 unless E extension is used, in which case 16.
32    const N: usize;
33
34    /// Offset in a set of registers
35    fn offset(self) -> u8;
36}
37
38// SAFETY: `Self::offset()` returns values within `0..Self::N` range
39const unsafe impl<Type> BasicRegister for EReg<Type>
40where
41    Self: [const] Register,
42{
43    const N: usize = 16;
44
45    #[inline(always)]
46    fn offset(self) -> u8 {
47        // SAFETY: Enum is `#[repr(u8)]` and doesn't have any fields
48        unsafe { core::mem::transmute::<Self, u8>(self) }
49    }
50}
51
52// SAFETY: `Self::offset()` returns values within `0..Self::N` range
53const unsafe impl<Type> BasicRegister for Reg<Type>
54where
55    Self: [const] Register,
56{
57    const N: usize = 32;
58
59    #[inline(always)]
60    fn offset(self) -> u8 {
61        // SAFETY: Enum is `#[repr(u8)]` and doesn't have any fields
62        unsafe { core::mem::transmute::<Self, u8>(self) }
63    }
64}
65
66/// A basic set of RISC-V GPRs (General Purpose Registers).
67///
68/// `ZEROSTORE` generic determines whether to zero `x0` register on write instead of checking
69/// register index on read. `match` loop usually performs better with branching, while threaded
70/// execution benefits from zeroing.
71#[derive(Debug, Clone, Copy)]
72#[repr(align(16))]
73pub struct BasicRegisters<Reg, const ZEROSTORE: bool = false>
74where
75    Reg: BasicRegister,
76{
77    regs: [Reg::Type; Reg::N],
78}
79
80impl<Reg, const ZEROSTORE: bool> Default for BasicRegisters<Reg, ZEROSTORE>
81where
82    Reg: BasicRegister,
83{
84    #[inline(always)]
85    fn default() -> Self {
86        Self {
87            regs: [Reg::Type::default(); _],
88        }
89    }
90}
91
92const impl<Reg, const ZEROSTORE: bool> RegisterFile<Reg> for BasicRegisters<Reg, ZEROSTORE>
93where
94    Reg: [const] BasicRegister,
95{
96    #[inline(always)]
97    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
98    fn read(&self, reg: Reg) -> Reg::Type {
99        if reg == Reg::ZERO && !ZEROSTORE {
100            // Always zero
101            return Reg::Type::default();
102        }
103
104        // SAFETY: register offset is always within bounds
105        *unsafe { self.regs.get_unchecked(usize::from(reg.offset())) }
106    }
107
108    #[inline(always)]
109    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
110    fn write(&mut self, reg: Reg, value: Reg::Type) {
111        // SAFETY: register offset is always within bounds
112        *unsafe { self.regs.get_unchecked_mut(usize::from(reg.offset())) } = value;
113        if ZEROSTORE {
114            // SAFETY: The register file always has at least one slot
115            *unsafe { self.regs.get_unchecked_mut(0) } = Reg::Type::default();
116        }
117    }
118}
119
120/// Basic interpreter state.
121///
122/// This is a simple container, which is not required to be used, is helpful for storing the whole
123/// state related to the interpreter together.
124#[derive(Debug)]
125pub struct BasicInterpreterState<Regs, Env, Memory, IF> {
126    /// General purpose registers
127    pub regs: Regs,
128    /// Execution environment.
129    ///
130    /// Extensions might use this to place additional constraints on `Env` to require additional
131    /// registers, handlers (like [`SystemInstructionHandler`]) or other resources. If no such
132    /// extension is used, `()` can be used as a placeholder.
133    pub env: Env,
134    /// Memory
135    pub memory: Memory,
136    /// Instruction fetcher
137    pub instruction_fetcher: IF,
138}
139
140impl<Regs, Env, Memory, IF> BasicInterpreterState<Regs, Env, Memory, IF> {
141    /// Execute the program with a given basic interpreter state.
142    ///
143    /// The implementation is designed to be efficient with little left to optimize further. Though
144    /// it is still possible to improve performance by applying additional constraints on the
145    /// program.
146    // TODO: It might be impractical to support `no-panic` here directly in a general case, but it
147    //  should be possible to do so for small extensions to verify the workflow
148    //
149    // Make sure each handler starts on a cache line boundary
150    #[rustc_align(64)]
151    pub fn execute<I>(&mut self) -> Result<(), ExecutionError<Address<I>>>
152    where
153        Regs: RegisterFile<<I as Instruction>::Reg>,
154        I: ExecutableInstruction<Regs, Env, Memory, IF>,
155        Memory: VirtualMemory,
156        IF: InstructionFetcher<I, Memory> + ProgramCounter<Address<I>, Memory>,
157    {
158        replace_with_or_abort_and_return(
159            &mut self.instruction_fetcher,
160            #[inline(always)]
161            |mut instruction_fetcher| {
162                loop {
163                    let instruction = match instruction_fetcher.fetch_instruction(&self.memory) {
164                        FetchInstructionResult::Instruction(instruction) => instruction,
165                        FetchInstructionResult::Continue => {
166                            cold_path();
167                            continue;
168                        }
169                        FetchInstructionResult::Break => {
170                            cold_path();
171                            break;
172                        }
173                        FetchInstructionResult::Err(error) => {
174                            cold_path();
175                            return (Err(error), instruction_fetcher);
176                        }
177                    };
178
179                    let Rs1Rs2Operands { rs1, rs2 } = instruction.get_rs1_rs2_operands();
180                    let rs1rs2_values = Rs1Rs2OperandValues {
181                        rs1_value: self.regs.read(rs1),
182                        rs2_value: self.regs.read(rs2),
183                    };
184
185                    let outcome = instruction.execute(
186                        rs1rs2_values,
187                        &mut self.regs,
188                        &mut self.env,
189                        &mut self.memory,
190                        &mut instruction_fetcher,
191                    );
192
193                    let control_flow =
194                        match outcome {
195                            ExecutionResult::Continue { rd, value } => {
196                                self.regs.write(rd, value);
197                                continue;
198                            }
199                            ExecutionResult::ContinueNoWrite => {
200                                continue;
201                            }
202                            ExecutionResult::Branch { offset } => instruction_fetcher
203                                .set_pc_relative(&self.memory, instruction.size(), offset),
204                            ExecutionResult::Jump { target } => {
205                                instruction_fetcher.set_pc(&self.memory, target)
206                            }
207                            ExecutionResult::Break => {
208                                cold_path();
209                                break;
210                            }
211                            ExecutionResult::Err(error) => {
212                                cold_path();
213                                return (Err(error), instruction_fetcher);
214                            }
215                        };
216
217                    match control_flow {
218                        Ok(ControlFlow::Continue(())) => {}
219                        Ok(ControlFlow::Break(())) => {
220                            cold_path();
221                            break;
222                        }
223                        Err(error) => {
224                            cold_path();
225                            return (Err(error), instruction_fetcher);
226                        }
227                    }
228                }
229
230                (Ok(()), instruction_fetcher)
231            },
232        )
233    }
234}
235
236/// Basic memory implementation.
237///
238/// Flat structure, no rwx protections, no alignment requirements. It uses stack, so for larger
239/// allocation it'll need to be boxed (zero-initialized is fine) or a custom implementation to be
240/// used.
241///
242/// This implementation is intentionally basic and correct, but not the most performant. It is
243/// possible to have a more efficient implementation that skips certain checks by placing additional
244/// constraints on the program.
245///
246/// This works for simpler cases, while a more sophisticated implementation might prevent certain
247/// memory from being writable, supporting actual virtual memory with dynamically allocated memory
248/// pages, etc.
249#[derive(Debug, Copy, Clone)]
250#[repr(align(16))]
251pub struct BasicMemory<const BASE_ADDR: u64, const SIZE: usize> {
252    data: [u8; SIZE],
253}
254
255const impl<const BASE_ADDR: u64, const SIZE: usize> VirtualMemory for BasicMemory<BASE_ADDR, SIZE> {
256    #[inline(always)]
257    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
258    fn read<T>(&self, address: u64) -> Result<T, VirtualMemoryError>
259    where
260        T: BasicInt,
261    {
262        let Some(offset) = address.checked_sub(BASE_ADDR) else {
263            cold_path();
264            return Err(VirtualMemoryError::OutOfBoundsRead { address });
265        };
266
267        if offset.saturating_add(size_of::<T>() as u64) > self.data.len() as u64 {
268            cold_path();
269            return Err(VirtualMemoryError::OutOfBoundsRead { address });
270        }
271
272        // SAFETY: Only reading basic integers from initialized memory
273        unsafe {
274            Ok(self
275                .data
276                .as_ptr()
277                .cast::<T>()
278                .byte_add(offset as usize)
279                .read_unaligned())
280        }
281    }
282
283    #[inline(always)]
284    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
285    unsafe fn read_unchecked<T>(&self, address: u64) -> T
286    where
287        T: BasicInt,
288    {
289        // SAFETY: Guaranteed by function contract
290        unsafe {
291            let offset = address.unchecked_sub(BASE_ADDR) as usize;
292            self.data
293                .as_ptr()
294                .cast::<T>()
295                .byte_add(offset)
296                .read_unaligned()
297        }
298    }
299
300    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
301    fn read_slice(&self, address: u64, len: u32) -> Result<&[u8], VirtualMemoryError> {
302        let Some(offset) = address.checked_sub(BASE_ADDR) else {
303            cold_path();
304            return Err(VirtualMemoryError::OutOfBoundsRead { address });
305        };
306
307        if offset > self.data.len() as u64 {
308            cold_path();
309            return Err(VirtualMemoryError::OutOfBoundsRead { address });
310        }
311
312        self.data
313            .get(offset as usize..)
314            .and_then(const |data| data.get(..len as usize))
315            .ok_or(VirtualMemoryError::OutOfBoundsRead { address })
316    }
317
318    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
319    fn read_slice_up_to(&self, address: u64, len: u32) -> &[u8] {
320        let Some(offset) = address.checked_sub(BASE_ADDR) else {
321            cold_path();
322            return &[];
323        };
324
325        if offset > self.data.len() as u64 {
326            cold_path();
327            return &[];
328        }
329
330        let remaining = self.data.get(offset as usize..).unwrap_or_default();
331        remaining.get(..len as usize).unwrap_or(remaining)
332    }
333
334    #[inline(always)]
335    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
336    fn write<T>(&mut self, address: u64, value: T) -> Result<(), VirtualMemoryError>
337    where
338        T: BasicInt,
339    {
340        let Some(offset) = address.checked_sub(BASE_ADDR) else {
341            cold_path();
342            return Err(VirtualMemoryError::OutOfBoundsWrite { address });
343        };
344
345        if offset.saturating_add(size_of::<T>() as u64) > self.data.len() as u64 {
346            cold_path();
347            return Err(VirtualMemoryError::OutOfBoundsWrite { address });
348        }
349
350        // SAFETY: Only writing basic integers to initialized memory
351        unsafe {
352            self.data
353                .as_mut_ptr()
354                .cast::<T>()
355                .byte_add(offset as usize)
356                .write_unaligned(value);
357        }
358
359        Ok(())
360    }
361
362    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
363    fn write_slice(&mut self, address: u64, data: &[u8]) -> Result<(), VirtualMemoryError> {
364        let Some(offset) = address.checked_sub(BASE_ADDR) else {
365            cold_path();
366            return Err(VirtualMemoryError::OutOfBoundsWrite { address });
367        };
368
369        if offset > self.data.len() as u64 {
370            cold_path();
371            return Err(VirtualMemoryError::OutOfBoundsWrite { address });
372        }
373
374        let len = data.len();
375        let Some(target_data) = self
376            .data
377            .get_mut(offset as usize..)
378            .and_then(const |data| data.get_mut(..len))
379        else {
380            cold_path();
381            return Err(VirtualMemoryError::OutOfBoundsWrite { address });
382        };
383
384        target_data.copy_from_slice(data);
385
386        Ok(())
387    }
388}
389
390impl<const BASE_ADDR: u64, const SIZE: usize> Default for BasicMemory<BASE_ADDR, SIZE> {
391    #[inline(always)]
392    fn default() -> Self {
393        Self { data: [0; _] }
394    }
395}
396
397impl<const BASE_ADDR: u64, const SIZE: usize> BasicMemory<BASE_ADDR, SIZE> {
398    #[cfg(feature = "alloc")]
399    pub fn new_boxed() -> Box<Self> {
400        // SAFETY: Zeroed memory is a valid invariant
401        unsafe { Box::<Self>::new_zeroed().assume_init() }
402    }
403
404    /// Get a mutable slice of memory.
405    ///
406    /// This is primarily useful for setting up the program and should not be used beyond that.
407    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
408    pub const fn get_mut_bytes(
409        &mut self,
410        address: u64,
411        size: usize,
412    ) -> Result<&mut [u8], VirtualMemoryError> {
413        let Some(offset) = address.checked_sub(BASE_ADDR) else {
414            cold_path();
415            return Err(VirtualMemoryError::OutOfBoundsRead { address });
416        };
417        let offset = offset as usize;
418
419        let Some(slice) = self
420            .data
421            .get_mut(offset..)
422            .and_then(const |data| data.get_mut(..size))
423        else {
424            cold_path();
425            return Err(VirtualMemoryError::OutOfBoundsRead { address });
426        };
427
428        Ok(slice)
429    }
430}
431
432/// Basic instruction fetcher implementation.
433///
434/// This implementation is intentionally basic and correct, but not the most performant. It is
435/// possible to have a more efficient implementation that skips certain checks by placing additional
436/// constraints on the constructor.
437///
438/// Note that it loads instructions from anywhere in memory. This works, but it is likely that you
439/// want to restrict this to a specific executable region of memory.
440#[derive(Debug, Copy, Clone)]
441pub struct BasicInstructionFetcher<I>
442where
443    I: Instruction,
444{
445    return_trap_address: Address<I>,
446    pc: Address<I>,
447}
448
449const impl<I, Memory> ProgramCounter<Address<I>, Memory> for BasicInstructionFetcher<I>
450where
451    I: [const] Instruction,
452    Memory: [const] VirtualMemory,
453{
454    #[inline(always)]
455    fn get_pc(&self) -> Address<I> {
456        self.pc
457    }
458
459    #[inline(always)]
460    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
461    unsafe fn try_set_pc_relative(&mut self, instruction_size: u8, offset: i32) -> bool {
462        let old_pc = <Self as ProgramCounter<_, Memory>>::old_pc(self, instruction_size);
463        let pc = old_pc.wrapping_add_signed(offset);
464        // Stored either way: on the way out it is what `failed_branch()` reports on, and until then
465        // nothing else is allowed to look at it
466        self.pc = pc;
467
468        pc != self.return_trap_address && pc.as_u64().is_multiple_of(u64::from(I::alignment()))
469    }
470
471    #[cold]
472    #[inline(never)]
473    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
474    unsafe fn failed_branch(
475        &mut self,
476        memory: &Memory,
477    ) -> Result<ControlFlow<()>, ExecutionError<Address<I>>> {
478        // The program counter holds the refused target, and `set_pc()` is what says what is wrong
479        // with it
480        self.set_pc(memory, self.pc)
481    }
482
483    #[inline]
484    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
485    fn set_pc(
486        &mut self,
487        _memory: &Memory,
488        pc: Address<I>,
489    ) -> Result<ControlFlow<()>, ExecutionError<Address<I>>> {
490        if pc == self.return_trap_address {
491            cold_path();
492            return Ok(ControlFlow::Break(()));
493        }
494
495        if !pc.as_u64().is_multiple_of(u64::from(I::alignment())) {
496            cold_path();
497            return Err(ExecutionError::UnalignedInstruction {
498                address: PackedAddress::new(pc),
499            });
500        }
501
502        self.pc = pc;
503
504        Ok(ControlFlow::Continue(()))
505    }
506}
507
508const impl<I, Memory> InstructionFetcher<I, Memory> for BasicInstructionFetcher<I>
509where
510    I: [const] Instruction,
511    Memory: [const] VirtualMemory,
512{
513    #[inline]
514    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
515    fn peek_instruction(&mut self, memory: &Memory) -> FetchInstructionResult<I> {
516        let instruction = match memory.read(self.pc.as_u64()).or_else(const |error| {
517            cold_path();
518            // Attempt to read a 16-bit compressed instruction
519            if let Ok(instruction) = memory.read::<u16>(self.pc.as_u64())
520                && (instruction & 0b11) != 0b11
521            {
522                return Ok(u32::from(instruction));
523            }
524            Err(error)
525        }) {
526            Ok(instruction) => instruction,
527            Err(error) => {
528                cold_path();
529                return FetchInstructionResult::Err(ExecutionError::from(error));
530            }
531        };
532
533        let Some(instruction) = I::try_decode(instruction) else {
534            cold_path();
535            return FetchInstructionResult::Err(ExecutionError::IllegalInstruction {
536                address: PackedAddress::new(self.pc),
537            });
538        };
539        FetchInstructionResult::Instruction(instruction)
540    }
541
542    #[inline(always)]
543    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
544    unsafe fn advance(&mut self, instruction_size: u8) {
545        self.pc = self.pc.wrapping_add_signed(i32::from(instruction_size));
546    }
547
548    #[inline]
549    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
550    fn fetch_instruction(&mut self, memory: &Memory) -> FetchInstructionResult<I> {
551        let result = InstructionFetcher::<I, Memory>::peek_instruction(self, memory);
552
553        if let FetchInstructionResult::Instruction(instruction) = result {
554            // SAFETY: The instruction was just peeked successfully, and this is the only place
555            // that moves past it
556            unsafe {
557                InstructionFetcher::<I, Memory>::advance(self, instruction.size());
558            }
559        }
560
561        result
562    }
563}
564
565impl<I> BasicInstructionFetcher<I>
566where
567    I: Instruction,
568{
569    /// Create a new instance.
570    ///
571    /// `return_trap_address` is the address at which the interpreter will stop execution
572    /// (gracefully).
573    #[inline(always)]
574    pub const fn new(return_trap_address: Address<I>, pc: Address<I>) -> Self {
575        Self {
576            return_trap_address,
577            pc,
578        }
579    }
580}
581
582/// System instruction handler that results in illegal instruction for all system calls and does
583/// nothing for other system instructions.
584///
585/// Being stateless, it can be used as the whole execution environment of a configuration that
586/// needs nothing else from it.
587#[derive(Debug, Default, Clone, Copy)]
588pub struct IllegalEcallSystemInstructionHandler;
589
590const impl<Reg, Regs, Memory, PC> SystemInstructionHandler<Reg, Regs, Memory, PC>
591    for IllegalEcallSystemInstructionHandler
592where
593    Reg: [const] Register,
594    PC: [const] ProgramCounter<Reg::Type, Memory>,
595{
596    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
597    fn handle_ecall(
598        &mut self,
599        _regs: &mut Regs,
600        _memory: &mut Memory,
601        program_counter: &mut PC,
602    ) -> Result<ControlFlow<()>, ExecutionError<Reg::Type>> {
603        Err(ExecutionError::IllegalInstruction {
604            address: PackedAddress::new(program_counter.old_pc(size_of::<u32>() as u8)),
605        })
606    }
607}
608
609const impl WrsHandler for IllegalEcallSystemInstructionHandler {}