@@ -258,7 +258,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
258258 Insn :: IfTrue { val, target } => return gen_if_true ( jit, asm, opnd ! ( val) , target) ,
259259 Insn :: IfFalse { val, target } => return gen_if_false ( jit, asm, opnd ! ( val) , target) ,
260260 Insn :: SendWithoutBlock { call_info, cd, state, self_val, args, .. } => gen_send_without_block ( jit, asm, call_info, * cd, & function. frame_state ( * state) , self_val, args) ?,
261- Insn :: SendWithoutBlockDirect { iseq, self_val, args, .. } => gen_send_without_block_direct ( cb, jit, asm, * iseq, opnd ! ( self_val) , args) ?,
261+ Insn :: SendWithoutBlockDirect { cme , iseq, self_val, args, state , .. } => gen_send_without_block_direct ( cb, jit, asm, * cme , * iseq, opnd ! ( self_val) , args, & function . frame_state ( * state ) ) ?,
262262 Insn :: Return { val } => return Some ( gen_return ( asm, opnd ! ( val) ) ?) ,
263263 Insn :: FixnumAdd { left, right, state } => gen_fixnum_add ( jit, asm, opnd ! ( left) , opnd ! ( right) , & function. frame_state ( * state) ) ?,
264264 Insn :: FixnumSub { left, right, state } => gen_fixnum_sub ( jit, asm, opnd ! ( left) , opnd ! ( right) , & function. frame_state ( * state) ) ?,
@@ -484,8 +484,16 @@ fn gen_send_without_block(
484484 self_val : & InsnId ,
485485 args : & Vec < InsnId > ,
486486) -> Option < lir:: Opnd > {
487- // Spill the receiver and the arguments onto the stack. They need to be marked by GC and may be caller-saved registers.
487+ // Spill locals onto the stack.
488+ // TODO: Don't spill locals eagerly; lazily reify frames
489+ asm_comment ! ( asm, "spill locals" ) ;
490+ for ( idx, & insn_id) in state. locals ( ) . enumerate ( ) {
491+ asm. mov ( Opnd :: mem ( 64 , SP , ( -local_idx_to_ep_offset ( jit. iseq , idx) - 1 ) * SIZEOF_VALUE_I32 ) , jit. get_opnd ( insn_id) ?) ;
492+ }
493+ // Spill the receiver and the arguments onto the stack.
494+ // They need to be on the interpreter stack to let the interpreter access them.
488495 // TODO: Avoid spilling operands that have been spilled before.
496+ asm_comment ! ( asm, "spill receiver and arguments" ) ;
489497 for ( idx, & insn_id) in [ * self_val] . iter ( ) . chain ( args. iter ( ) ) . enumerate ( ) {
490498 // Currently, we don't move the SP register. So it's equal to the base pointer.
491499 let stack_opnd = Opnd :: mem ( 64 , SP , idx as i32 * SIZEOF_VALUE_I32 ) ;
@@ -515,10 +523,40 @@ fn gen_send_without_block_direct(
515523 cb : & mut CodeBlock ,
516524 jit : & mut JITState ,
517525 asm : & mut Assembler ,
526+ cme : * const rb_callable_method_entry_t ,
518527 iseq : IseqPtr ,
519528 recv : Opnd ,
520529 args : & Vec < InsnId > ,
530+ state : & FrameState ,
521531) -> Option < lir:: Opnd > {
532+ // Save cfp->pc and cfp->sp for the caller frame
533+ gen_save_pc ( asm, state) ;
534+ gen_save_sp ( asm, state. stack ( ) . len ( ) - args. len ( ) - 1 ) ; // -1 for receiver
535+
536+ // Spill the virtual stack and the locals of the caller onto the stack
537+ // TODO: Lazily materialize caller frames on side exits or when needed
538+ asm_comment ! ( asm, "spill locals and stack" ) ;
539+ for ( idx, & insn_id) in state. locals ( ) . enumerate ( ) {
540+ asm. mov ( Opnd :: mem ( 64 , SP , ( -local_idx_to_ep_offset ( jit. iseq , idx) - 1 ) * SIZEOF_VALUE_I32 ) , jit. get_opnd ( insn_id) ?) ;
541+ }
542+ for ( idx, & insn_id) in state. stack ( ) . enumerate ( ) {
543+ asm. mov ( Opnd :: mem ( 64 , SP , idx as i32 * SIZEOF_VALUE_I32 ) , jit. get_opnd ( insn_id) ?) ;
544+ }
545+
546+ // Set up the new frame
547+ // TODO: Lazily materialize caller frames on side exits or when needed
548+ gen_push_frame ( asm, args. len ( ) , state, ControlFrame {
549+ recv,
550+ iseq,
551+ cme,
552+ frame_type : VM_FRAME_MAGIC_METHOD | VM_ENV_FLAG_LOCAL ,
553+ } ) ;
554+
555+ asm_comment ! ( asm, "switch to new SP register" ) ;
556+ let local_size = unsafe { get_iseq_body_local_table_size ( iseq) } as usize ;
557+ let new_sp = asm. add ( SP , ( ( state. stack ( ) . len ( ) + local_size - args. len ( ) + VM_ENV_DATA_SIZE as usize ) * SIZEOF_VALUE ) . into ( ) ) ;
558+ asm. mov ( SP , new_sp) ;
559+
522560 asm_comment ! ( asm, "switch to new CFP" ) ;
523561 let new_cfp = asm. sub ( CFP , RUBY_SIZEOF_CONTROL_FRAME . into ( ) ) ;
524562 asm. mov ( CFP , new_cfp) ;
@@ -537,7 +575,15 @@ fn gen_send_without_block_direct(
537575 jit. branch_iseqs . push ( ( branch. clone ( ) , iseq) ) ;
538576 // TODO(max): Add a PatchPoint here that can side-exit the function if the callee messed with
539577 // the frame's locals
540- Some ( asm. ccall_with_branch ( dummy_ptr, c_args, & branch) )
578+ let ret = asm. ccall_with_branch ( dummy_ptr, c_args, & branch) ;
579+
580+ // If a callee side-exits, i.e. returns Qundef, propagate the return value to the caller.
581+ // The caller will side-exit the callee into the interpreter.
582+ // TODO: Let side exit code pop all JIT frames to optimize away this cmp + je.
583+ asm. cmp ( ret, Qundef . into ( ) ) ;
584+ asm. je ( ZJITState :: get_exit_trampoline ( ) . into ( ) ) ;
585+
586+ Some ( ret)
541587}
542588
543589/// Compile an array duplication instruction
@@ -749,6 +795,45 @@ fn gen_save_sp(asm: &mut Assembler, stack_size: usize) {
749795 asm. mov ( cfp_sp, sp_addr) ;
750796}
751797
798+ /// Frame metadata written by gen_push_frame()
799+ struct ControlFrame {
800+ recv : Opnd ,
801+ iseq : IseqPtr ,
802+ cme : * const rb_callable_method_entry_t ,
803+ frame_type : u32 ,
804+ }
805+
806+ /// Compile an interpreter frame
807+ fn gen_push_frame ( asm : & mut Assembler , argc : usize , state : & FrameState , frame : ControlFrame ) {
808+ // Locals are written by the callee frame on side-exits or non-leaf calls
809+
810+ // See vm_push_frame() for details
811+ asm_comment ! ( asm, "push cme, specval, frame type" ) ;
812+ // ep[-2]: cref of cme
813+ let local_size = unsafe { get_iseq_body_local_table_size ( frame. iseq ) } as i32 ;
814+ let ep_offset = state. stack ( ) . len ( ) as i32 + local_size - argc as i32 + VM_ENV_DATA_SIZE as i32 - 1 ;
815+ asm. store ( Opnd :: mem ( 64 , SP , ( ep_offset - 2 ) * SIZEOF_VALUE_I32 ) , VALUE :: from ( frame. cme ) . into ( ) ) ;
816+ // ep[-1]: block_handler or prev EP
817+ // block_handler is not supported for now
818+ asm. store ( Opnd :: mem ( 64 , SP , ( ep_offset - 1 ) * SIZEOF_VALUE_I32 ) , VM_BLOCK_HANDLER_NONE . into ( ) ) ;
819+ // ep[0]: ENV_FLAGS
820+ asm. store ( Opnd :: mem ( 64 , SP , ep_offset * SIZEOF_VALUE_I32 ) , frame. frame_type . into ( ) ) ;
821+
822+ // Write to the callee CFP
823+ fn cfp_opnd ( offset : i32 ) -> Opnd {
824+ Opnd :: mem ( 64 , CFP , offset - ( RUBY_SIZEOF_CONTROL_FRAME as i32 ) )
825+ }
826+
827+ asm_comment ! ( asm, "push callee control frame" ) ;
828+ // cfp_opnd(RUBY_OFFSET_CFP_PC): written by the callee frame on side-exits or non-leaf calls
829+ // cfp_opnd(RUBY_OFFSET_CFP_SP): written by the callee frame on side-exits or non-leaf calls
830+ asm. mov ( cfp_opnd ( RUBY_OFFSET_CFP_ISEQ ) , VALUE :: from ( frame. iseq ) . into ( ) ) ;
831+ asm. mov ( cfp_opnd ( RUBY_OFFSET_CFP_SELF ) , frame. recv ) ;
832+ let ep = asm. lea ( Opnd :: mem ( 64 , SP , ep_offset * SIZEOF_VALUE_I32 ) ) ;
833+ asm. mov ( cfp_opnd ( RUBY_OFFSET_CFP_EP ) , ep) ;
834+ asm. mov ( cfp_opnd ( RUBY_OFFSET_CFP_BLOCK_CODE ) , 0 . into ( ) ) ;
835+ }
836+
752837/// Return a register we use for the basic block argument at a given index
753838fn param_reg ( idx : usize ) -> Reg {
754839 // To simplify the implementation, allocate a fixed register for each basic block argument for now.
@@ -764,10 +849,13 @@ fn param_reg(idx: usize) -> Reg {
764849
765850/// Inverse of ep_offset_to_local_idx(). See ep_offset_to_local_idx() for details.
766851fn local_idx_to_ep_offset ( iseq : IseqPtr , local_idx : usize ) -> i32 {
767- let local_table_size: i32 = unsafe { get_iseq_body_local_table_size ( iseq) }
768- . try_into ( )
769- . unwrap ( ) ;
770- local_table_size - local_idx as i32 - 1 + VM_ENV_DATA_SIZE as i32
852+ let local_size = unsafe { get_iseq_body_local_table_size ( iseq) } ;
853+ local_size_and_idx_to_ep_offset ( local_size as usize , local_idx)
854+ }
855+
856+ /// Convert the number of locals and a local index to an offset in the EP
857+ pub fn local_size_and_idx_to_ep_offset ( local_size : usize , local_idx : usize ) -> i32 {
858+ local_size as i32 - local_idx as i32 - 1 + VM_ENV_DATA_SIZE as i32
771859}
772860
773861/// Convert ISEQ into High-level IR
@@ -816,9 +904,8 @@ impl Assembler {
816904 move |code_ptr, _| {
817905 start_branch. start_addr . set ( Some ( code_ptr) ) ;
818906 } ,
819- move |code_ptr, cb | {
907+ move |code_ptr, _ | {
820908 end_branch. end_addr . set ( Some ( code_ptr) ) ;
821- ZJITState :: add_iseq_return_addr ( code_ptr. raw_ptr ( cb) ) ;
822909 } ,
823910 )
824911 }
0 commit comments