diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index a30ff1df88fdd9..02afda470aec47 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -69,8 +69,9 @@ jobs: - test_task: 'zjit-bindgen' hint: 'To fix: use patch in logs' - configure: '--enable-zjit=dev --with-gcc=clang-14' - libclang_path: '/usr/lib/llvm-14/lib/libclang.so.1' + configure: '--enable-zjit=dev --with-gcc=clang-16' + clang_path: '/usr/bin/clang-16' + runs-on: 'ubuntu-24.04' # for clang-16 - test_task: 'test-bundled-gems' configure: '--enable-zjit=dev' @@ -87,7 +88,7 @@ jobs: RUST_BACKTRACE: 1 ZJIT_RB_BUG: 1 - runs-on: ubuntu-22.04 + runs-on: ${{ matrix.runs-on || 'ubuntu-22.04' }} if: >- ${{!(false @@ -175,7 +176,7 @@ jobs: PRECHECK_BUNDLED_GEMS: 'no' SYNTAX_SUGGEST_TIMEOUT: '5' ZJIT_BINDGEN_DIFF_OPTS: '--exit-code' - LIBCLANG_PATH: ${{ matrix.libclang_path }} + CLANG_PATH: ${{ matrix.clang_path }} TESTS: ${{ matrix.test_all_opts }} continue-on-error: ${{ matrix.continue-on-test_task || false }} diff --git a/insns.def b/insns.def index 239fe85aa51439..eef0d3f5dc1124 100644 --- a/insns.def +++ b/insns.def @@ -1519,6 +1519,7 @@ opt_aref * default_proc. This is a method call. So opt_aref is * (surprisingly) not leaf. */ // attr bool leaf = false; /* has rb_funcall() */ /* calls #yield */ +// attr bool zjit_profile = true; { val = vm_opt_aref(recv, obj); diff --git a/zjit.c b/zjit.c index d877c0bacbd507..e17abc1b37ff29 100644 --- a/zjit.c +++ b/zjit.c @@ -235,7 +235,7 @@ rb_zjit_print_exception(void) rb_warn("Ruby error: %"PRIsVALUE"", rb_funcall(exception, rb_intern("full_message"), 0)); } -enum { +enum zjit_exported_constants { RB_INVALID_SHAPE_ID = INVALID_SHAPE_ID, }; diff --git a/zjit.rb b/zjit.rb index 75c57f9a35c195..87ff52f55a1c21 100644 --- a/zjit.rb +++ b/zjit.rb @@ -156,6 +156,7 @@ def stats_string # Show counters independent from exit_* or dynamic_send_* print_counters_with_prefix(prefix: 'not_inlined_cfuncs_', prompt: 'not inlined C methods', buf:, stats:, limit: 20) + print_counters_with_prefix(prefix: 'not_annotated_cfuncs_', prompt: 'not annotated C methods', buf:, stats:, limit: 20) # Show fallback counters, ordered by the typical amount of fallbacks for the prefix at the time print_counters_with_prefix(prefix: 'unspecialized_def_type_', prompt: 'not optimized method types', buf:, stats:, limit: 20) diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index e1d19f9442c62b..64b235b838baff 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -290,7 +290,7 @@ fn main() { .allowlist_function("rb_zjit_insn_leaf") .allowlist_type("robject_offsets") .allowlist_type("rstring_offsets") - .allowlist_var("RB_INVALID_SHAPE_ID") + .allowlist_type("zjit_exported_constants") .allowlist_function("rb_assert_holding_vm_lock") .allowlist_function("rb_jit_shape_too_complex_p") .allowlist_function("rb_jit_multi_ractor_p") diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index b1a2cb672641ef..4b9331e05b0892 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -409,7 +409,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::CCallWithFrame { cd, state, args, .. } if args.len() > C_ARG_OPNDS.len() => gen_send_without_block(jit, asm, *cd, &function.frame_state(*state), SendFallbackReason::CCallWithFrameTooManyArgs), Insn::CCallWithFrame { cfunc, args, cme, state, .. } => gen_ccall_with_frame(jit, asm, *cfunc, opnds!(args), *cme, &function.frame_state(*state)), - Insn::CCallVariadic { cfunc, recv, args, name: _, cme, state } => { + Insn::CCallVariadic { cfunc, recv, args, name: _, cme, state, return_type: _, elidable: _ } => { gen_ccall_variadic(jit, asm, *cfunc, opnd!(recv), opnds!(args), *cme, &function.frame_state(*state)) } Insn::GetIvar { self_val, id, state: _ } => gen_getivar(asm, opnd!(self_val), *id), diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 2d8a8eb11e7036..ab442841ff267a 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -696,9 +696,10 @@ pub const YARVINSN_zjit_opt_gt: ruby_vminsn_type = 231; pub const YARVINSN_zjit_opt_ge: ruby_vminsn_type = 232; pub const YARVINSN_zjit_opt_and: ruby_vminsn_type = 233; pub const YARVINSN_zjit_opt_or: ruby_vminsn_type = 234; -pub const YARVINSN_zjit_opt_empty_p: ruby_vminsn_type = 235; -pub const YARVINSN_zjit_opt_not: ruby_vminsn_type = 236; -pub const VM_INSTRUCTION_SIZE: ruby_vminsn_type = 237; +pub const YARVINSN_zjit_opt_aref: ruby_vminsn_type = 235; +pub const YARVINSN_zjit_opt_empty_p: ruby_vminsn_type = 236; +pub const YARVINSN_zjit_opt_not: ruby_vminsn_type = 237; +pub const VM_INSTRUCTION_SIZE: ruby_vminsn_type = 238; pub type ruby_vminsn_type = u32; pub type rb_iseq_callback = ::std::option::Option< unsafe extern "C" fn(arg1: *const rb_iseq_t, arg2: *mut ::std::os::raw::c_void), @@ -722,8 +723,8 @@ pub const DEFINED_REF: defined_type = 15; pub const DEFINED_FUNC: defined_type = 16; pub const DEFINED_CONST_FROM: defined_type = 17; pub type defined_type = u32; -pub const RB_INVALID_SHAPE_ID: _bindgen_ty_38 = 4294967295; -pub type _bindgen_ty_38 = u32; +pub const RB_INVALID_SHAPE_ID: zjit_exported_constants = 4294967295; +pub type zjit_exported_constants = u32; pub const ROBJECT_OFFSET_AS_HEAP_FIELDS: robject_offsets = 16; pub const ROBJECT_OFFSET_AS_ARY: robject_offsets = 16; pub type robject_offsets = u32; diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 41cff3403bcdfa..7467cb50c85446 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -31,6 +31,18 @@ pub struct FnProperties { pub elidable: bool, } +/// A safe default for un-annotated Ruby methods: we can't optimize them or their returned values. +impl Default for FnProperties { + fn default() -> Self { + Self { + no_gc: false, + leaf: false, + return_type: types::BasicObject, + elidable: false, + } + } +} + impl Annotations { /// Query about properties of a C method pub fn get_cfunc_properties(&self, method: *const rb_callable_method_entry_t) -> Option { @@ -140,11 +152,12 @@ pub fn init() -> Annotations { let builtin_funcs = &mut HashMap::new(); macro_rules! annotate { - ($module:ident, $method_name:literal, $return_type:expr, $($properties:ident),+) => { + ($module:ident, $method_name:literal, $return_type:expr $(, $properties:ident)*) => { + #[allow(unused_mut)] let mut props = FnProperties { no_gc: false, leaf: false, elidable: false, return_type: $return_type }; $( props.$properties = true; - )+ + )* annotate_c_method(cfuncs, unsafe { $module }, $method_name, props); } } @@ -167,11 +180,14 @@ pub fn init() -> Annotations { annotate!(rb_mKernel, "itself", types::BasicObject, no_gc, leaf, elidable); annotate!(rb_cString, "bytesize", types::Fixnum, no_gc, leaf); + annotate!(rb_cString, "to_s", types::StringExact); annotate!(rb_cModule, "name", types::StringExact.union(types::NilClass), no_gc, leaf, elidable); annotate!(rb_cModule, "===", types::BoolExact, no_gc, leaf); annotate!(rb_cArray, "length", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cArray, "size", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cArray, "empty?", types::BoolExact, no_gc, leaf, elidable); + annotate!(rb_cArray, "reverse", types::ArrayExact, leaf, elidable); + annotate!(rb_cArray, "join", types::StringExact); annotate!(rb_cHash, "empty?", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cNilClass, "nil?", types::TrueClass, no_gc, leaf, elidable); annotate!(rb_mKernel, "nil?", types::FalseClass, no_gc, leaf, elidable); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 26145f46226fbb..2fe8eb79700349 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -656,7 +656,9 @@ pub enum Insn { args: Vec, cme: *const rb_callable_method_entry_t, name: ID, - state: InsnId + state: InsnId, + return_type: Type, + elidable: bool, }, /// Call a variadic C function with signature: func(int argc, VALUE *argv, VALUE recv) @@ -668,6 +670,8 @@ pub enum Insn { cme: *const rb_callable_method_entry_t, name: ID, state: InsnId, + return_type: Type, + elidable: bool, }, /// Un-optimized fallback implementation (dynamic dispatch) for send-ish instructions @@ -845,6 +849,7 @@ impl Insn { Insn::LoadIvarEmbedded { .. } => false, Insn::LoadIvarExtended { .. } => false, Insn::CCall { elidable, .. } => !elidable, + Insn::CCallWithFrame { elidable, .. } => !elidable, Insn::ObjectAllocClass { .. } => false, // TODO: NewRange is effects free if we can prove the two ends to be Fixnum, // but we don't have type information here in `impl Insn`. See rb_range_new(). @@ -1039,7 +1044,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Ok(()) }, Insn::CCallWithFrame { cfunc, args, name, .. } => { - write!(f, "CallCFunc {}@{:p}", name.contents_lossy(), self.ptr_map.map_ptr(cfunc))?; + write!(f, "CCallWithFrame {}@{:p}", name.contents_lossy(), self.ptr_map.map_ptr(cfunc))?; for arg in args { write!(f, ", {arg}")?; } @@ -1564,9 +1569,9 @@ impl Function { &ObjectAlloc { val, state } => ObjectAlloc { val: find!(val), state }, &ObjectAllocClass { class, state } => ObjectAllocClass { class, state: find!(state) }, &CCall { cfunc, ref args, name, return_type, elidable } => CCall { cfunc, args: find_vec!(args), name, return_type, elidable }, - &CCallWithFrame { cd, cfunc, ref args, cme, name, state } => CCallWithFrame { cd, cfunc, args: find_vec!(args), cme, name, state: find!(state) }, - &CCallVariadic { cfunc, recv, ref args, cme, name, state } => CCallVariadic { - cfunc, recv: find!(recv), args: find_vec!(args), cme, name, state + &CCallWithFrame { cd, cfunc, ref args, cme, name, state, return_type, elidable } => CCallWithFrame { cd, cfunc, args: find_vec!(args), cme, name, state: find!(state), return_type, elidable }, + &CCallVariadic { cfunc, recv, ref args, cme, name, state, return_type, elidable } => CCallVariadic { + cfunc, recv: find!(recv), args: find_vec!(args), cme, name, state, return_type, elidable }, &Defined { op_type, obj, pushval, v, state } => Defined { op_type, obj, pushval, v: find!(v), state: find!(state) }, &DefinedIvar { self_val, pushval, id, state } => DefinedIvar { self_val: find!(self_val), pushval, id, state }, @@ -1665,9 +1670,9 @@ impl Function { Insn::NewRangeFixnum { .. } => types::RangeExact, Insn::ObjectAlloc { .. } => types::HeapObject, Insn::ObjectAllocClass { class, .. } => Type::from_class(*class), - Insn::CCallWithFrame { .. } => types::BasicObject, + &Insn::CCallWithFrame { return_type, .. } => return_type, Insn::CCall { return_type, .. } => *return_type, - Insn::CCallVariadic { .. } => types::BasicObject, + &Insn::CCallVariadic { return_type, .. } => return_type, Insn::GuardType { val, guard_type, .. } => self.type_of(*val).intersection(*guard_type), Insn::GuardTypeNot { .. } => types::BasicObject, Insn::GuardBitEquals { val, expected, .. } => self.type_of(*val).intersection(Type::from_value(*expected)), @@ -2322,10 +2327,12 @@ impl Function { let ci_flags = unsafe { vm_ci_flag(call_info) }; + // Filter for simple call sites (i.e. no splats etc.) if ci_flags & VM_CALL_ARGS_SIMPLE == 0 { return Err(()); } + // Commit to the replacement. Put PatchPoint. gen_patch_points_for_optimized_ccall(fun, block, recv_class, method_id, method, state); if recv_class.instance_can_have_singleton_class() { fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass: recv_class }, state }); @@ -2338,18 +2345,22 @@ impl Function { let mut cfunc_args = vec![recv]; cfunc_args.append(&mut args); + let props = ZJITState::get_method_annotations().get_cfunc_properties(method); + if props.is_none() && get_option!(stats) { + count_not_annotated_cfunc(fun, block, method); + } + let props = props.unwrap_or_default(); + let return_type = props.return_type; + let elidable = props.elidable; // Filter for a leaf and GC free function - use crate::cruby_methods::FnProperties; - // Filter for simple call sites (i.e. no splats etc.) - // Commit to the replacement. Put PatchPoint. - if let Some(FnProperties { leaf: true, no_gc: true, return_type, elidable }) = ZJITState::get_method_annotations().get_cfunc_properties(method) { + if props.leaf && props.no_gc { let ccall = fun.push_insn(block, Insn::CCall { cfunc, args: cfunc_args, name: method_id, return_type, elidable }); fun.make_equal_to(send_insn_id, ccall); } else { if get_option!(stats) { count_not_inlined_cfunc(fun, block, method); } - let ccall = fun.push_insn(block, Insn::CCallWithFrame { cd, cfunc, args: cfunc_args, cme: method, name: method_id, state }); + let ccall = fun.push_insn(block, Insn::CCallWithFrame { cd, cfunc, args: cfunc_args, cme: method, name: method_id, state, return_type, elidable }); fun.make_equal_to(send_insn_id, ccall); } @@ -2375,6 +2386,13 @@ impl Function { } let cfunc = unsafe { get_mct_func(cfunc) }.cast(); + let props = ZJITState::get_method_annotations().get_cfunc_properties(method); + if props.is_none() && get_option!(stats) { + count_not_annotated_cfunc(fun, block, method); + } + let props = props.unwrap_or_default(); + let return_type = props.return_type; + let elidable = props.elidable; let ccall = fun.push_insn(block, Insn::CCallVariadic { cfunc, recv, @@ -2382,6 +2400,8 @@ impl Function { cme: method, name: method_id, state, + return_type, + elidable, }); fun.make_equal_to(send_insn_id, ccall); @@ -2414,6 +2434,19 @@ impl Function { fun.push_insn(block, Insn::IncrCounterPtr { counter_ptr }); } + fn count_not_annotated_cfunc(fun: &mut Function, block: BlockId, cme: *const rb_callable_method_entry_t) { + let owner = unsafe { (*cme).owner }; + let called_id = unsafe { (*cme).called_id }; + let class_name = get_class_name(owner); + let method_name = called_id.contents_lossy(); + let qualified_method_name = format!("{}#{}", class_name, method_name); + let not_annotated_cfunc_counter_pointers = ZJITState::get_not_annotated_cfunc_counter_pointers(); + let counter_ptr = not_annotated_cfunc_counter_pointers.entry(qualified_method_name.clone()).or_insert_with(|| Box::new(0)); + let counter_ptr = &mut **counter_ptr as *mut u64; + + fun.push_insn(block, Insn::IncrCounterPtr { counter_ptr }); + } + for block in self.rpo() { let old_insns = std::mem::take(&mut self.blocks[block.0].insns); assert!(self.blocks[block.0].insns.is_empty()); @@ -9069,6 +9102,64 @@ mod opt_tests { "); } + #[test] + fn test_opt_aref_array() { + eval(" + arr = [1,2,3] + def test(arr) = arr[0] + test(arr) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[0] = Const Value(0) + PatchPoint MethodRedefined(Array@0x1000, []@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v26:ArrayExact = GuardType v9, ArrayExact + v27:BasicObject = CCallVariadic []@0x1038, v26, v13 + CheckInterrupts + Return v27 + "); + assert_snapshot!(inspect("test [1,2,3]"), @"1"); + } + + #[test] + fn test_opt_aref_hash() { + eval(" + arr = {0 => 4} + def test(arr) = arr[0] + test(arr) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[0] = Const Value(0) + PatchPoint MethodRedefined(Hash@0x1000, []@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Hash@0x1000) + v26:HashExact = GuardType v9, HashExact + v27:BasicObject = CCallWithFrame []@0x1038, v26, v13 + CheckInterrupts + Return v27 + "); + assert_snapshot!(inspect("test({0 => 4})"), @"4"); + } + #[test] fn test_eliminate_new_range() { eval(" @@ -10894,7 +10985,7 @@ mod opt_tests { v11:HashExact = NewHash PatchPoint MethodRedefined(Hash@0x1000, dup@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Hash@0x1000) - v24:BasicObject = CallCFunc dup@0x1038, v11 + v24:BasicObject = CCallWithFrame dup@0x1038, v11 v15:BasicObject = SendWithoutBlock v24, :freeze CheckInterrupts Return v15 @@ -10987,7 +11078,7 @@ mod opt_tests { v11:ArrayExact = NewArray PatchPoint MethodRedefined(Array@0x1000, dup@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) - v24:BasicObject = CallCFunc dup@0x1038, v11 + v24:BasicObject = CCallWithFrame dup@0x1038, v11 v15:BasicObject = SendWithoutBlock v24, :freeze CheckInterrupts Return v15 @@ -11081,7 +11172,7 @@ mod opt_tests { v12:StringExact = StringCopy v10 PatchPoint MethodRedefined(String@0x1008, dup@0x1010, cme:0x1018) PatchPoint NoSingletonClass(String@0x1008) - v25:BasicObject = CallCFunc dup@0x1040, v12 + v25:BasicObject = CCallWithFrame dup@0x1040, v12 v16:BasicObject = SendWithoutBlock v25, :freeze CheckInterrupts Return v16 @@ -11176,7 +11267,7 @@ mod opt_tests { v12:StringExact = StringCopy v10 PatchPoint MethodRedefined(String@0x1008, dup@0x1010, cme:0x1018) PatchPoint NoSingletonClass(String@0x1008) - v25:BasicObject = CallCFunc dup@0x1040, v12 + v25:BasicObject = CCallWithFrame dup@0x1040, v12 v16:BasicObject = SendWithoutBlock v25, :-@ CheckInterrupts Return v16 @@ -11318,7 +11409,7 @@ mod opt_tests { PatchPoint MethodRedefined(Array@0x1008, to_s@0x1010, cme:0x1018) PatchPoint NoSingletonClass(Array@0x1008) v30:ArrayExact = GuardType v9, ArrayExact - v31:BasicObject = CallCFunc to_s@0x1040, v30 + v31:BasicObject = CCallWithFrame to_s@0x1040, v30 v17:String = AnyToString v9, str: v31 v19:StringExact = StringConcat v13, v17 CheckInterrupts @@ -12371,4 +12462,106 @@ mod opt_tests { Return v14 "); } + + #[test] + fn test_array_reverse_returns_array() { + eval(r#" + def test = [].reverse + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:ArrayExact = NewArray + PatchPoint MethodRedefined(Array@0x1000, reverse@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v22:ArrayExact = CCallWithFrame reverse@0x1038, v11 + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_array_reverse_is_elidable() { + eval(r#" + def test + [].reverse + 5 + end + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:ArrayExact = NewArray + PatchPoint MethodRedefined(Array@0x1000, reverse@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v16:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v16 + "); + } + + #[test] + fn test_array_join_returns_string() { + eval(r#" + def test = [].join "," + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:ArrayExact = NewArray + v12:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v14:StringExact = StringCopy v12 + PatchPoint MethodRedefined(Array@0x1008, join@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v25:StringExact = CCallVariadic join@0x1040, v11, v14 + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_string_to_s_returns_string() { + eval(r#" + def test = "".to_s + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:StringExact = StringCopy v10 + PatchPoint MethodRedefined(String@0x1008, to_s@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(String@0x1008) + v23:StringExact = CCallWithFrame to_s@0x1040, v12 + CheckInterrupts + Return v23 + "); + } } diff --git a/zjit/src/profile.rs b/zjit/src/profile.rs index 9588a541827f35..67f2fdc7403822 100644 --- a/zjit/src/profile.rs +++ b/zjit/src/profile.rs @@ -73,6 +73,7 @@ fn profile_insn(bare_opcode: ruby_vminsn_type, ec: EcPtr) { YARVINSN_opt_and => profile_operands(profiler, profile, 2), YARVINSN_opt_or => profile_operands(profiler, profile, 2), YARVINSN_opt_empty_p => profile_operands(profiler, profile, 1), + YARVINSN_opt_aref => profile_operands(profiler, profile, 2), YARVINSN_opt_not => profile_operands(profiler, profile, 1), YARVINSN_getinstancevariable => profile_self(profiler, profile), YARVINSN_objtostring => profile_operands(profiler, profile, 1), diff --git a/zjit/src/state.rs b/zjit/src/state.rs index 409cac7e9bb421..1b766d5bc4b5aa 100644 --- a/zjit/src/state.rs +++ b/zjit/src/state.rs @@ -54,6 +54,9 @@ pub struct ZJITState { /// Counter pointers for full frame C functions full_frame_cfunc_counter_pointers: HashMap>, + /// Counter pointers for un-annotated C functions + not_annotated_frame_cfunc_counter_pointers: HashMap>, + /// Locations of side exists within generated code exit_locations: Option, } @@ -98,6 +101,7 @@ impl ZJITState { function_stub_hit_trampoline, exit_trampoline_with_counter: exit_trampoline, full_frame_cfunc_counter_pointers: HashMap::new(), + not_annotated_frame_cfunc_counter_pointers: HashMap::new(), exit_locations, }; unsafe { ZJIT_STATE = Some(zjit_state); } @@ -167,6 +171,11 @@ impl ZJITState { &mut ZJITState::get_instance().full_frame_cfunc_counter_pointers } + /// Get a mutable reference to non-annotated cfunc counter pointers + pub fn get_not_annotated_cfunc_counter_pointers() -> &'static mut HashMap> { + &mut ZJITState::get_instance().not_annotated_frame_cfunc_counter_pointers + } + /// Was --zjit-save-compiled-iseqs specified? pub fn should_log_compiled_iseqs() -> bool { get_option!(log_compiled_iseqs).is_some() diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index a9b7270444a7a5..6898053dca789b 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -480,6 +480,13 @@ pub extern "C" fn rb_zjit_stats(_ec: EcPtr, _self: VALUE, target_key: VALUE) -> set_stat_usize!(hash, &key_string, **counter); } + // Set not annotated cfunc counters + let not_annotated_cfuncs = ZJITState::get_not_annotated_cfunc_counter_pointers(); + for (signature, counter) in not_annotated_cfuncs.iter() { + let key_string = format!("not_annotated_cfuncs_{}", signature); + set_stat_usize!(hash, &key_string, **counter); + } + hash }