summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--test/ruby/test_zjit.rb18
-rw-r--r--zjit/src/codegen.rs31
-rw-r--r--zjit/src/hir.rs134
-rw-r--r--zjit/src/hir/opt_tests.rs176
-rw-r--r--zjit/src/stats.rs13
5 files changed, 351 insertions, 21 deletions
diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb
index 18e10f5206..81d9404070 100644
--- a/test/ruby/test_zjit.rb
+++ b/test/ruby/test_zjit.rb
@@ -617,6 +617,15 @@ class TestZJIT < Test::Unit::TestCase
}, call_threshold: 2
end
+ def test_send_kwarg_with_too_many_args_to_c_call
+ assert_compiles '"a b c d {kwargs: :e}"', %q{
+ def test(a:, b:, c:, d:, e:) = sprintf("%s %s %s %s %s", a, b, c, d, kwargs: e)
+ def entry = test(e: :e, d: :d, c: :c, a: :a, b: :b)
+ entry
+ entry
+ }, call_threshold: 2
+ end
+
def test_send_kwrest
assert_compiles '{a: 3}', %q{
def test(**kwargs) = kwargs
@@ -635,6 +644,15 @@ class TestZJIT < Test::Unit::TestCase
}, call_threshold: 2
end
+ def test_send_req_opt_kwreq
+ assert_compiles '[[1, 2, 3], [-1, -2, -3]]', %q{
+ def test(a, b = 2, c:) = [a, b, c]
+ def entry = [test(1, c: 3), test(-1, -2, c: -3)] # specify all, change kw order
+ entry
+ entry
+ }, call_threshold: 2
+ end
+
def test_send_req_opt_kwreq_kwopt
assert_compiles '[[1, 2, 3, 4], [-1, -2, -3, -4]]', %q{
def test(a, b = 2, c:, d: 4) = [a, b, c, d]
diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs
index 43fdc2f06e..8c5946e6a6 100644
--- a/zjit/src/codegen.rs
+++ b/zjit/src/codegen.rs
@@ -384,6 +384,9 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
// Give up SendWithoutBlockDirect for 6+ args since asm.ccall() doesn't support it.
Insn::SendWithoutBlockDirect { cd, state, args, .. } if args.len() + 1 > C_ARG_OPNDS.len() => // +1 for self
gen_send_without_block(jit, asm, *cd, &function.frame_state(*state), SendFallbackReason::TooManyArgsForLir),
+ // Give up SendWithoutBlockDirect for 5+ args (plus 1 arg for keyword bits) since asm.ccall() doesn't support it.
+ Insn::SendWithoutBlockDirect { cd, state, args, iseq, .. } if args.len() + 2 > C_ARG_OPNDS.len() && unsafe { rb_get_iseq_flags_has_kw(*iseq) } => // +1 for self +1 for keyword bits
+ gen_send_without_block(jit, asm, *cd, &function.frame_state(*state), SendFallbackReason::TooManyArgsForLir),
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)),
&Insn::InvokeSuper { cd, blockiseq, state, reason, .. } => gen_invokesuper(jit, asm, cd, blockiseq, &function.frame_state(state), reason),
&Insn::InvokeBlock { cd, state, reason, .. } => gen_invokeblock(jit, asm, cd, &function.frame_state(state), reason),
@@ -1337,6 +1340,20 @@ fn gen_send_without_block_direct(
specval,
});
+ // Write "keyword_bits" to the callee's frame if the callee accepts keywords.
+ // This is a synthetic local/parameter that the callee reads via checkkeyword to determine
+ // which optional keyword arguments need their defaults evaluated.
+ if unsafe { rb_get_iseq_flags_has_kw(iseq) } {
+ let keyword = unsafe { rb_get_iseq_body_param_keyword(iseq) };
+ let bits_start = unsafe { (*keyword).bits_start } as usize;
+ // Currently we only support required keywords, so all bits are 0 (all keywords specified).
+ // TODO: When supporting optional keywords, calculate actual unspecified_bits here.
+ let unspecified_bits = VALUE::fixnum_from_usize(0);
+ let bits_offset = (state.stack().len() - args.len() + bits_start) * SIZEOF_VALUE;
+ asm_comment!(asm, "write keyword bits to callee frame");
+ asm.store(Opnd::mem(64, SP, bits_offset as i32), unspecified_bits.into());
+ }
+
asm_comment!(asm, "switch to new SP register");
let sp_offset = (state.stack().len() + local_size - args.len() + VM_ENV_DATA_SIZE.to_usize()) * SIZEOF_VALUE;
let new_sp = asm.add(SP, sp_offset.into());
@@ -1351,13 +1368,23 @@ fn gen_send_without_block_direct(
let mut c_args = vec![recv];
c_args.extend(&args);
+ if unsafe { rb_get_iseq_flags_has_kw(iseq) } {
+ // Currently we only get to this point if all the accepted keyword args are required.
+ let unspecified_bits = 0;
+ // For each optional keyword that isn't passed we would `unspecified_bits |= (0x01 << idx)`.
+ c_args.push(VALUE::fixnum_from_usize(unspecified_bits).into());
+ }
+
let params = unsafe { iseq.params() };
let num_optionals_passed = if params.flags.has_opt() != 0 {
// See vm_call_iseq_setup_normal_opt_start in vm_inshelper.c
let lead_num = params.lead_num as u32;
let opt_num = params.opt_num as u32;
- assert!(args.len() as u32 <= lead_num + opt_num);
- let num_optionals_passed = args.len() as u32 - lead_num;
+ let keyword = params.keyword;
+ let kw_req_num = if keyword.is_null() { 0 } else { unsafe { (*keyword).required_num } } as u32;
+ let req_num = lead_num + kw_req_num;
+ assert!(args.len() as u32 <= req_num + opt_num);
+ let num_optionals_passed = args.len() as u32 - req_num;
num_optionals_passed
} else {
0
diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs
index 523a757b30..442f0500c4 100644
--- a/zjit/src/hir.rs
+++ b/zjit/src/hir.rs
@@ -616,6 +616,10 @@ pub enum SendFallbackReason {
SendWithoutBlockNotOptimizedMethodTypeOptimized(OptimizedMethodType),
SendWithoutBlockBopRedefined,
SendWithoutBlockOperandsNotFixnum,
+ SendWithoutBlockDirectKeywordMismatch,
+ SendWithoutBlockDirectOptionalKeywords,
+ SendWithoutBlockDirectKeywordCountMismatch,
+ SendWithoutBlockDirectMissingKeyword,
SendPolymorphic,
SendMegamorphic,
SendNoProfiles,
@@ -632,6 +636,8 @@ pub enum SendFallbackReason {
/// The call has at least one feature on the caller or callee side that the optimizer does not
/// support.
ComplexArgPass,
+ /// Caller has keyword arguments but callee doesn't expect them; need to convert to hash.
+ UnexpectedKeywordArgs,
/// Initial fallback reason for every instruction, which should be mutated to
/// a more actionable reason when an attempt to specialize the instruction fails.
Uncategorized(ruby_vminsn_type),
@@ -1570,11 +1576,22 @@ fn can_direct_send(function: &mut Function, block: BlockId, iseq: *const rb_iseq
use Counter::*;
if 0 != params.flags.has_rest() { count_failure(complex_arg_pass_param_rest) }
if 0 != params.flags.has_post() { count_failure(complex_arg_pass_param_post) }
- if 0 != params.flags.has_kw() { count_failure(complex_arg_pass_param_kw) }
- if 0 != params.flags.has_kwrest() { count_failure(complex_arg_pass_param_kwrest) }
if 0 != params.flags.has_block() { count_failure(complex_arg_pass_param_block) }
if 0 != params.flags.forwardable() { count_failure(complex_arg_pass_param_forwardable) }
+ if 0 != params.flags.has_kwrest() { count_failure(complex_arg_pass_param_kwrest) }
+ if 0 != params.flags.has_kw() {
+ let keyword = params.keyword;
+ if !keyword.is_null() {
+ let num = unsafe { (*keyword).num };
+ let required_num = unsafe { (*keyword).required_num };
+ // Only support required keywords for now (no optional keywords)
+ if num != required_num {
+ count_failure(complex_arg_pass_param_kw_opt)
+ }
+ }
+ }
+
if !can_send {
function.set_dynamic_send_reason(send_insn, ComplexArgPass);
return false;
@@ -1583,9 +1600,12 @@ fn can_direct_send(function: &mut Function, block: BlockId, iseq: *const rb_iseq
// Because we exclude e.g. post parameters above, they are also excluded from the sum below.
let lead_num = params.lead_num;
let opt_num = params.opt_num;
+ let keyword = params.keyword;
+ let kw_req_num = if keyword.is_null() { 0 } else { unsafe { (*keyword).required_num } };
+ let req_num = lead_num + kw_req_num;
can_send = c_int::try_from(args.len())
.as_ref()
- .map(|argc| (lead_num..=lead_num + opt_num).contains(argc))
+ .map(|argc| (req_num..=req_num + opt_num).contains(argc))
.unwrap_or(false);
if !can_send {
function.set_dynamic_send_reason(send_insn, ArgcParamMismatch);
@@ -2299,6 +2319,72 @@ impl Function {
}
}
+ /// Reorder keyword arguments to match the callee's expectation.
+ ///
+ /// Returns Ok with reordered arguments if successful, or Err with the fallback reason if not.
+ fn reorder_keyword_arguments(
+ &self,
+ args: &[InsnId],
+ kwarg: *const rb_callinfo_kwarg,
+ iseq: IseqPtr,
+ ) -> Result<Vec<InsnId>, SendFallbackReason> {
+ let callee_keyword = unsafe { rb_get_iseq_body_param_keyword(iseq) };
+ if callee_keyword.is_null() {
+ // Caller is passing kwargs but callee doesn't expect them.
+ return Err(SendWithoutBlockDirectKeywordMismatch);
+ }
+
+ let caller_kw_count = unsafe { get_cikw_keyword_len(kwarg) } as usize;
+ let callee_kw_count = unsafe { (*callee_keyword).num } as usize;
+ let callee_kw_required = unsafe { (*callee_keyword).required_num } as usize;
+ let callee_kw_table = unsafe { (*callee_keyword).table };
+
+ // For now, only handle the case where all keywords are required.
+ if callee_kw_count != callee_kw_required {
+ return Err(SendWithoutBlockDirectOptionalKeywords);
+ }
+ if caller_kw_count != callee_kw_count {
+ return Err(SendWithoutBlockDirectKeywordCountMismatch);
+ }
+
+ // The keyword arguments are the last arguments in the args vector.
+ let kw_args_start = args.len() - caller_kw_count;
+
+ // Build a mapping from caller keywords to their positions.
+ let mut caller_kw_order: Vec<ID> = Vec::with_capacity(caller_kw_count);
+ for i in 0..caller_kw_count {
+ let sym = unsafe { get_cikw_keywords_idx(kwarg, i as i32) };
+ let id = unsafe { rb_sym2id(sym) };
+ caller_kw_order.push(id);
+ }
+
+ // Reorder keyword arguments to match callee expectation.
+ let mut reordered_kw_args: Vec<InsnId> = Vec::with_capacity(callee_kw_count);
+ for i in 0..callee_kw_count {
+ let expected_id = unsafe { *callee_kw_table.add(i) };
+
+ // Find where this keyword is in the caller's order
+ let mut found = false;
+ for (j, &caller_id) in caller_kw_order.iter().enumerate() {
+ if caller_id == expected_id {
+ reordered_kw_args.push(args[kw_args_start + j]);
+ found = true;
+ break;
+ }
+ }
+
+ if !found {
+ // Required keyword not provided by caller which will raise an ArgumentError.
+ return Err(SendWithoutBlockDirectMissingKeyword);
+ }
+ }
+
+ // Replace the keyword arguments with the reordered ones.
+ let mut processed_args = args[..kw_args_start].to_vec();
+ processed_args.extend(reordered_kw_args);
+ Ok(processed_args)
+ }
+
/// Resolve the receiver type for method dispatch optimization.
///
/// Takes the receiver's Type, receiver HIR instruction, and ISEQ instruction index.
@@ -2514,6 +2600,7 @@ impl Function {
cme = unsafe { rb_aliased_callable_method_entry(cme) };
def_type = unsafe { get_cme_def_type(cme) };
}
+
if def_type == VM_METHOD_TYPE_ISEQ {
// TODO(max): Allow non-iseq; cache cme
// Only specialize positional-positional calls
@@ -2529,7 +2616,21 @@ impl Function {
if let Some(profiled_type) = profiled_type {
recv = self.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state });
}
- let send_direct = self.push_insn(block, Insn::SendWithoutBlockDirect { recv, cd, cme, iseq, args, state });
+
+ let kwarg = unsafe { rb_vm_ci_kwarg(ci) };
+ let processed_args = if !kwarg.is_null() {
+ match self.reorder_keyword_arguments(&args, kwarg, iseq) {
+ Ok(reordered) => reordered,
+ Err(reason) => {
+ self.set_dynamic_send_reason(insn_id, reason);
+ self.push_insn_id(block, insn_id); continue;
+ }
+ }
+ } else {
+ args.clone()
+ };
+
+ let send_direct = self.push_insn(block, Insn::SendWithoutBlockDirect { recv, cd, cme, iseq, args: processed_args, state });
self.make_equal_to(insn_id, send_direct);
} else if def_type == VM_METHOD_TYPE_BMETHOD {
let procv = unsafe { rb_get_def_bmethod_proc((*cme).def) };
@@ -2564,7 +2665,21 @@ impl Function {
if let Some(profiled_type) = profiled_type {
recv = self.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state });
}
- let send_direct = self.push_insn(block, Insn::SendWithoutBlockDirect { recv, cd, cme, iseq, args, state });
+
+ let kwarg = unsafe { rb_vm_ci_kwarg(ci) };
+ let processed_args = if !kwarg.is_null() {
+ match self.reorder_keyword_arguments(&args, kwarg, iseq) {
+ Ok(reordered) => reordered,
+ Err(reason) => {
+ self.set_dynamic_send_reason(insn_id, reason);
+ self.push_insn_id(block, insn_id); continue;
+ }
+ }
+ } else {
+ args.clone()
+ };
+
+ let send_direct = self.push_insn(block, Insn::SendWithoutBlockDirect { recv, cd, cme, iseq, args: processed_args, state });
self.make_equal_to(insn_id, send_direct);
} else if def_type == VM_METHOD_TYPE_IVAR && args.is_empty() {
// Check if we're accessing ivars of a Class or Module object as they require single-ractor mode.
@@ -3106,7 +3221,7 @@ impl Function {
// When seeing &block argument, fall back to dynamic dispatch for now
// TODO: Support block forwarding
- if unspecializable_call_type(ci_flags) {
+ if unspecializable_c_call_type(ci_flags) {
fun.count_complex_call_features(block, ci_flags);
fun.set_dynamic_send_reason(send_insn_id, ComplexArgPass);
return Err(());
@@ -5018,9 +5133,14 @@ fn unhandled_call_type(flags: u32) -> Result<(), CallType> {
Ok(())
}
+/// If a given call to a c func uses overly complex arguments, then we won't specialize.
+fn unspecializable_c_call_type(flags: u32) -> bool {
+ ((flags & VM_CALL_KWARG) != 0) ||
+ unspecializable_call_type(flags)
+}
+
/// If a given call uses overly complex arguments, then we won't specialize.
fn unspecializable_call_type(flags: u32) -> bool {
- ((flags & VM_CALL_KWARG) != 0) ||
((flags & VM_CALL_ARGS_SPLAT) != 0) ||
((flags & VM_CALL_ARGS_BLOCKARG) != 0)
}
diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs
index 81a2cea806..a6099f47be 100644
--- a/zjit/src/hir/opt_tests.rs
+++ b/zjit/src/hir/opt_tests.rs
@@ -2761,10 +2761,10 @@ mod hir_opt_tests {
}
#[test]
- fn dont_specialize_call_to_iseq_with_kw() {
+ fn specialize_call_to_iseq_with_multiple_required_kw() {
eval("
- def foo(a:) = 1
- def test = foo(a: 1)
+ def foo(a:, b:) = [a, b]
+ def test = foo(a: 1, b: 2)
test
test
");
@@ -2779,7 +2779,162 @@ mod hir_opt_tests {
Jump bb2(v4)
bb2(v6:BasicObject):
v11:Fixnum[1] = Const Value(1)
- IncrCounter complex_arg_pass_caller_kwarg
+ v13:Fixnum[2] = Const Value(2)
+ PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010)
+ PatchPoint NoSingletonClass(Object@0x1000)
+ v22:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)]
+ v23:BasicObject = SendWithoutBlockDirect v22, :foo (0x1038), v11, v13
+ CheckInterrupts
+ Return v23
+ ");
+ }
+
+ #[test]
+ fn specialize_call_to_iseq_with_required_kw_reorder() {
+ eval("
+ def foo(a:, b:, c:) = [a, b, c]
+ def test = foo(c: 3, a: 1, b: 2)
+ test
+ test
+ ");
+ assert_snapshot!(hir_string("test"), @r"
+ fn test@<compiled>:3:
+ bb0():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ Jump bb2(v1)
+ bb1(v4:BasicObject):
+ EntryPoint JIT(0)
+ Jump bb2(v4)
+ bb2(v6:BasicObject):
+ v11:Fixnum[3] = Const Value(3)
+ v13:Fixnum[1] = Const Value(1)
+ v15:Fixnum[2] = Const Value(2)
+ PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010)
+ PatchPoint NoSingletonClass(Object@0x1000)
+ v24:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)]
+ v25:BasicObject = SendWithoutBlockDirect v24, :foo (0x1038), v13, v15, v11
+ CheckInterrupts
+ Return v25
+ ");
+ }
+
+ #[test]
+ fn specialize_call_to_iseq_with_positional_and_required_kw_reorder() {
+ eval("
+ def foo(x, a:, b:) = [x, a, b]
+ def test = foo(0, b: 2, a: 1)
+ test
+ test
+ ");
+ assert_snapshot!(hir_string("test"), @r"
+ fn test@<compiled>:3:
+ bb0():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ Jump bb2(v1)
+ bb1(v4:BasicObject):
+ EntryPoint JIT(0)
+ Jump bb2(v4)
+ bb2(v6:BasicObject):
+ v11:Fixnum[0] = Const Value(0)
+ v13:Fixnum[2] = Const Value(2)
+ v15:Fixnum[1] = Const Value(1)
+ PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010)
+ PatchPoint NoSingletonClass(Object@0x1000)
+ v24:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)]
+ v25:BasicObject = SendWithoutBlockDirect v24, :foo (0x1038), v11, v15, v13
+ CheckInterrupts
+ Return v25
+ ");
+ }
+
+ #[test]
+ fn dont_specialize_call_with_positional_and_optional_kw() {
+ eval("
+ def foo(x, a: 1) = [x, a]
+ def test = foo(0, a: 2)
+ test
+ test
+ ");
+ assert_snapshot!(hir_string("test"), @r"
+ fn test@<compiled>:3:
+ bb0():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ Jump bb2(v1)
+ bb1(v4:BasicObject):
+ EntryPoint JIT(0)
+ Jump bb2(v4)
+ bb2(v6:BasicObject):
+ v11:Fixnum[0] = Const Value(0)
+ v13:Fixnum[2] = Const Value(2)
+ IncrCounter complex_arg_pass_param_kw_opt
+ v15:BasicObject = SendWithoutBlock v6, :foo, v11, v13
+ CheckInterrupts
+ Return v15
+ ");
+ }
+
+ #[test]
+ fn specialize_call_with_pos_optional_and_req_kw() {
+ eval("
+ def foo(r, x = 2, a:, b:) = [x, a]
+ def test = [foo(1, a: 3, b: 4), foo(1, 2, b: 4, a: 3)] # with and without the optional, change kw order
+ test
+ test
+ ");
+ assert_snapshot!(hir_string("test"), @r"
+ fn test@<compiled>:3:
+ bb0():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ Jump bb2(v1)
+ bb1(v4:BasicObject):
+ EntryPoint JIT(0)
+ Jump bb2(v4)
+ bb2(v6:BasicObject):
+ v11:Fixnum[1] = Const Value(1)
+ v13:Fixnum[3] = Const Value(3)
+ v15:Fixnum[4] = Const Value(4)
+ PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010)
+ PatchPoint NoSingletonClass(Object@0x1000)
+ v37:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)]
+ v38:BasicObject = SendWithoutBlockDirect v37, :foo (0x1038), v11, v13, v15
+ v20:Fixnum[1] = Const Value(1)
+ v22:Fixnum[2] = Const Value(2)
+ v24:Fixnum[4] = Const Value(4)
+ v26:Fixnum[3] = Const Value(3)
+ PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010)
+ PatchPoint NoSingletonClass(Object@0x1000)
+ v41:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)]
+ v42:BasicObject = SendWithoutBlockDirect v41, :foo (0x1038), v20, v22, v26, v24
+ v30:ArrayExact = NewArray v38, v42
+ CheckInterrupts
+ Return v30
+ ");
+ }
+
+ #[test]
+ fn test_send_call_to_iseq_with_optional_kw() {
+ eval("
+ def foo(a: 1) = a
+ def test = foo(a: 2)
+ test
+ test
+ ");
+ assert_snapshot!(hir_string("test"), @r"
+ fn test@<compiled>:3:
+ bb0():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ Jump bb2(v1)
+ bb1(v4:BasicObject):
+ EntryPoint JIT(0)
+ Jump bb2(v4)
+ bb2(v6:BasicObject):
+ v11:Fixnum[2] = Const Value(2)
+ IncrCounter complex_arg_pass_param_kw_opt
v13:BasicObject = SendWithoutBlock v6, :foo, v11
CheckInterrupts
Return v13
@@ -2805,7 +2960,7 @@ mod hir_opt_tests {
Jump bb2(v4)
bb2(v6:BasicObject):
v11:Fixnum[1] = Const Value(1)
- IncrCounter complex_arg_pass_caller_kwarg
+ IncrCounter complex_arg_pass_param_kwrest
v13:BasicObject = SendWithoutBlock v6, :foo, v11
CheckInterrupts
Return v13
@@ -2813,7 +2968,7 @@ mod hir_opt_tests {
}
#[test]
- fn dont_specialize_call_to_iseq_with_param_kw() {
+ fn dont_specialize_call_to_iseq_with_optional_param_kw() {
eval("
def foo(int: 1) = int + 1
def test = foo
@@ -2830,7 +2985,7 @@ mod hir_opt_tests {
EntryPoint JIT(0)
Jump bb2(v4)
bb2(v6:BasicObject):
- IncrCounter complex_arg_pass_param_kw
+ IncrCounter complex_arg_pass_param_kw_opt
v11:BasicObject = SendWithoutBlock v6, :foo
CheckInterrupts
Return v11
@@ -2882,7 +3037,6 @@ mod hir_opt_tests {
v11:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
v12:StringExact = StringCopy v11
v14:Fixnum[1] = Const Value(1)
- IncrCounter complex_arg_pass_caller_kwarg
v16:BasicObject = SendWithoutBlock v6, :sprintf, v12, v14
CheckInterrupts
Return v16
@@ -3180,8 +3334,8 @@ mod hir_opt_tests {
v13:NilClass = Const Value(nil)
PatchPoint MethodRedefined(Hash@0x1008, new@0x1009, cme:0x1010)
v46:HashExact = ObjectAllocClass Hash:VALUE(0x1008)
- IncrCounter complex_arg_pass_param_kw
IncrCounter complex_arg_pass_param_block
+ IncrCounter complex_arg_pass_param_kw_opt
v20:BasicObject = SendWithoutBlock v46, :initialize
CheckInterrupts
CheckInterrupts
@@ -9028,9 +9182,9 @@ mod hir_opt_tests {
bb2(v6:BasicObject):
v11:Fixnum[1] = Const Value(1)
IncrCounter complex_arg_pass_param_rest
- IncrCounter complex_arg_pass_param_kw
- IncrCounter complex_arg_pass_param_kwrest
IncrCounter complex_arg_pass_param_block
+ IncrCounter complex_arg_pass_param_kwrest
+ IncrCounter complex_arg_pass_param_kw_opt
v13:BasicObject = SendWithoutBlock v6, :fancy, v11
CheckInterrupts
Return v13
diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs
index 2272404b69..7d54f40c8d 100644
--- a/zjit/src/stats.rs
+++ b/zjit/src/stats.rs
@@ -222,6 +222,10 @@ make_counters! {
send_fallback_too_many_args_for_lir,
send_fallback_send_without_block_bop_redefined,
send_fallback_send_without_block_operands_not_fixnum,
+ send_fallback_send_without_block_direct_keyword_mismatch,
+ send_fallback_send_without_block_direct_optional_keywords,
+ send_fallback_send_without_block_direct_keyword_count_mismatch,
+ send_fallback_send_without_block_direct_missing_keyword,
send_fallback_send_polymorphic,
send_fallback_send_megamorphic,
send_fallback_send_no_profiles,
@@ -231,6 +235,8 @@ make_counters! {
// The call has at least one feature on the caller or callee side
// that the optimizer does not support.
send_fallback_one_or_more_complex_arg_pass,
+ // Caller has keyword arguments but callee doesn't expect them.
+ send_fallback_unexpected_keyword_args,
send_fallback_bmethod_non_iseq_proc,
send_fallback_obj_to_string_not_string,
send_fallback_send_cfunc_variadic,
@@ -347,7 +353,7 @@ make_counters! {
// Unsupported parameter features
complex_arg_pass_param_rest,
complex_arg_pass_param_post,
- complex_arg_pass_param_kw,
+ complex_arg_pass_param_kw_opt,
complex_arg_pass_param_kwrest,
complex_arg_pass_param_block,
complex_arg_pass_param_forwardable,
@@ -546,12 +552,17 @@ pub fn send_fallback_counter(reason: crate::hir::SendFallbackReason) -> Counter
TooManyArgsForLir => send_fallback_too_many_args_for_lir,
SendWithoutBlockBopRedefined => send_fallback_send_without_block_bop_redefined,
SendWithoutBlockOperandsNotFixnum => send_fallback_send_without_block_operands_not_fixnum,
+ SendWithoutBlockDirectKeywordMismatch => send_fallback_send_without_block_direct_keyword_mismatch,
+ SendWithoutBlockDirectOptionalKeywords => send_fallback_send_without_block_direct_optional_keywords,
+ SendWithoutBlockDirectKeywordCountMismatch=> send_fallback_send_without_block_direct_keyword_count_mismatch,
+ SendWithoutBlockDirectMissingKeyword => send_fallback_send_without_block_direct_missing_keyword,
SendPolymorphic => send_fallback_send_polymorphic,
SendMegamorphic => send_fallback_send_megamorphic,
SendNoProfiles => send_fallback_send_no_profiles,
SendCfuncVariadic => send_fallback_send_cfunc_variadic,
SendCfuncArrayVariadic => send_fallback_send_cfunc_array_variadic,
ComplexArgPass => send_fallback_one_or_more_complex_arg_pass,
+ UnexpectedKeywordArgs => send_fallback_unexpected_keyword_args,
ArgcParamMismatch => send_fallback_argc_param_mismatch,
BmethodNonIseqProc => send_fallback_bmethod_non_iseq_proc,
SendNotOptimizedMethodType(_) => send_fallback_send_not_optimized_method_type,