[YJIT] Fix block and splat handling when forwarding

This commit fixes splat and block handling when calling in to a
forwarding iseq.  In the case of a splat we need to avoid expanding the
array to the stack.  We need to also ensure the CI write is flushed to
the SP, otherwise it's possible for a block handler to clobber the CI

[ruby-core:118360]
This commit is contained in:
Aaron Patterson 2024-06-21 10:14:08 -07:00 коммит произвёл Aaron Patterson
Родитель 43d7db3828
Коммит 4cbc41d5e5
2 изменённых файлов: 78 добавлений и 9 удалений

Просмотреть файл

@ -5077,3 +5077,47 @@ assert_equal 'ok', <<~'RUBY'
:ok
RUBY
assert_equal 'ok', <<~'RUBY'
class MyRelation
def callee(...)
:ok
end
def uncached(...)
callee(...)
end
def takes_block(&block)
# push blockhandler
uncached(&block) # CI1
end
end
relation = MyRelation.new
relation.takes_block { }
RUBY
assert_equal 'ok', <<~'RUBY'
def _exec_scope(...)
instance_exec(...)
end
def ok args, body
_exec_scope(*args, &body)
end
ok([], -> { "ok" })
RUBY
assert_equal 'ok', <<~'RUBY'
def _exec_scope(...)
instance_exec(...)
end
def ok args, body
_exec_scope(*args, &body)
end
ok(["ok"], ->(x) { x })
RUBY

Просмотреть файл

@ -7116,9 +7116,18 @@ 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;
let forwarding_call = unsafe { rb_get_iseq_flags_forwardable(iseq) };
// Is this iseq tagged as "forwardable"? Iseqs that take `...` as a
// parameter are tagged as forwardable (e.g. `def foo(...); end`)
let forwarding = unsafe { rb_get_iseq_flags_forwardable(iseq) };
// If a "forwardable" iseq has been called with a splat, then we _do not_
// want to expand the splat to the stack. So we'll only consider this
// a splat call if the callee iseq is not forwardable. For example,
// we do not want to handle the following code:
//
// `def foo(...); end; foo(*blah)`
let splat_call = (flags & VM_CALL_ARGS_SPLAT != 0) && !forwarding;
// For computing offsets to callee locals
let num_params = unsafe { get_iseq_body_param_size(iseq) as i32 };
@ -7162,12 +7171,12 @@ fn gen_send_iseq(
exit_if_supplying_kw_and_has_no_kw(asm, supplying_kws, doing_kw_call)?;
exit_if_supplying_kws_and_accept_no_kwargs(asm, supplying_kws, iseq)?;
exit_if_doing_kw_and_splat(asm, doing_kw_call, flags)?;
if !forwarding_call {
if !forwarding {
exit_if_wrong_number_arguments(asm, arg_setup_block, opts_filled, flags, opt_num, iseq_has_rest)?;
}
exit_if_doing_kw_and_opts_missing(asm, doing_kw_call, opts_missing)?;
exit_if_has_rest_and_optional_and_block(asm, iseq_has_rest, opt_num, iseq, block_arg)?;
if forwarding_call && flags & VM_CALL_OPT_SEND != 0 {
if forwarding && flags & VM_CALL_OPT_SEND != 0 {
gen_counter_incr(asm, Counter::send_iseq_send_forwarding);
return None;
}
@ -7465,7 +7474,12 @@ fn gen_send_iseq(
Counter::guard_send_block_arg_type,
);
let callee_ep = -argc + num_locals + VM_ENV_DATA_SIZE as i32 - 1;
// If this is a forwardable iseq, adjust the stack size accordingly
let callee_ep = if forwarding {
-1 + num_locals + VM_ENV_DATA_SIZE as i32
} else {
-argc + num_locals + VM_ENV_DATA_SIZE as i32 - 1
};
let callee_specval = callee_ep + VM_ENV_DATA_INDEX_SPECVAL;
if callee_specval < 0 {
// Can't write to sp[-n] since that's where the arguments are
@ -7679,7 +7693,8 @@ fn gen_send_iseq(
}
}
if !forwarding_call {
// Don't nil fill forwarding iseqs
if !forwarding {
// Nil-initialize missing optional parameters
nil_fill(
"nil-initialize missing optionals",
@ -7709,9 +7724,13 @@ fn gen_send_iseq(
);
}
if forwarding_call {
if forwarding {
assert_eq!(1, num_params);
asm.mov(asm.stack_opnd(-1), VALUE(ci as usize).into());
// Write the CI in to the stack and ensure that it actually gets
// flushed to memory
let ci_opnd = asm.stack_opnd(-1);
asm.ctx.dealloc_temp_reg(ci_opnd.stack_idx());
asm.mov(ci_opnd, VALUE(ci as usize).into());
}
// Points to the receiver operand on the stack unless a captured environment is used
@ -7731,7 +7750,7 @@ fn gen_send_iseq(
jit_save_pc(jit, asm);
// Adjust the callee's stack pointer
let callee_sp = if forwarding_call {
let callee_sp = if forwarding {
let offs = num_locals + VM_ENV_DATA_SIZE as i32;
asm.lea(asm.ctx.sp_opnd(offs))
} else {
@ -7805,6 +7824,12 @@ fn gen_send_iseq(
callee_ctx.set_local_type(arg_idx.try_into().unwrap(), arg_type);
}
// If we're in a forwarding callee, there will be one unknown type
// written in to the local table (the caller's CI object)
if forwarding {
callee_ctx.set_local_type(0, Type::Unknown)
}
let recv_type = if captured_self {
Type::Unknown // we don't track the type information of captured->self for now
} else {