Skip to main content

ab_riscv_interpreter/
basic.rs

1//! Basic implementations of various interpreter traits
2
3// Only available on 64-bit hosts. The position of the fetcher in the decoded stream is a host
4// pointer, which a relative branch moves by its `i32` offset times the decoded stream bytes per
5// guest byte. On a 32-bit host that product doesn't always fit into `isize`, so the pointer wraps
6// around, and a far branch can land on a wrong instruction. Clamping the move avoids that, but a
7// refused target is kept in the position for the cold continuation, since threaded dispatch passes
8// nothing but the two pointers of the fetcher to it, so a clamped target is reported with a wrong
9// address. Supporting 32-bit hosts needs room for the real target somewhere else.
10#[cfg(all(feature = "alloc", target_pointer_width = "64"))]
11mod eager_instruction_fetcher;
12#[cfg(test)]
13mod tests;
14
15#[cfg(all(feature = "alloc", target_pointer_width = "64"))]
16pub use crate::basic::eager_instruction_fetcher::{
17    BasicEagerDecodeError, BasicEagerInstructionFetcher, BasicEagerInstructions,
18};
19use crate::zawrs::WrsHandler;
20use crate::zifencei::FenceIHandler;
21use crate::{
22    Address, BasicInt, ExecutableInstruction, ExecutionError, ExecutionResult,
23    FetchInstructionResult, InstructionFetcher, InstructionReg, PackedAddress, ProgramCounter,
24    RegisterFile, Rs1Rs2OperandValues, Rs1Rs2Operands, SystemInstructionHandler, VirtualMemory,
25    VirtualMemoryError,
26};
27use ab_riscv_primitives::prelude::*;
28#[cfg(feature = "alloc")]
29use alloc::boxed::Box;
30use core::hint::{assert_unchecked, cold_path};
31use core::ops::ControlFlow;
32use replace_with::replace_with_or_abort_and_return;
33
34/// Offset of a register in [`BasicRegisters`], which is always below [`Self::COUNT`]
35#[derive(Debug, Clone, Copy)]
36#[derive_const(PartialEq, Eq)]
37#[repr(transparent)]
38pub struct RegisterOffset(u8);
39
40const impl From<RegisterOffset> for usize {
41    #[inline(always)]
42    fn from(offset: RegisterOffset) -> Self {
43        // SAFETY: The field is private to this module and only set by the constructor, which
44        // rejects offsets that are not below `RegisterOffset::COUNT`
45        unsafe {
46            assert_unchecked(usize::from(offset.0) < RegisterOffset::COUNT);
47        }
48        Self::from(offset.0)
49    }
50}
51
52impl RegisterOffset {
53    /// The number of distinct offsets, the maximum number of general purpose registers
54    pub type const COUNT: usize = 32;
55
56    /// Create a new instance.
57    ///
58    /// Returns `None` if `offset` is not below [`Self::COUNT`].
59    #[inline(always)]
60    pub const fn new(offset: u8) -> Option<Self> {
61        if usize::from(offset) < Self::COUNT {
62            Some(Self(offset))
63        } else {
64            None
65        }
66    }
67}
68
69/// Basic general purpose register to be used with [`BasicRegisters`]
70pub const trait BasicRegister
71where
72    Self: [const] Register,
73{
74    /// Offset in a set of registers.
75    ///
76    /// Distinct registers must have distinct offsets for correct behavior, but it is not a safety
77    /// requirement.
78    fn offset(self) -> RegisterOffset;
79}
80
81const impl<Type> BasicRegister for EReg<Type>
82where
83    Self: [const] Register,
84{
85    #[inline(always)]
86    fn offset(self) -> RegisterOffset {
87        let offset = match self {
88            Self::Zero => 0,
89            Self::Ra => 1,
90            Self::Sp => 2,
91            Self::Gp => 3,
92            Self::Tp => 4,
93            Self::T0 => 5,
94            Self::T1 => 6,
95            Self::T2 => 7,
96            Self::S0 => 8,
97            Self::S1 => 9,
98            Self::A0 => 10,
99            Self::A1 => 11,
100            Self::A2 => 12,
101            Self::A3 => 13,
102            Self::A4 => 14,
103            Self::A5 => 15,
104        };
105        RegisterOffset::new(offset).expect("All offsets above are below 16; qed")
106    }
107}
108
109const impl<Type> BasicRegister for Reg<Type>
110where
111    Self: [const] Register,
112{
113    #[inline(always)]
114    fn offset(self) -> RegisterOffset {
115        let offset = match self {
116            Self::Zero => 0,
117            Self::Ra => 1,
118            Self::Sp => 2,
119            Self::Gp => 3,
120            Self::Tp => 4,
121            Self::T0 => 5,
122            Self::T1 => 6,
123            Self::T2 => 7,
124            Self::S0 => 8,
125            Self::S1 => 9,
126            Self::A0 => 10,
127            Self::A1 => 11,
128            Self::A2 => 12,
129            Self::A3 => 13,
130            Self::A4 => 14,
131            Self::A5 => 15,
132            Self::A6 => 16,
133            Self::A7 => 17,
134            Self::S2 => 18,
135            Self::S3 => 19,
136            Self::S4 => 20,
137            Self::S5 => 21,
138            Self::S6 => 22,
139            Self::S7 => 23,
140            Self::S8 => 24,
141            Self::S9 => 25,
142            Self::S10 => 26,
143            Self::S11 => 27,
144            Self::T3 => 28,
145            Self::T4 => 29,
146            Self::T5 => 30,
147            Self::T6 => 31,
148        };
149        RegisterOffset::new(offset).expect("All offsets above are below 32; qed")
150    }
151}
152
153/// A basic set of RISC-V GPRs (General Purpose Registers).
154///
155/// `ZEROSTORE` generic determines whether to zero `x0` register on write instead of checking
156/// register index on read. `match` loop usually performs better with branching, while threaded
157/// execution benefits from zeroing.
158///
159/// There is a slot for every possible [`RegisterOffset`], even with fewer registers (like with the
160/// E extension), so that any offset is valid and no bounds checks are needed.
161#[derive(Debug, Clone, Copy)]
162#[repr(align(16))]
163pub struct BasicRegisters<Reg, const ZEROSTORE: bool = false>
164where
165    Reg: BasicRegister,
166{
167    regs: [Reg::Type; RegisterOffset::COUNT],
168}
169
170impl<Reg, const ZEROSTORE: bool> Default for BasicRegisters<Reg, ZEROSTORE>
171where
172    Reg: BasicRegister,
173{
174    #[inline(always)]
175    fn default() -> Self {
176        Self {
177            regs: [Reg::Type::default(); _],
178        }
179    }
180}
181
182const impl<Reg, const ZEROSTORE: bool> RegisterFile<Reg> for BasicRegisters<Reg, ZEROSTORE>
183where
184    Reg: [const] BasicRegister,
185{
186    #[inline(always)]
187    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
188    fn read(&self, reg: Reg) -> Reg::Type {
189        if reg == Reg::ZERO && !ZEROSTORE {
190            // Always zero
191            return Reg::Type::default();
192        }
193
194        *self
195            .regs
196            .get(usize::from(reg.offset()))
197            .expect("There is a register for every offset; qed")
198    }
199
200    #[inline(always)]
201    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
202    fn write(&mut self, reg: Reg, value: Reg::Type) {
203        *self
204            .regs
205            .get_mut(usize::from(reg.offset()))
206            .expect("There is a register for every offset; qed") = value;
207        if ZEROSTORE {
208            *self
209                .regs
210                .get_mut(usize::from(Reg::ZERO.offset()))
211                .expect("There is a register for every offset; qed") = Reg::Type::default();
212        }
213    }
214}
215
216/// Basic interpreter state.
217///
218/// This is a simple container, which is not required to be used, is helpful for storing the whole
219/// state related to the interpreter together.
220#[derive(Debug)]
221pub struct BasicInterpreterState<Regs, Env, Memory, IF> {
222    /// General purpose registers
223    pub regs: Regs,
224    /// Execution environment.
225    ///
226    /// Extensions might use this to place additional constraints on `Env` to require additional
227    /// registers, handlers (like [`SystemInstructionHandler`]) or other resources. If no such
228    /// extension is used, `()` can be used as a placeholder.
229    pub env: Env,
230    /// Memory
231    pub memory: Memory,
232    /// Instruction fetcher
233    pub instruction_fetcher: IF,
234}
235
236impl<Regs, Env, Memory, IF> BasicInterpreterState<Regs, Env, Memory, IF> {
237    /// Execute the program with a given basic interpreter state.
238    ///
239    /// The implementation is designed to be efficient with little left to optimize further. Though
240    /// it is still possible to improve performance by applying additional constraints on the
241    /// program.
242    ///
243    /// When execution fails, the program counter stays where the failure left it:
244    /// * after a branch whose target was refused, at that target, so calling this again fails the
245    ///   same way
246    /// * after an instruction that failed, or a jump whose target [`ProgramCounter::set_pc()`]
247    ///   refused, right after that instruction, so calling this again continues with the next one
248    /// * after a failed fetch, wherever the instruction fetcher left it, which for
249    ///   [`BasicInstructionFetcher`] is the address it couldn't fetch from
250    // TODO: It might be impractical to support `no-panic` here directly in a general case, but it
251    //  should be possible to do so for small extensions to verify the workflow
252    //
253    // Make sure each handler starts on a cache line boundary
254    #[rustc_align(64)]
255    pub fn execute<I>(&mut self) -> Result<(), ExecutionError<Address<I>>>
256    where
257        Regs: RegisterFile<InstructionReg<I>>,
258        I: ExecutableInstruction<Regs, Env, Memory, IF>,
259        Memory: VirtualMemory,
260        IF: InstructionFetcher<I, Memory> + ProgramCounter<Address<I>, Memory>,
261    {
262        replace_with_or_abort_and_return(
263            &mut self.instruction_fetcher,
264            #[inline(always)]
265            |mut instruction_fetcher| {
266                // The last execution may have stopped where there is nothing to fetch
267                let pc = instruction_fetcher.get_pc();
268                match instruction_fetcher.set_pc(&self.memory, pc) {
269                    Ok(ControlFlow::Continue(())) => {}
270                    Ok(ControlFlow::Break(())) => {
271                        cold_path();
272                        return (Ok(()), instruction_fetcher);
273                    }
274                    Err(error) => {
275                        cold_path();
276                        return (Err(error), instruction_fetcher);
277                    }
278                }
279
280                loop {
281                    // SAFETY: `set_pc()` above returned `Continue`, and the loop only comes back
282                    // here after fetching returned `Continue`, after an instruction continued, or
283                    // after its branch or jump was accepted
284                    let fetched = unsafe { instruction_fetcher.fetch_instruction(&self.memory) };
285                    let instruction = match fetched {
286                        FetchInstructionResult::Instruction(instruction) => instruction,
287                        FetchInstructionResult::Continue => {
288                            cold_path();
289                            continue;
290                        }
291                        FetchInstructionResult::Break => {
292                            cold_path();
293                            break;
294                        }
295                        FetchInstructionResult::Err(error) => {
296                            cold_path();
297                            return (Err(error), instruction_fetcher);
298                        }
299                    };
300
301                    let Rs1Rs2Operands { rs1, rs2 } = instruction.get_rs1_rs2_operands();
302                    let rs1rs2_values = Rs1Rs2OperandValues {
303                        rs1_value: self.regs.read(rs1),
304                        rs2_value: self.regs.read(rs2),
305                    };
306
307                    let outcome = instruction.execute(
308                        rs1rs2_values,
309                        &mut self.regs,
310                        &mut self.env,
311                        &mut self.memory,
312                        &instruction_fetcher,
313                    );
314
315                    let control_flow =
316                        match outcome {
317                            ExecutionResult::Continue { rd, value } => {
318                                self.regs.write(rd, value);
319                                continue;
320                            }
321                            ExecutionResult::ContinueNoWrite => {
322                                continue;
323                            }
324                            ExecutionResult::Branch { offset } => instruction_fetcher
325                                .set_pc_relative(&self.memory, instruction.size(), offset),
326                            ExecutionResult::Jump { target } => {
327                                instruction_fetcher.set_pc(&self.memory, target)
328                            }
329                            ExecutionResult::Break => {
330                                cold_path();
331                                break;
332                            }
333                            ExecutionResult::Err(error) => {
334                                cold_path();
335                                return (Err(error), instruction_fetcher);
336                            }
337                        };
338
339                    match control_flow {
340                        Ok(ControlFlow::Continue(())) => {}
341                        Ok(ControlFlow::Break(())) => {
342                            cold_path();
343                            break;
344                        }
345                        Err(error) => {
346                            cold_path();
347                            return (Err(error), instruction_fetcher);
348                        }
349                    }
350                }
351
352                (Ok(()), instruction_fetcher)
353            },
354        )
355    }
356}
357
358/// Ensures [`BasicMemory`] doesn't wrap around the end of the address space
359const NON_WRAPPING_MEMORY<const BASE_ADDR: u64, const SIZE: usize>: usize = {
360    assert!(
361        BASE_ADDR.checked_add(SIZE as u64).is_some(),
362        "`BasicMemory` must not wrap around the end of the address space, or an address below its \
363        base address could wrap into it"
364    );
365    0
366};
367
368/// Basic memory implementation.
369///
370/// Flat structure, no rwx protections, no alignment requirements. It uses stack, so for larger
371/// allocation it'll need to be boxed (zero-initialized is fine) or a custom implementation to be
372/// used.
373///
374/// `BASE_ADDR + SIZE` must fit into `u64`, meaning the memory region can't wrap around or end at
375/// the very end of the address space, which is checked at compile time. This is what allows an
376/// address below `BASE_ADDR` to be recognized by the bounds check that follows the subtraction of
377/// the base address rather than by a check of its own.
378///
379/// This implementation is intentionally basic and correct, but not the most performant. It is
380/// possible to have a more efficient implementation that skips certain checks by placing additional
381/// constraints on the program.
382///
383/// This works for simpler cases, while a more sophisticated implementation might prevent certain
384/// memory from being writable, supporting actual virtual memory with dynamically allocated memory
385/// pages, etc.
386#[derive(Debug, Copy, Clone)]
387#[repr(align(16))]
388pub struct BasicMemory<const BASE_ADDR: u64, const SIZE: usize> {
389    data: [u8; SIZE],
390}
391
392const impl<const BASE_ADDR: u64, const SIZE: usize> VirtualMemory for BasicMemory<BASE_ADDR, SIZE>
393where
394    [(); NON_WRAPPING_MEMORY::<BASE_ADDR, SIZE>]:,
395{
396    #[inline(always)]
397    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
398    fn read<T>(&self, address: u64) -> Result<T, VirtualMemoryError>
399    where
400        T: BasicInt,
401    {
402        let offset = Self::offset(address);
403
404        if offset.saturating_add(size_of::<T>() as u64) > self.data.len() as u64 {
405            cold_path();
406            return Err(VirtualMemoryError::OutOfBoundsRead { address });
407        }
408
409        // SAFETY: Only reading basic integers from initialized memory
410        unsafe {
411            Ok(self
412                .data
413                .as_ptr()
414                .cast::<T>()
415                .byte_add(offset as usize)
416                .read_unaligned())
417        }
418    }
419
420    #[inline(always)]
421    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
422    unsafe fn read_unchecked<T>(&self, address: u64) -> T
423    where
424        T: BasicInt,
425    {
426        // SAFETY: Guaranteed by function contract
427        unsafe {
428            let offset = address.unchecked_sub(BASE_ADDR) as usize;
429            self.data
430                .as_ptr()
431                .cast::<T>()
432                .byte_add(offset)
433                .read_unaligned()
434        }
435    }
436
437    #[inline(always)]
438    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
439    fn read_slice(&self, address: u64, len: u32) -> Result<&[u8], VirtualMemoryError> {
440        let result = self.read_slice_inner(address, len);
441        if let Ok(bytes) = result {
442            // SAFETY: `read_slice_inner()` returns exactly `len` bytes on success. Asserted here
443            // rather than inside so that callers can rely on it without the implementation being
444            // inlined.
445            unsafe {
446                assert_unchecked(bytes.len() == len as usize);
447            }
448        }
449        result
450    }
451
452    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
453    fn read_slice_up_to(&self, address: u64, len: u32) -> &[u8] {
454        let offset = Self::offset(address);
455
456        if offset > self.data.len() as u64 {
457            cold_path();
458            return &[];
459        }
460
461        let remaining = self.data.get(offset as usize..).unwrap_or_default();
462        remaining.get(..len as usize).unwrap_or(remaining)
463    }
464
465    #[inline(always)]
466    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
467    fn write<T>(&mut self, address: u64, value: T) -> Result<(), VirtualMemoryError>
468    where
469        T: BasicInt,
470    {
471        let offset = Self::offset(address);
472
473        if offset.saturating_add(size_of::<T>() as u64) > self.data.len() as u64 {
474            cold_path();
475            return Err(VirtualMemoryError::OutOfBoundsWrite { address });
476        }
477
478        // SAFETY: Only writing basic integers to initialized memory
479        unsafe {
480            self.data
481                .as_mut_ptr()
482                .cast::<T>()
483                .byte_add(offset as usize)
484                .write_unaligned(value);
485        }
486
487        Ok(())
488    }
489
490    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
491    fn write_slice(&mut self, address: u64, data: &[u8]) -> Result<(), VirtualMemoryError> {
492        let offset = Self::offset(address);
493
494        if offset > self.data.len() as u64 {
495            cold_path();
496            return Err(VirtualMemoryError::OutOfBoundsWrite { address });
497        }
498
499        let len = data.len();
500        let Some(target_data) = self
501            .data
502            .get_mut(offset as usize..)
503            .and_then(const |data| data.get_mut(..len))
504        else {
505            cold_path();
506            return Err(VirtualMemoryError::OutOfBoundsWrite { address });
507        };
508
509        target_data.copy_from_slice(data);
510
511        Ok(())
512    }
513}
514
515impl<const BASE_ADDR: u64, const SIZE: usize> Default for BasicMemory<BASE_ADDR, SIZE>
516where
517    [(); NON_WRAPPING_MEMORY::<BASE_ADDR, SIZE>]:,
518{
519    #[inline(always)]
520    fn default() -> Self {
521        Self { data: [0; _] }
522    }
523}
524
525impl<const BASE_ADDR: u64, const SIZE: usize> BasicMemory<BASE_ADDR, SIZE>
526where
527    [(); NON_WRAPPING_MEMORY::<BASE_ADDR, SIZE>]:,
528{
529    /// Offset of `address` within this memory region.
530    ///
531    /// Subtraction wraps rather than being checked: an address below `BASE_ADDR` produces an offset
532    /// so large that the bounds check every caller does next rejects it, which is one comparison
533    /// instead of two on every memory access.
534    #[inline(always)]
535    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
536    const fn offset(address: u64) -> u64 {
537        address.wrapping_sub(BASE_ADDR)
538    }
539
540    /// Implementation of [`VirtualMemory::read_slice()`], returns exactly `len` bytes on success
541    #[inline]
542    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
543    const fn read_slice_inner(&self, address: u64, len: u32) -> Result<&[u8], VirtualMemoryError> {
544        let offset = Self::offset(address);
545
546        if offset > self.data.len() as u64 {
547            cold_path();
548            return Err(VirtualMemoryError::OutOfBoundsRead { address });
549        }
550
551        self.data
552            .get(offset as usize..)
553            .and_then(const |data| data.get(..len as usize))
554            .ok_or(VirtualMemoryError::OutOfBoundsRead { address })
555    }
556
557    #[cfg(feature = "alloc")]
558    pub fn new_boxed() -> Box<Self> {
559        // SAFETY: Zeroed memory is a valid invariant
560        unsafe { Box::<Self>::new_zeroed().assume_init() }
561    }
562
563    /// Get a mutable slice of memory.
564    ///
565    /// This is primarily useful for setting up the program and should not be used beyond that.
566    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
567    pub const fn get_mut_bytes(
568        &mut self,
569        address: u64,
570        size: usize,
571    ) -> Result<&mut [u8], VirtualMemoryError> {
572        let Some(offset) = address.checked_sub(BASE_ADDR) else {
573            cold_path();
574            return Err(VirtualMemoryError::OutOfBoundsRead { address });
575        };
576        let offset = offset as usize;
577
578        let Some(slice) = self
579            .data
580            .get_mut(offset..)
581            .and_then(const |data| data.get_mut(..size))
582        else {
583            cold_path();
584            return Err(VirtualMemoryError::OutOfBoundsRead { address });
585        };
586
587        Ok(slice)
588    }
589}
590
591/// Basic instruction fetcher implementation.
592///
593/// This implementation is intentionally basic and correct, but not the most performant. It is
594/// possible to have a more efficient implementation that skips certain checks by placing additional
595/// constraints on the constructor.
596///
597/// Note that it loads instructions from anywhere in memory. This works, but it is likely that you
598/// want to restrict this to a specific executable region of memory.
599///
600/// Every fetch is a bounds-checked memory read, so fetching is allowed at any program counter,
601/// whatever moved it there.
602#[derive(Debug, Copy, Clone)]
603pub struct BasicInstructionFetcher<I>
604where
605    I: Instruction,
606{
607    return_trap_address: Address<I>,
608    pc: Address<I>,
609}
610
611const impl<I, Memory> ProgramCounter<Address<I>, Memory> for BasicInstructionFetcher<I>
612where
613    I: [const] Instruction,
614    Memory: [const] VirtualMemory,
615{
616    #[inline(always)]
617    fn get_pc(&self) -> Address<I> {
618        self.pc
619    }
620
621    #[inline(always)]
622    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
623    unsafe fn try_set_pc_relative(&mut self, instruction_size: u8, offset: i32) -> bool {
624        let old_pc = <Self as ProgramCounter<_, Memory>>::old_pc(self, instruction_size);
625        let pc = old_pc.wrapping_add_signed(offset);
626        // Stored either way: on the way out it is what `failed_branch()` reports on, and until then
627        // nothing else is allowed to look at it
628        self.pc = pc;
629
630        pc != self.return_trap_address && pc.as_u64().is_multiple_of(u64::from(I::ALIGNMENT))
631    }
632
633    #[cold]
634    #[inline(never)]
635    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
636    unsafe fn failed_branch(
637        &mut self,
638        memory: &Memory,
639    ) -> Result<ControlFlow<()>, ExecutionError<Address<I>>> {
640        // The program counter holds the refused target, and `set_pc()` is what says what is wrong
641        // with it
642        self.set_pc(memory, self.pc)
643    }
644
645    #[inline]
646    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
647    fn set_pc(
648        &mut self,
649        _memory: &Memory,
650        pc: Address<I>,
651    ) -> Result<ControlFlow<()>, ExecutionError<Address<I>>> {
652        if pc == self.return_trap_address {
653            cold_path();
654            // Execution stops at the trap, so that is the program counter `get_pc()` reports
655            self.pc = pc;
656            return Ok(ControlFlow::Break(()));
657        }
658
659        if !pc.as_u64().is_multiple_of(u64::from(I::ALIGNMENT)) {
660            cold_path();
661            return Err(ExecutionError::UnalignedInstruction {
662                address: PackedAddress::new(pc),
663            });
664        }
665
666        self.pc = pc;
667
668        Ok(ControlFlow::Continue(()))
669    }
670}
671
672// SAFETY: Fetching is a checked memory read with no `unsafe` code, so it is sound at any program
673// counter. The program counter is a plain field that only methods taking `&mut self` write.
674// `peeked_instruction()` returns `peeked` itself, which is the instruction `peek_instruction()`
675// returned.
676const unsafe impl<I, Memory> InstructionFetcher<I, Memory> for BasicInstructionFetcher<I>
677where
678    I: [const] Instruction,
679    Memory: [const] VirtualMemory,
680{
681    type Peeked = I;
682
683    #[inline(always)]
684    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
685    unsafe fn peeked_instruction<'a>(&'a self, peeked: &'a I) -> &'a I {
686        peeked
687    }
688
689    #[inline]
690    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
691    unsafe fn peek_instruction(&mut self, memory: &Memory) -> FetchInstructionResult<I> {
692        let instruction = match memory.read(self.pc.as_u64()).or_else(const |error| {
693            cold_path();
694            // Attempt to read a 16-bit compressed instruction
695            if let Ok(instruction) = memory.read::<u16>(self.pc.as_u64())
696                && (instruction & 0b11) != 0b11
697            {
698                return Ok(u32::from(instruction));
699            }
700            Err(error)
701        }) {
702            Ok(instruction) => instruction,
703            Err(error) => {
704                cold_path();
705                return FetchInstructionResult::Err(ExecutionError::from(error));
706            }
707        };
708
709        let Some(instruction) = I::try_decode(instruction) else {
710            cold_path();
711            return FetchInstructionResult::Err(ExecutionError::IllegalInstruction {
712                address: PackedAddress::new(self.pc),
713            });
714        };
715        FetchInstructionResult::Instruction(instruction)
716    }
717
718    #[inline(always)]
719    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
720    unsafe fn advance(&mut self, instruction_size: u8) {
721        self.pc = self.pc.wrapping_add_signed(i32::from(instruction_size));
722    }
723
724    #[inline]
725    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
726    unsafe fn fetch_instruction(&mut self, memory: &Memory) -> FetchInstructionResult<I> {
727        // SAFETY: Same requirements as for this method, which the caller upholds
728        let result = unsafe { InstructionFetcher::<I, Memory>::peek_instruction(self, memory) };
729
730        if let FetchInstructionResult::Instruction(instruction) = result {
731            // SAFETY: The instruction was just peeked successfully, and this is the only place
732            // that moves past it
733            unsafe {
734                InstructionFetcher::<I, Memory>::advance(self, instruction.size());
735            }
736        }
737
738        result
739    }
740}
741
742impl<I> BasicInstructionFetcher<I>
743where
744    I: Instruction,
745{
746    /// Create a new instance.
747    ///
748    /// `return_trap_address` is the address at which the interpreter will stop execution
749    /// (gracefully).
750    #[inline(always)]
751    pub const fn new(return_trap_address: Address<I>, pc: Address<I>) -> Self {
752        Self {
753            return_trap_address,
754            pc,
755        }
756    }
757}
758
759/// System instruction handler that results in illegal instruction for all system calls and does
760/// nothing for other system instructions.
761///
762/// Being stateless, it can be used as the whole execution environment of a configuration that
763/// needs nothing else from it.
764#[derive(Debug, Default, Clone, Copy)]
765pub struct IllegalEcallSystemInstructionHandler;
766
767const impl<Reg, Regs, Memory, PC> SystemInstructionHandler<Reg, Regs, Memory, PC>
768    for IllegalEcallSystemInstructionHandler
769where
770    Reg: [const] Register,
771    PC: [const] ProgramCounter<Reg::Type, Memory>,
772{
773    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
774    fn handle_ecall(
775        &mut self,
776        _regs: &mut Regs,
777        _memory: &mut Memory,
778        program_counter: &PC,
779    ) -> ExecutionResult<Reg> {
780        ExecutionResult::Err(ExecutionError::IllegalInstruction {
781            address: PackedAddress::new(program_counter.old_pc(size_of::<u32>() as u8)),
782        })
783    }
784}
785
786const impl WrsHandler for IllegalEcallSystemInstructionHandler {}
787
788const impl FenceIHandler for IllegalEcallSystemInstructionHandler {}