diff options
| -rw-r--r-- | test/ruby/test_zjit.rb | 18 | ||||
| -rw-r--r-- | zjit/src/codegen.rs | 31 | ||||
| -rw-r--r-- | zjit/src/hir.rs | 134 | ||||
| -rw-r--r-- | zjit/src/hir/opt_tests.rs | 176 | ||||
| -rw-r--r-- | zjit/src/stats.rs | 13 |
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, |
