@@ -626,7 +626,10 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
626626 & Insn :: Send { cd, block : Some ( BlockHandler :: BlockIseq ( blockiseq) ) , state, reason, .. } => gen_send ( jit, asm, cd, blockiseq, & function. frame_state ( state) , reason) ,
627627 & Insn :: Send { cd, block : Some ( BlockHandler :: BlockArg ) , state, reason, .. } => gen_send ( jit, asm, cd, std:: ptr:: null ( ) , & function. frame_state ( state) , reason) ,
628628 & Insn :: SendForward { cd, blockiseq, state, reason, .. } => gen_send_forward ( jit, asm, cd, blockiseq, & function. frame_state ( state) , reason) ,
629- Insn :: SendDirect { cme, iseq, recv, args, kw_bits, block, state, .. } => gen_send_iseq_direct ( cb, jit, asm, * cme, * iseq, opnd ! ( recv) , opnds ! ( args) , * kw_bits, & function. frame_state ( * state) , * block) ,
629+ & Insn :: SendDirect { cme, iseq, recv, ref args, kw_bits, block, state, .. } => gen_send_iseq_direct (
630+ cb, jit, asm, cme, iseq, opnd ! ( recv) , opnds ! ( args) ,
631+ kw_bits, & function. frame_state ( state) , block,
632+ ) ,
630633 & Insn :: InvokeSuper { cd, blockiseq, state, reason, .. } => gen_invokesuper ( jit, asm, cd, blockiseq, & function. frame_state ( state) , reason) ,
631634 & Insn :: InvokeSuperForward { cd, blockiseq, state, reason, .. } => gen_invokesuperforward ( jit, asm, cd, blockiseq, & function. frame_state ( state) , reason) ,
632635 & Insn :: InvokeBlock { cd, state, reason, .. } => gen_invokeblock ( jit, asm, cd, & function. frame_state ( state) , reason) ,
@@ -1669,7 +1672,7 @@ fn gen_send_iseq_direct(
16691672 } ;
16701673
16711674 // Make a method call. The target address will be rewritten once compiled.
1672- let iseq_call = IseqCall :: new ( iseq, num_optionals_passed) ;
1675+ let iseq_call = IseqCall :: new ( iseq, num_optionals_passed. try_into ( ) . expect ( "checked in HIR" ) , args . len ( ) . try_into ( ) . expect ( "checked in HIR" ) ) ;
16731676 let dummy_ptr = cb. get_write_ptr ( ) . raw_ptr ( cb) ;
16741677 jit. iseq_calls . push ( iseq_call. clone ( ) ) ;
16751678 let ret = asm. ccall_with_iseq_call ( dummy_ptr, c_args, & iseq_call) ;
@@ -3081,22 +3084,71 @@ c_callable! {
30813084 // mutability (Cell<IseqPtr>) requires exclusive access.
30823085 let iseq_call = unsafe { Rc :: from_raw( iseq_call_ptr as * const IseqCall ) } ;
30833086 let iseq = iseq_call. iseq. get( ) ;
3087+ let argc = iseq_call. argc;
3088+ let num_opts_filled = iseq_call. jit_entry_idx;
30843089
30853090 // JIT-to-JIT calls don't eagerly fill nils to non-parameter locals.
30863091 // If we side-exit from function_stub_hit (before JIT code runs), we need to set them here.
3087- fn prepare_for_exit( iseq: IseqPtr , cfp: CfpPtr , sp: * mut VALUE , compile_error: & CompileError ) {
3092+ fn prepare_for_exit( iseq: IseqPtr , cfp: CfpPtr , sp: * mut VALUE , argc : u16 , num_opts_filled : u16 , compile_error: & CompileError ) {
30883093 unsafe {
30893094 // Caller frames are materialized by jit_exec() after the entry trampoline returns.
30903095 // The current frame's pc and iseq are already set by function_stub_hit before this point.
30913096
30923097 // Set SP which gen_push_frame() doesn't set
30933098 rb_set_cfp_sp( cfp, sp) ;
30943099
3095- // Fill nils to uninitialized (non-argument) locals
30963100 let local_size = get_iseq_body_local_table_size( iseq) . to_usize( ) ;
3097- let num_params = iseq. params( ) . size. to_usize( ) ;
3098- let base = sp. offset( -local_size_and_idx_to_bp_offset( local_size, num_params) as isize ) ;
3099- slice:: from_raw_parts_mut( base, local_size - num_params) . fill( Qnil ) ;
3101+ let params = iseq. params( ) ;
3102+ let params_size = params. size. to_usize( ) ;
3103+ let frame_base = sp. offset( -local_size_and_idx_to_bp_offset( local_size, 0 ) as isize ) ;
3104+ let locals = slice:: from_raw_parts_mut( frame_base, local_size) ;
3105+ // Fill nils to uninitialized (non-parameter) locals
3106+ locals. get_mut( params_size..) . unwrap_or_default( ) . fill( Qnil ) ;
3107+
3108+ // SendDirect packs args without gaps for unfilled optionals.
3109+ // When we exit to the interpreter, we need to shift args right
3110+ // to create the gap and nil-fill the unfilled optional slots.
3111+ //
3112+ // Example: def target(req, a = a, b = b, kw:); target(1, kw: 2)
3113+ // lead_num=1, opt_num=2, opts_filled=0, argc=2
3114+ //
3115+ // locals[] as placed by SendDirect (argc=2, no gaps):
3116+ // [req, kw_val, ?, ?, ?, ...]
3117+ // 0 1
3118+ // ^----caller's args----^
3119+ //
3120+ // locals[] expected by interpreter (params_size=4):
3121+ // [req, a, b, kw_val, ?, ...]
3122+ // 0 1 2 3
3123+ // ^nil ^nil^--moved--^
3124+ //
3125+ // gap_start = lead_num + opts_filled = 1
3126+ // gap_end = lead_num + opt_num = 3
3127+ // We move locals[gap_start..argc] to locals[gap_end..], then
3128+ // nil-fill locals[gap_start..gap_end].
3129+ let opt_num: usize = params. opt_num. try_into( ) . expect( "ISEQ opt_num should be non-negative" ) ;
3130+ let opts_filled = num_opts_filled. to_usize( ) ;
3131+ let opts_unfilled = opt_num. saturating_sub( opts_filled) ;
3132+ if opts_unfilled > 0 {
3133+ let argc = argc. to_usize( ) ;
3134+ let lead_num: usize = params. lead_num. try_into( ) . expect( "ISEQ lead_num should be non-negative" ) ;
3135+ let param_locals = & mut locals[ ..params_size] ;
3136+ // Gap of unspecified optional parameters
3137+ let gap_start = lead_num + opts_filled;
3138+ let gap_end = lead_num + opt_num;
3139+ // When there are arguments in the gap, shift them past the gap
3140+ let args_overlapping_gap = gap_start..argc;
3141+ if !args_overlapping_gap. is_empty( ) {
3142+ assert!(
3143+ gap_end. checked_add( args_overlapping_gap. len( ) )
3144+ . is_some_and( |new_end| new_end <= param_locals. len( ) ) ,
3145+ "shift past gap out-of-bounds. params={params:#?} args_overlapping_gap={args_overlapping_gap:?}"
3146+ ) ;
3147+ param_locals. copy_within( args_overlapping_gap, gap_end) ;
3148+ }
3149+ // Nil-fill the now-vacant optional parameter slots
3150+ param_locals[ gap_start..gap_end] . fill( Qnil ) ;
3151+ }
31003152 }
31013153
31023154 // Increment a compile error counter for --zjit-stats
@@ -3126,7 +3178,7 @@ c_callable! {
31263178 // We'll use this Rc again, so increment the ref count decremented by from_raw.
31273179 unsafe { Rc :: increment_strong_count( iseq_call_ptr as * const IseqCall ) ; }
31283180
3129- prepare_for_exit( iseq, cfp, sp, compile_error) ;
3181+ prepare_for_exit( iseq, cfp, sp, argc , num_opts_filled , compile_error) ;
31303182 return ZJITState :: get_exit_trampoline_with_counter( ) . raw_ptr( cb) ;
31313183 }
31323184
@@ -3141,7 +3193,7 @@ c_callable! {
31413193 // We'll use this Rc again, so increment the ref count decremented by from_raw.
31423194 unsafe { Rc :: increment_strong_count( iseq_call_ptr as * const IseqCall ) ; }
31433195
3144- prepare_for_exit( iseq, cfp, sp, & compile_error) ;
3196+ prepare_for_exit( iseq, cfp, sp, argc , num_opts_filled , & compile_error) ;
31453197 ZJITState :: get_exit_trampoline_with_counter( )
31463198 } ) ;
31473199 cb. mark_all_executable( ) ;
@@ -3466,7 +3518,10 @@ pub struct IseqCall {
34663518 pub iseq : Cell < IseqPtr > ,
34673519
34683520 /// Index that corresponds to [crate::hir::jit_entry_insns]
3469- jit_entry_idx : u32 ,
3521+ jit_entry_idx : u16 ,
3522+
3523+ /// Argument count passing to the HIR function
3524+ argc : u16 ,
34703525
34713526 /// Position where the call instruction starts
34723527 start_addr : Cell < Option < CodePtr > > ,
@@ -3479,12 +3534,13 @@ pub type IseqCallRef = Rc<IseqCall>;
34793534
34803535impl IseqCall {
34813536 /// Allocate a new IseqCall
3482- fn new ( iseq : IseqPtr , jit_entry_idx : u32 ) -> IseqCallRef {
3537+ fn new ( iseq : IseqPtr , jit_entry_idx : u16 , argc : u16 ) -> IseqCallRef {
34833538 let iseq_call = IseqCall {
34843539 iseq : Cell :: new ( iseq) ,
34853540 start_addr : Cell :: new ( None ) ,
34863541 end_addr : Cell :: new ( None ) ,
34873542 jit_entry_idx,
3543+ argc,
34883544 } ;
34893545 Rc :: new ( iseq_call)
34903546 }
0 commit comments