@@ -612,7 +612,10 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
612612 & Insn :: Send { cd, blockiseq : None , state, reason, .. } => gen_send_without_block ( jit, asm, cd, & function. frame_state ( state) , reason) ,
613613 & Insn :: Send { cd, blockiseq : Some ( blockiseq) , state, reason, .. } => gen_send ( jit, asm, cd, blockiseq, & function. frame_state ( state) , reason) ,
614614 & Insn :: SendForward { cd, blockiseq, state, reason, .. } => gen_send_forward ( jit, asm, cd, blockiseq, & function. frame_state ( state) , reason) ,
615- Insn :: SendDirect { cme, iseq, recv, args, kw_bits, blockiseq, state, .. } => gen_send_iseq_direct ( cb, jit, asm, * cme, * iseq, opnd ! ( recv) , opnds ! ( args) , * kw_bits, & function. frame_state ( * state) , * blockiseq) ,
615+ & Insn :: SendDirect { cme, iseq, recv, ref args, kw_bits, blockiseq, state, .. } => gen_send_iseq_direct (
616+ cb, jit, asm, cme, iseq, opnd ! ( recv) , opnds ! ( args) ,
617+ kw_bits, & function. frame_state ( state) , blockiseq,
618+ ) ,
616619 & Insn :: InvokeSuper { cd, blockiseq, state, reason, .. } => gen_invokesuper ( jit, asm, cd, blockiseq, & function. frame_state ( state) , reason) ,
617620 & Insn :: InvokeSuperForward { cd, blockiseq, state, reason, .. } => gen_invokesuperforward ( jit, asm, cd, blockiseq, & function. frame_state ( state) , reason) ,
618621 & Insn :: InvokeBlock { cd, state, reason, .. } => gen_invokeblock ( jit, asm, cd, & function. frame_state ( state) , reason) ,
@@ -1652,7 +1655,7 @@ fn gen_send_iseq_direct(
16521655 } ;
16531656
16541657 // Make a method call. The target address will be rewritten once compiled.
1655- let iseq_call = IseqCall :: new ( iseq, num_optionals_passed) ;
1658+ let iseq_call = IseqCall :: new ( iseq, num_optionals_passed. try_into ( ) . expect ( "checked in HIR" ) , args . len ( ) . try_into ( ) . expect ( "checked in HIR" ) ) ;
16561659 let dummy_ptr = cb. get_write_ptr ( ) . raw_ptr ( cb) ;
16571660 jit. iseq_calls . push ( iseq_call. clone ( ) ) ;
16581661 let ret = asm. ccall_with_iseq_call ( dummy_ptr, c_args, & iseq_call) ;
@@ -2964,22 +2967,71 @@ c_callable! {
29642967 // function_stub_hit_body() may allocate and call gc_validate_pc(), so we always set PC.
29652968 let iseq_call = unsafe { Rc :: from_raw( iseq_call_ptr as * const IseqCall ) } ;
29662969 let iseq = iseq_call. iseq. get( ) ;
2970+ let argc = iseq_call. argc;
2971+ let num_opts_filled = iseq_call. jit_entry_idx;
29672972 let entry_insn_idxs = crate :: hir:: jit_entry_insns( iseq) ;
29682973 let pc = unsafe { rb_iseq_pc_at_idx( iseq, entry_insn_idxs[ iseq_call. jit_entry_idx. to_usize( ) ] ) } ;
29692974 unsafe { rb_set_cfp_pc( cfp, pc) } ;
29702975
29712976 // JIT-to-JIT calls don't eagerly fill nils to non-parameter locals.
29722977 // If we side-exit from function_stub_hit (before JIT code runs), we need to set them here.
2973- fn prepare_for_exit( iseq: IseqPtr , cfp: CfpPtr , sp: * mut VALUE , compile_error: & CompileError ) {
2978+ fn prepare_for_exit( iseq: IseqPtr , cfp: CfpPtr , sp: * mut VALUE , argc : u16 , num_opts_filled : u16 , compile_error: & CompileError ) {
29742979 unsafe {
29752980 // Set SP which gen_push_frame() doesn't set
29762981 rb_set_cfp_sp( cfp, sp) ;
29772982
2978- // Fill nils to uninitialized (non-argument) locals
29792983 let local_size = get_iseq_body_local_table_size( iseq) . to_usize( ) ;
2980- let num_params = iseq. params( ) . size. to_usize( ) ;
2981- let base = sp. offset( -local_size_and_idx_to_bp_offset( local_size, num_params) as isize ) ;
2982- slice:: from_raw_parts_mut( base, local_size - num_params) . fill( Qnil ) ;
2984+ let params = iseq. params( ) ;
2985+ let params_size = params. size. to_usize( ) ;
2986+ let frame_base = sp. offset( -local_size_and_idx_to_bp_offset( local_size, 0 ) as isize ) ;
2987+ let locals = slice:: from_raw_parts_mut( frame_base, local_size) ;
2988+ // Fill nils to uninitialized (non-parameter) locals
2989+ locals. get_mut( params_size..) . unwrap_or_default( ) . fill( Qnil ) ;
2990+
2991+ // SendDirect packs args without gaps for unfilled optionals.
2992+ // When we exit to the interpreter, we need to shift args right
2993+ // to create the gap and nil-fill the unfilled optional slots.
2994+ //
2995+ // Example: def target(req, a = a, b = b, kw:); target(1, kw: 2)
2996+ // lead_num=1, opt_num=2, opts_filled=0, argc=2
2997+ //
2998+ // locals[] as placed by SendDirect (argc=2, no gaps):
2999+ // [req, kw_val, ?, ?, ?, ...]
3000+ // 0 1
3001+ // ^----caller's args----^
3002+ //
3003+ // locals[] expected by interpreter (params_size=4):
3004+ // [req, a, b, kw_val, ?, ...]
3005+ // 0 1 2 3
3006+ // ^nil ^nil^--moved--^
3007+ //
3008+ // gap_start = lead_num + opts_filled = 1
3009+ // gap_end = lead_num + opt_num = 3
3010+ // We move locals[gap_start..argc] to locals[gap_end..], then
3011+ // nil-fill locals[gap_start..gap_end].
3012+ let opt_num: usize = params. opt_num. try_into( ) . expect( "ISEQ opt_num should be non-negative" ) ;
3013+ let opts_filled = num_opts_filled. to_usize( ) ;
3014+ let opts_unfilled = opt_num. saturating_sub( opts_filled) ;
3015+ if opts_unfilled > 0 {
3016+ let argc = argc. to_usize( ) ;
3017+ let lead_num: usize = params. lead_num. try_into( ) . expect( "ISEQ lead_num should be non-negative" ) ;
3018+ let param_locals = & mut locals[ ..params_size] ;
3019+ // Gap of unspecified optional parameters
3020+ let gap_start = lead_num + opts_filled;
3021+ let gap_end = lead_num + opt_num;
3022+ // When there are arguments in the gap, shift them past the gap
3023+ let args_overlapping_gap = gap_start..argc;
3024+ if !args_overlapping_gap. is_empty( ) {
3025+ assert!(
3026+ gap_end. checked_add( args_overlapping_gap. len( ) )
3027+ . is_some_and( |new_end| new_end <= param_locals. len( ) ) ,
3028+ "shift past gap out-of-bounds. params={params:#?} args_overlapping_gap={args_overlapping_gap:?}"
3029+ ) ;
3030+ param_locals. copy_within( args_overlapping_gap, gap_end) ;
3031+ }
3032+ // Nil-fill the now-vacant optional parameter slots
3033+ param_locals[ gap_start..gap_end] . fill( Qnil ) ;
3034+ }
29833035 }
29843036
29853037 // Increment a compile error counter for --zjit-stats
@@ -3003,7 +3055,7 @@ c_callable! {
30033055 // We'll use this Rc again, so increment the ref count decremented by from_raw.
30043056 unsafe { Rc :: increment_strong_count( iseq_call_ptr as * const IseqCall ) ; }
30053057
3006- prepare_for_exit( iseq, cfp, sp, compile_error) ;
3058+ prepare_for_exit( iseq, cfp, sp, argc , num_opts_filled , compile_error) ;
30073059 return ZJITState :: get_exit_trampoline_with_counter( ) . raw_ptr( cb) ;
30083060 }
30093061
@@ -3018,7 +3070,7 @@ c_callable! {
30183070 // We'll use this Rc again, so increment the ref count decremented by from_raw.
30193071 unsafe { Rc :: increment_strong_count( iseq_call_ptr as * const IseqCall ) ; }
30203072
3021- prepare_for_exit( iseq, cfp, sp, & compile_error) ;
3073+ prepare_for_exit( iseq, cfp, sp, argc , num_opts_filled , & compile_error) ;
30223074 ZJITState :: get_exit_trampoline_with_counter( )
30233075 } ) ;
30243076 cb. mark_all_executable( ) ;
@@ -3343,7 +3395,10 @@ pub struct IseqCall {
33433395 pub iseq : Cell < IseqPtr > ,
33443396
33453397 /// Index that corresponds to [crate::hir::jit_entry_insns]
3346- jit_entry_idx : u32 ,
3398+ jit_entry_idx : u16 ,
3399+
3400+ /// Argument count passing to the HIR function
3401+ argc : u16 ,
33473402
33483403 /// Position where the call instruction starts
33493404 start_addr : Cell < Option < CodePtr > > ,
@@ -3356,12 +3411,13 @@ pub type IseqCallRef = Rc<IseqCall>;
33563411
33573412impl IseqCall {
33583413 /// Allocate a new IseqCall
3359- fn new ( iseq : IseqPtr , jit_entry_idx : u32 ) -> IseqCallRef {
3414+ fn new ( iseq : IseqPtr , jit_entry_idx : u16 , argc : u16 ) -> IseqCallRef {
33603415 let iseq_call = IseqCall {
33613416 iseq : Cell :: new ( iseq) ,
33623417 start_addr : Cell :: new ( None ) ,
33633418 end_addr : Cell :: new ( None ) ,
33643419 jit_entry_idx,
3420+ argc,
33653421 } ;
33663422 Rc :: new ( iseq_call)
33673423 }
0 commit comments