summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--zjit/src/hir.rs63
-rw-r--r--zjit/src/hir_type/mod.rs20
2 files changed, 74 insertions, 9 deletions
diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs
index 369ee0a137..e20ba45eb8 100644
--- a/zjit/src/hir.rs
+++ b/zjit/src/hir.rs
@@ -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;
}
@@ -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);
}
@@ -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))))
@@ -3355,6 +3365,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("
def test = 'abc'.bytesize
diff --git a/zjit/src/hir_type/mod.rs b/zjit/src/hir_type/mod.rs
index b438b14242..f19c724417 100644
--- a/zjit/src/hir_type/mod.rs
+++ b/zjit/src/hir_type/mod.rs
@@ -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