YJIT: Fix argument clobbering in some block_arg+rest_param calls (#8647)
Previously, for block argument callsites with some specific argument count and callee local variable count combinations, YJIT ended up writing over arguments that are supposed to be collected into a rest parameter array unmodified. Detect when clobbering would happen and avoid it. Also, place the block handler after the stack overflow check, since it writes to new stack space. Reported-by: Takashi Kokubun <takashikkbn@gmail.com>
This commit is contained in:
parent
511571b5ff
commit
0bf1749e9f
@ -1,3 +1,15 @@
|
|||||||
|
# regression test for callee block handler overlapping with arguments
|
||||||
|
assert_equal '3', %q{
|
||||||
|
def foo(_req, *args) = args.last
|
||||||
|
|
||||||
|
def call_foo = foo(0, 1, 2, 3, &->{})
|
||||||
|
|
||||||
|
call_foo
|
||||||
|
}
|
||||||
|
|
||||||
|
# call leaf builtin with a block argument
|
||||||
|
assert_equal '0', "0.abs(&nil)"
|
||||||
|
|
||||||
# regression test for invokeblock iseq guard
|
# regression test for invokeblock iseq guard
|
||||||
assert_equal 'ok', %q{
|
assert_equal 'ok', %q{
|
||||||
return :ok unless defined?(GC.compact)
|
return :ok unless defined?(GC.compact)
|
||||||
|
@ -6043,48 +6043,7 @@ fn gen_send_iseq(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We will not have None from here. You can use stack_pop / stack_pop.
|
// Shortcut for special `Primitive.attr! :leaf` builtins
|
||||||
|
|
||||||
match block_arg_type {
|
|
||||||
Some(Type::Nil) => {
|
|
||||||
// We have a nil block arg, so let's pop it off the args
|
|
||||||
asm.stack_pop(1);
|
|
||||||
}
|
|
||||||
Some(Type::BlockParamProxy) => {
|
|
||||||
// We don't need the actual stack value
|
|
||||||
asm.stack_pop(1);
|
|
||||||
}
|
|
||||||
Some(Type::TProc) => {
|
|
||||||
// Place the proc as the block handler. We do this early because
|
|
||||||
// the block arg being at the top of the stack gets in the way of
|
|
||||||
// rest param handling later. Also, since there are C calls that
|
|
||||||
// come later, we can't hold this value in a register and place it
|
|
||||||
// near the end when we push a new control frame.
|
|
||||||
asm_comment!(asm, "guard block arg is a proc");
|
|
||||||
// Simple predicate, no need for jit_prepare_routine_call().
|
|
||||||
let is_proc = asm.ccall(rb_obj_is_proc as _, vec![asm.stack_opnd(0)]);
|
|
||||||
asm.cmp(is_proc, Qfalse.into());
|
|
||||||
jit_chain_guard(
|
|
||||||
JCC_JE,
|
|
||||||
jit,
|
|
||||||
asm,
|
|
||||||
ocb,
|
|
||||||
SEND_MAX_DEPTH,
|
|
||||||
Counter::guard_send_block_arg_type,
|
|
||||||
);
|
|
||||||
|
|
||||||
let proc = asm.stack_pop(1);
|
|
||||||
let callee_ep = -argc + num_locals + VM_ENV_DATA_SIZE as i32 - 1;
|
|
||||||
let callee_specval = callee_ep + VM_ENV_DATA_INDEX_SPECVAL;
|
|
||||||
let callee_specval = asm.ctx.sp_opnd(callee_specval as isize * SIZEOF_VALUE as isize);
|
|
||||||
asm.store(callee_specval, proc);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
// Nothing to do
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
|
|
||||||
let builtin_attrs = unsafe { rb_yjit_iseq_builtin_attrs(iseq) };
|
let builtin_attrs = unsafe { rb_yjit_iseq_builtin_attrs(iseq) };
|
||||||
let builtin_func_raw = unsafe { rb_yjit_builtin_function(iseq) };
|
let builtin_func_raw = unsafe { rb_yjit_builtin_function(iseq) };
|
||||||
let builtin_func = if builtin_func_raw.is_null() { None } else { Some(builtin_func_raw) };
|
let builtin_func = if builtin_func_raw.is_null() { None } else { Some(builtin_func_raw) };
|
||||||
@ -6094,6 +6053,18 @@ fn gen_send_iseq(
|
|||||||
if builtin_argc + 1 < (C_ARG_OPNDS.len() as i32) {
|
if builtin_argc + 1 < (C_ARG_OPNDS.len() as i32) {
|
||||||
asm_comment!(asm, "inlined leaf builtin");
|
asm_comment!(asm, "inlined leaf builtin");
|
||||||
|
|
||||||
|
// We pop the block arg without using it because:
|
||||||
|
// - the builtin is leaf, so it promises to not `yield`.
|
||||||
|
// - no leaf builtins have block param at the time of writing, and
|
||||||
|
// adding one requires interpreter changes to support.
|
||||||
|
if block_arg_type.is_some() {
|
||||||
|
if iseq_has_block_param {
|
||||||
|
gen_counter_incr(asm, Counter::send_iseq_leaf_builtin_block_arg_block_param);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
asm.stack_pop(1);
|
||||||
|
}
|
||||||
|
|
||||||
// Skip this if it doesn't trigger GC
|
// Skip this if it doesn't trigger GC
|
||||||
if builtin_attrs & BUILTIN_ATTR_NO_GC == 0 {
|
if builtin_attrs & BUILTIN_ATTR_NO_GC == 0 {
|
||||||
// The callee may allocate, e.g. Integer#abs on a Bignum.
|
// The callee may allocate, e.g. Integer#abs on a Bignum.
|
||||||
@ -6135,6 +6106,51 @@ fn gen_send_iseq(
|
|||||||
asm.cmp(CFP, stack_limit);
|
asm.cmp(CFP, stack_limit);
|
||||||
asm.jbe(Target::side_exit(Counter::guard_send_se_cf_overflow));
|
asm.jbe(Target::side_exit(Counter::guard_send_se_cf_overflow));
|
||||||
|
|
||||||
|
match block_arg_type {
|
||||||
|
Some(Type::Nil) => {
|
||||||
|
// We have a nil block arg, so let's pop it off the args
|
||||||
|
asm.stack_pop(1);
|
||||||
|
}
|
||||||
|
Some(Type::BlockParamProxy) => {
|
||||||
|
// We don't need the actual stack value
|
||||||
|
asm.stack_pop(1);
|
||||||
|
}
|
||||||
|
Some(Type::TProc) => {
|
||||||
|
// Place the proc as the block handler. We do this early because
|
||||||
|
// the block arg being at the top of the stack gets in the way of
|
||||||
|
// rest param handling later. Also, since there are C calls that
|
||||||
|
// come later, we can't hold this value in a register and place it
|
||||||
|
// near the end when we push a new control frame.
|
||||||
|
asm_comment!(asm, "guard block arg is a proc");
|
||||||
|
// Simple predicate, no need for jit_prepare_routine_call().
|
||||||
|
let is_proc = asm.ccall(rb_obj_is_proc as _, vec![asm.stack_opnd(0)]);
|
||||||
|
asm.cmp(is_proc, Qfalse.into());
|
||||||
|
jit_chain_guard(
|
||||||
|
JCC_JE,
|
||||||
|
jit,
|
||||||
|
asm,
|
||||||
|
ocb,
|
||||||
|
SEND_MAX_DEPTH,
|
||||||
|
Counter::guard_send_block_arg_type,
|
||||||
|
);
|
||||||
|
|
||||||
|
let callee_ep = -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
|
||||||
|
gen_counter_incr(asm, Counter::send_iseq_clobbering_block_arg);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let proc = asm.stack_pop(1); // Pop first, as argc doesn't account for the block arg
|
||||||
|
let callee_specval = asm.ctx.sp_opnd(callee_specval as isize * SIZEOF_VALUE as isize);
|
||||||
|
asm.store(callee_specval, proc);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// Nothing to do
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
// push_splat_args does stack manipulation so we can no longer side exit
|
// push_splat_args does stack manipulation so we can no longer side exit
|
||||||
if let Some(array_length) = splat_array_length {
|
if let Some(array_length) = splat_array_length {
|
||||||
if !iseq_has_rest {
|
if !iseq_has_rest {
|
||||||
|
@ -267,6 +267,8 @@ make_counters! {
|
|||||||
send_attrset_kwargs,
|
send_attrset_kwargs,
|
||||||
send_iseq_tailcall,
|
send_iseq_tailcall,
|
||||||
send_iseq_arity_error,
|
send_iseq_arity_error,
|
||||||
|
send_iseq_clobbering_block_arg,
|
||||||
|
send_iseq_leaf_builtin_block_arg_block_param,
|
||||||
send_iseq_only_keywords,
|
send_iseq_only_keywords,
|
||||||
send_iseq_kwargs_req_and_opt_missing,
|
send_iseq_kwargs_req_and_opt_missing,
|
||||||
send_iseq_kwargs_mismatch,
|
send_iseq_kwargs_mismatch,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user