diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index 4262cc87b9cfec..bc4fef7ce2427f 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -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' diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 5dbe9ec52449c8..813f88c53b6713 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -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' diff --git a/doc/contributing/glossary.md b/doc/contributing/glossary.md index 6f9c33502820ce..fb5570f5e2562b 100644 --- a/doc/contributing/glossary.md +++ b/doc/contributing/glossary.md @@ -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. | diff --git a/lib/erb.rb b/lib/erb.rb index de490c089e3a9f..b9b4ef7a1deec3 100644 --- a/lib/erb.rb +++ b/lib/erb.rb @@ -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 diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index f0109bda8b147c..c1e8cfa8059f08 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -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 diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 85fed25d24e48f..67841d2fdc721e 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -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, @@ -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()), @@ -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, diff --git a/zjit.rb b/zjit.rb index e3d9d4c728f459..eb47a704708cae 100644 --- a/zjit.rb +++ b/zjit.rb @@ -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) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 03228db0bb7c01..bd7ab847861605 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -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( diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 6b1d22ac111e38..136764f137b99b 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -286,6 +286,7 @@ impl Const { } } +#[derive(Clone, Copy)] pub enum RangeType { Inclusive = 0, // include the end value Exclusive = 1, // exclude the end value @@ -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 for RangeType { fn from(flag: u32) -> Self { match flag { @@ -324,12 +317,6 @@ impl From for RangeType { } } -impl From 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 { @@ -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, @@ -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 { + 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() } @@ -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)) @@ -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, @@ -4971,12 +4994,41 @@ mod tests { assert_snapshot!(hir_string("test"), @r" fn test@: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@: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(" @@ -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 "); } @@ -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 diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 012b8be250b43f..4f38e4a131abf8 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -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, @@ -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, } }