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}