@@ -6117,6 +6117,7 @@ fn gen_send_iseq(
6117
6117
let supplying_kws = unsafe { vm_ci_flag ( ci) & VM_CALL_KWARG } != 0 ;
6118
6118
let iseq_has_rest = unsafe { get_iseq_flags_has_rest ( iseq) } ;
6119
6119
let iseq_has_block_param = unsafe { get_iseq_flags_has_block ( iseq) } ;
6120
+ let arg_setup_block = captured_opnd. is_some ( ) ; // arg_setup_type: arg_setup_block (invokeblock)
6120
6121
6121
6122
// For computing offsets to callee locals
6122
6123
let num_params = unsafe { get_iseq_body_param_size ( iseq) } ;
@@ -6137,10 +6138,10 @@ fn gen_send_iseq(
6137
6138
// Arity handling and optional parameter setup
6138
6139
let mut opts_filled = argc - required_num - kw_arg_num;
6139
6140
let opt_num = unsafe { get_iseq_body_param_opt_num ( iseq) } ;
6140
- // We have a rest parameter so there could be more args
6141
- // than are required + optional. Those will go in rest .
6141
+ // With a rest parameter or a yield to a block,
6142
+ // callers can pass more than required + optional .
6142
6143
// So we cap ops_filled at opt_num.
6143
- if iseq_has_rest {
6144
+ if iseq_has_rest || arg_setup_block {
6144
6145
opts_filled = min ( opts_filled, opt_num) ;
6145
6146
}
6146
6147
let mut opts_missing: i32 = opt_num - opts_filled;
@@ -6159,11 +6160,17 @@ fn gen_send_iseq(
6159
6160
exit_if_supplying_kws_and_accept_no_kwargs ( asm, supplying_kws, iseq) ?;
6160
6161
exit_if_splat_and_zsuper ( asm, flags) ?;
6161
6162
exit_if_doing_kw_and_splat ( asm, doing_kw_call, flags) ?;
6162
- exit_if_wrong_number_arguments ( asm, opts_filled, flags, opt_num, iseq_has_rest) ?;
6163
+ exit_if_wrong_number_arguments ( asm, arg_setup_block , opts_filled, flags, opt_num, iseq_has_rest) ?;
6163
6164
exit_if_doing_kw_and_opts_missing ( asm, doing_kw_call, opts_missing) ?;
6164
6165
exit_if_has_rest_and_optional_and_block ( asm, iseq_has_rest, opt_num, iseq, block_arg) ?;
6165
6166
let block_arg_type = exit_if_unsupported_block_arg_type ( jit, asm, block_arg) ?;
6166
6167
6168
+ // Bail if we can't drop extra arguments for a yield by just popping them
6169
+ if supplying_kws && arg_setup_block && argc > ( kw_arg_num + required_num + opt_num) {
6170
+ gen_counter_incr ( asm, Counter :: send_iseq_complex_discard_extras) ;
6171
+ return None ;
6172
+ }
6173
+
6167
6174
// Block parameter handling. This mirrors setup_parameters_complex().
6168
6175
if iseq_has_block_param {
6169
6176
if unsafe { get_iseq_body_local_iseq ( iseq) == iseq } {
@@ -6249,35 +6256,6 @@ fn gen_send_iseq(
6249
6256
}
6250
6257
}
6251
6258
6252
- // Check if we need the arg0 splat handling of vm_callee_setup_block_arg()
6253
- // Also known as "autosplat" inside setup_parameters_complex()
6254
- let arg_setup_block = captured_opnd. is_some ( ) ; // arg_setup_type: arg_setup_block (invokeblock)
6255
- let block_arg0_splat = arg_setup_block && argc == 1 && unsafe {
6256
- ( get_iseq_flags_has_lead ( iseq) || opt_num > 1 )
6257
- && !get_iseq_flags_ambiguous_param0 ( iseq)
6258
- } ;
6259
- if block_arg0_splat {
6260
- // If block_arg0_splat, we still need side exits after splat, but
6261
- // doing push_splat_args here disallows it. So bail out.
6262
- if flags & VM_CALL_ARGS_SPLAT != 0 && !iseq_has_rest {
6263
- gen_counter_incr ( asm, Counter :: invokeblock_iseq_arg0_args_splat) ;
6264
- return None ;
6265
- }
6266
- // The block_arg0_splat implementation is for the rb_simple_iseq_p case,
6267
- // but doing_kw_call means it's not a simple ISEQ.
6268
- if doing_kw_call {
6269
- gen_counter_incr ( asm, Counter :: invokeblock_iseq_arg0_has_kw) ;
6270
- return None ;
6271
- }
6272
- // The block_arg0_splat implementation cannot deal with optional parameters.
6273
- // This is a setup_parameters_complex() situation and interacts with the
6274
- // starting position of the callee.
6275
- if opt_num > 1 {
6276
- gen_counter_incr ( asm, Counter :: invokeblock_iseq_arg0_optional) ;
6277
- return None ;
6278
- }
6279
- }
6280
-
6281
6259
let splat_array_length = if flags & VM_CALL_ARGS_SPLAT != 0 {
6282
6260
let array = jit. peek_at_stack ( & asm. ctx , if block_arg { 1 } else { 0 } ) ;
6283
6261
let array_length = if array == Qnil {
@@ -6318,6 +6296,33 @@ fn gen_send_iseq(
6318
6296
None
6319
6297
} ;
6320
6298
6299
+ // Check if we need the arg0 splat handling of vm_callee_setup_block_arg()
6300
+ // Also known as "autosplat" inside setup_parameters_complex().
6301
+ // Autosplat checks argc == 1 after splat and kwsplat processing, so make
6302
+ // sure to amend this if we start support kw_splat.
6303
+ let block_arg0_splat = arg_setup_block
6304
+ && ( argc == 1 || ( argc == 2 && splat_array_length == Some ( 0 ) ) )
6305
+ && !supplying_kws && !doing_kw_call
6306
+ && unsafe {
6307
+ ( get_iseq_flags_has_lead ( iseq) || opt_num > 1 )
6308
+ && !get_iseq_flags_ambiguous_param0 ( iseq)
6309
+ } ;
6310
+ if block_arg0_splat {
6311
+ // If block_arg0_splat, we still need side exits after splat, but
6312
+ // the splat modifies the stack which breaks side exits. So bail out.
6313
+ if flags & VM_CALL_ARGS_SPLAT != 0 {
6314
+ gen_counter_incr ( asm, Counter :: invokeblock_iseq_arg0_args_splat) ;
6315
+ return None ;
6316
+ }
6317
+ // The block_arg0_splat implementation cannot deal with optional parameters.
6318
+ // This is a setup_parameters_complex() situation and interacts with the
6319
+ // starting position of the callee.
6320
+ if opt_num > 1 {
6321
+ gen_counter_incr ( asm, Counter :: invokeblock_iseq_arg0_optional) ;
6322
+ return None ;
6323
+ }
6324
+ }
6325
+
6321
6326
// Adjust `opts_filled` and `opts_missing` taking
6322
6327
// into account the size of the splat expansion.
6323
6328
if let Some ( len) = splat_array_length {
@@ -6605,6 +6610,19 @@ fn gen_send_iseq(
6605
6610
asm. store ( rest_param, rest_param_array) ;
6606
6611
}
6607
6612
6613
+ // Pop surplus positional arguments when yielding
6614
+ if arg_setup_block {
6615
+ let extras = argc - required_num - opt_num;
6616
+ if extras > 0 {
6617
+ // Checked earlier. If there are keyword args, then
6618
+ // the positional arguments are not at the stack top.
6619
+ assert_eq ! ( 0 , kw_arg_num) ;
6620
+
6621
+ asm. stack_pop ( extras as usize ) ;
6622
+ argc = required_num + opt_num;
6623
+ }
6624
+ }
6625
+
6608
6626
if doing_kw_call {
6609
6627
// Here we're calling a method with keyword arguments and specifying
6610
6628
// keyword arguments at this call site.
@@ -7034,11 +7052,18 @@ fn exit_if_doing_kw_and_splat(asm: &mut Assembler, doing_kw_call: bool, flags: u
7034
7052
}
7035
7053
7036
7054
#[ must_use]
7037
- fn exit_if_wrong_number_arguments ( asm : & mut Assembler , opts_filled : i32 , flags : u32 , opt_num : i32 , iseq_has_rest : bool ) -> Option < ( ) > {
7055
+ fn exit_if_wrong_number_arguments (
7056
+ asm : & mut Assembler ,
7057
+ args_setup_block : bool ,
7058
+ opts_filled : i32 ,
7059
+ flags : u32 ,
7060
+ opt_num : i32 ,
7061
+ iseq_has_rest : bool ,
7062
+ ) -> Option < ( ) > {
7038
7063
// Too few arguments and no splat to make up for it
7039
7064
let too_few = opts_filled < 0 && flags & VM_CALL_ARGS_SPLAT == 0 ;
7040
- // Too many arguments and no place to put them (i.e. rest arg)
7041
- let too_many = opts_filled > opt_num && !iseq_has_rest;
7065
+ // Too many arguments and no sink that take them
7066
+ let too_many = opts_filled > opt_num && !( iseq_has_rest || args_setup_block ) ;
7042
7067
7043
7068
exit_if ( asm, too_few || too_many, Counter :: send_iseq_arity_error)
7044
7069
}
0 commit comments