Skip to content
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
4 changes: 2 additions & 2 deletions .github/workflows/zjit-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ jobs:
test_all_opts: '--seed=46450'

- test_task: 'check'
run_opts: '--zjit-call-threshold=1 --zjit-disable-hir-opt'
specopts: '-T --zjit-call-threshold=1 -T --zjit-disable-hir-opt'
run_opts: '--zjit-disable-hir-opt --zjit-call-threshold=1'
specopts: '-T --zjit-disable-hir-opt -T --zjit-call-threshold=1'
configure: '--enable-zjit=dev'
test_all_opts: '--seed=46450'

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/zjit-ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ jobs:
test_all_opts: '--seed=39471'

- test_task: 'check'
run_opts: '--zjit-call-threshold=1 --zjit-disable-hir-opt'
specopts: '-T --zjit-call-threshold=1 -T --zjit-disable-hir-opt'
run_opts: '--zjit-disable-hir-opt --zjit-call-threshold=1'
specopts: '-T --zjit-disable-hir-opt -T --zjit-call-threshold=1'
configure: '--enable-zjit=dev'
test_all_opts: '--seed=39471'

Expand Down
2 changes: 2 additions & 0 deletions doc/contributing/glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ Just a list of acronyms I've run across in the Ruby source code and their meanin
| `me` | Method Entry. Refers to an `rb_method_entry_t` struct, the internal representation of a Ruby method. |
| MRI | Matz's Ruby Implementation |
| `pc` | Program Counter. Usually the instruction that will be executed _next_ by the VM. Pointed to by the `cfp` and incremented by the VM |
| `snt` | Shared Native Thread. OS thread on which many ruby threads can run. Ruby threads from different ractors can even run on the same SNT. Ruby threads can switch SNTs when they context switch. SNTs are used in the M:N threading model. By default, non-main ractors use this model.
| `dnt` | Dedicated Native Thread. OS thread on which only one ruby thread can run. The ruby thread always runs on that same OS thread. DNTs are used in the 1:1 threading model. By default, the main ractor uses this model.
| `sp` | Stack Pointer. The top of the stack. The VM executes instructions in the `iseq` and instructions will push and pop values on the stack. The VM updates the `sp` on the `cfp` to point at the top of the stack|
| `svar` | Special Variable. Refers to special local variables like `$~` and `$_`. See the `getspecial` instruction in `insns.def` |
| `VALUE` | VALUE is a pointer to a ruby object from the Ruby C code. |
Expand Down
4 changes: 2 additions & 2 deletions lib/erb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1173,12 +1173,12 @@ def def_module(methodname='erb')
# template = ERB.new(html)
# ```
#
# Create a base class that has `@arg1` and `arg2`:
# Create a base class that has `@arg1` and `@arg2`:
#
# ```
# class MyBaseClass
# def initialize(arg1, arg2)
# @arg1 = arg1;
# @arg1 = arg1
# @arg2 = arg2
# end
# end
Expand Down
7 changes: 7 additions & 0 deletions test/ruby/test_zjit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,13 @@ def entry = test(a: 1, b: 2)
}
end

def test_send_ccall_variadic_with_different_receiver_classes
assert_compiles '[true, true]', %q{
def test(obj) = obj.start_with?("a")
[test("abc"), test(:abc)]
}, call_threshold: 2
end

def test_forwardable_iseq
assert_compiles '1', %q{
def test(...) = 1
Expand Down
6 changes: 3 additions & 3 deletions yjit/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3148,7 +3148,7 @@ fn gen_set_ivar(
asm.ccall(
rb_vm_setinstancevariable as *const u8,
vec![
Opnd::const_ptr(jit.iseq as *const u8),
VALUE(jit.iseq as usize).into(),
Opnd::mem(64, CFP, RUBY_OFFSET_CFP_SELF),
ivar_name.into(),
val_opnd,
Expand Down Expand Up @@ -10207,7 +10207,7 @@ fn gen_getclassvariable(
let val_opnd = asm.ccall(
rb_vm_getclassvariable as *const u8,
vec![
Opnd::mem(64, CFP, RUBY_OFFSET_CFP_ISEQ),
VALUE(jit.iseq as usize).into(),
CFP,
Opnd::UImm(jit.get_arg(0).as_u64()),
Opnd::UImm(jit.get_arg(1).as_u64()),
Expand All @@ -10231,7 +10231,7 @@ fn gen_setclassvariable(
asm.ccall(
rb_vm_setclassvariable as *const u8,
vec![
Opnd::mem(64, CFP, RUBY_OFFSET_CFP_ISEQ),
VALUE(jit.iseq as usize).into(),
CFP,
Opnd::UImm(jit.get_arg(0).as_u64()),
val,
Expand Down
3 changes: 2 additions & 1 deletion zjit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ def stats_string

# Show non-exit counters
print_counters_with_prefix(prefix: 'dynamic_send_type_', prompt: 'dynamic send types', buf:, stats:, limit: 20)
print_counters_with_prefix(prefix: 'send_fallback_', prompt: 'send fallback def_types', buf:, stats:, limit: 20)
print_counters_with_prefix(prefix: 'unspecialized_def_type_', prompt: 'send fallback unspecialized def_types', buf:, stats:, limit: 20)
print_counters_with_prefix(prefix: 'send_fallback_', prompt: 'dynamic send types', buf:, stats:, limit: 20)

# Show exit counters, ordered by the typical amount of exits for the prefix at the time
print_counters_with_prefix(prefix: 'unhandled_yarv_insn_', prompt: 'unhandled YARV insns', buf:, stats:, limit: 20)
Expand Down
2 changes: 1 addition & 1 deletion zjit/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1290,7 +1290,7 @@ fn gen_new_range(
gen_prepare_non_leaf_call(jit, asm, state);

// Call rb_range_new(low, high, flag)
asm_ccall!(asm, rb_range_new, low, high, (flag as i64).into())
asm_ccall!(asm, rb_range_new, low, high, (flag as i32).into())
}

fn gen_new_range_fixnum(
Expand Down
92 changes: 73 additions & 19 deletions zjit/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ impl Const {
}
}

#[derive(Clone, Copy)]
pub enum RangeType {
Inclusive = 0, // include the end value
Exclusive = 1, // exclude the end value
Expand All @@ -306,14 +307,6 @@ impl std::fmt::Debug for RangeType {
}
}

impl Clone for RangeType {
fn clone(&self) -> Self {
*self
}
}

impl Copy for RangeType {}

impl From<u32> for RangeType {
fn from(flag: u32) -> Self {
match flag {
Expand All @@ -324,12 +317,6 @@ impl From<u32> for RangeType {
}
}

impl From<RangeType> for u32 {
fn from(range_type: RangeType) -> Self {
range_type as u32
}
}

/// Special regex backref symbol types
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum SpecialBackrefSymbol {
Expand Down Expand Up @@ -1569,7 +1556,7 @@ impl Function {
Insn::InvokeBlock { .. } => types::BasicObject,
Insn::InvokeBuiltin { return_type, .. } => return_type.unwrap_or(types::BasicObject),
Insn::Defined { pushval, .. } => Type::from_value(*pushval).union(types::NilClass),
Insn::DefinedIvar { .. } => types::BasicObject,
Insn::DefinedIvar { pushval, .. } => Type::from_value(*pushval).union(types::NilClass),
Insn::GetConstantPath { .. } => types::BasicObject,
Insn::ArrayMax { .. } => types::BasicObject,
Insn::GetGlobal { .. } => types::BasicObject,
Expand Down Expand Up @@ -1690,6 +1677,28 @@ impl Function {
None
}

/// Return whether a given HIR instruction as profiled by the interpreter is polymorphic or
/// whether it lacks a profile entirely.
///
/// * `Some(true)` if polymorphic
/// * `Some(false)` if monomorphic
/// * `None` if no profiled information so far
fn is_polymorphic_at(&self, insn: InsnId, iseq_insn_idx: usize) -> Option<bool> {
let profiles = self.profiles.as_ref()?;
let entries = profiles.types.get(&iseq_insn_idx)?;
let insn = self.chase_insn(insn);
for (entry_insn, entry_type_summary) in entries {
if self.union_find.borrow().find_const(*entry_insn) == insn {
if !entry_type_summary.is_monomorphic() && !entry_type_summary.is_skewed_polymorphic() {
return Some(true);
} else {
return Some(false);
}
}
}
None
}

fn likely_is_fixnum(&self, val: InsnId, profiled_type: ProfiledType) -> bool {
self.is_a(val, types::Fixnum) || profiled_type.is_fixnum()
}
Expand Down Expand Up @@ -1840,6 +1849,15 @@ impl Function {
// If we know that self is reasonably monomorphic from profile information, guard and use it to fold the lookup at compile-time.
// TODO(max): Figure out how to handle top self?
let Some(recv_type) = self.profiled_type_of_at(recv, frame_state.insn_idx) else {
if get_option!(stats) {
match self.is_polymorphic_at(recv, frame_state.insn_idx) {
Some(true) => self.push_insn(block, Insn::IncrCounter(Counter::send_fallback_polymorphic)),
// If the class isn't known statically, then it should not also be monomorphic
Some(false) => panic!("Should not have monomorphic profile at this point in this branch"),
None => self.push_insn(block, Insn::IncrCounter(Counter::send_fallback_no_profiles)),

};
}
self.push_insn_id(block, insn_id); continue;
};
(recv_type.class(), Some(recv_type))
Expand Down Expand Up @@ -2151,6 +2169,11 @@ impl Function {
state
});

if let Some(profiled_type) = profiled_type {
// Guard receiver class
recv = fun.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state });
}

let cfun = unsafe { get_mct_func(cfunc) }.cast();
let ccall = fun.push_insn(block, Insn::CCallVariadic {
cfun,
Expand Down Expand Up @@ -4971,12 +4994,41 @@ mod tests {
assert_snapshot!(hir_string("test"), @r"
fn test@<compiled>:2:
bb0(v0:BasicObject):
v5:BasicObject = DefinedIvar v0, :@foo
v5:StringExact|NilClass = DefinedIvar v0, :@foo
CheckInterrupts
Return v5
");
}

#[test]
fn if_defined_ivar() {
eval("
def test
if defined?(@foo)
3
else
4
end
end
");
assert_contains_opcode("test", YARVINSN_definedivar);
assert_snapshot!(hir_string("test"), @r"
fn test@<compiled>:3:
bb0(v0:BasicObject):
v5:TrueClass|NilClass = DefinedIvar v0, :@foo
CheckInterrupts
v8:CBool = Test v5
IfFalse v8, bb1(v0)
v12:Fixnum[3] = Const Value(3)
CheckInterrupts
Return v12
bb1(v18:BasicObject):
v22:Fixnum[4] = Const Value(4)
CheckInterrupts
Return v22
");
}

#[test]
fn defined() {
eval("
Expand Down Expand Up @@ -7084,9 +7136,10 @@ mod opt_tests {
v4:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
v6:StringExact = StringCopy v4
PatchPoint MethodRedefined(Object@0x1008, puts@0x1010, cme:0x1018)
v16:BasicObject = CCallVariadic puts@0x1040, v0, v6
v16:HeapObject[class_exact*:Object@VALUE(0x1008)] = GuardType v0, HeapObject[class_exact*:Object@VALUE(0x1008)]
v17:BasicObject = CCallVariadic puts@0x1040, v16, v6
CheckInterrupts
Return v16
Return v17
");
}

Expand Down Expand Up @@ -8468,7 +8521,8 @@ mod opt_tests {
PatchPoint MethodRedefined(Set@0x1008, new@0x1010, cme:0x1018)
v10:HeapObject = ObjectAlloc v34
PatchPoint MethodRedefined(Set@0x1008, initialize@0x1040, cme:0x1048)
v39:BasicObject = CCallVariadic initialize@0x1070, v10
v39:HeapObject[class_exact:Set] = GuardType v10, HeapObject[class_exact:Set]
v40:BasicObject = CCallVariadic initialize@0x1070, v39
CheckInterrupts
CheckInterrupts
Return v10
Expand Down
55 changes: 29 additions & 26 deletions zjit/src/stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,19 +138,22 @@ make_counters! {
dynamic_send_type_invokesuper,

// Method call def_type related to fallback to dynamic dispatch
send_fallback_iseq,
send_fallback_cfunc,
send_fallback_attrset,
send_fallback_ivar,
send_fallback_bmethod,
send_fallback_zsuper,
send_fallback_alias,
send_fallback_undef,
send_fallback_not_implemented,
send_fallback_optimized,
send_fallback_missing,
send_fallback_refined,
send_fallback_null,
unspecialized_def_type_iseq,
unspecialized_def_type_cfunc,
unspecialized_def_type_attrset,
unspecialized_def_type_ivar,
unspecialized_def_type_bmethod,
unspecialized_def_type_zsuper,
unspecialized_def_type_alias,
unspecialized_def_type_undef,
unspecialized_def_type_not_implemented,
unspecialized_def_type_optimized,
unspecialized_def_type_missing,
unspecialized_def_type_refined,
unspecialized_def_type_null,

send_fallback_polymorphic,
send_fallback_no_profiles,

// Writes to the VM frame
vm_write_pc_count,
Expand Down Expand Up @@ -252,19 +255,19 @@ pub fn send_fallback_counter(def_type: crate::hir::MethodType) -> Counter {
use crate::stats::Counter::*;

match def_type {
Iseq => send_fallback_iseq,
Cfunc => send_fallback_cfunc,
Attrset => send_fallback_attrset,
Ivar => send_fallback_ivar,
Bmethod => send_fallback_bmethod,
Zsuper => send_fallback_zsuper,
Alias => send_fallback_alias,
Undefined => send_fallback_undef,
NotImplemented => send_fallback_not_implemented,
Optimized => send_fallback_optimized,
Missing => send_fallback_missing,
Refined => send_fallback_refined,
Null => send_fallback_null,
Iseq => unspecialized_def_type_iseq,
Cfunc => unspecialized_def_type_cfunc,
Attrset => unspecialized_def_type_attrset,
Ivar => unspecialized_def_type_ivar,
Bmethod => unspecialized_def_type_bmethod,
Zsuper => unspecialized_def_type_zsuper,
Alias => unspecialized_def_type_alias,
Undefined => unspecialized_def_type_undef,
NotImplemented => unspecialized_def_type_not_implemented,
Optimized => unspecialized_def_type_optimized,
Missing => unspecialized_def_type_missing,
Refined => unspecialized_def_type_refined,
Null => unspecialized_def_type_null,
}
}

Expand Down