1 //! Support executing the interpreter loop through tail-calls rather than a 2 //! source-level `loop`. 3 //! 4 //! This is an alternative means of executing the interpreter loop of Pulley. 5 //! The other method is in `match_loop.rs` which is a `loop` over a `match` 6 //! (more-or-less). This file instead transitions between opcodes with 7 //! tail-calls. 8 //! 9 //! At this time this module is more performant but disabled by default. Rust 10 //! does not have guaranteed tail call elimination at this time so this is not 11 //! a suitable means of writing an interpreter loop. That being said this is 12 //! included nonetheless for us to experiment and analyze with. 13 //! 14 //! There are two methods of using this module: 15 //! 16 //! * `RUSTFLAGS=--cfg=pulley_assume_llvm_makes_tail_calls` - this compilation 17 //! flag indicates that we should assume that LLVM will optimize to making 18 //! tail calls for things that look like tail calls. Practically this 19 //! probably only happens with `--release` and for popular native 20 //! architectures. It's up to the person compiling to manually 21 //! audit/verify/test that TCO is happening. 22 //! 23 //! * `RUSTFLAGS=--cfg=pulley_tail_calls` - this compilation flag indicates that 24 //! Rust's nightly-only support for guaranteed tail calls should be used. This 25 //! uses the `become` keyword, for example. At this time this feature of Rust 26 //! is highly experimental and not even complete. It only passes `cargo check` 27 //! at this time but doesn't actually run anywhere. 28 29 use super::*; 30 use crate::decode::{unwrap_uninhabited, ExtendedOpVisitor}; 31 use crate::opcode::Opcode; 32 use crate::ExtendedOpcode; 33 34 type Handler = fn(Interpreter<'_>) -> Done; 35 36 /// The extra indirection through a macro is necessary to avoid a compiler error 37 /// when compiling without `#![feature(explicit_tail_calls)]` enabled (via 38 /// `--cfg pulley_tail_calls`). 39 /// 40 /// It seems rustc first parses the function, encounters `become` and emits 41 /// an error about using an unstable keyword on a stable compiler, then applies 42 /// `#[cfg(...)` after parsing to disable the function. 43 /// 44 /// Macro bodies are just bags of tokens; the body is not parsed until after 45 /// they are expanded, and this macro is only expanded when `pulley_tail_calls` 46 /// is enabled. 47 #[cfg(pulley_tail_calls)] 48 macro_rules! tail_call { 49 ($e:expr) => { 50 become $e 51 }; 52 } 53 54 #[cfg(pulley_assume_llvm_makes_tail_calls)] 55 macro_rules! tail_call { 56 ($e:expr) => { 57 return $e 58 }; 59 } 60 61 impl Interpreter<'_> { 62 pub fn run(mut self) -> Done { 63 // Perform a dynamic dispatch through a function pointer indexed by 64 // opcode. 65 let opcode = unwrap_uninhabited(Opcode::decode(self.bytecode())); 66 let handler = OPCODE_HANDLER_TABLE[opcode as usize]; 67 tail_call!(handler(self)); 68 } 69 } 70 71 /// Same as `Interpreter::run`, except for extended opcodes. 72 fn run_extended(mut i: Interpreter<'_>) -> Done { 73 let opcode = unwrap_uninhabited(ExtendedOpcode::decode(i.bytecode())); 74 let handler = EXTENDED_OPCODE_HANDLER_TABLE[opcode as usize]; 75 tail_call!(handler(i)); 76 } 77 78 static OPCODE_HANDLER_TABLE: [Handler; Opcode::MAX as usize + 1] = { 79 macro_rules! define_opcode_handler_table { 80 ($( 81 $( #[$attr:meta] )* 82 $snake_name:ident = $name:ident $( { 83 $( 84 $( #[$field_attr:meta] )* 85 $field:ident : $field_ty:ty 86 ),* 87 } )?; 88 )*) => { 89 [ 90 $($snake_name,)* // refers to functions defined down below 91 run_extended, 92 ] 93 }; 94 } 95 96 for_each_op!(define_opcode_handler_table) 97 }; 98 99 // same as above, but without a +1 for handling of extended ops as this is the 100 // extended ops. 101 static EXTENDED_OPCODE_HANDLER_TABLE: [Handler; ExtendedOpcode::MAX as usize] = { 102 macro_rules! define_extended_opcode_handler_table { 103 ($( 104 $( #[$attr:meta] )* 105 $snake_name:ident = $name:ident $( { 106 $( 107 $( #[$field_attr:meta] )* 108 $field:ident : $field_ty:ty 109 ),* 110 } )?; 111 )*) => { 112 [ 113 $($snake_name,)* // refers to functions defined down below 114 ] 115 }; 116 } 117 118 for_each_extended_op!(define_extended_opcode_handler_table) 119 }; 120 121 // Define a top-level function for each opcode. Each function here is the 122 // destination of the indirect return-call-indirect of above. Each function is 123 // also specialized to a single opcode and should be thoroughly inlined to 124 // ensure that everything "boils away". 125 macro_rules! define_opcode_handler { 126 ($( 127 $( #[$attr:meta] )* 128 $snake_name:ident = $name:ident $( { 129 $( 130 $( #[$field_attr:meta] )* 131 $field:ident : $field_ty:ty 132 ),* 133 } )?; 134 )*) => {$( 135 fn $snake_name(mut i: Interpreter<'_>) -> Done { 136 $( 137 let ($($field,)*) = unwrap_uninhabited( 138 crate::decode::operands::$snake_name(i.bytecode()) 139 ); 140 )? 141 let _ = &mut i; 142 let mut debug = debug::Debug(i); 143 match debug.$snake_name($($($field),*)?) { 144 ControlFlow::Continue(()) => tail_call!(debug.0.run()), 145 ControlFlow::Break(done) => done, 146 } 147 } 148 )*}; 149 } 150 151 for_each_op!(define_opcode_handler); 152 for_each_extended_op!(define_opcode_handler); 153