Skip to main content

ab_riscv_interpreter/basic/
eager_instruction_fetcher.rs

1//! Instruction fetcher that walks a program decoded upfront
2
3#[cfg(test)]
4mod tests;
5
6use crate::{
7    Address, ExecutionError, FetchInstructionResult, InstructionFetcher, PackedAddress,
8    ProgramCounter, VirtualMemory, VirtualMemoryError,
9};
10use ab_riscv_primitives::prelude::*;
11use alloc::alloc::{alloc, dealloc, handle_alloc_error};
12use core::alloc::Layout;
13use core::hint::cold_path;
14use core::marker::PhantomData;
15use core::ops::ControlFlow;
16use core::ptr::NonNull;
17use core::{fmt, mem};
18
19/// Everything [`BasicEagerInstructionFetcher`] needs besides its position within the decoded
20/// instruction stream.
21///
22/// This lives in a single heap allocation whose tail holds the decoded instructions themselves,
23/// starting `BasicEagerInstructions::INSTRUCTIONS_OFFSET` bytes from the beginning of it. That is
24/// what keeps the fetcher itself down to two pointers, so it fits into two argument registers when
25/// threaded through tail-called instruction handlers by value.
26#[derive(Debug)]
27#[repr(C)]
28struct BasicEagerInstructionFetcherState<I>
29where
30    I: Instruction,
31{
32    /// Number of decoded instructions stored right after this header
33    instructions_len: usize,
34    /// Size of the decoded instructions stored right after this header, in bytes.
35    ///
36    /// The same information as `instructions_len`, kept in the unit a relative branch measures
37    /// its target in, so that the bounds check on every taken branch is a single comparison
38    /// rather than a multiplication and a comparison. Deriving either from the other at run time
39    /// would cost an instruction on a hot path, hence both are stored.
40    instructions_size: usize,
41    /// Guest address that corresponds to the first decoded instruction
42    base_addr: Address<I>,
43    /// Guest address at which execution stops gracefully
44    return_trap_address: Address<I>,
45}
46
47/// Instructions decoded upfront, which [`BasicEagerInstructionFetcher`] walks.
48///
49/// Decoding a program once instead of on every fetch is what makes this faster than
50/// [`BasicInstructionFetcher`](super::BasicInstructionFetcher), at the cost of holding the whole
51/// decoded program in memory and of not seeing writes the program makes to the memory it was
52/// decoded from.
53///
54/// The decoded stream has one slot per [`Instruction::ALIGNMENT`] bytes of guest code, which is
55/// the granularity at which an instruction of this instruction set can start, and what makes an
56/// address a position within the stream and back. With compressed instructions that is a halfword,
57/// so the second half of a 32-bit instruction gets a slot of its own, holding whatever those bytes
58/// decode to, which is only ever reached by jumping into the middle of an instruction. Without
59/// them, no address in the middle of an instruction is aligned in the first place, so there is
60/// nothing to hold a slot for and the stream is half the size.
61///
62/// Ownership of the allocation lives here rather than in the fetcher because the fetcher is moved
63/// through tail-called instruction handlers by value. A destructor on it would make every handler
64/// that can fail (every load, store, branch and jump) responsible for dropping it on the way out,
65/// which costs a stack frame, callee-saved register spills and a reload in the hot path of each of
66/// them, even though the failing path is never taken.
67pub struct BasicEagerInstructions<I>
68where
69    I: Instruction,
70{
71    /// State header, together with the decoded instructions themselves, in a single heap
72    /// allocation.
73    ///
74    /// This is a raw pointer rather than a `Box` on purpose: fetchers point into the same
75    /// allocation, and going through a `Box` would assert unique access to that allocation on
76    /// every use, invalidating pointers that must survive across all of them.
77    state: NonNull<BasicEagerInstructionFetcherState<I>>,
78}
79
80impl<I> fmt::Debug for BasicEagerInstructions<I>
81where
82    I: Instruction,
83{
84    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85        f.debug_struct("BasicEagerInstructions")
86            .field("instructions_len", &self.instructions_len())
87            .field("base_addr", &self.base_addr())
88            .field("return_trap_address", &self.return_trap_address())
89            .finish_non_exhaustive()
90    }
91}
92
93impl<I> Drop for BasicEagerInstructions<I>
94where
95    I: Instruction,
96{
97    fn drop(&mut self) {
98        let layout = Self::allocation_layout(self.instructions_len());
99
100        // SAFETY: Allocated with the global allocator using exactly this layout, and this is the
101        // only owner of the allocation
102        unsafe {
103            dealloc(self.state.as_ptr().cast::<u8>(), layout);
104        }
105    }
106}
107
108impl<I> BasicEagerInstructions<I>
109where
110    I: Instruction,
111{
112    /// Byte offset of the decoded instructions from the start of the allocation that
113    /// [`Self::state`] points at
114    const INSTRUCTIONS_OFFSET: usize =
115        size_of::<BasicEagerInstructionFetcherState<I>>().next_multiple_of(align_of::<I>());
116    /// Bytes of guest code that one slot of the decoded stream corresponds to.
117    ///
118    /// This is what turns a guest address into a position within the decoded stream and back, so
119    /// an instruction set whose slot is not a whole number of alignment steps has no such mapping
120    /// and is refused right here, at compile time.
121    const GUEST_BYTES_PER_SLOT: usize = {
122        assert!(
123            size_of::<I>().is_multiple_of(usize::from(I::ALIGNMENT)),
124            "Decoded instruction size must be a multiple of instruction alignment"
125        );
126
127        usize::from(I::ALIGNMENT)
128    };
129    /// Bytes of the decoded stream that one byte of guest code covers
130    const STREAM_BYTES_PER_GUEST_BYTE: usize = size_of::<I>() / Self::GUEST_BYTES_PER_SLOT;
131
132    /// Layout of the allocation holding [`BasicEagerInstructionFetcherState`] followed by
133    /// `instructions_len` decoded instructions
134    fn allocation_layout(instructions_len: usize) -> Layout {
135        let (layout, instructions_offset) = Layout::new::<BasicEagerInstructionFetcherState<I>>()
136            .extend(Layout::array::<I>(instructions_len).expect(
137                "Decoded stream that doesn't fit into the address space can't be allocated \
138                anyway; qed",
139            ))
140            .expect(
141                "Decoded stream that doesn't fit into the address space can't be allocated \
142                anyway; qed",
143            );
144
145        debug_assert_eq!(instructions_offset, Self::INSTRUCTIONS_OFFSET);
146
147        layout.pad_to_align()
148    }
149
150    /// Pointer to the first decoded instruction
151    #[inline(always)]
152    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
153    fn instructions(&self) -> NonNull<I> {
154        // SAFETY: Decoded instructions are stored at this offset of the same allocation as the
155        // state
156        unsafe { self.state.byte_add(Self::INSTRUCTIONS_OFFSET) }.cast::<I>()
157    }
158
159    /// Number of decoded instructions
160    #[inline(always)]
161    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
162    fn instructions_len(&self) -> usize {
163        // SAFETY: State is initialized in the constructor and valid for as long as `self` is
164        unsafe { (*self.state.as_ptr()).instructions_len }
165    }
166
167    /// Guest address that corresponds to the first decoded instruction
168    #[inline(always)]
169    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
170    fn base_addr(&self) -> Address<I> {
171        // SAFETY: State is initialized in the constructor and valid for as long as `self` is
172        unsafe { (*self.state.as_ptr()).base_addr }
173    }
174
175    /// Guest address at which execution stops gracefully
176    #[inline(always)]
177    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
178    fn return_trap_address(&self) -> Address<I> {
179        // SAFETY: State is initialized in the constructor and valid for as long as `self` is
180        unsafe { (*self.state.as_ptr()).return_trap_address }
181    }
182
183    /// Create a fetcher positioned at the instruction that guest address `pc` corresponds to
184    ///
185    /// # Safety
186    /// `pc` must be the address of one of the instructions [`Self::decode()`] was given, meaning
187    /// it is within `base_addr..base_addr + instructions.len()` and is a multiple of
188    /// [`Instruction::ALIGNMENT`], with `base_addr` and `instructions` being what that call
189    /// received.
190    #[inline(always)]
191    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
192    pub unsafe fn fetcher(&self, pc: Address<I>) -> BasicEagerInstructionFetcher<'_, I> {
193        const {
194            // When fetcher is used with threaded dispatch, it must fit into two argument registers
195            // to be passed through tail calls
196            assert!(
197                size_of::<BasicEagerInstructionFetcher<'_, I>>() == size_of::<NonNull<I>>() * 2,
198                "`BasicEagerInstructionFetcher` must be two pointers large"
199            );
200            // Drop glue on the fetcher would force a stack frame into every fallible handler, see
201            // `BasicEagerInstructions` for details
202            assert!(
203                !mem::needs_drop::<BasicEagerInstructionFetcher<'_, I>>(),
204                "`BasicEagerInstructionFetcher` must not have drop glue"
205            );
206        }
207
208        let instruction_offset =
209            (pc.as_u64() - self.base_addr().as_u64()) as usize / Self::GUEST_BYTES_PER_SLOT;
210
211        BasicEagerInstructionFetcher {
212            // SAFETY: Guaranteed by function contract, meaning `instruction_offset` is within
213            // bounds of the decoded stream
214            next_instruction: unsafe { self.instructions().add(instruction_offset) },
215            instructions: self.instructions(),
216            _instructions: PhantomData,
217        }
218    }
219
220    /// Decode `instructions` and create a new instance holding the result.
221    ///
222    /// `base_addr` is the guest address of the first instruction and `return_trap_address` is the
223    /// address at which the interpreter will stop execution (gracefully).
224    ///
225    /// Every [`Instruction::ALIGNMENT`] bytes of guest code own a slot of the decoded stream,
226    /// including, where instructions may be compressed, the second half of a 32-bit instruction,
227    /// which is only ever reached by jumping into the middle of one. Such a slot may or may not
228    /// decode into a valid instruction on its own, and `fallback` is what is stored when it
229    /// doesn't, so it only has to fail when executed (`unimp` is the canonical choice).
230    ///
231    /// # Safety
232    /// Execution of the resulting instruction stream skips the checks that
233    /// [`BasicInstructionFetcher`](super::BasicInstructionFetcher) does, which is where the
234    /// performance comes from. All of the following must hold:
235    /// * The instructions must end with an unconditional jump, so that execution can't fall through
236    ///   past the end of the decoded stream. Instruction fetching does not bounds-check the
237    ///   position, only [`ProgramCounter::set_pc()`] and [`ProgramCounter::try_set_pc_relative()`]
238    ///   do, which means the last instruction must be one that goes through them.
239    /// * `return_trap_address` must not fall inside the instructions. Instruction fetching does not
240    ///   compare against the return trap, so an address inside them would stop execution when
241    ///   jumped to, but not when reached by falling through.
242    /// * `base_addr` must be a multiple of [`Instruction::ALIGNMENT`], since it is the address of
243    ///   the first decoded instruction, and every position within the decoded stream is resolved
244    ///   relative to it.
245    /// * `base_addr + instructions.len()` must not overflow the address space, which is what makes
246    ///   the address of every decoded instruction representable.
247    /// * The memory the program executes with must contain these very instructions at `base_addr`,
248    ///   and the program must not modify them (there is no `Zifencei` support here). The decoded
249    ///   stream is a snapshot taken here, and it is what execution walks, so writes into the code
250    ///   region are not reflected in what is executed.
251    pub unsafe fn decode(
252        instructions: &[u8],
253        fallback: I,
254        return_trap_address: Address<I>,
255        base_addr: Address<I>,
256    ) -> Self {
257        // Exactly as many slots as there are whole alignment steps of guest code, trailing bytes
258        // that do not make up one have nothing to decode into
259        let instructions_len = instructions.len() / Self::GUEST_BYTES_PER_SLOT;
260        let layout = Self::allocation_layout(instructions_len);
261        // SAFETY: The state itself is always there, so the layout has non-zero size
262        let state = unsafe { alloc(layout) }.cast::<BasicEagerInstructionFetcherState<I>>();
263        let Some(state) = NonNull::new(state) else {
264            handle_alloc_error(layout);
265        };
266
267        // SAFETY: Freshly allocated for exactly this type, correctly aligned
268        unsafe {
269            state.write(BasicEagerInstructionFetcherState {
270                instructions_len,
271                // Does not overflow, the layout above was just computed from it
272                instructions_size: instructions_len * size_of::<I>(),
273                base_addr,
274                return_trap_address,
275            });
276        }
277
278        // The decoded instructions are uninitialized until the loop below writes every one of them,
279        // and nothing reads them in between. Instructions are `Copy`, so even dropping the instance
280        // in that state would just deallocate.
281        let instance = Self { state };
282        let decoded_instructions = instance.instructions();
283
284        for slot_index in 0..instructions_len {
285            let offset = slot_index * Self::GUEST_BYTES_PER_SLOT;
286            let instruction = Self::decode_instruction(instructions, offset, fallback);
287
288            // SAFETY: The allocation was made for exactly `instructions_len` instructions, and
289            // this writes each of them once
290            unsafe {
291                decoded_instructions.add(slot_index).write(instruction);
292            }
293        }
294
295        instance
296    }
297
298    /// Decode the instruction that the decoded stream's slot starting `offset` bytes into
299    /// `instructions` holds.
300    ///
301    /// The caller iterates over exactly the slots that whole alignment steps of guest code make up,
302    /// so there is always at least one such step left at `offset`.
303    ///
304    /// This is where all of the decoding lives, so that what remains of [`Self::decode()`] is the
305    /// allocation, which is the only part of it that can't be proven panic-free.
306    #[inline(always)]
307    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
308    fn decode_instruction(instructions: &[u8], offset: usize, fallback: I) -> I {
309        let instruction = match instructions.get(offset..) {
310            Some([byte_0, byte_1, byte_2, byte_3, ..]) => {
311                u32::from_le_bytes([*byte_0, *byte_1, *byte_2, *byte_3])
312            }
313            // Only reachable where instructions may be compressed: the last halfword of guest code
314            // has nothing following it to read, so it is zero-extended into a word, which decodes
315            // only if it is a compressed instruction
316            Some([byte_0, byte_1, ..]) => u32::from_le_bytes([*byte_0, *byte_1, 0, 0]),
317            // Not reachable through the above, and a slot with less than a halfword of guest code
318            // has nothing that could decode anyway
319            _ => {
320                return fallback;
321            }
322        };
323
324        I::try_decode(instruction).unwrap_or(fallback)
325    }
326}
327
328/// Eager instruction fetcher walks instructions that [`BasicEagerInstructions`] decoded upfront.
329///
330/// This is a plain `Copy` cursor without a destructor, see [`BasicEagerInstructions`] for why.
331#[derive(Copy, Clone)]
332#[repr(C)]
333pub struct BasicEagerInstructionFetcher<'a, I>
334where
335    I: Instruction,
336{
337    /// The instruction to be returned by the next [`InstructionFetcher::fetch_instruction()`]
338    /// call.
339    ///
340    /// A pointer rather than an offset helps LLVM with SROA and aliasing analysis, so it can
341    /// retain this in a native register instead of recomputing it from an offset on every
342    /// fetch.
343    next_instruction: NonNull<I>,
344    /// The first decoded instruction, borrowed from [`BasicEagerInstructions`].
345    ///
346    /// This points at the instructions rather than at the state header in front of them because
347    /// the instructions are what every branch and jump measures its target against, while the
348    /// header is read at a constant offset that the addressing mode absorbs. Pointing at the
349    /// header instead costs the taken path of every branch an addition to find the instructions.
350    instructions: NonNull<I>,
351    /// Fetcher borrows the decoded instructions it walks
352    _instructions: PhantomData<&'a BasicEagerInstructions<I>>,
353}
354
355impl<I> fmt::Debug for BasicEagerInstructionFetcher<'_, I>
356where
357    I: Instruction,
358{
359    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
360        f.debug_struct("BasicEagerInstructionFetcher")
361            .field("next_instruction", &self.next_instruction)
362            .field("instructions_len", &self.instructions_len())
363            .field("base_addr", &self.base_addr())
364            .field("return_trap_address", &self.return_trap_address())
365            .finish_non_exhaustive()
366    }
367}
368
369impl<I, Memory> ProgramCounter<Address<I>, Memory> for BasicEagerInstructionFetcher<'_, I>
370where
371    I: Instruction,
372    Memory: VirtualMemory,
373{
374    #[inline(always)]
375    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
376    fn get_pc(&self) -> Address<I> {
377        let decoded_instruction_byte_offset = self
378            .next_instruction
379            .as_ptr()
380            .addr()
381            .wrapping_sub(self.instructions.as_ptr().addr());
382
383        Address::<I>::truncate_from_u64(
384            self.base_addr().as_u64()
385                + (decoded_instruction_byte_offset
386                    / BasicEagerInstructions::<I>::STREAM_BYTES_PER_GUEST_BYTE)
387                    as u64,
388        )
389    }
390
391    /// Moves within the decoded stream instead of resolving an address and converting it back,
392    /// which is what going through [`Self::set_pc()`] would do.
393    ///
394    /// One comparison and one test are all this needs to recognize every target it cannot resolve:
395    /// one past the end of the decoded stream, a backwards branch that ran off its start, and an
396    /// unaligned one. The return trap sits outside the decoded stream, so a branch to it fails the
397    /// bounds check here too and is answered by [`Self::failed_branch()`] like any other target
398    /// this refuses.
399    #[inline(always)]
400    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
401    unsafe fn try_set_pc_relative(&mut self, instruction_size: u8, offset: i32) -> bool {
402        // Byte offset from the instruction being executed to the branch target. The program counter
403        // is advanced during instruction fetching, so that instruction starts `instruction_size`
404        // bytes back.
405        let offset = (offset as isize).wrapping_sub(isize::from(instruction_size));
406        // Every alignment step of guest code owns one decoded instruction, so the target is
407        // reached by moving within the decoded stream
408        let byte_delta =
409            offset * BasicEagerInstructions::<I>::STREAM_BYTES_PER_GUEST_BYTE.cast_signed();
410        // This may land outside the decoded stream (including before its start), which is fine:
411        // `wrapping_byte_offset()` only computes an address, it never dereferences the pointer, and
412        // the bounds check below rejects such a target before it is ever used
413        let new_next_instruction = self
414            .next_instruction
415            .as_ptr()
416            .wrapping_byte_offset(byte_delta);
417        // Stored either way: on the way out it is the target `failed_branch()` reports on, and
418        // until then nothing else is allowed to look at it
419        // SAFETY: A wrapped pointer is never null, and nothing here dereferences it
420        self.next_instruction = unsafe { NonNull::new_unchecked(new_next_instruction) };
421
422        let decoded_instruction_byte_offset = new_next_instruction
423            .addr()
424            .wrapping_sub(self.instructions.as_ptr().addr());
425
426        // A target that does not land on a decoded instruction sits between two guest
427        // instructions, which makes it an unaligned instruction rather than something to round to
428        // the start of one. That rule lives in `set_pc()`, so rather than restating it here, where
429        // it could drift, such a target simply fails to qualify, as does one past the end of the
430        // decoded stream, which a backwards branch that ran off its start wraps around into.
431        decoded_instruction_byte_offset < self.instructions_size()
432            && decoded_instruction_byte_offset.is_multiple_of(size_of::<I>())
433    }
434
435    /// Turns the refused target back into an address and hands it to [`Self::set_pc()`], which is
436    /// where the rules about what is and is not an instruction address live.
437    #[cold]
438    #[inline(never)]
439    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
440    unsafe fn failed_branch(
441        &mut self,
442        memory: &Memory,
443    ) -> Result<ControlFlow<()>, ExecutionError<Address<I>>> {
444        // Signed, because a backwards branch that ran off the start of the decoded stream is
445        // exactly one of the targets that gets here
446        let decoded_instruction_byte_offset = self
447            .next_instruction
448            .as_ptr()
449            .addr()
450            .wrapping_sub(self.instructions.as_ptr().addr())
451            .cast_signed();
452        // Every alignment step of guest code owns one decoded instruction, and the position is
453        // always a whole number of them from the start of the stream, so this is exact
454        let address =
455            Address::<I>::truncate_from_u64(self.base_addr().as_u64().wrapping_add_signed(
456                (decoded_instruction_byte_offset
457                    / BasicEagerInstructions::<I>::STREAM_BYTES_PER_GUEST_BYTE.cast_signed())
458                    as i64,
459            ));
460
461        self.set_pc(memory, address)
462    }
463
464    #[inline]
465    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
466    fn set_pc(
467        &mut self,
468        _memory: &Memory,
469        pc: Address<I>,
470    ) -> Result<ControlFlow<()>, ExecutionError<Address<I>>> {
471        if pc == self.return_trap_address() {
472            cold_path();
473            return Ok(ControlFlow::Break(()));
474        }
475
476        let address = pc.as_u64();
477
478        if !address.is_multiple_of(u64::from(I::ALIGNMENT)) {
479            cold_path();
480            return Err(ExecutionError::UnalignedInstruction {
481                address: PackedAddress::new(pc),
482            });
483        }
484
485        let Some(offset) = address.checked_sub(self.base_addr().as_u64()) else {
486            cold_path();
487            return Err(ExecutionError::OutOfBoundsRead {
488                address: PackedAddress::new(address),
489            });
490        };
491        let instruction_offset =
492            offset as usize / BasicEagerInstructions::<I>::GUEST_BYTES_PER_SLOT;
493
494        if instruction_offset >= self.instructions_len() {
495            cold_path();
496            return Err(VirtualMemoryError::OutOfBoundsRead { address }.into());
497        }
498
499        // SAFETY: `instruction_offset` was just checked to be within bounds of the decoded stream
500        self.next_instruction = unsafe { self.instructions.add(instruction_offset) };
501
502        Ok(ControlFlow::Continue(()))
503    }
504}
505
506impl<I, Memory> InstructionFetcher<I, Memory> for BasicEagerInstructionFetcher<'_, I>
507where
508    I: Instruction,
509    Memory: VirtualMemory,
510{
511    /// Nothing, the decoded stream is where the instruction stays and the position is the
512    /// fetcher's own, so a threaded handler loads each operand it uses straight from there
513    type Peeked = ();
514
515    #[inline(always)]
516    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
517    fn peek_instruction(&mut self, _memory: &Memory) -> FetchInstructionResult<I, ()> {
518        FetchInstructionResult::Instruction(())
519    }
520
521    #[inline(always)]
522    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
523    fn peeked_instruction<'a>(&'a self, (): &'a ()) -> &'a I {
524        // SAFETY: `BasicEagerInstructions::decode()` guarantees that the last instruction is a
525        // jump, which means going through `Self::set_pc()` method does the necessary bounds check,
526        // so the position always points at a decoded instruction, which is borrowed for as long
527        // as `self` is
528        unsafe { self.next_instruction.as_ref() }
529    }
530
531    #[inline(always)]
532    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
533    unsafe fn advance(&mut self, instruction_size: u8) {
534        let byte_advance = usize::from(instruction_size)
535            * BasicEagerInstructions::<I>::STREAM_BYTES_PER_GUEST_BYTE;
536        // Wrapping because nothing here dereferences the pointer: the contract of this method is
537        // what makes the resulting position a decoded instruction, and the bounds check that
538        // matters lives in `set_pc()`
539        // SAFETY: A wrapped pointer is never null, and nothing here dereferences it
540        self.next_instruction = unsafe {
541            NonNull::new_unchecked(
542                self.next_instruction
543                    .as_ptr()
544                    .wrapping_byte_add(byte_advance),
545            )
546        };
547    }
548
549    #[inline(always)]
550    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
551    fn fetch_instruction(&mut self, _memory: &Memory) -> FetchInstructionResult<I> {
552        // SAFETY: The position always points at a decoded instruction, see
553        // `Self::peeked_instruction()`
554        let instruction = unsafe { self.next_instruction.read() };
555
556        // SAFETY: The instruction was just read successfully, and this is the only place that
557        // moves past it
558        unsafe {
559            InstructionFetcher::<I, Memory>::advance(self, instruction.size());
560        }
561
562        FetchInstructionResult::Instruction(instruction)
563    }
564}
565
566impl<I> BasicEagerInstructionFetcher<'_, I>
567where
568    I: Instruction,
569{
570    /// State header that [`BasicEagerInstructions`] keeps in front of the decoded instructions
571    #[inline(always)]
572    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
573    fn state(&self) -> &BasicEagerInstructionFetcherState<I> {
574        // SAFETY: Decoded instructions are stored at this offset from the state in the same
575        // allocation, which `BasicEagerInstructions` initialized and this fetcher borrows for as
576        // long as it is alive
577        unsafe {
578            self.instructions
579                .byte_sub(BasicEagerInstructions::<I>::INSTRUCTIONS_OFFSET)
580                .cast::<BasicEagerInstructionFetcherState<I>>()
581                .as_ref()
582        }
583    }
584
585    /// Number of decoded instructions
586    #[inline(always)]
587    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
588    fn instructions_len(&self) -> usize {
589        self.state().instructions_len
590    }
591
592    /// Size of the decoded instructions in bytes
593    #[inline(always)]
594    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
595    fn instructions_size(&self) -> usize {
596        self.state().instructions_size
597    }
598
599    /// Guest address that corresponds to the first decoded instruction
600    #[inline(always)]
601    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
602    fn base_addr(&self) -> Address<I> {
603        self.state().base_addr
604    }
605
606    /// Guest address at which execution stops gracefully
607    #[inline(always)]
608    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
609    fn return_trap_address(&self) -> Address<I> {
610        self.state().return_trap_address
611    }
612}