Thanks to visit codestin.com
Credit goes to github.com

Skip to content

ZJIT: Fold Send into SendWithoutBlockDirect if we know the class statically #13172

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 54 additions & 9 deletions zjit/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -978,18 +978,22 @@ impl Function {
self.try_rewrite_fixnum_op(block, insn_id, &|left, right| Insn::FixnumGt { left, right }, BOP_GT, self_val, args[0], payload, state),
Insn::SendWithoutBlock { self_val, call_info: CallInfo { method_name }, args, state, .. } if method_name == ">=" && args.len() == 1 =>
self.try_rewrite_fixnum_op(block, insn_id, &|left, right| Insn::FixnumGe { left, right }, BOP_GE, self_val, args[0], payload, state),
Insn::SendWithoutBlock { self_val, call_info, cd, args, state } => {
Insn::SendWithoutBlock { mut self_val, call_info, cd, args, state } => {
let frame_state = self.frame_state(state);
let self_type = match payload.get_operand_types(frame_state.insn_idx) {
Some([self_type, ..]) if self_type.is_top_self() => self_type,
_ => { self.push_insn_id(block, insn_id); continue; }
let (klass, guard_equal_to) = if let Some(klass) = self.type_of(self_val).runtime_exact_ruby_class() {
// If we know the class statically, use it to fold the lookup at compile-time.
(klass, None)
} else {
// If we know that self is top-self from profile information, guard and use it to fold the lookup at compile-time.
match payload.get_operand_types(frame_state.insn_idx) {
Some([self_type, ..]) if self_type.is_top_self() => (self_type.exact_ruby_class().unwrap(), self_type.ruby_object()),
_ => { self.push_insn_id(block, insn_id); continue; }
}
};
let top_self = self_type.ruby_object().unwrap();
let top_self_klass = top_self.class_of();
let ci = unsafe { get_call_data_ci(cd) }; // info about the call site
let mid = unsafe { vm_ci_mid(ci) };
// Do method lookup
let mut cme = unsafe { rb_callable_method_entry(top_self_klass, mid) };
let mut cme = unsafe { rb_callable_method_entry(klass, mid) };
if cme.is_null() {
self.push_insn_id(block, insn_id); continue;
}
Expand All @@ -998,11 +1002,14 @@ impl Function {
cme = unsafe { rb_check_overloaded_cme(cme, ci) };
let def_type = unsafe { get_cme_def_type(cme) };
if def_type != VM_METHOD_TYPE_ISEQ {
// TODO(max): Allow non-iseq; cache cme
self.push_insn_id(block, insn_id); continue;
}
self.push_insn(block, Insn::PatchPoint(Invariant::MethodRedefined { klass: top_self_klass, method: mid }));
self.push_insn(block, Insn::PatchPoint(Invariant::MethodRedefined { klass, method: mid }));
let iseq = unsafe { get_def_iseq_ptr((*cme).def) };
let self_val = self.push_insn(block, Insn::GuardBitEquals { val: self_val, expected: top_self, state });
if let Some(expected) = guard_equal_to {
self_val = self.push_insn(block, Insn::GuardBitEquals { val: self_val, expected, state });
}
let send_direct = self.push_insn(block, Insn::SendWithoutBlockDirect { self_val, call_info, cd, iseq, args, state });
self.make_equal_to(insn_id, send_direct);
}
Expand Down Expand Up @@ -1038,6 +1045,9 @@ impl Function {
// TODO(alan): there was a seemingly a miscomp here if you swap with
// `inexact_ruby_class`. Theoretically it can call a method too general
// for the receiver. Confirm and add a test.
//
// TODO(max): Use runtime_exact_ruby_class so we can also specialize on known (not just
// profiled) types.
let (recv_class, recv_type) = payload.get_operand_types(iseq_insn_idx)
.and_then(|types| types.get(argc as usize))
.and_then(|recv_type| recv_type.exact_ruby_class().and_then(|class| Some((class, recv_type))))
Expand Down Expand Up @@ -3354,6 +3364,41 @@ mod opt_tests {
"#]]);
}

#[test]
fn const_send_direct_integer() {
eval("
def test(x) = 1.zero?
");
assert_optimized_method_hir("test", expect![[r#"
fn test:
bb0(v0:BasicObject):
v2:Fixnum[1] = Const Value(1)
PatchPoint MethodRedefined(Integer@0x1000, zero?@0x1008)
v7:BasicObject = SendWithoutBlockDirect v2, :zero? (0x1010)
Return v7
"#]]);
}

#[test]
fn class_known_send_direct_array() {
eval("
def test(x)
a = [1,2,3]
a.first
end
");
assert_optimized_method_hir("test", expect![[r#"
fn test:
bb0(v0:BasicObject):
v1:NilClassExact = Const Value(nil)
v3:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
v5:ArrayExact = ArrayDup v3
PatchPoint MethodRedefined(Array@0x1008, first@0x1010)
v10:BasicObject = SendWithoutBlockDirect v5, :first (0x1018)
Return v10
"#]]);
}

#[test]
fn string_bytesize_simple() {
eval("
Expand Down
20 changes: 20 additions & 0 deletions zjit/src/hir_type/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,26 @@ impl Type {
}
}

/// Return a pointer to the Ruby class that an object of this Type would have at run-time, if
/// known. This includes classes for HIR types such as ArrayExact or NilClassExact, which have
/// canonical Type representations that lack an explicit specialization in their `spec` fields.
pub fn runtime_exact_ruby_class(&self) -> Option<VALUE> {
if let Some(val) = self.exact_ruby_class() {
return Some(val);
}
if self.is_subtype(types::ArrayExact) { return Some(unsafe { rb_cArray }); }
if self.is_subtype(types::FalseClassExact) { return Some(unsafe { rb_cFalseClass }); }
if self.is_subtype(types::FloatExact) { return Some(unsafe { rb_cFloat }); }
if self.is_subtype(types::HashExact) { return Some(unsafe { rb_cHash }); }
if self.is_subtype(types::IntegerExact) { return Some(unsafe { rb_cInteger }); }
if self.is_subtype(types::NilClassExact) { return Some(unsafe { rb_cNilClass }); }
if self.is_subtype(types::ObjectExact) { return Some(unsafe { rb_cObject }); }
if self.is_subtype(types::StringExact) { return Some(unsafe { rb_cString }); }
if self.is_subtype(types::SymbolExact) { return Some(unsafe { rb_cSymbol }); }
if self.is_subtype(types::TrueClassExact) { return Some(unsafe { rb_cTrueClass }); }
None
}

/// Check bit equality of two `Type`s. Do not use! You are probably looking for [`Type::is_subtype`].
pub fn bit_equal(&self, other: Type) -> bool {
self.bits == other.bits && self.spec == other.spec
Expand Down
Loading