@@ -6140,18 +6140,16 @@ fn gen_send_cfunc(
6140
6140
let cfunc_argc = unsafe { get_mct_argc ( cfunc) } ;
6141
6141
let mut argc = argc;
6142
6142
6143
+ // Splat call to a C method that takes `VALUE *` and `len`
6144
+ let variable_splat = flags & VM_CALL_ARGS_SPLAT != 0 && cfunc_argc == -1 ;
6145
+ let block_arg = flags & VM_CALL_ARGS_BLOCKARG != 0 ;
6146
+
6143
6147
// If the function expects a Ruby array of arguments
6144
6148
if cfunc_argc < 0 && cfunc_argc != -1 {
6145
6149
gen_counter_incr ( asm, Counter :: send_cfunc_ruby_array_varg) ;
6146
6150
return None ;
6147
6151
}
6148
6152
6149
- // We aren't handling a vararg cfuncs with splat currently.
6150
- if flags & VM_CALL_ARGS_SPLAT != 0 && cfunc_argc == -1 {
6151
- gen_counter_incr ( asm, Counter :: send_args_splat_cfunc_var_args) ;
6152
- return None ;
6153
- }
6154
-
6155
6153
exit_if_kwsplat_non_nil ( asm, flags, Counter :: send_cfunc_kw_splat_non_nil) ?;
6156
6154
let kw_splat = flags & VM_CALL_KW_SPLAT != 0 ;
6157
6155
@@ -6217,6 +6215,18 @@ fn gen_send_cfunc(
6217
6215
asm. cmp ( CFP , stack_limit) ;
6218
6216
asm. jbe ( Target :: side_exit ( Counter :: guard_send_se_cf_overflow) ) ;
6219
6217
6218
+ // Guard for variable length splat call before any modifications to the stack
6219
+ if variable_splat {
6220
+ let splat_array = asm. stack_opnd ( i32:: from ( kw_splat) + i32:: from ( block_arg) ) ;
6221
+ guard_object_is_array ( asm, splat_array, splat_array. into ( ) , Counter :: guard_send_splat_not_array) ;
6222
+
6223
+ asm_comment ! ( asm, "guard variable length splat call servicable" ) ;
6224
+ let sp = asm. ctx . sp_opnd ( 0 ) ;
6225
+ let proceed = asm. ccall ( rb_yjit_splat_varg_checks as _ , vec ! [ sp, splat_array, CFP ] ) ;
6226
+ asm. cmp ( proceed, Qfalse . into ( ) ) ;
6227
+ asm. je ( Target :: side_exit ( Counter :: guard_send_cfunc_bad_splat_vargs) ) ;
6228
+ }
6229
+
6220
6230
// Number of args which will be passed through to the callee
6221
6231
// This is adjusted by the kwargs being combined into a hash.
6222
6232
let mut passed_argc = if kw_arg. is_null ( ) {
@@ -6242,7 +6252,6 @@ fn gen_send_cfunc(
6242
6252
return None ;
6243
6253
}
6244
6254
6245
- let block_arg = flags & VM_CALL_ARGS_BLOCKARG != 0 ;
6246
6255
let block_arg_type = if block_arg {
6247
6256
Some ( asm. ctx . get_opnd_type ( StackOpnd ( 0 ) ) )
6248
6257
} else {
@@ -6287,9 +6296,9 @@ fn gen_send_cfunc(
6287
6296
argc -= 1 ;
6288
6297
}
6289
6298
6290
- // push_splat_args does stack manipulation so we can no longer side exit
6291
- if flags & VM_CALL_ARGS_SPLAT != 0 {
6292
- assert ! ( cfunc_argc >= 0 ) ;
6299
+ // Splat handling when C method takes a static number of arguments.
6300
+ // push_splat_args() does stack manipulation so we can no longer side exit
6301
+ if flags & VM_CALL_ARGS_SPLAT != 0 && cfunc_argc >= 0 {
6293
6302
let required_args : u32 = ( cfunc_argc as u32 ) . saturating_sub ( argc as u32 - 1 ) ;
6294
6303
// + 1 because we pass self
6295
6304
if required_args + 1 >= C_ARG_OPNDS . len ( ) as u32 {
@@ -6312,15 +6321,34 @@ fn gen_send_cfunc(
6312
6321
handle_opt_send_shift_stack ( asm, argc) ;
6313
6322
}
6314
6323
6324
+ // Push a dynamic number of items from the splat array to the stack when calling a vargs method
6325
+ let dynamic_splat_size = if variable_splat {
6326
+ asm_comment ! ( asm, "variable length splat" ) ;
6327
+ let just_splat = usize:: from ( !kw_splat && kw_arg. is_null ( ) ) . into ( ) ;
6328
+ let stack_splat_array = asm. lea ( asm. stack_opnd ( 0 ) ) ;
6329
+ Some ( asm. ccall ( rb_yjit_splat_varg_cfunc as _ , vec ! [ stack_splat_array, just_splat] ) )
6330
+ } else {
6331
+ None
6332
+ } ;
6333
+
6315
6334
// Points to the receiver operand on the stack
6316
6335
let recv = asm. stack_opnd ( argc) ;
6317
6336
6318
6337
// Store incremented PC into current control frame in case callee raises.
6319
6338
jit_save_pc ( jit, asm) ;
6320
6339
6321
- // Increment the stack pointer by 3 (in the callee)
6322
- // sp += 3
6323
- let sp = asm. lea ( asm. ctx . sp_opnd ( SIZEOF_VALUE_I32 * 3 ) ) ;
6340
+ // Find callee's SP with space for metadata.
6341
+ // Usually sp+3.
6342
+ let sp = if let Some ( splat_size) = dynamic_splat_size {
6343
+ // Compute the callee's SP at runtime in case we accept a variable size for the splat array
6344
+ const _: ( ) = assert ! ( SIZEOF_VALUE == 8 , "opting for a shift since mul on A64 takes no immediates" ) ;
6345
+ let splat_size_bytes = asm. lshift ( splat_size, 3usize . into ( ) ) ;
6346
+ // 3 items for method metadata, minus one to remove the splat array
6347
+ let static_stack_top = asm. lea ( asm. ctx . sp_opnd ( SIZEOF_VALUE_I32 * 2 ) ) ;
6348
+ asm. add ( static_stack_top, splat_size_bytes)
6349
+ } else {
6350
+ asm. lea ( asm. ctx . sp_opnd ( SIZEOF_VALUE_I32 * 3 ) )
6351
+ } ;
6324
6352
6325
6353
let specval = if block_arg_type == Some ( Type :: BlockParamProxy ) {
6326
6354
SpecVal :: BlockHandler ( Some ( BlockHandler :: BlockParamProxy ) )
@@ -6382,8 +6410,17 @@ fn gen_send_cfunc(
6382
6410
else if cfunc_argc == -1 {
6383
6411
// The method gets a pointer to the first argument
6384
6412
// rb_f_puts(int argc, VALUE *argv, VALUE recv)
6413
+
6414
+ let passed_argc_opnd = if let Some ( splat_size) = dynamic_splat_size {
6415
+ // The final argc is the size of the splat, minus one for the splat array itself
6416
+ asm. add ( splat_size, ( passed_argc - 1 ) . into ( ) )
6417
+ } else {
6418
+ // Without a splat, passed_argc is static
6419
+ Opnd :: Imm ( passed_argc. into ( ) )
6420
+ } ;
6421
+
6385
6422
vec ! [
6386
- Opnd :: Imm ( passed_argc . into ( ) ) ,
6423
+ passed_argc_opnd ,
6387
6424
asm. lea( asm. ctx. sp_opnd( -argc * SIZEOF_VALUE_I32 ) ) ,
6388
6425
asm. stack_opnd( argc) ,
6389
6426
]
0 commit comments