@@ -6,9 +6,10 @@ use std::rc::Rc;
66use std:: sync:: { Arc , Mutex } ;
77use crate :: codegen:: local_size_and_idx_to_ep_offset;
88use crate :: cruby:: { Qundef , RUBY_OFFSET_CFP_PC , RUBY_OFFSET_CFP_SP , SIZEOF_VALUE_I32 , vm_stack_canary} ;
9- use crate :: hir:: SideExitReason ;
9+ use crate :: hir:: { Invariant , SideExitReason } ;
1010use crate :: options:: { TraceExits , debug, get_option} ;
1111use crate :: cruby:: VALUE ;
12+ use crate :: payload:: IseqPayload ;
1213use crate :: stats:: { exit_counter_ptr, exit_counter_ptr_for_opcode, side_exit_counter, CompileError } ;
1314use crate :: virtualmem:: CodePtr ;
1415use crate :: asm:: { CodeBlock , Label } ;
@@ -25,7 +26,7 @@ pub use crate::backend::current::{
2526pub static JIT_PRESERVED_REGS : & [ Opnd ] = & [ CFP , SP , EC ] ;
2627
2728// Memory operand base
28- #[ derive( Clone , Copy , PartialEq , Eq , Debug ) ]
29+ #[ derive( Clone , Copy , PartialEq , Eq , Debug , Hash ) ]
2930pub enum MemBase
3031{
3132 /// Register: Every Opnd::Mem should have MemBase::Reg as of emit.
@@ -37,7 +38,7 @@ pub enum MemBase
3738}
3839
3940// Memory location
40- #[ derive( Copy , Clone , PartialEq , Eq ) ]
41+ #[ derive( Copy , Clone , PartialEq , Eq , Hash ) ]
4142pub struct Mem
4243{
4344 // Base register number or instruction index
@@ -87,7 +88,7 @@ impl fmt::Debug for Mem {
8788}
8889
8990/// Operand to an IR instruction
90- #[ derive( Clone , Copy , PartialEq , Eq ) ]
91+ #[ derive( Clone , Copy , PartialEq , Eq , Hash ) ]
9192pub enum Opnd
9293{
9394 None , // For insns with no output
@@ -298,6 +299,14 @@ impl From<VALUE> for Opnd {
298299 }
299300}
300301
302+ /// Context for a side exit. If `SideExit` matches, it reuses the same code.
303+ #[ derive( Clone , Debug , Eq , Hash , PartialEq ) ]
304+ pub struct SideExit {
305+ pub pc : Opnd ,
306+ pub stack : Vec < Opnd > ,
307+ pub locals : Vec < Opnd > ,
308+ }
309+
301310/// Branch target (something that we can jump to)
302311/// for branch instructions
303312#[ derive( Clone , Debug ) ]
@@ -309,13 +318,10 @@ pub enum Target
309318 Label ( Label ) ,
310319 /// Side exit to the interpreter
311320 SideExit {
312- pc : * const VALUE ,
313- stack : Vec < Opnd > ,
314- locals : Vec < Opnd > ,
315- /// We use this to enrich asm comments.
321+ /// Context used for compiling the side exit
322+ exit : SideExit ,
323+ /// We use this to increment exit counters
316324 reason : SideExitReason ,
317- /// Some if the side exit should write this label. We use it for patch points.
318- label : Option < Label > ,
319325 } ,
320326}
321327
@@ -525,7 +531,7 @@ pub enum Insn {
525531 Or { left : Opnd , right : Opnd , out : Opnd } ,
526532
527533 /// Patch point that will be rewritten to a jump to a side exit on invalidation.
528- PatchPoint ( Target ) ,
534+ PatchPoint { target : Target , invariant : Invariant , payload : * mut IseqPayload } ,
529535
530536 /// Make sure the last PatchPoint has enough space to insert a jump.
531537 /// We insert this instruction at the end of each block so that the jump
@@ -590,7 +596,7 @@ impl Insn {
590596 Insn :: Jonz ( _, target) |
591597 Insn :: Label ( target) |
592598 Insn :: LeaJumpTarget { target, .. } |
593- Insn :: PatchPoint ( target) => {
599+ Insn :: PatchPoint { target, .. } => {
594600 Some ( target)
595601 }
596602 _ => None ,
@@ -652,7 +658,7 @@ impl Insn {
652658 Insn :: Mov { .. } => "Mov" ,
653659 Insn :: Not { .. } => "Not" ,
654660 Insn :: Or { .. } => "Or" ,
655- Insn :: PatchPoint ( _ ) => "PatchPoint" ,
661+ Insn :: PatchPoint { .. } => "PatchPoint" ,
656662 Insn :: PadPatchPoint => "PadPatchPoint" ,
657663 Insn :: PosMarker ( _) => "PosMarker" ,
658664 Insn :: RShift { .. } => "RShift" ,
@@ -750,7 +756,7 @@ impl Insn {
750756 Insn :: Jonz ( _, target) |
751757 Insn :: Label ( target) |
752758 Insn :: LeaJumpTarget { target, .. } |
753- Insn :: PatchPoint ( target) => Some ( target) ,
759+ Insn :: PatchPoint { target, .. } => Some ( target) ,
754760 _ => None
755761 }
756762 }
@@ -797,8 +803,8 @@ impl<'a> Iterator for InsnOpndIterator<'a> {
797803 Insn :: Jz ( target) |
798804 Insn :: Label ( target) |
799805 Insn :: LeaJumpTarget { target, .. } |
800- Insn :: PatchPoint ( target) => {
801- if let Target :: SideExit { stack, locals, .. } = target {
806+ Insn :: PatchPoint { target, .. } => {
807+ if let Target :: SideExit { exit : SideExit { stack, locals, .. } , .. } = target {
802808 let stack_idx = self . idx ;
803809 if stack_idx < stack. len ( ) {
804810 let opnd = & stack[ stack_idx] ;
@@ -823,7 +829,7 @@ impl<'a> Iterator for InsnOpndIterator<'a> {
823829 return Some ( opnd) ;
824830 }
825831
826- if let Target :: SideExit { stack, locals, .. } = target {
832+ if let Target :: SideExit { exit : SideExit { stack, locals, .. } , .. } = target {
827833 let stack_idx = self . idx - 1 ;
828834 if stack_idx < stack. len ( ) {
829835 let opnd = & stack[ stack_idx] ;
@@ -966,8 +972,8 @@ impl<'a> InsnOpndMutIterator<'a> {
966972 Insn :: Jz ( target) |
967973 Insn :: Label ( target) |
968974 Insn :: LeaJumpTarget { target, .. } |
969- Insn :: PatchPoint ( target) => {
970- if let Target :: SideExit { stack, locals, .. } = target {
975+ Insn :: PatchPoint { target, .. } => {
976+ if let Target :: SideExit { exit : SideExit { stack, locals, .. } , .. } = target {
971977 let stack_idx = self . idx ;
972978 if stack_idx < stack. len ( ) {
973979 let opnd = & mut stack[ stack_idx] ;
@@ -992,7 +998,7 @@ impl<'a> InsnOpndMutIterator<'a> {
992998 return Some ( opnd) ;
993999 }
9941000
995- if let Target :: SideExit { stack, locals, .. } = target {
1001+ if let Target :: SideExit { exit : SideExit { stack, locals, .. } , .. } = target {
9961002 let stack_idx = self . idx - 1 ;
9971003 if stack_idx < stack. len ( ) {
9981004 let opnd = & mut stack[ stack_idx] ;
@@ -1779,82 +1785,108 @@ impl Assembler
17791785
17801786 /// Compile Target::SideExit and convert it into Target::CodePtr for all instructions
17811787 pub fn compile_exits ( & mut self ) {
1788+ /// Compile the main side-exit code. This function takes only SideExit so
1789+ /// that it can be safely deduplicated by using SideExit as a dedup key.
1790+ fn compile_exit ( asm : & mut Assembler , exit : & SideExit ) {
1791+ let SideExit { pc, stack, locals } = exit;
1792+
1793+ asm_comment ! ( asm, "save cfp->pc" ) ;
1794+ asm. store ( Opnd :: mem ( 64 , CFP , RUBY_OFFSET_CFP_PC ) , * pc) ;
1795+
1796+ asm_comment ! ( asm, "save cfp->sp" ) ;
1797+ asm. lea_into ( Opnd :: mem ( 64 , CFP , RUBY_OFFSET_CFP_SP ) , Opnd :: mem ( 64 , SP , stack. len ( ) as i32 * SIZEOF_VALUE_I32 ) ) ;
1798+
1799+ if !stack. is_empty ( ) {
1800+ asm_comment ! ( asm, "write stack slots: {}" , join_opnds( & stack, ", " ) ) ;
1801+ for ( idx, & opnd) in stack. iter ( ) . enumerate ( ) {
1802+ asm. store ( Opnd :: mem ( 64 , SP , idx as i32 * SIZEOF_VALUE_I32 ) , opnd) ;
1803+ }
1804+ }
1805+
1806+ if !locals. is_empty ( ) {
1807+ asm_comment ! ( asm, "write locals: {}" , join_opnds( & locals, ", " ) ) ;
1808+ for ( idx, & opnd) in locals. iter ( ) . enumerate ( ) {
1809+ asm. store ( Opnd :: mem ( 64 , SP , ( -local_size_and_idx_to_ep_offset ( locals. len ( ) , idx) - 1 ) * SIZEOF_VALUE_I32 ) , opnd) ;
1810+ }
1811+ }
1812+
1813+ asm_comment ! ( asm, "exit to the interpreter" ) ;
1814+ asm. frame_teardown ( & [ ] ) ; // matching the setup in gen_entry_point()
1815+ asm. cret ( Opnd :: UImm ( Qundef . as_u64 ( ) ) ) ;
1816+ }
1817+
17821818 fn join_opnds ( opnds : & Vec < Opnd > , delimiter : & str ) -> String {
17831819 opnds. iter ( ) . map ( |opnd| format ! ( "{opnd}" ) ) . collect :: < Vec < _ > > ( ) . join ( delimiter)
17841820 }
17851821
1822+ // Extract targets first so that we can update instructions while referencing part of them.
17861823 let mut targets = HashMap :: new ( ) ;
17871824 for ( idx, insn) in self . insns . iter ( ) . enumerate ( ) {
17881825 if let Some ( target @ Target :: SideExit { .. } ) = insn. target ( ) {
17891826 targets. insert ( idx, target. clone ( ) ) ;
17901827 }
17911828 }
17921829
1830+ // Map from SideExit to compiled Label. This table is used to deduplicate side exit code.
1831+ let mut compiled_exits: HashMap < SideExit , Label > = HashMap :: new ( ) ;
1832+
17931833 for ( idx, target) in targets {
17941834 // Compile a side exit. Note that this is past the split pass and alloc_regs(),
17951835 // so you can't use an instruction that returns a VReg.
1796- if let Target :: SideExit { pc, stack, locals, reason, label } = target {
1797- asm_comment ! ( self , "Exit: {reason}" ) ;
1798- let side_exit_label = if let Some ( label) = label {
1799- Target :: Label ( label)
1800- } else {
1801- self . new_label ( "side_exit" )
1802- } ;
1803- self . write_label ( side_exit_label. clone ( ) ) ;
1804-
1805- // Restore the PC and the stack for regular side exits. We don't do this for
1806- // side exits right after JIT-to-JIT calls, which restore them before the call.
1807- asm_comment ! ( self , "write stack slots: {}" , join_opnds( & stack, ", " ) ) ;
1808- for ( idx, & opnd) in stack. iter ( ) . enumerate ( ) {
1809- self . store ( Opnd :: mem ( 64 , SP , idx as i32 * SIZEOF_VALUE_I32 ) , opnd) ;
1810- }
1811-
1812- asm_comment ! ( self , "write locals: {}" , join_opnds( & locals, ", " ) ) ;
1813- for ( idx, & opnd) in locals. iter ( ) . enumerate ( ) {
1814- self . store ( Opnd :: mem ( 64 , SP , ( -local_size_and_idx_to_ep_offset ( locals. len ( ) , idx) - 1 ) * SIZEOF_VALUE_I32 ) , opnd) ;
1815- }
1816-
1817- asm_comment ! ( self , "save cfp->pc" ) ;
1818- self . store ( Opnd :: mem ( 64 , CFP , RUBY_OFFSET_CFP_PC ) , Opnd :: const_ptr ( pc) ) ;
1819-
1820- asm_comment ! ( self , "save cfp->sp" ) ;
1821- self . lea_into ( Opnd :: mem ( 64 , CFP , RUBY_OFFSET_CFP_SP ) , Opnd :: mem ( 64 , SP , stack. len ( ) as i32 * SIZEOF_VALUE_I32 ) ) ;
1822-
1823- // Using C_RET_OPND as an additional scratch register, which is no longer used
1824- if get_option ! ( stats) {
1825- asm_comment ! ( self , "increment a side exit counter" ) ;
1826- self . incr_counter ( Opnd :: const_ptr ( exit_counter_ptr ( reason) ) , 1 . into ( ) ) ;
1827-
1828- if let SideExitReason :: UnhandledYARVInsn ( opcode) = reason {
1829- asm_comment ! ( self , "increment an unhandled YARV insn counter" ) ;
1830- self . incr_counter ( Opnd :: const_ptr ( exit_counter_ptr_for_opcode ( opcode) ) , 1 . into ( ) ) ;
1836+ if let Target :: SideExit { exit : exit @ SideExit { pc, .. } , reason } = target {
1837+ // Only record the exit if `trace_side_exits` is defined and the counter is either the one specified
1838+ let should_record_exit = get_option ! ( trace_side_exits) . map ( |trace| match trace {
1839+ TraceExits :: All => true ,
1840+ TraceExits :: Counter ( counter) if counter == side_exit_counter ( reason) => true ,
1841+ _ => false ,
1842+ } ) . unwrap_or ( false ) ;
1843+
1844+ // If enabled, instrument exits first, and then jump to a shared exit.
1845+ let counted_exit = if get_option ! ( stats) || should_record_exit {
1846+ let counted_exit = self . new_label ( "counted_exit" ) ;
1847+ self . write_label ( counted_exit. clone ( ) ) ;
1848+ asm_comment ! ( self , "Counted Exit: {reason}" ) ;
1849+
1850+ if get_option ! ( stats) {
1851+ asm_comment ! ( self , "increment a side exit counter" ) ;
1852+ self . incr_counter ( Opnd :: const_ptr ( exit_counter_ptr ( reason) ) , 1 . into ( ) ) ;
1853+
1854+ if let SideExitReason :: UnhandledYARVInsn ( opcode) = reason {
1855+ asm_comment ! ( self , "increment an unhandled YARV insn counter" ) ;
1856+ self . incr_counter ( Opnd :: const_ptr ( exit_counter_ptr_for_opcode ( opcode) ) , 1 . into ( ) ) ;
1857+ }
18311858 }
1832- }
1833-
1834- if get_option ! ( trace_side_exits) . is_some ( ) {
1835- // Get the corresponding `Counter` for the current `SideExitReason`.
1836- let side_exit_counter = side_exit_counter ( reason) ;
1837-
1838- // Only record the exit if `trace_side_exits` is defined and the counter is either the one specified
1839- let should_record_exit = get_option ! ( trace_side_exits)
1840- . map ( |trace| match trace {
1841- TraceExits :: All => true ,
1842- TraceExits :: Counter ( counter) if counter == side_exit_counter => true ,
1843- _ => false ,
1844- } )
1845- . unwrap_or ( false ) ;
18461859
18471860 if should_record_exit {
1848- asm_ccall ! ( self , rb_zjit_record_exit_stack, Opnd :: const_ptr( pc as * const u8 ) ) ;
1861+ // Preserve caller-saved registers that may be used in the shared exit.
1862+ self . cpush_all ( ) ;
1863+ asm_ccall ! ( self , rb_zjit_record_exit_stack, pc) ;
1864+ self . cpop_all ( ) ;
18491865 }
1850- }
18511866
1852- asm_comment ! ( self , "exit to the interpreter" ) ;
1853- self . frame_teardown ( & [ ] ) ; // matching the setup in :bb0-prologue:
1854- self . mov ( C_RET_OPND , Opnd :: UImm ( Qundef . as_u64 ( ) ) ) ;
1855- self . cret ( C_RET_OPND ) ;
1867+ // If the side exit has already been compiled, jump to it.
1868+ // Otherwise, let it fall through and compile the exit next.
1869+ if let Some ( & exit_label) = compiled_exits. get ( & exit) {
1870+ self . jmp ( Target :: Label ( exit_label) ) ;
1871+ }
1872+ Some ( counted_exit)
1873+ } else {
1874+ None
1875+ } ;
1876+
1877+ // Compile the shared side exit if not compiled yet
1878+ let compiled_exit = if let Some ( & compiled_exit) = compiled_exits. get ( & exit) {
1879+ Target :: Label ( compiled_exit)
1880+ } else {
1881+ let new_exit = self . new_label ( "side_exit" ) ;
1882+ self . write_label ( new_exit. clone ( ) ) ;
1883+ asm_comment ! ( self , "Exit: {pc}" ) ;
1884+ compile_exit ( self , & exit) ;
1885+ compiled_exits. insert ( exit, new_exit. unwrap_label ( ) ) ;
1886+ new_exit
1887+ } ;
18561888
1857- * self . insns [ idx] . target_mut ( ) . unwrap ( ) = side_exit_label ;
1889+ * self . insns [ idx] . target_mut ( ) . unwrap ( ) = counted_exit . unwrap_or ( compiled_exit ) ;
18581890 }
18591891 }
18601892 }
@@ -2268,8 +2300,8 @@ impl Assembler {
22682300 out
22692301 }
22702302
2271- pub fn patch_point ( & mut self , target : Target ) {
2272- self . push_insn ( Insn :: PatchPoint ( target) ) ;
2303+ pub fn patch_point ( & mut self , target : Target , invariant : Invariant , payload : * mut IseqPayload ) {
2304+ self . push_insn ( Insn :: PatchPoint { target, invariant , payload } ) ;
22732305 }
22742306
22752307 pub fn pad_patch_point ( & mut self ) {
0 commit comments