@@ -6,8 +6,8 @@ use crate::profile::get_or_create_iseq_payload;
6
6
use crate :: state:: ZJITState ;
7
7
use crate :: { asm:: CodeBlock , cruby:: * , options:: debug, virtualmem:: CodePtr } ;
8
8
use crate :: invariants:: { iseq_escapes_ep, track_no_ep_escape_assumption} ;
9
- use crate :: backend:: lir:: { self , asm_comment, Assembler , Opnd , Target , CFP , C_ARG_OPNDS , C_RET_OPND , EC , SP } ;
10
- use crate :: hir:: { iseq_to_hir, Block , BlockId , BranchEdge , CallInfo , RangeType , SELF_PARAM_IDX } ;
9
+ use crate :: backend:: lir:: { self , asm_comment, Assembler , Opnd , Target , CFP , C_ARG_OPNDS , C_RET_OPND , EC , SP , NATIVE_STACK_PTR } ;
10
+ use crate :: hir:: { iseq_to_hir, Block , BlockId , BranchEdge , RangeType , SELF_PARAM_IDX } ;
11
11
use crate :: hir:: { Const , FrameState , Function , Insn , InsnId } ;
12
12
use crate :: hir_type:: { types:: Fixnum , Type } ;
13
13
use crate :: options:: get_option;
@@ -257,8 +257,10 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
257
257
Insn :: Jump ( branch) => return gen_jump ( jit, asm, branch) ,
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
- 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) ?,
260
+ Insn :: LookupMethod { self_val, method_id, state } => gen_lookup_method ( jit, asm, opnd ! ( self_val) , * method_id, & function. frame_state ( * state) ) ?,
261
+ Insn :: CallMethod { callable, cd, self_val, args, state } => gen_call_method ( jit, asm, opnd ! ( callable) , * cd, opnd ! ( self_val) , args, & function. frame_state ( * state) ) ?,
262
+ Insn :: CallCFunc { cfunc, self_val, args, .. } => gen_call_cfunc ( jit, asm, * cfunc, opnd ! ( self_val) , args) ?,
263
+ Insn :: CallIseq { iseq, self_val, args, .. } => gen_send_without_block_direct ( cb, jit, asm, * iseq, opnd ! ( self_val) , args) ?,
262
264
Insn :: Return { val } => return Some ( gen_return ( asm, opnd ! ( val) ) ?) ,
263
265
Insn :: FixnumAdd { left, right, state } => gen_fixnum_add ( jit, asm, opnd ! ( left) , opnd ! ( right) , & function. frame_state ( * state) ) ?,
264
266
Insn :: FixnumSub { left, right, state } => gen_fixnum_sub ( jit, asm, opnd ! ( left) , opnd ! ( right) , & function. frame_state ( * state) ) ?,
@@ -455,38 +457,109 @@ fn gen_if_false(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, branch:
455
457
}
456
458
457
459
/// Compile a dynamic dispatch without block
458
- fn gen_send_without_block (
460
+ fn gen_lookup_method (
459
461
jit : & mut JITState ,
460
462
asm : & mut Assembler ,
461
- call_info : & CallInfo ,
462
- cd : * const rb_call_data ,
463
+ recv : Opnd ,
464
+ method_id : ID ,
465
+ state : & FrameState ,
466
+ ) -> Option < lir:: Opnd > {
467
+ asm_comment ! ( asm, "get the class of the receiver with rb_obj_class" ) ;
468
+ let class = asm. ccall ( rb_obj_class as * const u8 , vec ! [ recv] ) ;
469
+ // TODO(max): Figure out if we need to do anything here to save state to CFP
470
+ let method_opnd = Opnd :: UImm ( method_id. 0 . into ( ) ) ;
471
+ asm_comment ! ( asm, "call rb_callable_method_entry" ) ;
472
+ let result = asm. ccall (
473
+ rb_callable_method_entry as * const u8 ,
474
+ vec ! [ class, method_opnd] ,
475
+ ) ;
476
+ asm. test ( result, result) ;
477
+ asm. jz ( side_exit ( jit, state) ?) ;
478
+ Some ( result)
479
+ }
480
+
481
+ fn gen_call_method (
482
+ jit : & mut JITState ,
483
+ asm : & mut Assembler ,
484
+ callable : Opnd ,
485
+ cd : CallDataPtr ,
486
+ recv : Opnd ,
487
+ args : & Vec < InsnId > ,
463
488
state : & FrameState ,
464
- self_val : & InsnId ,
489
+ ) -> Option < Opnd > {
490
+ // Don't push recv; it is passed in separately.
491
+ asm_comment ! ( asm, "make stack-allocated array of {} args" , args. len( ) ) ;
492
+ for & arg in args. iter ( ) . rev ( ) {
493
+ asm. cpush ( jit. get_opnd ( arg) ?) ;
494
+ }
495
+ // Save PC for GC
496
+ gen_save_pc ( asm, state) ;
497
+ // Call rb_zjit_vm_call0_no_splat, which will push a frame
498
+ // TODO(max): Figure out if we need to manually handle stack alignment and how to do it
499
+ let call_info = unsafe { rb_get_call_data_ci ( cd) } ;
500
+ let method_id = unsafe { rb_vm_ci_mid ( call_info) } ;
501
+ asm_comment ! ( asm, "get stack pointer" ) ;
502
+ let sp = asm. lea ( Opnd :: mem ( VALUE_BITS , NATIVE_STACK_PTR , 0 ) ) ;
503
+ asm_comment ! ( asm, "call rb_zjit_vm_call0_no_splat" ) ;
504
+ let result = asm. ccall (
505
+ rb_zjit_vm_call0_no_splat as * const u8 ,
506
+ vec ! [ EC , recv, Opnd :: UImm ( method_id. 0 ) , Opnd :: UImm ( args. len( ) . try_into( ) . unwrap( ) ) , sp, callable] ,
507
+ ) ;
508
+ // Pop all the args off the stack
509
+ asm_comment ! ( asm, "clear stack-allocated array of {} args" , args. len( ) ) ;
510
+ let new_sp = asm. add ( NATIVE_STACK_PTR , ( args. len ( ) * SIZEOF_VALUE ) . into ( ) ) ;
511
+ asm. mov ( NATIVE_STACK_PTR , new_sp) ;
512
+ Some ( result)
513
+ }
514
+
515
+ /// Compile an interpreter frame
516
+ fn gen_push_frame ( asm : & mut Assembler , recv : Opnd ) {
517
+ // Write to a callee CFP
518
+ fn cfp_opnd ( offset : i32 ) -> Opnd {
519
+ Opnd :: mem ( 64 , CFP , offset - ( RUBY_SIZEOF_CONTROL_FRAME as i32 ) )
520
+ }
521
+
522
+ asm_comment ! ( asm, "push callee control frame" ) ;
523
+ asm. mov ( cfp_opnd ( RUBY_OFFSET_CFP_SELF ) , recv) ;
524
+ // TODO: Write more fields as needed
525
+ }
526
+
527
+ fn gen_call_cfunc (
528
+ jit : & mut JITState ,
529
+ asm : & mut Assembler ,
530
+ cfunc : CFuncPtr ,
531
+ recv : Opnd ,
465
532
args : & Vec < InsnId > ,
466
533
) -> Option < lir:: Opnd > {
467
- // Spill the receiver and the arguments onto the stack. They need to be marked by GC and may be caller-saved registers.
468
- // TODO: Avoid spilling operands that have been spilled before.
469
- for ( idx, & insn_id) in [ * self_val] . iter ( ) . chain ( args. iter ( ) ) . enumerate ( ) {
470
- // Currently, we don't move the SP register. So it's equal to the base pointer.
471
- let stack_opnd = Opnd :: mem ( 64 , SP , idx as i32 * SIZEOF_VALUE_I32 ) ;
472
- asm. mov ( stack_opnd, jit. get_opnd ( insn_id) ?) ;
534
+ let cfunc_argc = unsafe { get_mct_argc ( cfunc) } ;
535
+ // NB: The presence of self is assumed (no need for +1).
536
+ if args. len ( ) != cfunc_argc as usize {
537
+ // TODO(max): We should check this at compile-time. If we have an arity mismatch at this
538
+ // point, we should side-exit (we're definitely going to raise) and if we don't, we should
539
+ // not check anything.
540
+ todo ! ( "Arity mismatch" ) ;
473
541
}
474
542
475
- // Save PC and SP
476
- gen_save_pc ( asm, state) ;
477
- gen_save_sp ( asm, 1 + args. len ( ) ) ; // +1 for receiver
543
+ // Set up the new frame
544
+ gen_push_frame ( asm, recv) ;
545
+
546
+ asm_comment ! ( asm, "switch to new CFP" ) ;
547
+ let new_cfp = asm. sub ( CFP , RUBY_SIZEOF_CONTROL_FRAME . into ( ) ) ;
548
+ asm. mov ( CFP , new_cfp) ;
549
+ asm. store ( Opnd :: mem ( 64 , EC , RUBY_OFFSET_EC_CFP ) , CFP ) ;
478
550
479
- asm_comment ! ( asm, "call #{} with dynamic dispatch" , call_info. method_name) ;
480
- unsafe extern "C" {
481
- fn rb_vm_opt_send_without_block ( ec : EcPtr , cfp : CfpPtr , cd : VALUE ) -> VALUE ;
551
+ // Set up arguments
552
+ let mut c_args: Vec < Opnd > = vec ! [ recv] ;
553
+ for & arg in args. iter ( ) {
554
+ c_args. push ( jit. get_opnd ( arg) ?) ;
482
555
}
483
- let ret = asm. ccall (
484
- rb_vm_opt_send_without_block as * const u8 ,
485
- vec ! [ EC , CFP , ( cd as usize ) . into( ) ] ,
486
- ) ;
556
+
557
+ // Make a method call. The target address will be rewritten once compiled.
558
+ let cfun = unsafe { get_mct_func ( cfunc) } . cast ( ) ;
487
559
// TODO(max): Add a PatchPoint here that can side-exit the function if the callee messed with
488
560
// the frame's locals
489
-
561
+ let ret = asm. ccall ( cfun, c_args) ;
562
+ gen_pop_frame ( asm) ;
490
563
Some ( ret)
491
564
}
492
565
@@ -590,14 +663,18 @@ fn gen_new_range(
590
663
new_range
591
664
}
592
665
593
- /// Compile code that exits from JIT code with a return value
594
- fn gen_return ( asm : & mut Assembler , val : lir:: Opnd ) -> Option < ( ) > {
666
+ fn gen_pop_frame ( asm : & mut Assembler ) {
595
667
// Pop the current frame (ec->cfp++)
596
668
// Note: the return PC is already in the previous CFP
597
669
asm_comment ! ( asm, "pop stack frame" ) ;
598
670
let incr_cfp = asm. add ( CFP , RUBY_SIZEOF_CONTROL_FRAME . into ( ) ) ;
599
671
asm. mov ( CFP , incr_cfp) ;
600
672
asm. mov ( Opnd :: mem ( 64 , EC , RUBY_OFFSET_EC_CFP ) , CFP ) ;
673
+ }
674
+
675
+ /// Compile code that exits from JIT code with a return value
676
+ fn gen_return ( asm : & mut Assembler , val : lir:: Opnd ) -> Option < ( ) > {
677
+ gen_pop_frame ( asm) ;
601
678
602
679
asm. frame_teardown ( ) ;
603
680
0 commit comments