Path: blob/main/pulley/src/interp/tail_loop.rs
1692 views
//! Support executing the interpreter loop through tail-calls rather than a1//! source-level `loop`.2//!3//! This is an alternative means of executing the interpreter loop of Pulley.4//! The other method is in `match_loop.rs` which is a `loop` over a `match`5//! (more-or-less). This file instead transitions between opcodes with6//! tail-calls.7//!8//! At this time this module is more performant but disabled by default. Rust9//! does not have guaranteed tail call elimination on stable at this time so10//! this is not a suitable means of writing an interpreter loop. That being said11//! this is included nonetheless for us to experiment and analyze with.12//!13//! There are two methods of using this module:14//!15//! * `RUSTFLAGS=--cfg=pulley_assume_llvm_makes_tail_calls` - this compilation16//! flag indicates that we should assume that LLVM will optimize to making17//! tail calls for things that look like tail calls. Practically this18//! probably only happens with `--release` and for popular native19//! architectures. It's up to the person compiling to manually20//! audit/verify/test that TCO is happening.21//!22//! * `RUSTFLAGS=--cfg=pulley_tail_calls` - this compilation flag indicates that23//! Rust's nightly-only support for guaranteed tail calls should be used. This24//! uses the `become` keyword, for example. At this time this feature of Rust25//! is highly experimental and may not be complete. This is only lightly26//! tested in CI.2728use super::*;29use crate::ExtendedOpcode;30use crate::decode::ExtendedOpVisitor;31use crate::opcode::Opcode;32use crate::profile::ExecutingPcRef;3334/// ABI signature of each opcode handler.35///36/// Note that this "explodes" the internals of `Interpreter` to individual37/// arguments to help get them all into registers.38type Handler = fn(&mut MachineState, UnsafeBytecodeStream, ExecutingPcRef<'_>) -> Done;3940/// The extra indirection through a macro is necessary to avoid a compiler error41/// when compiling without `#![feature(explicit_tail_calls)]` enabled (via42/// `--cfg pulley_tail_calls`).43///44/// It seems rustc first parses the function, encounters `become` and emits45/// an error about using an unstable keyword on a stable compiler, then applies46/// `#[cfg(...)` after parsing to disable the function.47///48/// Macro bodies are just bags of tokens; the body is not parsed until after49/// they are expanded, and this macro is only expanded when `pulley_tail_calls`50/// is enabled.51#[cfg(pulley_tail_calls)]52macro_rules! tail_call {53($e:expr) => {54become $e55};56}5758#[cfg(pulley_assume_llvm_makes_tail_calls)]59macro_rules! tail_call {60($e:expr) => {61return $e62};63}6465impl Interpreter<'_> {66pub fn run(self) -> Done {67dispatch(self.state, self.pc, self.executing_pc)68}69}7071fn debug<'a>(72state: &'a mut MachineState,73pc: UnsafeBytecodeStream,74executing_pc: ExecutingPcRef<'a>,75) -> debug::Debug<'a> {76debug::Debug(Interpreter {77state,78pc,79executing_pc,80})81}8283fn dispatch(84state: &mut MachineState,85pc: UnsafeBytecodeStream,86executing_pc: ExecutingPcRef<'_>,87) -> Done {88// Perform a dynamic dispatch through a function pointer indexed by89// opcode.90let mut debug = debug(state, pc, executing_pc);91debug.before_visit();92let Ok(opcode) = Opcode::decode(debug.bytecode());93let handler = OPCODE_HANDLER_TABLE[opcode as usize];94tail_call!(handler(debug.0.state, debug.0.pc, debug.0.executing_pc));95}9697/// Same as `Interpreter::run`, except for extended opcodes.98fn run_extended(99state: &mut MachineState,100pc: UnsafeBytecodeStream,101pc_ref: ExecutingPcRef<'_>,102) -> Done {103let mut i = debug(state, pc, pc_ref);104let Ok(opcode) = ExtendedOpcode::decode(i.bytecode());105let handler = EXTENDED_OPCODE_HANDLER_TABLE[opcode as usize];106tail_call!(handler(i.0.state, i.0.pc, i.0.executing_pc));107}108109static OPCODE_HANDLER_TABLE: [Handler; Opcode::MAX as usize + 1] = {110macro_rules! define_opcode_handler_table {111($(112$( #[$attr:meta] )*113$snake_name:ident = $name:ident $( {114$(115$( #[$field_attr:meta] )*116$field:ident : $field_ty:ty117),*118} )?;119)*) => {120[121$($snake_name,)* // refers to functions defined down below122run_extended,123]124};125}126127for_each_op!(define_opcode_handler_table)128};129130// same as above, but without a +1 for handling of extended ops as this is the131// extended ops.132static EXTENDED_OPCODE_HANDLER_TABLE: [Handler; ExtendedOpcode::MAX as usize] = {133macro_rules! define_extended_opcode_handler_table {134($(135$( #[$attr:meta] )*136$snake_name:ident = $name:ident $( {137$(138$( #[$field_attr:meta] )*139$field:ident : $field_ty:ty140),*141} )?;142)*) => {143[144$($snake_name,)* // refers to functions defined down below145]146};147}148149for_each_extended_op!(define_extended_opcode_handler_table)150};151152// Define a top-level function for each opcode. Each function here is the153// destination of the indirect return-call-indirect of above. Each function is154// also specialized to a single opcode and should be thoroughly inlined to155// ensure that everything "boils away".156macro_rules! define_opcode_handler {157($(158$( #[$attr:meta] )*159$snake_name:ident = $name:ident $( {160$(161$( #[$field_attr:meta] )*162$field:ident : $field_ty:ty163),*164} )?;165)*) => {$(166fn $snake_name(167state: &mut MachineState,168pc: UnsafeBytecodeStream,169executing_pc: ExecutingPcRef<'_>,170) -> Done {171let mut debug = debug(state, pc, executing_pc);172$(173let Ok(($($field,)*)) = crate::decode::operands::$snake_name(debug.0.bytecode());174)?175let result = debug.$snake_name($($($field),*)?);176debug.after_visit();177match result {178ControlFlow::Continue(()) => {179tail_call!(dispatch(debug.0.state, debug.0.pc, debug.0.executing_pc))180}181ControlFlow::Break(done) => done,182}183}184)*};185}186187for_each_op!(define_opcode_handler);188for_each_extended_op!(define_opcode_handler);189190191