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}