From d13a49fc50be54f01488932b27679b94028fef62 Mon Sep 17 00:00:00 2001 From: Daichi Kamiyama <32436625+dak2@users.noreply.github.com> Date: Wed, 29 Jul 2026 09:57:20 +0900 Subject: [PATCH] ZJIT: Fix kwargs handling in invokeblock ifunc specialization (#17971) gen_invokeblock_ifunc drops the callinfo and passes argv straight to rb_vm_yield_with_cfunc, which hardcodes kw_splat = 0, so keyword arguments lost their keyword-ness. Gate the ifunc specialization on block_call_inlinable, the same check used for the inline ISEQ path. --- zjit/src/codegen_tests.rs | 12 ++++++++++++ zjit/src/hir.rs | 2 +- zjit/src/hir/opt_tests.rs | 28 ++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/zjit/src/codegen_tests.rs b/zjit/src/codegen_tests.rs index d03bd2d65e..a943f06d57 100644 --- a/zjit/src/codegen_tests.rs +++ b/zjit/src/codegen_tests.rs @@ -6012,6 +6012,18 @@ fn test_invokeblock_ifunc_map() { assert_snapshot!(assert_compiles("test"), @"[2, 4, 6]"); } +#[test] +fn test_invokeblock_ifunc_kwarg() { + eval(" + def foo + yield 1, a: 2 + end + def test = enum_for(:foo).to_a + test + "); + assert_snapshot!(assert_compiles("test"), @"[[1, {a: 2}]]"); +} + #[test] fn test_ccall_variadic_with_multiple_args() { eval(" diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index d0a1bb270e..6d3fa90f10 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -9326,7 +9326,7 @@ fn add_iseq_to_hir( Some(summary.bucket(0).class()) }); - let is_ifunc = (flags & (VM_CALL_ARGS_SPLAT | VM_CALL_KW_SPLAT)) == 0 + let is_ifunc = (flags & (VM_CALL_ARGS_SPLAT | VM_CALL_KW_SPLAT | VM_CALL_KWARG)) == 0 && block_handler_class.is_some_and(|obj| unsafe { rb_IMEMO_TYPE_P(obj, imemo_ifunc) == 1 }); // If the block handler is a known simple ISEQ block with exact arity and no diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 1682fe23ec..4d50dc5177 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -17913,6 +17913,34 @@ mod hir_opt_tests { "); } + #[test] + fn test_invokeblock_ifunc_kwarg() { + eval(" + def foo + yield 1, a: 2 + end + def test = enum_for(:foo).to_a + test + "); + assert_snapshot!(hir_string("foo"), @" + fn foo@:3: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb3(v1) + bb2(): + EntryPoint JIT(0) + v4:BasicObject = LoadArg :self@0 + Jump bb3(v4) + bb3(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + v12:Fixnum[2] = Const Value(2) + v14:BasicObject = InvokeBlock v10, v12 # SendFallbackReason: InvokeBlock: not yet specialized + CheckInterrupts + Return v14 + "); + } + #[test] fn test_dedup_guard_type() { // Two subtractions on the same Fixnum operand `n` each require a