Skip to content

Commit 59f55f8

Browse files
tobiclaude
andcommitted
ZJIT: Eliminate GuardType on frozen objects
When GuardType operates on a value that is a known frozen object (from a constant), we can check at compile time if the object's type matches the guard. Since frozen objects cannot change their class, the runtime guard becomes unnecessary. This optimization eliminates redundant type guards when accessing methods on frozen constant objects, complementing the LoadField folding for ivar reads. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 4cd6661 commit 59f55f8

File tree

2 files changed

+150
-4
lines changed

2 files changed

+150
-4
lines changed

zjit/src/hir.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3362,10 +3362,22 @@ impl Function {
33623362
let mut new_insns = vec![];
33633363
for insn_id in old_insns {
33643364
let replacement_id = match self.find(insn_id) {
3365-
Insn::GuardType { val, guard_type, .. } if self.is_a(val, guard_type) => {
3366-
self.make_equal_to(insn_id, val);
3367-
// Don't bother re-inferring the type of val; we already know it.
3368-
continue;
3365+
Insn::GuardType { val, guard_type, .. } => {
3366+
// If we already know the type matches, eliminate the guard
3367+
if self.is_a(val, guard_type) {
3368+
self.make_equal_to(insn_id, val);
3369+
continue;
3370+
}
3371+
// If GuardType is on a frozen object, we can check at compile time if the
3372+
// object's type matches the guard. Frozen objects can't change class.
3373+
let val_type = self.type_of(val);
3374+
if let Some(obj) = val_type.ruby_object() {
3375+
if obj.is_frozen() && Type::from_value(obj).is_subtype(guard_type) {
3376+
self.make_equal_to(insn_id, val);
3377+
continue;
3378+
}
3379+
}
3380+
insn_id
33693381
}
33703382
Insn::FixnumAdd { left, right, .. } => {
33713383
self.fold_fixnum_bop(insn_id, left, right, |l, r| match (l, r) {

zjit/src/hir/opt_tests.rs

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8633,4 +8633,138 @@ mod hir_opt_tests {
86338633
Return v60
86348634
");
86358635
}
8636+
8637+
#[test]
8638+
fn test_eliminate_guard_type_on_frozen_object() {
8639+
// When a frozen constant object matches the guard type, eliminate the GuardType
8640+
eval(r#"
8641+
class C
8642+
def foo = 42
8643+
end
8644+
FROZEN = C.new.freeze
8645+
def test = FROZEN.foo
8646+
test; test
8647+
"#);
8648+
// The GuardType should be eliminated because FROZEN is a frozen C object
8649+
// and the guard is for HeapObject[class_exact:C]
8650+
assert_snapshot!(hir_string("test"), @r"
8651+
fn test@<compiled>:6:
8652+
bb0():
8653+
EntryPoint interpreter
8654+
v1:BasicObject = LoadSelf
8655+
Jump bb2(v1)
8656+
bb1(v4:BasicObject):
8657+
EntryPoint JIT(0)
8658+
Jump bb2(v4)
8659+
bb2(v6:BasicObject):
8660+
PatchPoint SingleRactorMode
8661+
PatchPoint StableConstantNames(0x1000, FROZEN)
8662+
v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008))
8663+
PatchPoint MethodRedefined(C@0x1010, foo@0x1018, cme:0x1020)
8664+
PatchPoint NoSingletonClass(C@0x1010)
8665+
IncrCounter inline_iseq_optimized_send_count
8666+
v25:Fixnum[42] = Const Value(42)
8667+
CheckInterrupts
8668+
Return v25
8669+
");
8670+
}
8671+
8672+
#[test]
8673+
fn test_eliminate_guard_type_on_unfrozen_constant_object() {
8674+
// Even unfrozen constant objects have their GuardType eliminated because
8675+
// the type system already knows the exact class from the constant VALUE.
8676+
// The frozen object optimization is defense-in-depth for edge cases.
8677+
eval(r#"
8678+
class C
8679+
def foo = 42
8680+
end
8681+
UNFROZEN = C.new # Not frozen, but class is still known
8682+
def test = UNFROZEN.foo
8683+
test; test
8684+
"#);
8685+
// The GuardType is eliminated because the constant's type is fully known
8686+
assert_snapshot!(hir_string("test"), @r"
8687+
fn test@<compiled>:6:
8688+
bb0():
8689+
EntryPoint interpreter
8690+
v1:BasicObject = LoadSelf
8691+
Jump bb2(v1)
8692+
bb1(v4:BasicObject):
8693+
EntryPoint JIT(0)
8694+
Jump bb2(v4)
8695+
bb2(v6:BasicObject):
8696+
PatchPoint SingleRactorMode
8697+
PatchPoint StableConstantNames(0x1000, UNFROZEN)
8698+
v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008))
8699+
PatchPoint MethodRedefined(C@0x1010, foo@0x1018, cme:0x1020)
8700+
PatchPoint NoSingletonClass(C@0x1010)
8701+
IncrCounter inline_iseq_optimized_send_count
8702+
v25:Fixnum[42] = Const Value(42)
8703+
CheckInterrupts
8704+
Return v25
8705+
");
8706+
}
8707+
8708+
#[test]
8709+
fn test_eliminate_guard_type_on_frozen_string_constant() {
8710+
// Frozen string literals should also benefit from this optimization
8711+
eval(r#"
8712+
# frozen_string_literal: true
8713+
STR = "hello"
8714+
def test = STR.size
8715+
test; test
8716+
"#);
8717+
// The GuardType for StringExact should be eliminated
8718+
assert_snapshot!(hir_string("test"), @r"
8719+
fn test@<compiled>:4:
8720+
bb0():
8721+
EntryPoint interpreter
8722+
v1:BasicObject = LoadSelf
8723+
Jump bb2(v1)
8724+
bb1(v4:BasicObject):
8725+
EntryPoint JIT(0)
8726+
Jump bb2(v4)
8727+
bb2(v6:BasicObject):
8728+
PatchPoint SingleRactorMode
8729+
PatchPoint StableConstantNames(0x1000, STR)
8730+
v21:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
8731+
PatchPoint MethodRedefined(String@0x1010, size@0x1018, cme:0x1020)
8732+
PatchPoint NoSingletonClass(String@0x1010)
8733+
IncrCounter inline_cfunc_optimized_send_count
8734+
v26:Fixnum = CCall String#size@0x1048, v21
8735+
CheckInterrupts
8736+
Return v26
8737+
");
8738+
}
8739+
8740+
#[test]
8741+
fn test_eliminate_guard_type_on_frozen_array_constant() {
8742+
// Frozen arrays should also benefit from this optimization
8743+
eval(r#"
8744+
ARR = [1, 2, 3].freeze
8745+
def test = ARR.size
8746+
test; test
8747+
"#);
8748+
// The GuardType for ArrayExact should be eliminated
8749+
assert_snapshot!(hir_string("test"), @r"
8750+
fn test@<compiled>:3:
8751+
bb0():
8752+
EntryPoint interpreter
8753+
v1:BasicObject = LoadSelf
8754+
Jump bb2(v1)
8755+
bb1(v4:BasicObject):
8756+
EntryPoint JIT(0)
8757+
Jump bb2(v4)
8758+
bb2(v6:BasicObject):
8759+
PatchPoint SingleRactorMode
8760+
PatchPoint StableConstantNames(0x1000, ARR)
8761+
v21:ArrayExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
8762+
PatchPoint MethodRedefined(Array@0x1010, size@0x1018, cme:0x1020)
8763+
PatchPoint NoSingletonClass(Array@0x1010)
8764+
IncrCounter inline_cfunc_optimized_send_count
8765+
v26:Fixnum = CCall Array#size@0x1048, v21
8766+
CheckInterrupts
8767+
Return v26
8768+
");
8769+
}
86368770
}

0 commit comments

Comments
 (0)