Skip to content

Commit a412167

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 363ad0a commit a412167

3 files changed

Lines changed: 43 additions & 7 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 & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,17 +1068,29 @@ fn gen_guard_type(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, guard
10681068
asm.cmp(val, Qtrue.into());
10691069
asm.jne(side_exit(jit, state, GuardType(guard_type))?);
10701070
} else if guard_type.is_subtype(types::FalseClass) {
1071-
assert!(Qfalse.as_i64() == 0);
1072-
asm.test(val, val);
1071+
asm.cmp(val, Qfalse.into());
10731072
asm.jne(side_exit(jit, state, GuardType(guard_type))?);
1073+
} else if guard_type.is_immediate() {
1074+
// All immediate types' guard should have been handled above
1075+
panic!("unexpected immediate guard type: {guard_type}");
10741076
} else if let Some(expected_class) = guard_type.runtime_exact_ruby_class() {
1075-
asm_comment!(asm, "guard exact class");
1077+
asm_comment!(asm, "guard exact class for non-immediate types");
10761078

1077-
// Get the class of the value
1078-
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+
asm.cmp(val, Qfalse.into());
1087+
asm.je(side_exit.clone());
1088+
1089+
// Load the class from the object's klass field
1090+
let klass = asm.load(Opnd::mem(64, val, RUBY_OFFSET_RBASIC_KLASS));
10791091

10801092
asm.cmp(klass, Opnd::Value(expected_class));
1081-
asm.jne(side_exit(jit, state, GuardType(guard_type))?);
1093+
asm.jne(side_exit);
10821094
} else {
10831095
unimplemented!("unsupported type: {guard_type}");
10841096
}

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)