pub struct BasicEagerInstructions<I>where
I: Instruction,{ /* private fields */ }Expand description
Instructions decoded upfront, which BasicEagerInstructionFetcher walks.
Decoding a program once instead of on every fetch is what makes this faster than
BasicInstructionFetcher, at the cost of holding the whole
decoded program in memory and of not seeing writes the program makes to the memory it was
decoded from.
The decoded stream has one slot per Instruction::ALIGNMENT bytes of guest code, which is
the granularity at which an instruction of this instruction set can start, and what makes an
address a position within the stream and back. With compressed instructions that is a halfword,
so the second half of a 32-bit instruction gets a slot of its own, holding whatever those bytes
decode to, which is only ever reached by jumping into the middle of an instruction. Without
them, no address in the middle of an instruction is aligned in the first place, so there is
nothing to hold a slot for and the stream is half the size.
The decoded stream is followed by one more slot holding the fallback instruction, which is where execution ends up when it falls through the end of the decoded stream.
Ownership of the allocation lives here rather than in the fetcher because the fetcher is moved through tail-called instruction handlers by value. A destructor on it would make every handler that can fail (every load, store, branch and jump) responsible for dropping it on the way out, which costs a stack frame, callee-saved register spills and a reload in the hot path of each of them, even though the failing path is never taken.
Implementations§
Source§impl<I> BasicEagerInstructions<I>where
I: Instruction,
impl<I> BasicEagerInstructions<I>where
I: Instruction,
Sourcepub fn fetcher(
&self,
pc: <<<I as Instruction>::Hart as HartConfig>::Reg as Register>::Type,
) -> Option<BasicEagerInstructionFetcher<'_, I>>
pub fn fetcher( &self, pc: <<<I as Instruction>::Hart as HartConfig>::Reg as Register>::Type, ) -> Option<BasicEagerInstructionFetcher<'_, I>>
Create a fetcher positioned at the instruction that guest address pc corresponds to.
Returns None unless pc is the address of one of the instructions Self::decode() was
given, meaning it is within base_addr..base_addr + instructions.len() and is a multiple of
Instruction::ALIGNMENT.
Sourcepub unsafe fn decode(
instructions: &[u8],
fallback: I,
return_trap_address: <<<I as Instruction>::Hart as HartConfig>::Reg as Register>::Type,
base_addr: <<<I as Instruction>::Hart as HartConfig>::Reg as Register>::Type,
) -> Result<Self, BasicEagerDecodeError>
pub unsafe fn decode( instructions: &[u8], fallback: I, return_trap_address: <<<I as Instruction>::Hart as HartConfig>::Reg as Register>::Type, base_addr: <<<I as Instruction>::Hart as HartConfig>::Reg as Register>::Type, ) -> Result<Self, BasicEagerDecodeError>
Decode instructions and create a new instance holding the result.
Execution runs the frozen set of instructions decoded here, before execution starts.
base_addr is the guest address of the first instruction and return_trap_address is the
address at which the interpreter will stop execution (gracefully).
Every Instruction::ALIGNMENT bytes of guest code own a slot of the decoded stream,
including, where instructions may be compressed, the second half of a 32-bit instruction,
which is only ever reached by jumping into the middle of one. Such a slot may or may not
decode into a valid instruction on its own, and fallback is what is stored when it
doesn’t.
Execution moves past an instruction by its Instruction::size(), so an instruction whose
size is zero, isn’t a multiple of Instruction::ALIGNMENT or is larger than the guest
code left is stored as fallback too.
fallback is also stored right after the last slot, where execution ends up when it falls
through the end of the decoded stream.
Returns an error in these cases:
BasicEagerDecodeError::UnalignedBaseAddresswhenbase_addris not a multiple ofInstruction::ALIGNMENT. It is the address of the first decoded instruction, and every position within the decoded stream is resolved relative to it.BasicEagerDecodeError::CodeOverflowsAddressSpacewhenbase_addr + instructions.len()is larger than the largest guest address, which isu32::MAXfor a 32-bit guest. This keeps the address of every decoded instruction representable, and the address right after the code too.BasicEagerDecodeError::ReturnTrapInsideCodewhenreturn_trap_addressis withinbase_addr..base_addr + instructions.len(). Relative jumps and branches, and falling through, reach a decoded instruction without comparing its address against the return trap. Only an absolute jump would stop at a trap there. The address right after the code is accepted. Every jump or branch to it stops at the trap, and falling through the end of the code runsfallback, wherever the trap is.BasicEagerDecodeError::ReturnTrapTooFarwhenreturn_trap_addressis too far frombase_addr, as explained below.
Execution that stops at the return trap leaves the fetcher where the trap would be in the
decoded stream, and ProgramCounter::get_pc() turns that position back into the trap
address. The position is the trap’s distance from base_addr in guest bytes, multiplied by
the number of decoded stream bytes per guest byte. That product must fit into isize. The
distance is the trap address minus base_addr, computed in 64 bits and read as a signed
number. For a 64-bit guest, that is the shorter way around its address space, so a trap near
the top of it is close to code near the bottom. For a 32-bit guest, whose addresses are
zero-extended first, it is the plain difference.
§Safety
Execution of the resulting instruction stream skips the checks that
BasicInstructionFetcher does, which is where the
performance comes from. Both of the following must hold:
fallbackmust never continue to the next instruction when executed (unimp, which always fails, is the canonical choice). Instruction fetching does not bounds-check the position, onlyProgramCounter::set_pc()andProgramCounter::try_set_pc_relative()do, and falling through the end of the decoded stream lands onfallback, which must not fall through any further.Instruction::size()must return the same value every time for the same instruction. Decoding checks the size of each instruction once, and execution moves past the instruction by whatever size it returns later, without checking it again.