From 2ed5a02fcca4da4acf4c8c3d7ee4c392fc18d948 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Thu, 2 Oct 2025 17:03:25 +0100 Subject: ZJIT: Add `NoSingletonClass` patch point (#14680) * ZJIT: Add NoSingletonClass patch point This patch point makes sure that when the object has a singleton class, the JIT code is invalidated. As of now, this is only needed for C call optimization. In YJIT, the singleton class guard only applies to Array, Hash, and String. But in ZJIT, we may optimize C calls from gems (e.g. `sqlite3`). So the patch point needs to be applied to a broader range of classes. * ZJIT: Only generate NoSingletonClass guard when the type can have singleton class * ZJIT: Update or forget NoSingletonClass patch point when needed --- test/ruby/test_zjit.rb | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) (limited to 'test/ruby') diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 937cf44e19..83af7347d6 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -2912,6 +2912,65 @@ class TestZJIT < Test::Unit::TestCase }, call_threshold: 2, insns: [:opt_new] end + def test_singleton_class_invalidation_annotated_ccall + assert_compiles '[false, true]', %q{ + def define_singleton(obj, define) + if define + # Wrap in C method frame to avoid exiting JIT on defineclass + [nil].reverse_each do + class << obj + def ==(_) + true + end + end + end + end + false + end + + def test(define) + obj = BasicObject.new + # This == call gets compiled to a CCall + obj == define_singleton(obj, define) + end + + result = [] + result << test(false) # Compiles BasicObject#== + result << test(true) # Should use singleton#== now + result + }, call_threshold: 2 + end + + def test_singleton_class_invalidation_optimized_variadic_ccall + assert_compiles '[1, 1000]', %q{ + def define_singleton(arr, define) + if define + # Wrap in C method frame to avoid exiting JIT on defineclass + [nil].reverse_each do + class << arr + def push(x) + super(x * 1000) + end + end + end + end + 1 + end + + def test(define) + arr = [] + val = define_singleton(arr, define) + arr.push(val) # This CCall should be invalidated if singleton was defined + arr[0] + end + + result = [] + result << test(false) # Compiles Array#push as CCall + result << test(true) # Singleton defined, CCall should be invalidated + result + }, call_threshold: 2 + end + private # Assert that every method call in `test_script` can be compiled by ZJIT -- cgit v1.2.3