Skip to content

Commit 5d35e24

Browse files
committed
ZJIT: Check argument count matches callee's parameters
1 parent a0cce40 commit 5d35e24

File tree

4 files changed

+81
-10
lines changed

4 files changed

+81
-10
lines changed

zjit/src/codegen.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
380380
&Insn::SendWithoutBlock { cd, state, reason, .. } => gen_send_without_block(jit, asm, cd, &function.frame_state(state), reason),
381381
// Give up SendWithoutBlockDirect for 6+ args since asm.ccall() doesn't support it.
382382
Insn::SendWithoutBlockDirect { cd, state, args, .. } if args.len() + 1 > C_ARG_OPNDS.len() => // +1 for self
383-
gen_send_without_block(jit, asm, *cd, &function.frame_state(*state), SendFallbackReason::SendWithoutBlockDirectTooManyArgs),
383+
gen_send_without_block(jit, asm, *cd, &function.frame_state(*state), SendFallbackReason::TooManyArgsForLir),
384384
Insn::SendWithoutBlockDirect { cme, iseq, recv, args, state, .. } => gen_send_without_block_direct(cb, jit, asm, *cme, *iseq, opnd!(recv), opnds!(args), &function.frame_state(*state)),
385385
&Insn::InvokeSuper { cd, blockiseq, state, reason, .. } => gen_invokesuper(jit, asm, cd, blockiseq, &function.frame_state(state), reason),
386386
&Insn::InvokeBlock { cd, state, reason, .. } => gen_invokeblock(jit, asm, cd, &function.frame_state(state), reason),

zjit/src/hir.rs

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::{
99
cast::IntoUsize, codegen::local_idx_to_ep_offset, cruby::*, payload::{get_or_create_iseq_payload, IseqPayload}, options::{debug, get_option, DumpHIR}, state::ZJITState
1010
};
1111
use std::{
12-
cell::RefCell, collections::{HashMap, HashSet, VecDeque}, ffi::{c_void, c_uint, CStr}, fmt::Display, mem::{align_of, size_of}, ptr, slice::Iter
12+
cell::RefCell, collections::{HashMap, HashSet, VecDeque}, ffi::{c_void, c_uint, c_int, CStr}, fmt::Display, mem::{align_of, size_of}, ptr, slice::Iter
1313
};
1414
use crate::hir_type::{Type, types};
1515
use crate::bitset::BitSet;
@@ -593,7 +593,6 @@ pub enum SendFallbackReason {
593593
SendWithoutBlockCfuncArrayVariadic,
594594
SendWithoutBlockNotOptimizedMethodType(MethodType),
595595
SendWithoutBlockNotOptimizedMethodTypeOptimized(OptimizedMethodType),
596-
SendWithoutBlockDirectTooManyArgs,
597596
SendWithoutBlockBopRedefined,
598597
SendWithoutBlockOperandsNotFixnum,
599598
SendPolymorphic,
@@ -604,8 +603,11 @@ pub enum SendFallbackReason {
604603
SendNotOptimizedMethodType(MethodType),
605604
CCallWithFrameTooManyArgs,
606605
ObjToStringNotString,
606+
TooManyArgsForLir,
607607
/// The Proc object for a BMETHOD is not defined by an ISEQ. (See `enum rb_block_type`.)
608608
BmethodNonIseqProc,
609+
/// Caller supplies too few or too many arguments than what the callee's parameters expects.
610+
ArgcParamMismatch,
609611
/// The call has at least one feature on the caller or callee side that the optimizer does not
610612
/// support.
611613
ComplexArgPass,
@@ -1457,7 +1459,7 @@ pub enum ValidationError {
14571459
MiscValidationError(InsnId, String),
14581460
}
14591461

1460-
fn can_direct_send(function: &mut Function, block: BlockId, iseq: *const rb_iseq_t) -> bool {
1462+
fn can_direct_send(function: &mut Function, block: BlockId, iseq: *const rb_iseq_t, send_insn: InsnId, args: &[InsnId]) -> bool {
14611463
let mut can_send = true;
14621464
let mut count_failure = |counter| {
14631465
can_send = false;
@@ -1472,6 +1474,24 @@ fn can_direct_send(function: &mut Function, block: BlockId, iseq: *const rb_iseq
14721474
if unsafe { rb_get_iseq_flags_has_block(iseq) } { count_failure(complex_arg_pass_param_block) }
14731475
if unsafe { rb_get_iseq_flags_forwardable(iseq) } { count_failure(complex_arg_pass_param_forwardable) }
14741476

1477+
if !can_send {
1478+
function.set_dynamic_send_reason(send_insn, ComplexArgPass);
1479+
return false;
1480+
}
1481+
1482+
// Check argument count against callee's parameters. Note that correctness for this calculation
1483+
// relies on rejecting features above.
1484+
let lead_num = unsafe { get_iseq_body_param_lead_num(iseq) };
1485+
let opt_num = unsafe { get_iseq_body_param_opt_num(iseq) };
1486+
can_send = c_int::try_from(args.len())
1487+
.as_ref()
1488+
.map(|argc| (lead_num..=lead_num + opt_num).contains(argc))
1489+
.unwrap_or(false);
1490+
if !can_send {
1491+
function.set_dynamic_send_reason(send_insn, ArgcParamMismatch);
1492+
return false
1493+
}
1494+
14751495
can_send
14761496
}
14771497

@@ -2358,8 +2378,7 @@ impl Function {
23582378
// Only specialize positional-positional calls
23592379
// TODO(max): Handle other kinds of parameter passing
23602380
let iseq = unsafe { get_def_iseq_ptr((*cme).def) };
2361-
if !can_direct_send(self, block, iseq) {
2362-
self.set_dynamic_send_reason(insn_id, ComplexArgPass);
2381+
if !can_direct_send(self, block, iseq, insn_id, args.as_slice()) {
23632382
self.push_insn_id(block, insn_id); continue;
23642383
}
23652384
self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state });
@@ -2384,8 +2403,7 @@ impl Function {
23842403
let capture = unsafe { proc_block.as_.captured.as_ref() };
23852404
let iseq = unsafe { *capture.code.iseq.as_ref() };
23862405

2387-
if !can_direct_send(self, block, iseq) {
2388-
self.set_dynamic_send_reason(insn_id, ComplexArgPass);
2406+
if !can_direct_send(self, block, iseq, insn_id, args.as_slice()) {
23892407
self.push_insn_id(block, insn_id); continue;
23902408
}
23912409
// Can't pass a block to a block for now

zjit/src/hir/opt_tests.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6183,6 +6183,57 @@ mod hir_opt_tests {
61836183
");
61846184
}
61856185

6186+
#[test]
6187+
fn test_dont_optimize_when_passing_too_many_args() {
6188+
eval(r#"
6189+
public def foo(lead, opt=raise) = opt
6190+
def test = 0.foo(3, 3, 3)
6191+
"#);
6192+
assert_snapshot!(hir_string("test"), @r"
6193+
fn test@<compiled>:3:
6194+
bb0():
6195+
EntryPoint interpreter
6196+
v1:BasicObject = LoadSelf
6197+
Jump bb2(v1)
6198+
bb1(v4:BasicObject):
6199+
EntryPoint JIT(0)
6200+
Jump bb2(v4)
6201+
bb2(v6:BasicObject):
6202+
v10:Fixnum[0] = Const Value(0)
6203+
v12:Fixnum[3] = Const Value(3)
6204+
v14:Fixnum[3] = Const Value(3)
6205+
v16:Fixnum[3] = Const Value(3)
6206+
IncrCounter complex_arg_pass_param_opt
6207+
v18:BasicObject = SendWithoutBlock v10, :foo, v12, v14, v16
6208+
CheckInterrupts
6209+
Return v18
6210+
");
6211+
}
6212+
6213+
#[test]
6214+
fn test_dont_optimize_when_passing_too_few_args() {
6215+
eval(r#"
6216+
public def foo(lead, opt=raise) = opt
6217+
def test = 0.foo
6218+
"#);
6219+
assert_snapshot!(hir_string("test"), @r"
6220+
fn test@<compiled>:3:
6221+
bb0():
6222+
EntryPoint interpreter
6223+
v1:BasicObject = LoadSelf
6224+
Jump bb2(v1)
6225+
bb1(v4:BasicObject):
6226+
EntryPoint JIT(0)
6227+
Jump bb2(v4)
6228+
bb2(v6:BasicObject):
6229+
v10:Fixnum[0] = Const Value(0)
6230+
IncrCounter complex_arg_pass_param_opt
6231+
v12:BasicObject = SendWithoutBlock v10, :foo
6232+
CheckInterrupts
6233+
Return v12
6234+
");
6235+
}
6236+
61866237
#[test]
61876238
fn test_dont_inline_integer_succ_with_args() {
61886239
eval("

zjit/src/stats.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,14 +178,15 @@ make_counters! {
178178
send_fallback_send_without_block_cfunc_array_variadic,
179179
send_fallback_send_without_block_not_optimized_method_type,
180180
send_fallback_send_without_block_not_optimized_method_type_optimized,
181-
send_fallback_send_without_block_direct_too_many_args,
181+
send_fallback_too_many_args_for_lir,
182182
send_fallback_send_without_block_bop_redefined,
183183
send_fallback_send_without_block_operands_not_fixnum,
184184
send_fallback_send_polymorphic,
185185
send_fallback_send_megamorphic,
186186
send_fallback_send_no_profiles,
187187
send_fallback_send_not_optimized_method_type,
188188
send_fallback_ccall_with_frame_too_many_args,
189+
send_fallback_argc_param_mismatch,
189190
// The call has at least one feature on the caller or callee side
190191
// that the optimizer does not support.
191192
send_fallback_one_or_more_complex_arg_pass,
@@ -476,7 +477,7 @@ pub fn send_fallback_counter(reason: crate::hir::SendFallbackReason) -> Counter
476477
SendWithoutBlockNotOptimizedMethodType(_) => send_fallback_send_without_block_not_optimized_method_type,
477478
SendWithoutBlockNotOptimizedMethodTypeOptimized(_)
478479
=> send_fallback_send_without_block_not_optimized_method_type_optimized,
479-
SendWithoutBlockDirectTooManyArgs => send_fallback_send_without_block_direct_too_many_args,
480+
TooManyArgsForLir => send_fallback_too_many_args_for_lir,
480481
SendWithoutBlockBopRedefined => send_fallback_send_without_block_bop_redefined,
481482
SendWithoutBlockOperandsNotFixnum => send_fallback_send_without_block_operands_not_fixnum,
482483
SendPolymorphic => send_fallback_send_polymorphic,
@@ -485,6 +486,7 @@ pub fn send_fallback_counter(reason: crate::hir::SendFallbackReason) -> Counter
485486
SendCfuncVariadic => send_fallback_send_cfunc_variadic,
486487
SendCfuncArrayVariadic => send_fallback_send_cfunc_array_variadic,
487488
ComplexArgPass => send_fallback_one_or_more_complex_arg_pass,
489+
ArgcParamMismatch => send_fallback_argc_param_mismatch,
488490
BmethodNonIseqProc => send_fallback_bmethod_non_iseq_proc,
489491
SendNotOptimizedMethodType(_) => send_fallback_send_not_optimized_method_type,
490492
CCallWithFrameTooManyArgs => send_fallback_ccall_with_frame_too_many_args,

0 commit comments

Comments
 (0)