Skip to content

Commit f52edf1

Browse files
authored
ZJIT: Specialize monomorphic DefinedIvar (ruby#15281)
This lets us constant-fold common monomorphic cases.
1 parent e5e8ac5 commit f52edf1

File tree

5 files changed

+172
-28
lines changed

5 files changed

+172
-28
lines changed

insns.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,7 @@ definedivar
745745
()
746746
(VALUE val)
747747
// attr bool leaf = false;
748+
// attr bool zjit_profile = true;
748749
{
749750
val = Qnil;
750751
if (!UNDEF_P(vm_getivar(GET_SELF(), id, GET_ISEQ(), ic, NULL, FALSE, Qundef))) {

zjit/src/cruby_bindings.inc.rs

Lines changed: 29 additions & 28 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

zjit/src/hir.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2820,6 +2820,38 @@ impl Function {
28202820
};
28212821
self.make_equal_to(insn_id, replacement);
28222822
}
2823+
Insn::DefinedIvar { self_val, id, pushval, state } => {
2824+
let frame_state = self.frame_state(state);
2825+
let Some(recv_type) = self.profiled_type_of_at(self_val, frame_state.insn_idx) else {
2826+
// No (monomorphic/skewed polymorphic) profile info
2827+
self.push_insn_id(block, insn_id); continue;
2828+
};
2829+
if recv_type.flags().is_immediate() {
2830+
// Instance variable lookups on immediate values are always nil
2831+
self.push_insn_id(block, insn_id); continue;
2832+
}
2833+
assert!(recv_type.shape().is_valid());
2834+
if !recv_type.flags().is_t_object() {
2835+
// Check if the receiver is a T_OBJECT
2836+
self.push_insn_id(block, insn_id); continue;
2837+
}
2838+
if recv_type.shape().is_too_complex() {
2839+
// too-complex shapes can't use index access
2840+
self.push_insn_id(block, insn_id); continue;
2841+
}
2842+
let self_val = self.push_insn(block, Insn::GuardType { val: self_val, guard_type: types::HeapBasicObject, state });
2843+
let _ = self.push_insn(block, Insn::GuardShape { val: self_val, shape: recv_type.shape(), state });
2844+
let mut ivar_index: u16 = 0;
2845+
let replacement = if unsafe { rb_shape_get_iv_index(recv_type.shape().0, id, &mut ivar_index) } {
2846+
self.push_insn(block, Insn::Const { val: Const::Value(pushval) })
2847+
} else {
2848+
// If there is no IVAR index, then the ivar was undefined when we
2849+
// entered the compiler. That means we can just return nil for this
2850+
// shape + iv name
2851+
self.push_insn(block, Insn::Const { val: Const::Value(Qnil) })
2852+
};
2853+
self.make_equal_to(insn_id, replacement);
2854+
}
28232855
_ => { self.push_insn_id(block, insn_id); }
28242856
}
28252857
}
@@ -4839,6 +4871,8 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
48394871
// profiled cfp->self.
48404872
if opcode == YARVINSN_getinstancevariable || opcode == YARVINSN_trace_getinstancevariable {
48414873
profiles.profile_self(&exit_state, self_param);
4874+
} else if opcode == YARVINSN_definedivar || opcode == YARVINSN_trace_definedivar {
4875+
profiles.profile_self(&exit_state, self_param);
48424876
} else if opcode == YARVINSN_invokeblock || opcode == YARVINSN_trace_invokeblock {
48434877
if get_option!(stats) {
48444878
let iseq_insn_idx = exit_state.insn_idx;

zjit/src/hir/opt_tests.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3373,6 +3373,113 @@ mod hir_opt_tests {
33733373
");
33743374
}
33753375

3376+
#[test]
3377+
fn test_specialize_monomorphic_definedivar_true() {
3378+
eval("
3379+
@foo = 4
3380+
def test = defined?(@foo)
3381+
test
3382+
");
3383+
assert_snapshot!(hir_string("test"), @r"
3384+
fn test@<compiled>:3:
3385+
bb0():
3386+
EntryPoint interpreter
3387+
v1:BasicObject = LoadSelf
3388+
Jump bb2(v1)
3389+
bb1(v4:BasicObject):
3390+
EntryPoint JIT(0)
3391+
Jump bb2(v4)
3392+
bb2(v6:BasicObject):
3393+
v15:HeapBasicObject = GuardType v6, HeapBasicObject
3394+
v16:HeapBasicObject = GuardShape v15, 0x1000
3395+
v17:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
3396+
CheckInterrupts
3397+
Return v17
3398+
");
3399+
}
3400+
3401+
#[test]
3402+
fn test_specialize_monomorphic_definedivar_false() {
3403+
eval("
3404+
def test = defined?(@foo)
3405+
test
3406+
");
3407+
assert_snapshot!(hir_string("test"), @r"
3408+
fn test@<compiled>:2:
3409+
bb0():
3410+
EntryPoint interpreter
3411+
v1:BasicObject = LoadSelf
3412+
Jump bb2(v1)
3413+
bb1(v4:BasicObject):
3414+
EntryPoint JIT(0)
3415+
Jump bb2(v4)
3416+
bb2(v6:BasicObject):
3417+
v15:HeapBasicObject = GuardType v6, HeapBasicObject
3418+
v16:HeapBasicObject = GuardShape v15, 0x1000
3419+
v17:NilClass = Const Value(nil)
3420+
CheckInterrupts
3421+
Return v17
3422+
");
3423+
}
3424+
3425+
#[test]
3426+
fn test_dont_specialize_definedivar_with_t_data() {
3427+
eval("
3428+
class C < Range
3429+
def test = defined?(@a)
3430+
end
3431+
obj = C.new 0, 1
3432+
obj.instance_variable_set(:@a, 1)
3433+
obj.test
3434+
TEST = C.instance_method(:test)
3435+
");
3436+
assert_snapshot!(hir_string_proc("TEST"), @r"
3437+
fn test@<compiled>:3:
3438+
bb0():
3439+
EntryPoint interpreter
3440+
v1:BasicObject = LoadSelf
3441+
Jump bb2(v1)
3442+
bb1(v4:BasicObject):
3443+
EntryPoint JIT(0)
3444+
Jump bb2(v4)
3445+
bb2(v6:BasicObject):
3446+
v10:StringExact|NilClass = DefinedIvar v6, :@a
3447+
CheckInterrupts
3448+
Return v10
3449+
");
3450+
}
3451+
3452+
#[test]
3453+
fn test_dont_specialize_polymorphic_definedivar() {
3454+
set_call_threshold(3);
3455+
eval("
3456+
class C
3457+
def test = defined?(@a)
3458+
end
3459+
obj = C.new
3460+
obj.instance_variable_set(:@a, 1)
3461+
obj.test
3462+
obj = C.new
3463+
obj.instance_variable_set(:@b, 1)
3464+
obj.test
3465+
TEST = C.instance_method(:test)
3466+
");
3467+
assert_snapshot!(hir_string_proc("TEST"), @r"
3468+
fn test@<compiled>:3:
3469+
bb0():
3470+
EntryPoint interpreter
3471+
v1:BasicObject = LoadSelf
3472+
Jump bb2(v1)
3473+
bb1(v4:BasicObject):
3474+
EntryPoint JIT(0)
3475+
Jump bb2(v4)
3476+
bb2(v6:BasicObject):
3477+
v10:StringExact|NilClass = DefinedIvar v6, :@a
3478+
CheckInterrupts
3479+
Return v10
3480+
");
3481+
}
3482+
33763483
#[test]
33773484
fn test_elide_freeze_with_frozen_hash() {
33783485
eval("

zjit/src/profile.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ fn profile_insn(bare_opcode: ruby_vminsn_type, ec: EcPtr) {
8282
YARVINSN_opt_aset => profile_operands(profiler, profile, 3),
8383
YARVINSN_opt_not => profile_operands(profiler, profile, 1),
8484
YARVINSN_getinstancevariable => profile_self(profiler, profile),
85+
YARVINSN_definedivar => profile_self(profiler, profile),
8586
YARVINSN_opt_regexpmatch2 => profile_operands(profiler, profile, 2),
8687
YARVINSN_objtostring => profile_operands(profiler, profile, 1),
8788
YARVINSN_opt_length => profile_operands(profiler, profile, 1),

0 commit comments

Comments
 (0)