diff --git a/internal/vm.h b/internal/vm.h index 7943027946..a89af3d9a9 100644 --- a/internal/vm.h +++ b/internal/vm.h @@ -76,6 +76,11 @@ VALUE rb_lambda_call(VALUE obj, ID mid, int argc, const VALUE *argv, VALUE data2); void rb_check_stack_overflow(void); +#if USE_YJIT +/* vm_exec.c */ +extern uint64_t rb_vm_insns_count; +#endif + /* vm_insnhelper.c */ VALUE rb_equal_opt(VALUE obj1, VALUE obj2); VALUE rb_eql_opt(VALUE obj1, VALUE obj2); diff --git a/vm_exec.c b/vm_exec.c index cc4df7351c..1a4e0ad514 100644 --- a/vm_exec.c +++ b/vm_exec.c @@ -11,6 +11,11 @@ #include +#if USE_YJIT +// The number of instructions executed on vm_exec_core. --yjit-stats uses this. +uint64_t rb_vm_insns_count = 0; +#endif + #if VM_COLLECT_USAGE_DETAILS static void vm_analysis_insn(int insn); #endif diff --git a/vm_insnhelper.h b/vm_insnhelper.h index 0e5f12c3b2..66895cd142 100644 --- a/vm_insnhelper.h +++ b/vm_insnhelper.h @@ -20,8 +20,11 @@ RUBY_EXTERN rb_serial_t ruby_vm_global_cvar_state; # define RJIT_STATS RUBY_DEBUG #endif -#if YJIT_STATS -#define YJIT_COLLECT_USAGE_INSN(insn) rb_yjit_collect_vm_usage_insn(insn) +#if USE_YJIT // We want vm_insns_count on any YJIT-enabled build +// Increment vm_insns_count for --yjit-stats. We increment this even when +// --yjit or --yjit-stats is not used because branching to skip it is slower. +// We also don't use ATOMIC_INC for performance, allowing inaccuracy on Ractors. +#define YJIT_COLLECT_USAGE_INSN(insn) rb_vm_insns_count++ #else #define YJIT_COLLECT_USAGE_INSN(insn) // none #endif diff --git a/yjit.h b/yjit.h index a640d5982a..a76dc6a850 100644 --- a/yjit.h +++ b/yjit.h @@ -30,7 +30,6 @@ bool rb_yjit_compile_new_iseqs(void); unsigned rb_yjit_call_threshold(void); void rb_yjit_invalidate_all_method_lookup_assumptions(void); void rb_yjit_cme_invalidate(rb_callable_method_entry_t *cme); -void rb_yjit_collect_vm_usage_insn(int insn); void rb_yjit_collect_binding_alloc(void); void rb_yjit_collect_binding_set(void); bool rb_yjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec); @@ -53,7 +52,6 @@ static inline bool rb_yjit_compile_new_iseqs(void) { return false; } static inline unsigned rb_yjit_call_threshold(void) { return UINT_MAX; } static inline void rb_yjit_invalidate_all_method_lookup_assumptions(void) {} static inline void rb_yjit_cme_invalidate(rb_callable_method_entry_t *cme) {} -static inline void rb_yjit_collect_vm_usage_insn(int insn) {} static inline void rb_yjit_collect_binding_alloc(void) {} static inline void rb_yjit_collect_binding_set(void) {} static inline bool rb_yjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec) { return false; } diff --git a/yjit.rb b/yjit.rb index dc4699c7e6..06a3fd5e14 100644 --- a/yjit.rb +++ b/yjit.rb @@ -167,14 +167,11 @@ module RubyVM::YJIT # Average length of instruction sequences executed by YJIT avg_len_in_yjit = total_exits > 0 ? retired_in_yjit.to_f / total_exits : 0 - # This only available on yjit stats builds - if stats.key?(:vm_insns_count) - # Proportion of instructions that retire in YJIT - total_insns_count = retired_in_yjit + stats[:vm_insns_count] - yjit_ratio_pct = 100.0 * retired_in_yjit.to_f / total_insns_count - stats[:total_insns_count] = total_insns_count - stats[:ratio_in_yjit] = yjit_ratio_pct - end + # Proportion of instructions that retire in YJIT + total_insns_count = retired_in_yjit + stats[:vm_insns_count] + yjit_ratio_pct = 100.0 * retired_in_yjit.to_f / total_insns_count + stats[:total_insns_count] = total_insns_count + stats[:ratio_in_yjit] = yjit_ratio_pct # Make those stats available in RubyVM::YJIT.runtime_stats as well stats[:side_exit_count] = side_exits @@ -318,14 +315,10 @@ module RubyVM::YJIT out.puts "object_shape_count: " + format_number(13, stats[:object_shape_count]) out.puts "side_exit_count: " + format_number(13, stats[:side_exit_count]) out.puts "total_exit_count: " + format_number(13, stats[:total_exit_count]) - out.puts "total_insns_count: " + format_number(13, stats[:total_insns_count]) if stats.key?(:total_insns_count) - if stats.key?(:vm_insns_count) - out.puts "vm_insns_count: " + format_number(13, stats[:vm_insns_count]) - end + out.puts "total_insns_count: " + format_number(13, stats[:total_insns_count]) + out.puts "vm_insns_count: " + format_number(13, stats[:vm_insns_count]) out.puts "yjit_insns_count: " + format_number(13, stats[:exec_instruction]) - if stats.key?(:ratio_in_yjit) - out.puts "ratio_in_yjit: " + ("%12.1f" % stats[:ratio_in_yjit]) + "%" - end + out.puts "ratio_in_yjit: " + ("%12.1f" % stats[:ratio_in_yjit]) + "%" out.puts "avg_len_in_yjit: " + ("%13.1f" % stats[:avg_len_in_yjit]) print_sorted_exit_counts(stats, out: out, prefix: "exit_") diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs index b9bef8e176..30b4c8b5d7 100644 --- a/yjit/bindgen/src/main.rs +++ b/yjit/bindgen/src/main.rs @@ -357,6 +357,9 @@ fn main() { .allowlist_function("rb_ivar_defined") .allowlist_function("rb_ivar_get") + // From internal/vm.h + .allowlist_var("rb_vm_insns_count") + // From include/ruby/internal/intern/vm.h .allowlist_function("rb_get_alloc_func") diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index 3a901b105b..c27fa1f29d 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -1135,6 +1135,7 @@ extern "C" { n: ::std::os::raw::c_long, elts: *const VALUE, ) -> VALUE; + pub static mut rb_vm_insns_count: u64; pub fn rb_method_entry_at(obj: VALUE, id: ID) -> *const rb_method_entry_t; pub fn rb_callable_method_entry(klass: VALUE, id: ID) -> *const rb_callable_method_entry_t; pub fn rb_callable_method_entry_or_negative( diff --git a/yjit/src/stats.rs b/yjit/src/stats.rs index 7f1a4e3d9f..216c7bddc8 100644 --- a/yjit/src/stats.rs +++ b/yjit/src/stats.rs @@ -369,7 +369,6 @@ make_counters! { binding_allocations, binding_set, - vm_insns_count, compiled_iseq_entry, compiled_iseq_count, compiled_blockid_count, @@ -507,7 +506,6 @@ fn rb_yjit_gen_stats_dict(context: bool) -> VALUE { let hash = unsafe { rb_hash_new() }; - // CodeBlock stats unsafe { // Get the inline and outlined code blocks let cb = CodegenGlobals::get_inline_cb(); @@ -547,6 +545,9 @@ fn rb_yjit_gen_stats_dict(context: bool) -> VALUE { hash_aset_usize!(hash, "live_context_count", live_context_count); hash_aset_usize!(hash, "live_context_size", live_context_count * context_size); } + + // VM instructions count + hash_aset_usize!(hash, "vm_insns_count", rb_vm_insns_count as usize); } // If we're not generating stats, the hash is done @@ -566,13 +567,6 @@ fn rb_yjit_gen_stats_dict(context: bool) -> VALUE { let counter_ptr = get_counter_ptr(counter_name); let counter_val = *counter_ptr; - #[cfg(not(feature = "stats"))] - if counter_name == &"vm_insns_count" { - // If the stats feature is disabled, we don't have vm_insns_count - // so we are going to exclude the key - continue; - } - // Put counter into hash let key = rust_str_to_sym(counter_name); let value = VALUE::fixnum_from_usize(counter_val as usize); @@ -750,12 +744,6 @@ pub extern "C" fn rb_yjit_reset_stats_bang(_ec: EcPtr, _ruby_self: VALUE) -> VAL return Qnil; } -/// Increment the number of instructions executed by the interpreter -#[no_mangle] -pub extern "C" fn rb_yjit_collect_vm_usage_insn() { - incr_counter!(vm_insns_count); -} - #[no_mangle] pub extern "C" fn rb_yjit_collect_binding_alloc() { incr_counter!(binding_allocations);