From 26ee2c2e829526d87403659a7f14a1cfb43d92fa Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Sun, 17 May 2026 18:03:51 -0700 Subject: [PATCH 1/6] Fix Method/UnboundMethod for refinement psuedo-zsuper methods fafb55877aaf34592278eb3ef9ba3f61473d0a56 fixed issues with refinement zsuper methods by converting them to a cfunc that calls super. This changes Method creation to recognize the cfunc used and skip those methods just as regular zsuper methods are skipped. This makes refinement zsuper function the same way as subclass zsuper in regards to Method/UnboundMethod. --- proc.c | 13 ++++++++++--- test/ruby/test_refinement.rb | 27 +++++++++++++++++++++++++++ vm_method.c | 6 +++--- 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/proc.c b/proc.c index c93ba2b636b55d..e0cc3199e50358 100644 --- a/proc.c +++ b/proc.c @@ -1853,6 +1853,8 @@ mnew_missing_by_name(VALUE klass, VALUE obj, VALUE *name, int scope, VALUE mclas return mnew_missing(klass, obj, SYM2ID(vid), mclass); } +VALUE rb_zsuper_to_super(int argc, VALUE *argv, VALUE self); + static VALUE mnew_internal(const rb_method_entry_t *me, VALUE klass, VALUE iclass, VALUE obj, ID id, VALUE mclass, int scope, int error) @@ -1878,8 +1880,9 @@ mnew_internal(const rb_method_entry_t *me, VALUE klass, VALUE iclass, rb_print_inaccessible(klass, id, visi); } } - if (me->def->type == VM_METHOD_TYPE_ZSUPER) { - if (me->defined_class) { + if (me->def->type == VM_METHOD_TYPE_ZSUPER || + (me->def->type == VM_METHOD_TYPE_CFUNC && me->def->body.cfunc.func == (rb_cfunc_t)rb_zsuper_to_super)) { + if (me->def->type == VM_METHOD_TYPE_ZSUPER && me->defined_class) { VALUE klass = RCLASS_SUPER(RCLASS_ORIGIN(me->defined_class)); id = me->def->original_id; me = (rb_method_entry_t *)rb_callable_method_entry_with_refinements(klass, id, &iclass); @@ -3071,7 +3074,11 @@ original_method_entry(VALUE mod, ID id) while ((me = rb_method_entry(mod, id)) != 0) { const rb_method_definition_t *def = me->def; - if (def->type != VM_METHOD_TYPE_ZSUPER) break; + + if (def->type != VM_METHOD_TYPE_ZSUPER && + (def->type != VM_METHOD_TYPE_CFUNC || + def->body.cfunc.func != (rb_cfunc_t)rb_zsuper_to_super)) break; + mod = RCLASS_SUPER(me->owner); id = def->original_id; } diff --git a/test/ruby/test_refinement.rb b/test/ruby/test_refinement.rb index c3ded524fc891d..5089135043929e 100644 --- a/test/ruby/test_refinement.rb +++ b/test/ruby/test_refinement.rb @@ -1675,6 +1675,33 @@ def a = :b end end + def test_zsuper_refinement_method_arity_and_parameters + assert_separately([], <<-"end;") + class A + private def a(b) = b + end + + class B < A + public :a + end + + module R + refine A do + public :a + end + end + using R + + m = B.instance_method(:a) + assert_equal(1, m.arity) + assert_equal([[:req, :b]], m.parameters) + + m = A.instance_method(:a) + assert_equal(1, m.arity) + assert_equal([[:req, :b]], m.parameters) + end; + end + def test_instance_methods bug8881 = '[ruby-core:57080] [Bug #8881]' assert_not_include(Foo.instance_methods(false), :z, bug8881) diff --git a/vm_method.c b/vm_method.c index 244ebbb720b0e0..5ef47e97ac51e5 100644 --- a/vm_method.c +++ b/vm_method.c @@ -1370,8 +1370,8 @@ check_override_opt_method(VALUE klass, VALUE mid) } } -static VALUE -zsuper_to_super(int argc, VALUE *argv, VALUE self) +VALUE +rb_zsuper_to_super(int argc, VALUE *argv, VALUE self) { return rb_call_super_kw(argc, argv, RB_PASS_CALLED_KEYWORDS); } @@ -1486,7 +1486,7 @@ rb_method_entry_make(VALUE klass, ID mid, VALUE defined_class, rb_method_visibil def = rb_method_definition_create(type, original_id); if (turn_zsuper_to_super) { def->type = VM_METHOD_TYPE_CFUNC; - def->body.cfunc.func = (rb_cfunc_t)zsuper_to_super; + def->body.cfunc.func = (rb_cfunc_t)rb_zsuper_to_super; def->body.cfunc.invoker = ractor_safe_call_cfunc_m1; def->body.cfunc.argc = -1; } From 143b6529559604573bb1da03f4a5999d93067e17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Mon, 18 May 2026 17:04:12 +0200 Subject: [PATCH 2/6] Remove warning about sequence points in GCC SIZED_REALLOC_N already assigns to its first argument, and the double assignment causes two writes to the same struct without a sequence point, which is undefined behavior. gc.c: In function 'rb_gc_register_address': gc.c:3758:36: warning: operation on 'vm->global_object_list' may be undefined [-Wsequence-point] --- gc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gc.c b/gc.c index 788d0751b521af..6f4997314833ea 100644 --- a/gc.c +++ b/gc.c @@ -3755,7 +3755,7 @@ rb_gc_register_address(VALUE *addr) RB_VM_LOCKING() { if (vm->global_object_list_size == vm->global_object_list_capa) { size_t new_capa = vm->global_object_list_capa ? vm->global_object_list_capa * 2 : 64; - vm->global_object_list = SIZED_REALLOC_N(vm->global_object_list, VALUE *, new_capa, vm->global_object_list_capa); + SIZED_REALLOC_N(vm->global_object_list, VALUE *, new_capa, vm->global_object_list_capa); vm->global_object_list_capa = new_capa; } From 8d170ee4b6ca802ca55fec792aea561fb4cc6119 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 18 May 2026 08:53:47 -0700 Subject: [PATCH 3/6] ZJIT: Fix a counter name for JITFrame writes (#16994) --- zjit.rb | 2 +- zjit/src/codegen.rs | 4 ++-- zjit/src/stats.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/zjit.rb b/zjit.rb index 480ffa15441f64..cd4f9188b208cc 100644 --- a/zjit.rb +++ b/zjit.rb @@ -157,7 +157,7 @@ def stats_string :gc_time_ns, :invalidation_time_ns, - :vm_write_pc_count, + :vm_write_jit_frame_count, :vm_write_sp_count, :vm_write_locals_count, :vm_write_stack_count, diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index acc5002a2a267c..f4d62fc7bcef43 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -2708,8 +2708,8 @@ fn gen_save_pc_for_gc(asm: &mut Assembler, state: &FrameState) { let opcode: usize = state.get_opcode().try_into().unwrap(); let next_pc: *const VALUE = unsafe { state.pc.offset(insn_len(opcode) as isize) }; - gen_incr_counter(asm, Counter::vm_write_pc_count); - asm_comment!(asm, "save PC to CFP"); + gen_incr_counter(asm, Counter::vm_write_jit_frame_count); + asm_comment!(asm, "save JITFrame to CFP"); if let Some(pc) = PC_POISON { asm.mov(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_PC), Opnd::const_ptr(pc)); } diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 22fbb2b23fc20f..57320a02e750bd 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -440,7 +440,7 @@ make_counters! { complex_arg_pass_caller_forwarding, // Writes to the VM frame - vm_write_pc_count, + vm_write_jit_frame_count, vm_write_sp_count, vm_write_locals_count, vm_write_stack_count, From f57fadb57d5b7909463ef64e54f07cce0b3d52cb Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 18 May 2026 08:57:54 -0700 Subject: [PATCH 4/6] ZJIT: Split test_dont_inline_integer_xor_with_bignum_or_boolean (#16993) When a single #[test] function holds multiple assert_snapshot! calls, only the first one is updated per cargo-insta run, so updating all of them takes multiple zjit-test -> zjit-test-update cycles. Splitting into one snapshot per test lets a single cycle update them all. --- zjit/src/hir/opt_tests.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index e8a0488023da88..3435347d338c21 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -11109,7 +11109,7 @@ mod hir_opt_tests { } #[test] - fn test_dont_inline_integer_xor_with_bignum_or_boolean() { + fn test_dont_inline_integer_xor_with_bignum_lhs() { eval(" def test(x, y) = x ^ y test(4 << 70, 1) @@ -11136,7 +11136,10 @@ mod hir_opt_tests { CheckInterrupts Return v28 "); + } + #[test] + fn test_dont_inline_integer_xor_with_bignum_rhs() { eval(" def test(x, y) = x ^ y test(1, 4 << 70) @@ -11163,7 +11166,10 @@ mod hir_opt_tests { CheckInterrupts Return v28 "); + } + #[test] + fn test_dont_inline_integer_xor_with_boolean() { eval(" def test(x, y) = x ^ y test(true, 0) From 871177408bfbeb1561641d761f0e92ddd1875130 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sun, 17 May 2026 10:14:57 -0400 Subject: [PATCH 5/6] [DOC] Improve docs for Binding#eval --- proc.c | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/proc.c b/proc.c index e0cc3199e50358..563f2393f3d2c9 100644 --- a/proc.c +++ b/proc.c @@ -385,19 +385,31 @@ rb_f_binding(VALUE self) } /* - * call-seq: - * binding.eval(string [, filename [,lineno]]) -> obj + * call-seq: + * binding.eval(string, filename = default_filename, lineno = 1) -> obj * - * Evaluates the Ruby expression(s) in string, in the - * binding's context. If the optional filename and - * lineno parameters are present, they will be used when - * reporting syntax errors. + * Evaluates the Ruby expression(s) in +string+ in the context of + * +self+. Returns the result of the last expression: * - * def get_binding(param) - * binding - * end + * def get_binding(param) = binding * b = get_binding("hello") * b.eval("param") #=> "hello" + * + * If the optional +filename+ is given, it will be used as the + * filename of the evaluation (for __FILE__ and errors). + * Otherwise, it will default to (eval at __FILE__:__LINE__) + * where __FILE__ and __LINE__ are the filename and + * line number of the caller, respectively: + * + * b.eval("puts __FILE__") # => "(eval at test.rb:4)" + * b.eval("puts __FILE__", "foobar.rb") # => "foobar.rb" + * + * If the optional +lineno+ is given, it will be used as the + * line number of the evaluation (for __LINE__ and errors). + * Otherwise, it will default to 1: + * + * b.eval("puts __LINE__") # => 1 + * b.eval("puts __LINE__", "foobar.rb", 10) # => 10 */ static VALUE From 4351ee9652611b12051ad07f8de7b47f7b35927f Mon Sep 17 00:00:00 2001 From: B6 <52599949+a5-stable@users.noreply.github.com> Date: Tue, 19 May 2026 02:01:05 +0900 Subject: [PATCH 6/6] ZJIT: StringExact/NilClass for GetSpecialSymbol and GetSpecialNumber (#17012) According to `rb_reg_nth_match`, `rb_reg_match_pre(post/last)`, `GetSpecialSymbol` and `GetSpecialNumber` always returns `StringExact` or `NilClass`. Fix `infer_type` to `StringExact|NilClass` instead of `BasicObject`. --- zjit/src/hir.rs | 4 +-- zjit/src/hir/tests.rs | 60 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index e350387dc27999..e5976a4045d642 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2998,8 +2998,8 @@ impl Function { Insn::GetEP { .. } => types::CPtr, Insn::LoadSelf => types::BasicObject, &Insn::LoadField { return_type, .. } => return_type, - Insn::GetSpecialSymbol { .. } => types::BasicObject, - Insn::GetSpecialNumber { .. } => types::BasicObject, + Insn::GetSpecialSymbol { .. } => types::StringExact.union(types::NilClass), + Insn::GetSpecialNumber { .. } => types::StringExact.union(types::NilClass), Insn::GetClassVar { .. } => types::BasicObject, Insn::ToNewArray { .. } => types::ArrayExact, Insn::ToArray { .. } => types::ArrayExact, diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index 7466d4bb1a071a..566cf7aeb7cf7a 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -5525,6 +5525,66 @@ pub(crate) mod hir_build_tests { assert!(hir.contains("BreakPoint")); assert!(hir.contains("Return v")); } + + #[test] + fn test_getspecialnumber() { + eval(" + def test(a) + a =~/(hello)/ + $1 + end + "); + assert_snapshot!(hir_string("test"), @" + fn test@:3: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:CPtr = LoadSP + v3:BasicObject = LoadField v2, :a@0x1000 + Jump bb3(v1, v3) + bb2(): + EntryPoint JIT(0) + v6:BasicObject = LoadArg :self@0 + v7:BasicObject = LoadArg :a@1 + Jump bb3(v6, v7) + bb3(v9:BasicObject, v10:BasicObject): + v15:RegexpExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v18:BasicObject = Send v10, :=~, v15 # SendFallbackReason: Uncategorized(opt_regexpmatch2) + v22:StringExact|NilClass = GetSpecialNumber 2 + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_getspecialsymbol() { + eval(" + def test(a) + a =~/(hello)/ + $& + end + "); + assert_snapshot!(hir_string("test"), @" + fn test@:3: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:CPtr = LoadSP + v3:BasicObject = LoadField v2, :a@0x1000 + Jump bb3(v1, v3) + bb2(): + EntryPoint JIT(0) + v6:BasicObject = LoadArg :self@0 + v7:BasicObject = LoadArg :a@1 + Jump bb3(v6, v7) + bb3(v9:BasicObject, v10:BasicObject): + v15:RegexpExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v18:BasicObject = Send v10, :=~, v15 # SendFallbackReason: Uncategorized(opt_regexpmatch2) + v22:StringExact|NilClass = GetSpecialSymbol LastMatch + CheckInterrupts + Return v22 + "); + } } /// Test successor and predecessor set computations.