Skip to content

Commit eab4b7b

Browse files
committed
ZJIT: Optimize class guards by directly reading klass field
Replace `rb_yarv_class_of` call with: - a constant check for special constants (nil, fixnums, symbols, etc) - a check for false - direct memory read at offset 8 for regular heap objects for the class check
1 parent fccd96c commit eab4b7b

3 files changed

Lines changed: 43 additions & 5 deletions

File tree

test/ruby/test_zjit.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1432,6 +1432,30 @@ def test(val) = val.nil?
14321432
}, call_threshold: 2, insns: [:opt_nil_p]
14331433
end
14341434

1435+
def test_basic_object_guard_works_with_immediate
1436+
assert_compiles 'NilClass', %q{
1437+
class Foo; end
1438+
1439+
def test(val) = val.class
1440+
1441+
test(Foo.new)
1442+
test(Foo.new)
1443+
test(nil)
1444+
}, call_threshold: 2
1445+
end
1446+
1447+
def test_basic_object_guard_works_with_false
1448+
assert_compiles 'FalseClass', %q{
1449+
class Foo; end
1450+
1451+
def test(val) = val.class
1452+
1453+
test(Foo.new)
1454+
test(Foo.new)
1455+
test(false)
1456+
}, call_threshold: 2
1457+
end
1458+
14351459
private
14361460

14371461
# Assert that every method call in `test_script` can be compiled by ZJIT

zjit/src/codegen.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1070,14 +1070,28 @@ fn gen_guard_type(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, guard
10701070
assert!(Qfalse.as_i64() == 0);
10711071
asm.test(val, val);
10721072
asm.jne(side_exit(jit, state, GuardType(guard_type))?);
1073+
} else if guard_type.is_immediate() {
1074+
// All immediate types' guard should be inlined
1075+
panic!("unexpected immediate guard type: {guard_type}");
10731076
} else if let Some(expected_class) = guard_type.runtime_exact_ruby_class() {
1074-
asm_comment!(asm, "guard exact class");
1077+
asm_comment!(asm, "guard exact class for non-immediate types");
10751078

1076-
// Get the class of the value
1077-
let klass = asm.ccall(rb_yarv_class_of as *const u8, vec![val]);
1079+
let side_exit = side_exit(jit, state, GuardType(guard_type))?;
1080+
1081+
// Check if it's a special constant
1082+
asm.test(val, (RUBY_IMMEDIATE_MASK as u64).into());
1083+
asm.jnz(side_exit.clone());
1084+
1085+
// Check if it's false
1086+
assert!(Qfalse.as_i64() == 0);
1087+
asm.test(val, val);
1088+
asm.jz(side_exit.clone());
1089+
1090+
// Load the class from the object's klass field
1091+
let klass = asm.load(Opnd::mem(64, val, RUBY_OFFSET_RBASIC_KLASS));
10781092

10791093
asm.cmp(klass, Opnd::Value(expected_class));
1080-
asm.jne(side_exit(jit, state, GuardType(guard_type))?);
1094+
asm.jne(side_exit);
10811095
} else {
10821096
unimplemented!("unsupported type: {guard_type}");
10831097
}

zjit/src/hir_type/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,7 @@ impl Type {
497497
}
498498
}
499499

500-
fn is_immediate(&self) -> bool {
500+
pub fn is_immediate(&self) -> bool {
501501
self.is_subtype(types::Immediate)
502502
}
503503

0 commit comments

Comments
 (0)