Skip to content

Commit d25eb1e

Browse files
authored
ZJIT: Optimize class guards by directly reading klass field (#14136)
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 96c9e1e commit d25eb1e

File tree

3 files changed

+43
-7
lines changed

3 files changed

+43
-7
lines changed

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
@@ -1069,17 +1069,29 @@ fn gen_guard_type(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, guard
10691069
asm.cmp(val, Qtrue.into());
10701070
asm.jne(side_exit(jit, state, GuardType(guard_type))?);
10711071
} else if guard_type.is_subtype(types::FalseClass) {
1072-
assert!(Qfalse.as_i64() == 0);
1073-
asm.test(val, val);
1072+
asm.cmp(val, Qfalse.into());
10741073
asm.jne(side_exit(jit, state, GuardType(guard_type))?);
1074+
} else if guard_type.is_immediate() {
1075+
// All immediate types' guard should have been handled above
1076+
panic!("unexpected immediate guard type: {guard_type}");
10751077
} else if let Some(expected_class) = guard_type.runtime_exact_ruby_class() {
1076-
asm_comment!(asm, "guard exact class");
1078+
asm_comment!(asm, "guard exact class for non-immediate types");
10771079

1078-
// Get the class of the value
1079-
let klass = asm.ccall(rb_yarv_class_of as *const u8, vec![val]);
1080+
let side_exit = side_exit(jit, state, GuardType(guard_type))?;
1081+
1082+
// Check if it's a special constant
1083+
asm.test(val, (RUBY_IMMEDIATE_MASK as u64).into());
1084+
asm.jnz(side_exit.clone());
1085+
1086+
// Check if it's false
1087+
asm.cmp(val, Qfalse.into());
1088+
asm.je(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));
10801092

10811093
asm.cmp(klass, Opnd::Value(expected_class));
1082-
asm.jne(side_exit(jit, state, GuardType(guard_type))?);
1094+
asm.jne(side_exit);
10831095
} else {
10841096
unimplemented!("unsupported type: {guard_type}");
10851097
}

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)