summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax Bernstein <rubybugs@bernsteinbear.com>2025-09-30 23:39:06 +0200
committerGitHub <noreply@github.com>2025-09-30 21:39:06 +0000
commit8cefb70e210348f509648df943eebe61ef708c3d (patch)
tree99dde5e4782f685af2514b0ed98ebbad075a5a03
parentdf2d1d5ad386c51ad9750282917ecacf2b343598 (diff)
ZJIT: Re-apply attr_writer inlining (#14678)
This re-applies https://github.com/ruby/ruby/pull/14629 / 40bb47665d3ff57e0f2eb5a9fd9e0109617015c9 by reverting https://github.com/ruby/ruby/pull/14673 / d4393772b89dab4f33c118a284d92dc80cd63c39. Co-authored-by: Alan Wu <XrXr@users.noreply.github.com>
-rw-r--r--test/ruby/test_zjit.rb43
-rw-r--r--zjit/src/hir.rs79
2 files changed, 121 insertions, 1 deletions
diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb
index 683d53f339..937cf44e19 100644
--- a/test/ruby/test_zjit.rb
+++ b/test/ruby/test_zjit.rb
@@ -1642,7 +1642,7 @@ class TestZJIT < Test::Unit::TestCase
}, call_threshold: 2, insns: [:opt_send_without_block]
end
- def test_attr_accessor
+ def test_attr_accessor_getivar
assert_compiles '[4, 4]', %q{
class C
attr_accessor :foo
@@ -1658,6 +1658,47 @@ class TestZJIT < Test::Unit::TestCase
}, call_threshold: 2, insns: [:opt_send_without_block]
end
+ def test_attr_accessor_setivar
+ assert_compiles '[5, 5]', %q{
+ class C
+ attr_accessor :foo
+
+ def initialize
+ @foo = 4
+ end
+ end
+
+ def test(c)
+ c.foo = 5
+ c.foo
+ end
+
+ c = C.new
+ [test(c), test(c)]
+ }, call_threshold: 2, insns: [:opt_send_without_block]
+ end
+
+ def test_attr_writer
+ assert_compiles '[5, 5]', %q{
+ class C
+ attr_writer :foo
+
+ def initialize
+ @foo = 4
+ end
+
+ def get_foo = @foo
+ end
+
+ def test(c)
+ c.foo = 5
+ c.get_foo
+ end
+ c = C.new
+ [test(c), test(c)]
+ }, call_threshold: 2, insns: [:opt_send_without_block]
+ end
+
def test_uncached_getconstant_path
assert_compiles RUBY_COPYRIGHT.dump, %q{
def test = RUBY_COPYRIGHT
diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs
index 7d6167d490..5588544b4f 100644
--- a/zjit/src/hir.rs
+++ b/zjit/src/hir.rs
@@ -2043,6 +2043,23 @@ impl Function {
}
let getivar = self.push_insn(block, Insn::GetIvar { self_val: recv, id, state });
self.make_equal_to(insn_id, getivar);
+ } else if let (VM_METHOD_TYPE_ATTRSET, &[val]) = (def_type, args.as_slice()) {
+ self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state });
+ 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 id = unsafe { get_cme_def_body_attr_id(cme) };
+
+ // Check if we're accessing ivars of a Class or Module object as they require single-ractor mode.
+ // We omit gen_prepare_non_leaf_call on gen_setivar, so it's unsafe to raise for multi-ractor mode.
+ if unsafe { rb_zjit_singleton_class_p(klass) } {
+ let attached = unsafe { rb_class_attached_object(klass) };
+ if unsafe { RB_TYPE_P(attached, RUBY_T_CLASS) || RB_TYPE_P(attached, RUBY_T_MODULE) } {
+ self.push_insn(block, Insn::PatchPoint { invariant: Invariant::SingleRactorMode, state });
+ }
+ }
+ self.push_insn(block, Insn::SetIvar { self_val: recv, id, val, state });
+ self.make_equal_to(insn_id, val);
} else {
self.set_dynamic_send_reason(insn_id, SendWithoutBlockNotOptimizedMethodType(MethodType::from(def_type)));
self.push_insn_id(block, insn_id); continue;
@@ -12190,4 +12207,66 @@ mod opt_tests {
Return v25
");
}
+
+ #[test]
+ fn test_inline_attr_accessor_set() {
+ eval("
+ class C
+ attr_accessor :foo
+ end
+
+ def test(o) = o.foo = 5
+ test C.new
+ test C.new
+ ");
+ assert_snapshot!(hir_string("test"), @r"
+ fn test@<compiled>:6:
+ bb0():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ v2:BasicObject = GetLocal l0, SP@4
+ Jump bb2(v1, v2)
+ bb1(v5:BasicObject, v6:BasicObject):
+ EntryPoint JIT(0)
+ Jump bb2(v5, v6)
+ bb2(v8:BasicObject, v9:BasicObject):
+ v14:Fixnum[5] = Const Value(5)
+ PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010)
+ v23:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C]
+ SetIvar v23, :@foo, v14
+ CheckInterrupts
+ Return v14
+ ");
+ }
+
+ #[test]
+ fn test_inline_attr_writer_set() {
+ eval("
+ class C
+ attr_writer :foo
+ end
+
+ def test(o) = o.foo = 5
+ test C.new
+ test C.new
+ ");
+ assert_snapshot!(hir_string("test"), @r"
+ fn test@<compiled>:6:
+ bb0():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ v2:BasicObject = GetLocal l0, SP@4
+ Jump bb2(v1, v2)
+ bb1(v5:BasicObject, v6:BasicObject):
+ EntryPoint JIT(0)
+ Jump bb2(v5, v6)
+ bb2(v8:BasicObject, v9:BasicObject):
+ v14:Fixnum[5] = Const Value(5)
+ PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010)
+ v23:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C]
+ SetIvar v23, :@foo, v14
+ CheckInterrupts
+ Return v14
+ ");
+ }
}