Skip to content

Commit

Permalink
YJIT: Support empty splat
Browse files Browse the repository at this point in the history
Previously we rejected empty splat calls to methods with no parameters
as `iseq_arity_error` which didn't work well with delegated calls.
  • Loading branch information
XrXr committed Feb 16, 2024
1 parent c4e30d2 commit b4327c1
Show file tree
Hide file tree
Showing 2 changed files with 18 additions and 6 deletions.
10 changes: 10 additions & 0 deletions test/ruby/test_yjit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1584,6 +1584,16 @@ def use = [fw(:ok), kw_fw(:ok)]
RUBY
end

def test_empty_splat
assert_compiles(<<~'RUBY', result: %i[ok ok], no_send_fallbacks: true)
def foo = :ok
def fw(...) = foo(...)
def use(empty) = [foo(*empty), fw]
use([])
RUBY
end

private

def code_gc_helpers
Expand Down
14 changes: 8 additions & 6 deletions yjit/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6522,6 +6522,7 @@ fn gen_send_iseq(
let iseq_has_block_param = unsafe { get_iseq_flags_has_block(iseq) };
let arg_setup_block = captured_opnd.is_some(); // arg_setup_type: arg_setup_block (invokeblock)
let kw_splat = flags & VM_CALL_KW_SPLAT != 0;
let splat_call = flags & VM_CALL_ARGS_SPLAT != 0;

// For computing offsets to callee locals
let num_params = unsafe { get_iseq_body_param_size(iseq) as i32 };
Expand All @@ -6539,8 +6540,9 @@ fn gen_send_iseq(
unsafe { get_cikw_keyword_len(kw_arg) }
};

// Arity handling and optional parameter setup
let mut opts_filled = argc - required_num - kw_arg_num - i32::from(kw_splat);
// Arity handling and optional parameter setup for positional arguments.
// Splats are handled later.
let mut opts_filled = argc - required_num - kw_arg_num - i32::from(kw_splat) - i32::from(splat_call);
let opt_num = unsafe { get_iseq_body_param_opt_num(iseq) };
// With a rest parameter or a yield to a block,
// callers can pass more than required + optional.
Expand Down Expand Up @@ -6594,7 +6596,7 @@ fn gen_send_iseq(
gen_iseq_kw_call_checks(asm, iseq, kw_arg, has_kwrest, kw_arg_num)?;
}

let splat_array_length = if flags & VM_CALL_ARGS_SPLAT != 0 {
let splat_array_length = if splat_call {
let array = jit.peek_at_stack(&asm.ctx, splat_pos as isize);
let array_length = if array == Qnil {
0
Expand Down Expand Up @@ -6648,7 +6650,7 @@ fn gen_send_iseq(
if block_arg0_splat {
// If block_arg0_splat, we still need side exits after splat, but
// the splat modifies the stack which breaks side exits. So bail out.
if flags & VM_CALL_ARGS_SPLAT != 0 {
if splat_call {
gen_counter_incr(asm, Counter::invokeblock_iseq_arg0_args_splat);
return None;
}
Expand Down Expand Up @@ -6772,7 +6774,7 @@ fn gen_send_iseq(
asm.cmp(CFP, stack_limit);
asm.jbe(Target::side_exit(Counter::guard_send_se_cf_overflow));

if iseq_has_rest && flags & VM_CALL_ARGS_SPLAT != 0 {
if iseq_has_rest && splat_call {
// Insert length guard for a call to copy_splat_args_for_rest_callee()
// that will come later. We will have made changes to
// the stack by spilling or handling __send__ shifting
Expand Down Expand Up @@ -6879,7 +6881,7 @@ fn gen_send_iseq(
jit_save_pc(jit, asm);
gen_save_sp(asm);

let rest_param_array = if flags & VM_CALL_ARGS_SPLAT != 0 {
let rest_param_array = if splat_call {
let non_rest_arg_count = argc - 1;
// We start by dupping the array because someone else might have
// a reference to it. This also normalizes to an ::Array instance.
Expand Down

0 comments on commit b4327c1

Please sign in to comment.