ZJIT: Inline attr_reader/attr_accessor (#14126)

We can rewrite SendWithoutBlock to GetIvar.
This commit is contained in:
Max Bernstein 2025-08-06 13:56:01 -07:00 committed by GitHub
parent 4a70f946a7
commit ba4a36e226
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 144 additions and 13 deletions

View file

@ -887,6 +887,38 @@ class TestZJIT < Test::Unit::TestCase
}
end
def test_attr_reader
assert_compiles '[4, 4]', %q{
class C
attr_reader :foo
def initialize
@foo = 4
end
end
def test(c) = c.foo
c = C.new
[test(c), test(c)]
}, call_threshold: 2, insns: [:opt_send_without_block]
end
def test_attr_accessor
assert_compiles '[4, 4]', %q{
class C
attr_accessor :foo
def initialize
@foo = 4
end
end
def test(c) = c.foo
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

View file

@ -1537,22 +1537,31 @@ impl Function {
// It allows you to use a faster ISEQ if possible.
cme = unsafe { rb_check_overloaded_cme(cme, ci) };
let def_type = unsafe { get_cme_def_type(cme) };
if def_type != VM_METHOD_TYPE_ISEQ {
if def_type == VM_METHOD_TYPE_ISEQ {
// TODO(max): Allow non-iseq; cache cme
// Only specialize positional-positional calls
// TODO(max): Handle other kinds of parameter passing
let iseq = unsafe { get_def_iseq_ptr((*cme).def) };
if !can_direct_send(iseq) {
self.push_insn_id(block, insn_id); continue;
}
self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state });
if let Some(profiled_type) = profiled_type {
self_val = self.push_insn(block, Insn::GuardType { val: self_val, guard_type: Type::from_profiled_type(profiled_type), state });
}
let send_direct = self.push_insn(block, Insn::SendWithoutBlockDirect { self_val, cd, cme, iseq, args, state });
self.make_equal_to(insn_id, send_direct);
} else if def_type == VM_METHOD_TYPE_IVAR && args.is_empty() {
self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state });
if let Some(profiled_type) = profiled_type {
self_val = self.push_insn(block, Insn::GuardType { val: self_val, guard_type: Type::from_profiled_type(profiled_type), state });
}
let id = unsafe { get_cme_def_body_attr_id(cme) };
let getivar = self.push_insn(block, Insn::GetIvar { self_val, id, state });
self.make_equal_to(insn_id, getivar);
} else {
self.push_insn_id(block, insn_id); continue;
}
// Only specialize positional-positional calls
// TODO(max): Handle other kinds of parameter passing
let iseq = unsafe { get_def_iseq_ptr((*cme).def) };
if !can_direct_send(iseq) {
self.push_insn_id(block, insn_id); continue;
}
self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state });
if let Some(profiled_type) = profiled_type {
self_val = self.push_insn(block, Insn::GuardType { val: self_val, guard_type: Type::from_profiled_type(profiled_type), state });
}
let send_direct = self.push_insn(block, Insn::SendWithoutBlockDirect { self_val, cd, cme, iseq, args, state });
self.make_equal_to(insn_id, send_direct);
}
Insn::GetConstantPath { ic, state, .. } => {
let idlist: *const ID = unsafe { (*ic).segments };
@ -7422,4 +7431,94 @@ mod opt_tests {
Return v7
"#]]);
}
#[test]
fn test_inline_attr_reader_constant() {
eval("
class C
attr_reader :foo
end
O = C.new
def test = O.foo
test
test
");
assert_optimized_method_hir("test", expect![[r#"
fn test@<compiled>:7:
bb0(v0:BasicObject):
PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, O)
v9:BasicObject[VALUE(0x1008)] = Const Value(VALUE(0x1008))
PatchPoint MethodRedefined(C@0x1010, foo@0x1018, cme:0x1020)
v11:BasicObject = GetIvar v9, :@foo
Return v11
"#]]);
}
#[test]
fn test_inline_attr_accessor_constant() {
eval("
class C
attr_accessor :foo
end
O = C.new
def test = O.foo
test
test
");
assert_optimized_method_hir("test", expect![[r#"
fn test@<compiled>:7:
bb0(v0:BasicObject):
PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, O)
v9:BasicObject[VALUE(0x1008)] = Const Value(VALUE(0x1008))
PatchPoint MethodRedefined(C@0x1010, foo@0x1018, cme:0x1020)
v11:BasicObject = GetIvar v9, :@foo
Return v11
"#]]);
}
#[test]
fn test_inline_attr_reader() {
eval("
class C
attr_reader :foo
end
def test(o) = o.foo
test C.new
test C.new
");
assert_optimized_method_hir("test", expect![[r#"
fn test@<compiled>:6:
bb0(v0:BasicObject, v1:BasicObject):
PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010)
v7:BasicObject[class_exact:C] = GuardType v1, BasicObject[class_exact:C]
v8:BasicObject = GetIvar v7, :@foo
Return v8
"#]]);
}
#[test]
fn test_inline_attr_accessor() {
eval("
class C
attr_accessor :foo
end
def test(o) = o.foo
test C.new
test C.new
");
assert_optimized_method_hir("test", expect![[r#"
fn test@<compiled>:6:
bb0(v0:BasicObject, v1:BasicObject):
PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010)
v7:BasicObject[class_exact:C] = GuardType v1, BasicObject[class_exact:C]
v8:BasicObject = GetIvar v7, :@foo
Return v8
"#]]);
}
}