@@ -258,7 +258,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
258
258
Insn :: IfTrue { val, target } => return gen_if_true ( jit, asm, opnd ! ( val) , target) ,
259
259
Insn :: IfFalse { val, target } => return gen_if_false ( jit, asm, opnd ! ( val) , target) ,
260
260
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 ) ) ?,
262
262
Insn :: Return { val } => return Some ( gen_return ( asm, opnd ! ( val) ) ?) ,
263
263
Insn :: FixnumAdd { left, right, state } => gen_fixnum_add ( jit, asm, opnd ! ( left) , opnd ! ( right) , & function. frame_state ( * state) ) ?,
264
264
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(
484
484
self_val : & InsnId ,
485
485
args : & Vec < InsnId > ,
486
486
) -> 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.
488
495
// TODO: Avoid spilling operands that have been spilled before.
496
+ asm_comment ! ( asm, "spill receiver and arguments" ) ;
489
497
for ( idx, & insn_id) in [ * self_val] . iter ( ) . chain ( args. iter ( ) ) . enumerate ( ) {
490
498
// Currently, we don't move the SP register. So it's equal to the base pointer.
491
499
let stack_opnd = Opnd :: mem ( 64 , SP , idx as i32 * SIZEOF_VALUE_I32 ) ;
@@ -515,10 +523,40 @@ fn gen_send_without_block_direct(
515
523
cb : & mut CodeBlock ,
516
524
jit : & mut JITState ,
517
525
asm : & mut Assembler ,
526
+ cme : * const rb_callable_method_entry_t ,
518
527
iseq : IseqPtr ,
519
528
recv : Opnd ,
520
529
args : & Vec < InsnId > ,
530
+ state : & FrameState ,
521
531
) -> 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
+
522
560
asm_comment ! ( asm, "switch to new CFP" ) ;
523
561
let new_cfp = asm. sub ( CFP , RUBY_SIZEOF_CONTROL_FRAME . into ( ) ) ;
524
562
asm. mov ( CFP , new_cfp) ;
@@ -537,7 +575,15 @@ fn gen_send_without_block_direct(
537
575
jit. branch_iseqs . push ( ( branch. clone ( ) , iseq) ) ;
538
576
// TODO(max): Add a PatchPoint here that can side-exit the function if the callee messed with
539
577
// 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)
541
587
}
542
588
543
589
/// Compile an array duplication instruction
@@ -749,6 +795,45 @@ fn gen_save_sp(asm: &mut Assembler, stack_size: usize) {
749
795
asm. mov ( cfp_sp, sp_addr) ;
750
796
}
751
797
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
+
752
837
/// Return a register we use for the basic block argument at a given index
753
838
fn param_reg ( idx : usize ) -> Reg {
754
839
// 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 {
764
849
765
850
/// Inverse of ep_offset_to_local_idx(). See ep_offset_to_local_idx() for details.
766
851
fn 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
771
859
}
772
860
773
861
/// Convert ISEQ into High-level IR
@@ -816,9 +904,8 @@ impl Assembler {
816
904
move |code_ptr, _| {
817
905
start_branch. start_addr . set ( Some ( code_ptr) ) ;
818
906
} ,
819
- move |code_ptr, cb | {
907
+ move |code_ptr, _ | {
820
908
end_branch. end_addr . set ( Some ( code_ptr) ) ;
821
- ZJITState :: add_iseq_return_addr ( code_ptr. raw_ptr ( cb) ) ;
822
909
} ,
823
910
)
824
911
}
0 commit comments