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/// Error for [`BasicEagerInstructions::decode()`]
48#[derive(Debug, thiserror::Error)]
49pub enum BasicEagerDecodeError {
50    /// Base address is not a multiple of the instruction alignment, see
51    /// [`BasicEagerInstructions::decode()`]
52    #[error("Base address is not a multiple of the instruction alignment")]
53    UnalignedBaseAddress,
54    /// Address right after the code doesn't fit into the address space, see
55    /// [`BasicEagerInstructions::decode()`]
56    #[error("Address right after the code doesn't fit into the address space")]
57    CodeOverflowsAddressSpace,
58    /// Return trap is inside the code, see [`BasicEagerInstructions::decode()`]
59    #[error("Return trap is inside the code")]
60    ReturnTrapInsideCode,
61    /// Return trap is too far from the base address, see [`BasicEagerInstructions::decode()`]
62    #[error("Return trap is too far from the base address")]
63    ReturnTrapTooFar,
64}
65
66/// Instructions decoded upfront, which [`BasicEagerInstructionFetcher`] walks.
67///
68/// Decoding a program once instead of on every fetch is what makes this faster than
69/// [`BasicInstructionFetcher`](super::BasicInstructionFetcher), at the cost of holding the whole
70/// decoded program in memory and of not seeing writes the program makes to the memory it was
71/// decoded from.
72///
73/// The decoded stream has one slot per [`Instruction::ALIGNMENT`] bytes of guest code, which is
74/// the granularity at which an instruction of this instruction set can start, and what makes an
75/// address a position within the stream and back. With compressed instructions that is a halfword,
76/// so the second half of a 32-bit instruction gets a slot of its own, holding whatever those bytes
77/// decode to, which is only ever reached by jumping into the middle of an instruction. Without
78/// them, no address in the middle of an instruction is aligned in the first place, so there is
79/// nothing to hold a slot for and the stream is half the size.
80///
81/// The decoded stream is followed by one more slot holding the fallback instruction, which is where
82/// execution ends up when it falls through the end of the decoded stream.
83///
84/// Ownership of the allocation lives here rather than in the fetcher because the fetcher is moved
85/// through tail-called instruction handlers by value. A destructor on it would make every handler
86/// that can fail (every load, store, branch and jump) responsible for dropping it on the way out,
87/// which costs a stack frame, callee-saved register spills and a reload in the hot path of each of
88/// them, even though the failing path is never taken.
89pub struct BasicEagerInstructions<I>
90where
91    I: Instruction,
92{
93    /// State header, together with the decoded instructions themselves, in a single heap
94    /// allocation.
95    ///
96    /// This is a raw pointer rather than a `Box` on purpose: fetchers point into the same
97    /// allocation, and going through a `Box` would assert unique access to that allocation on
98    /// every use, invalidating pointers that must survive across all of them.
99    state: NonNull<BasicEagerInstructionFetcherState<I>>,
100}
101
102impl<I> fmt::Debug for BasicEagerInstructions<I>
103where
104    I: Instruction,
105{
106    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107        f.debug_struct("BasicEagerInstructions")
108            .field("instructions_len", &self.instructions_len())
109            .field("base_addr", &self.base_addr())
110            .field("return_trap_address", &self.return_trap_address())
111            .finish_non_exhaustive()
112    }
113}
114
115impl<I> Drop for BasicEagerInstructions<I>
116where
117    I: Instruction,
118{
119    fn drop(&mut self) {
120        let layout = Self::allocation_layout(self.instructions_len());
121
122        // SAFETY: Allocated with the global allocator using exactly this layout, and this is the
123        // only owner of the allocation
124        unsafe {
125            dealloc(self.state.as_ptr().cast::<u8>(), layout);
126        }
127    }
128}
129
130impl<I> BasicEagerInstructions<I>
131where
132    I: Instruction,
133{
134    /// Byte offset of the decoded instructions from the start of the allocation that
135    /// [`Self::state`] points at
136    const INSTRUCTIONS_OFFSET: usize =
137        size_of::<BasicEagerInstructionFetcherState<I>>().next_multiple_of(align_of::<I>());
138    /// Bytes of guest code that one slot of the decoded stream corresponds to.
139    ///
140    /// This is what turns a guest address into a position within the decoded stream and back, so
141    /// an instruction set whose slot is not a whole number of alignment steps has no such mapping
142    /// and is refused right here, at compile time.
143    const GUEST_BYTES_PER_SLOT: usize = {
144        assert!(
145            size_of::<I>().is_multiple_of(usize::from(I::ALIGNMENT)),
146            "Decoded instruction size must be a multiple of instruction alignment"
147        );
148
149        usize::from(I::ALIGNMENT)
150    };
151    /// Bytes of the decoded stream that one byte of guest code covers
152    const STREAM_BYTES_PER_GUEST_BYTE: usize = size_of::<I>() / Self::GUEST_BYTES_PER_SLOT;
153
154    /// Layout of the allocation holding [`BasicEagerInstructionFetcherState`] followed by
155    /// `instructions_len` decoded instructions and the fallback instruction after them
156    fn allocation_layout(instructions_len: usize) -> Layout {
157        let (layout, instructions_offset) = Layout::new::<BasicEagerInstructionFetcherState<I>>()
158            // `+ 1` doesn't overflow, there are fewer slots than bytes of guest code, whose size
159            // fits into `isize`
160            .extend(Layout::array::<I>(instructions_len + 1).expect(
161                "Decoded stream that doesn't fit into the address space can't be allocated \
162                anyway; qed",
163            ))
164            .expect(
165                "Decoded stream that doesn't fit into the address space can't be allocated \
166                anyway; qed",
167            );
168
169        debug_assert_eq!(instructions_offset, Self::INSTRUCTIONS_OFFSET);
170
171        layout.pad_to_align()
172    }
173
174    /// Pointer to the first decoded instruction
175    #[inline(always)]
176    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
177    fn instructions(&self) -> NonNull<I> {
178        // SAFETY: Decoded instructions are stored at this offset of the same allocation as the
179        // state
180        unsafe { self.state.byte_add(Self::INSTRUCTIONS_OFFSET) }.cast::<I>()
181    }
182
183    /// State header in front of the decoded instructions
184    #[inline(always)]
185    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
186    fn state(&self) -> &BasicEagerInstructionFetcherState<I> {
187        // SAFETY: State is initialized in the constructor, never modified afterward, and valid for
188        // as long as `self` is
189        unsafe { self.state.as_ref() }
190    }
191
192    /// Number of decoded instructions
193    #[inline(always)]
194    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
195    fn instructions_len(&self) -> usize {
196        self.state().instructions_len
197    }
198
199    /// Guest address that corresponds to the first decoded instruction
200    #[inline(always)]
201    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
202    fn base_addr(&self) -> Address<I> {
203        self.state().base_addr
204    }
205
206    /// Guest address at which execution stops gracefully
207    #[inline(always)]
208    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
209    fn return_trap_address(&self) -> Address<I> {
210        self.state().return_trap_address
211    }
212
213    /// Create a fetcher positioned at the instruction that guest address `pc` corresponds to.
214    ///
215    /// Returns `None` unless `pc` is the address of one of the instructions [`Self::decode()`] was
216    /// given, meaning it is within `base_addr..base_addr + instructions.len()` and is a multiple of
217    /// [`Instruction::ALIGNMENT`].
218    #[inline]
219    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
220    pub fn fetcher(&self, pc: Address<I>) -> Option<BasicEagerInstructionFetcher<'_, I>> {
221        const {
222            // When fetcher is used with threaded dispatch, it must fit into two argument registers
223            // to be passed through tail calls
224            assert!(
225                size_of::<BasicEagerInstructionFetcher<'_, I>>() == size_of::<NonNull<I>>() * 2,
226                "`BasicEagerInstructionFetcher` must be two pointers large"
227            );
228            // Drop glue on the fetcher would force a stack frame into every fallible handler, see
229            // `BasicEagerInstructions` for details
230            assert!(
231                !mem::needs_drop::<BasicEagerInstructionFetcher<'_, I>>(),
232                "`BasicEagerInstructionFetcher` must not have drop glue"
233            );
234        }
235
236        let address = pc.as_u64();
237
238        if !address.is_multiple_of(u64::from(I::ALIGNMENT)) {
239            cold_path();
240            return None;
241        }
242
243        let offset = usize::try_from(address.checked_sub(self.base_addr().as_u64())?).ok()?;
244        let instruction_offset = offset / Self::GUEST_BYTES_PER_SLOT;
245
246        if instruction_offset >= self.instructions_len() {
247            cold_path();
248            return None;
249        }
250
251        Some(BasicEagerInstructionFetcher {
252            // SAFETY: `instruction_offset` was just checked to be within bounds of the decoded
253            // stream
254            next_instruction: unsafe { self.instructions().add(instruction_offset) }.as_ptr(),
255            instructions: self.instructions(),
256            _instructions: PhantomData,
257        })
258    }
259
260    /// Decode `instructions` and create a new instance holding the result.
261    ///
262    /// Execution runs the frozen set of instructions decoded here, before execution starts.
263    ///
264    /// `base_addr` is the guest address of the first instruction and `return_trap_address` is the
265    /// address at which the interpreter will stop execution (gracefully).
266    ///
267    /// Every [`Instruction::ALIGNMENT`] bytes of guest code own a slot of the decoded stream,
268    /// including, where instructions may be compressed, the second half of a 32-bit instruction,
269    /// which is only ever reached by jumping into the middle of one. Such a slot may or may not
270    /// decode into a valid instruction on its own, and `fallback` is what is stored when it
271    /// doesn't.
272    ///
273    /// Execution moves past an instruction by its [`Instruction::size()`], so an instruction whose
274    /// size is zero, isn't a multiple of [`Instruction::ALIGNMENT`] or is larger than the guest
275    /// code left is stored as `fallback` too.
276    ///
277    /// `fallback` is also stored right after the last slot, where execution ends up when it falls
278    /// through the end of the decoded stream.
279    ///
280    /// Returns an error in these cases:
281    /// * [`BasicEagerDecodeError::UnalignedBaseAddress`] when `base_addr` is not a multiple of
282    ///   [`Instruction::ALIGNMENT`]. It is the address of the first decoded instruction, and every
283    ///   position within the decoded stream is resolved relative to it.
284    /// * [`BasicEagerDecodeError::CodeOverflowsAddressSpace`] when `base_addr + instructions.len()`
285    ///   is larger than the largest guest address, which is `u32::MAX` for a 32-bit guest. This
286    ///   keeps the address of every decoded instruction representable, and the address right after
287    ///   the code too.
288    /// * [`BasicEagerDecodeError::ReturnTrapInsideCode`] when `return_trap_address` is within
289    ///   `base_addr..base_addr + instructions.len()`. Relative jumps and branches, and falling
290    ///   through, reach a decoded instruction without comparing its address against the return
291    ///   trap. Only an absolute jump would stop at a trap there. The address right after the code
292    ///   is accepted. Every jump or branch to it stops at the trap, and falling through the end of
293    ///   the code runs `fallback`, wherever the trap is.
294    /// * [`BasicEagerDecodeError::ReturnTrapTooFar`] when `return_trap_address` is too far from
295    ///   `base_addr`, as explained below.
296    ///
297    /// Execution that stops at the return trap leaves the fetcher where the trap would be in the
298    /// decoded stream, and [`ProgramCounter::get_pc()`] turns that position back into the trap
299    /// address. The position is the trap's distance from `base_addr` in guest bytes, multiplied by
300    /// the number of decoded stream bytes per guest byte. That product must fit into `isize`. The
301    /// distance is the trap address minus `base_addr`, computed in 64 bits and read as a signed
302    /// number. For a 64-bit guest, that is the shorter way around its address space, so a trap near
303    /// the top of it is close to code near the bottom. For a 32-bit guest, whose addresses are
304    /// zero-extended first, it is the plain difference.
305    ///
306    /// # Safety
307    /// Execution of the resulting instruction stream skips the checks that
308    /// [`BasicInstructionFetcher`](super::BasicInstructionFetcher) does, which is where the
309    /// performance comes from. Both of the following must hold:
310    /// * `fallback` must never continue to the next instruction when executed (`unimp`, which
311    ///   always fails, is the canonical choice). Instruction fetching does not bounds-check the
312    ///   position, only [`ProgramCounter::set_pc()`] and [`ProgramCounter::try_set_pc_relative()`]
313    ///   do, and falling through the end of the decoded stream lands on `fallback`, which must not
314    ///   fall through any further.
315    /// * [`Instruction::size()`] must return the same value every time for the same instruction.
316    ///   Decoding checks the size of each instruction once, and execution moves past the
317    ///   instruction by whatever size it returns later, without checking it again.
318    pub unsafe fn decode(
319        instructions: &[u8],
320        fallback: I,
321        return_trap_address: Address<I>,
322        base_addr: Address<I>,
323    ) -> Result<Self, BasicEagerDecodeError> {
324        if !base_addr.as_u64().is_multiple_of(u64::from(I::ALIGNMENT)) {
325            return Err(BasicEagerDecodeError::UnalignedBaseAddress);
326        }
327
328        // The largest guest address, which is `u32::MAX` for a 32-bit guest
329        let max_address = Address::<I>::truncate_from_u64(u64::MAX).as_u64();
330        // Lossless on the 64-bit hosts this is compiled for
331        let code_size = instructions.len() as u64;
332        // `base_addr` is a guest address too, so the subtraction doesn't underflow
333        if code_size > max_address - base_addr.as_u64() {
334            return Err(BasicEagerDecodeError::CodeOverflowsAddressSpace);
335        }
336
337        // A trap below the code wraps around to an offset larger than any code size that passed
338        // the check above
339        let return_trap_offset = return_trap_address
340            .as_u64()
341            .wrapping_sub(base_addr.as_u64());
342        if return_trap_offset < code_size {
343            return Err(BasicEagerDecodeError::ReturnTrapInsideCode);
344        }
345
346        // Where the return trap would be in the decoded stream must fit into `isize`, see above
347        if (return_trap_offset.cast_signed() as isize)
348            .checked_mul(Self::STREAM_BYTES_PER_GUEST_BYTE.cast_signed())
349            .is_none()
350        {
351            return Err(BasicEagerDecodeError::ReturnTrapTooFar);
352        }
353
354        // Exactly as many slots as there are whole alignment steps of guest code, trailing bytes
355        // that do not make up one have nothing to decode into
356        let instructions_len = instructions.len() / Self::GUEST_BYTES_PER_SLOT;
357        let layout = Self::allocation_layout(instructions_len);
358        // SAFETY: The state itself is always there, so the layout has non-zero size
359        let state = unsafe { alloc(layout) }.cast::<BasicEagerInstructionFetcherState<I>>();
360        let Some(state) = NonNull::new(state) else {
361            handle_alloc_error(layout);
362        };
363
364        // SAFETY: Freshly allocated for exactly this type, correctly aligned
365        unsafe {
366            state.write(BasicEagerInstructionFetcherState {
367                instructions_len,
368                // Does not overflow, the layout above was just computed from it
369                instructions_size: instructions_len * size_of::<I>(),
370                base_addr,
371                return_trap_address,
372            });
373        }
374
375        // The decoded instructions are uninitialized until the loop below writes every one of them,
376        // and nothing reads them in between. Instructions are `Copy`, so even dropping the instance
377        // in that state would just deallocate.
378        let instance = Self { state };
379        let decoded_instructions = instance.instructions();
380
381        for slot_index in 0..instructions_len {
382            let offset = slot_index * Self::GUEST_BYTES_PER_SLOT;
383            let instruction = Self::decode_instruction(instructions, offset, fallback);
384
385            // SAFETY: The allocation was made for `instructions_len` instructions followed by the
386            // fallback one, and this writes each of the former once
387            unsafe {
388                decoded_instructions.add(slot_index).write(instruction);
389            }
390        }
391
392        // SAFETY: The allocation was made for `instructions_len` instructions followed by the
393        // fallback one, and this writes the latter
394        unsafe {
395            decoded_instructions.add(instructions_len).write(fallback);
396        }
397
398        Ok(instance)
399    }
400
401    /// Decode the instruction that the decoded stream's slot starting `offset` bytes into
402    /// `instructions` holds.
403    ///
404    /// The caller iterates over exactly the slots that whole alignment steps of guest code make up,
405    /// so there is always at least one such step left at `offset`.
406    ///
407    /// This is where all of the decoding lives, so that what remains of [`Self::decode()`] is the
408    /// allocation, which is the only part of it that can't be proven panic-free.
409    #[inline(always)]
410    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
411    fn decode_instruction(instructions: &[u8], offset: usize, fallback: I) -> I {
412        let Some(guest_code_left) = instructions.get(offset..) else {
413            // Not reachable through the above
414            return fallback;
415        };
416        let instruction = match guest_code_left {
417            [byte_0, byte_1, byte_2, byte_3, ..] => {
418                u32::from_le_bytes([*byte_0, *byte_1, *byte_2, *byte_3])
419            }
420            // Only reachable where instructions may be compressed: the last halfword of guest code
421            // has nothing following it to read, so it is zero-extended into a word
422            [byte_0, byte_1, ..] => u32::from_le_bytes([*byte_0, *byte_1, 0, 0]),
423            // Not reachable through the above, and a slot with less than a halfword of guest code
424            // has nothing that could decode anyway
425            _ => {
426                return fallback;
427            }
428        };
429
430        I::try_decode(instruction)
431            .filter(|instruction| {
432                let size = usize::from(instruction.size());
433                // Execution moves past an instruction by its size, so the size must be a whole
434                // number of slots, and more than zero. Otherwise execution would continue between
435                // two slots, or run the same instruction forever. An instruction that doesn't fit
436                // into the guest code left would make execution skip the fallback instruction
437                // after the decoded stream when falling through it.
438                size != 0
439                    && size.is_multiple_of(Self::GUEST_BYTES_PER_SLOT)
440                    && size <= guest_code_left.len()
441            })
442            .unwrap_or(fallback)
443    }
444}
445
446/// Eager instruction fetcher walks instructions that [`BasicEagerInstructions`] decoded upfront.
447///
448/// This is a plain `Copy` cursor without a destructor, see [`BasicEagerInstructions`] for why.
449///
450/// The fallback instruction after the decoded stream has no address of its own, so
451/// [`ProgramCounter::set_pc()`] refuses the address right after the decoded stream as out of
452/// bounds, unless the return trap is there. Execution that starts again at that address therefore
453/// fails with [`ExecutionError::OutOfBoundsRead`] or stops at the trap, instead of running the
454/// fallback instruction.
455#[derive(Copy, Clone)]
456#[repr(C)]
457pub struct BasicEagerInstructionFetcher<'a, I>
458where
459    I: Instruction,
460{
461    /// The instruction to be returned by the next [`InstructionFetcher::fetch_instruction()`]
462    /// call.
463    ///
464    /// A pointer rather than an offset helps LLVM with SROA and aliasing analysis, so it can
465    /// retain this in a native register instead of recomputing it from an offset on every
466    /// fetch.
467    ///
468    /// Not a [`NonNull`], because the target a branch was refused for is stored here as well, and
469    /// a backwards branch can make that zero when the decoded stream is at a low address.
470    next_instruction: *const I,
471    /// The first decoded instruction, borrowed from [`BasicEagerInstructions`].
472    ///
473    /// This points at the instructions rather than at the state header in front of them because
474    /// the instructions are what every branch and jump measures its target against, while the
475    /// header is read at a constant offset that the addressing mode absorbs. Pointing at the
476    /// header instead costs the taken path of every branch an addition to find the instructions.
477    instructions: NonNull<I>,
478    /// Fetcher borrows the decoded instructions it walks
479    _instructions: PhantomData<&'a BasicEagerInstructions<I>>,
480}
481
482impl<I> fmt::Debug for BasicEagerInstructionFetcher<'_, I>
483where
484    I: Instruction,
485{
486    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
487        f.debug_struct("BasicEagerInstructionFetcher")
488            .field("next_instruction", &self.next_instruction)
489            .field("instructions_len", &self.instructions_len())
490            .field("base_addr", &self.base_addr())
491            .field("return_trap_address", &self.return_trap_address())
492            .finish_non_exhaustive()
493    }
494}
495
496impl<I, Memory> ProgramCounter<Address<I>, Memory> for BasicEagerInstructionFetcher<'_, I>
497where
498    I: Instruction,
499    Memory: VirtualMemory,
500{
501    #[inline(always)]
502    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
503    fn get_pc(&self) -> Address<I> {
504        // Signed, because a branch refused for the return trap below the decoded stream leaves the
505        // position there, and that is the program counter execution stops with
506        let decoded_instruction_byte_offset = self
507            .next_instruction
508            .addr()
509            .wrapping_sub(self.instructions.as_ptr().addr())
510            .cast_signed();
511        // The position is a whole number of decoded instructions from the start of the stream, so
512        // the division is exact, which makes it an arithmetic shift where possible
513        let guest_byte_offset =
514            if BasicEagerInstructions::<I>::STREAM_BYTES_PER_GUEST_BYTE.is_power_of_two() {
515                decoded_instruction_byte_offset
516                    >> BasicEagerInstructions::<I>::STREAM_BYTES_PER_GUEST_BYTE.trailing_zeros()
517            } else {
518                decoded_instruction_byte_offset
519                    / BasicEagerInstructions::<I>::STREAM_BYTES_PER_GUEST_BYTE.cast_signed()
520            };
521
522        Address::<I>::truncate_from_u64(
523            self.base_addr()
524                .as_u64()
525                .wrapping_add_signed(guest_byte_offset as i64),
526        )
527    }
528
529    /// Moves within the decoded stream instead of resolving an address and converting it back,
530    /// which is what going through [`Self::set_pc()`] would do.
531    ///
532    /// One comparison and one test are all this needs to recognize every target it cannot resolve:
533    /// one past the end of the decoded stream, a backwards branch that ran off its start, and an
534    /// unaligned one. [`BasicEagerInstructions::decode()`] refuses a return trap inside the code,
535    /// so a branch to the trap fails the bounds check here too and is answered by
536    /// [`Self::failed_branch()`] like any other target this refuses.
537    #[inline(always)]
538    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
539    unsafe fn try_set_pc_relative(&mut self, instruction_size: u8, offset: i32) -> bool {
540        // Byte offset from the instruction being executed to the branch target. The program counter
541        // is advanced during instruction fetching, so that instruction starts `instruction_size`
542        // bytes back.
543        let offset = (offset as isize).wrapping_sub(isize::from(instruction_size));
544        // Every alignment step of guest code owns one decoded instruction, so the target is
545        // reached by moving within the decoded stream
546        let byte_delta =
547            offset * BasicEagerInstructions::<I>::STREAM_BYTES_PER_GUEST_BYTE.cast_signed();
548        // This may land outside the decoded stream (including before its start), which is fine:
549        // `wrapping_byte_offset()` only computes an address, it never dereferences the pointer, and
550        // the bounds check below rejects such a target before it is ever used
551        let new_next_instruction = self.next_instruction.wrapping_byte_offset(byte_delta);
552        // Stored either way: on the way out it is the target `failed_branch()` reports on, and
553        // until then nothing else is allowed to look at it
554        self.next_instruction = new_next_instruction;
555
556        let decoded_instruction_byte_offset = new_next_instruction
557            .addr()
558            .wrapping_sub(self.instructions.as_ptr().addr());
559
560        // A target that does not land on a decoded instruction sits between two guest
561        // instructions, which makes it an unaligned instruction rather than something to round to
562        // the start of one. That rule lives in `set_pc()`, so rather than restating it here, where
563        // it could drift, such a target simply fails to qualify, as does one past the end of the
564        // decoded stream, which a backwards branch that ran off its start wraps around into.
565        decoded_instruction_byte_offset < self.instructions_size()
566            && decoded_instruction_byte_offset.is_multiple_of(size_of::<I>())
567    }
568
569    /// Turns the refused target back into an address and hands it to [`Self::set_pc()`], which is
570    /// where the rules about what is and is not an instruction address live.
571    #[cold]
572    #[inline(never)]
573    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
574    unsafe fn failed_branch(
575        &mut self,
576        memory: &Memory,
577    ) -> Result<ControlFlow<()>, ExecutionError<Address<I>>> {
578        // Signed, because a backwards branch that ran off the start of the decoded stream is
579        // exactly one of the targets that gets here
580        let decoded_instruction_byte_offset = self
581            .next_instruction
582            .addr()
583            .wrapping_sub(self.instructions.as_ptr().addr())
584            .cast_signed();
585        // Every alignment step of guest code owns one decoded instruction, and the position is
586        // always a whole number of them from the start of the stream, so this is exact
587        let address =
588            Address::<I>::truncate_from_u64(self.base_addr().as_u64().wrapping_add_signed(
589                (decoded_instruction_byte_offset
590                    / BasicEagerInstructions::<I>::STREAM_BYTES_PER_GUEST_BYTE.cast_signed())
591                    as i64,
592            ));
593
594        self.set_pc(memory, address)
595    }
596
597    #[inline]
598    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
599    fn set_pc(
600        &mut self,
601        _memory: &Memory,
602        pc: Address<I>,
603    ) -> Result<ControlFlow<()>, ExecutionError<Address<I>>> {
604        if pc == self.return_trap_address() {
605            cold_path();
606            // Move the position to where the trap would be in the decoded stream. `get_pc()` reads
607            // the program counter from the position, so it then reports the trap address. The
608            // position ends up outside the decoded stream, the same as after a refused branch.
609            let guest_byte_offset = pc
610                .as_u64()
611                .wrapping_sub(self.base_addr().as_u64())
612                .cast_signed() as isize;
613            // Doesn't overflow, `BasicEagerInstructions::decode()` checked this for the return trap
614            let byte_delta = guest_byte_offset
615                * BasicEagerInstructions::<I>::STREAM_BYTES_PER_GUEST_BYTE.cast_signed();
616            // Only computes an address, which may be outside the allocation, nothing reads from it
617            self.next_instruction = self.instructions.as_ptr().wrapping_byte_offset(byte_delta);
618            return Ok(ControlFlow::Break(()));
619        }
620
621        let address = pc.as_u64();
622
623        if !address.is_multiple_of(u64::from(I::ALIGNMENT)) {
624            cold_path();
625            return Err(ExecutionError::UnalignedInstruction {
626                address: PackedAddress::new(pc),
627            });
628        }
629
630        let Some(offset) = address.checked_sub(self.base_addr().as_u64()) else {
631            cold_path();
632            return Err(ExecutionError::OutOfBoundsRead {
633                address: PackedAddress::new(address),
634            });
635        };
636        // An offset that doesn't fit into `usize` is past the end of the decoded stream too
637        let Ok(offset) = usize::try_from(offset) else {
638            cold_path();
639            return Err(VirtualMemoryError::OutOfBoundsRead { address }.into());
640        };
641        let instruction_offset = offset / BasicEagerInstructions::<I>::GUEST_BYTES_PER_SLOT;
642
643        if instruction_offset >= self.instructions_len() {
644            cold_path();
645            return Err(VirtualMemoryError::OutOfBoundsRead { address }.into());
646        }
647
648        // SAFETY: `instruction_offset` was just checked to be within bounds of the decoded stream
649        self.next_instruction = unsafe { self.instructions.add(instruction_offset) }.as_ptr();
650
651        Ok(ControlFlow::Continue(()))
652    }
653}
654
655// SAFETY: Fetching reads at the position without checks, which is sound wherever fetching is
656// allowed, see `Self::peeked_instruction()`. The position is a plain pointer that only methods
657// taking `&mut self` write. `peeked_instruction()` reads the decoded instruction at the position,
658// and decoded instructions don't change while the fetcher borrows them.
659unsafe impl<I, Memory> InstructionFetcher<I, Memory> for BasicEagerInstructionFetcher<'_, I>
660where
661    I: Instruction,
662    Memory: VirtualMemory,
663{
664    /// Nothing, the decoded stream is where the instruction stays and the position is the
665    /// fetcher's own, so a threaded handler loads each operand it uses straight from there
666    type Peeked = ();
667
668    #[inline(always)]
669    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
670    unsafe fn peek_instruction(&mut self, _memory: &Memory) -> FetchInstructionResult<I, ()> {
671        FetchInstructionResult::Instruction(())
672    }
673
674    #[inline(always)]
675    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
676    unsafe fn peeked_instruction<'a>(&'a self, (): &'a ()) -> &'a I {
677        // SAFETY: The caller peeked with fetching allowed, and the position has not moved since.
678        // Wherever fetching is allowed, the position is at a decoded instruction or at the
679        // fallback instruction after them. `BasicEagerInstructions::fetcher()`, `Self::set_pc()`
680        // and `Self::try_set_pc_relative()` only accept positions within the decoded stream.
681        // Moving past an instruction that continues lands on a decoded instruction or on the
682        // fallback instruction after them. `BasicEagerInstructions::decode()` only keeps
683        // instructions whose size is a whole number of slots and fits into the guest code left,
684        // and its caller promises that the size doesn't change later. The fallback instruction
685        // never continues. The decoded instructions are borrowed for as long as `self` is.
686        unsafe { &*self.next_instruction }
687    }
688
689    #[inline(always)]
690    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
691    unsafe fn advance(&mut self, instruction_size: u8) {
692        let byte_advance = usize::from(instruction_size)
693            * BasicEagerInstructions::<I>::STREAM_BYTES_PER_GUEST_BYTE;
694        // Wrapping because nothing here dereferences the pointer: the contracts of this method and
695        // of `BasicEagerInstructions::decode()` are what make the resulting position a decoded
696        // instruction, and the bounds check that matters lives in `set_pc()`
697        self.next_instruction = self.next_instruction.wrapping_byte_add(byte_advance);
698    }
699
700    #[inline(always)]
701    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
702    unsafe fn fetch_instruction(&mut self, _memory: &Memory) -> FetchInstructionResult<I> {
703        // SAFETY: The caller makes sure fetching is allowed, so the position is at a decoded
704        // instruction, see `Self::peeked_instruction()`
705        let instruction = unsafe { self.next_instruction.read() };
706
707        // SAFETY: The instruction was just read successfully, and this is the only place that
708        // moves past it
709        unsafe {
710            InstructionFetcher::<I, Memory>::advance(self, instruction.size());
711        }
712
713        FetchInstructionResult::Instruction(instruction)
714    }
715}
716
717impl<I> BasicEagerInstructionFetcher<'_, I>
718where
719    I: Instruction,
720{
721    /// State header that [`BasicEagerInstructions`] keeps in front of the decoded instructions
722    #[inline(always)]
723    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
724    fn state(&self) -> &BasicEagerInstructionFetcherState<I> {
725        // SAFETY: Decoded instructions are stored at this offset from the state in the same
726        // allocation, which `BasicEagerInstructions` initialized and this fetcher borrows for as
727        // long as it is alive
728        unsafe {
729            self.instructions
730                .byte_sub(BasicEagerInstructions::<I>::INSTRUCTIONS_OFFSET)
731                .cast::<BasicEagerInstructionFetcherState<I>>()
732                .as_ref()
733        }
734    }
735
736    /// Number of decoded instructions
737    #[inline(always)]
738    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
739    fn instructions_len(&self) -> usize {
740        self.state().instructions_len
741    }
742
743    /// Size of the decoded instructions in bytes
744    #[inline(always)]
745    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
746    fn instructions_size(&self) -> usize {
747        self.state().instructions_size
748    }
749
750    /// Guest address that corresponds to the first decoded instruction
751    #[inline(always)]
752    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
753    fn base_addr(&self) -> Address<I> {
754        self.state().base_addr
755    }
756
757    /// Guest address at which execution stops gracefully
758    #[inline(always)]
759    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
760    fn return_trap_address(&self) -> Address<I> {
761        self.state().return_trap_address
762    }
763}