summaryrefslogtreecommitdiff
path: root/yjit/src/stats.rs
diff options
context:
space:
mode:
Diffstat (limited to 'yjit/src/stats.rs')
-rw-r--r--yjit/src/stats.rs271
1 files changed, 271 insertions, 0 deletions
diff --git a/yjit/src/stats.rs b/yjit/src/stats.rs
new file mode 100644
index 0000000000..5e42e4d6f0
--- /dev/null
+++ b/yjit/src/stats.rs
@@ -0,0 +1,271 @@
+//! Everything related to the collection of runtime stats in YJIT
+//! See the stats feature and the --yjit-stats command-line option
+
+use crate::codegen::CodegenGlobals;
+use crate::cruby::*;
+use crate::options::*;
+use crate::yjit::yjit_enabled_p;
+
+// YJIT exit counts for each instruction type
+static mut EXIT_OP_COUNT: [u64; VM_INSTRUCTION_SIZE] = [0; VM_INSTRUCTION_SIZE];
+
+// Macro to declare the stat counters
+macro_rules! make_counters {
+ ($($counter_name:ident,)+) => {
+ // Struct containing the counter values
+ #[derive(Default, Debug)]
+ pub struct Counters { $(pub $counter_name: u64),+ }
+
+ // Global counters instance, initialized to zero
+ pub static mut COUNTERS: Counters = Counters { $($counter_name: 0),+ };
+
+ // Counter names constant
+ const COUNTER_NAMES: &'static [&'static str] = &[ $(stringify!($counter_name)),+ ];
+
+ // Map a counter name string to a counter pointer
+ fn get_counter_ptr(name: &str) -> *mut u64 {
+ match name {
+ $( stringify!($counter_name) => { ptr_to_counter!($counter_name) } ),+
+ _ => panic!()
+ }
+ }
+ }
+}
+
+/// Macro to increment a counter by name
+macro_rules! incr_counter {
+ // Unsafe is ok here because options are initialized
+ // once before any Ruby code executes
+ ($counter_name:ident) => {
+ #[allow(unused_unsafe)]
+ {
+ unsafe { COUNTERS.$counter_name += 1 }
+ }
+ };
+}
+pub(crate) use incr_counter;
+
+/// Macro to get a raw pointer to a given counter
+macro_rules! ptr_to_counter {
+ ($counter_name:ident) => {
+ unsafe {
+ let ctr_ptr = std::ptr::addr_of_mut!(COUNTERS.$counter_name);
+ ctr_ptr
+ }
+ };
+}
+pub(crate) use ptr_to_counter;
+
+// Declare all the counters we track
+make_counters! {
+ exec_instruction,
+
+ send_keywords,
+ send_kw_splat,
+ send_args_splat,
+ send_block_arg,
+ send_ivar_set_method,
+ send_zsuper_method,
+ send_undef_method,
+ send_optimized_method,
+ send_optimized_method_send,
+ send_optimized_method_call,
+ send_optimized_method_block_call,
+ send_missing_method,
+ send_bmethod,
+ send_refined_method,
+ send_cfunc_ruby_array_varg,
+ send_cfunc_argc_mismatch,
+ send_cfunc_toomany_args,
+ send_cfunc_tracing,
+ send_cfunc_kwargs,
+ send_attrset_kwargs,
+ send_iseq_tailcall,
+ send_iseq_arity_error,
+ send_iseq_only_keywords,
+ send_iseq_kwargs_req_and_opt_missing,
+ send_iseq_kwargs_mismatch,
+ send_iseq_complex_callee,
+ send_not_implemented_method,
+ send_getter_arity,
+ send_se_cf_overflow,
+ send_se_protected_check_failed,
+
+ traced_cfunc_return,
+
+ invokesuper_me_changed,
+ invokesuper_block,
+
+ leave_se_interrupt,
+ leave_interp_return,
+ leave_start_pc_non_zero,
+
+ getivar_se_self_not_heap,
+ getivar_idx_out_of_range,
+ getivar_megamorphic,
+
+ setivar_se_self_not_heap,
+ setivar_idx_out_of_range,
+ setivar_val_heapobject,
+ setivar_name_not_mapped,
+ setivar_not_object,
+ setivar_frozen,
+
+ oaref_argc_not_one,
+ oaref_arg_not_fixnum,
+
+ opt_getinlinecache_miss,
+
+ binding_allocations,
+ binding_set,
+
+ vm_insns_count,
+ compiled_iseq_count,
+ compiled_block_count,
+ compilation_failure,
+
+ exit_from_branch_stub,
+
+ invalidation_count,
+ invalidate_method_lookup,
+ invalidate_bop_redefined,
+ invalidate_ractor_spawn,
+ invalidate_constant_state_bump,
+ invalidate_constant_ic_fill,
+
+ constant_state_bumps,
+
+ expandarray_splat,
+ expandarray_postarg,
+ expandarray_not_array,
+ expandarray_rhs_too_small,
+
+ gbpp_block_param_modified,
+ gbpp_block_handler_not_iseq,
+}
+
+//===========================================================================
+
+/// Primitive called in yjit.rb
+/// Check if stats generation is enabled
+#[no_mangle]
+pub extern "C" fn rb_yjit_stats_enabled_p(_ec: EcPtr, _ruby_self: VALUE) -> VALUE {
+ #[cfg(feature = "stats")]
+ if get_option!(gen_stats) {
+ return Qtrue;
+ }
+
+ return Qfalse;
+}
+
+/// Primitive called in yjit.rb.
+/// Export all YJIT statistics as a Ruby hash.
+#[no_mangle]
+pub extern "C" fn rb_yjit_get_stats(_ec: EcPtr, _ruby_self: VALUE) -> VALUE {
+ with_vm_lock(src_loc!(), || rb_yjit_gen_stats_dict())
+}
+
+/// Export all YJIT statistics as a Ruby hash.
+fn rb_yjit_gen_stats_dict() -> VALUE {
+ // If YJIT is not enabled, return Qnil
+ if !yjit_enabled_p() {
+ return Qnil;
+ }
+
+ let hash = unsafe { rb_hash_new() };
+
+ // Inline and outlined code size
+ unsafe {
+ // Get the inline and outlined code blocks
+ let cb = CodegenGlobals::get_inline_cb();
+ let ocb = CodegenGlobals::get_outlined_cb();
+
+ // Inline code size
+ let key = rust_str_to_sym("inline_code_size");
+ let value = VALUE::fixnum_from_usize(cb.get_write_pos());
+ rb_hash_aset(hash, key, value);
+
+ // Outlined code size
+ let key = rust_str_to_sym("outlined_code_size");
+ let value = VALUE::fixnum_from_usize(ocb.unwrap().get_write_pos());
+ rb_hash_aset(hash, key, value);
+ }
+
+ // If we're not generating stats, the hash is done
+ if !get_option!(gen_stats) {
+ return hash;
+ }
+
+ // If the stats feature is enabled
+ #[cfg(feature = "stats")]
+ unsafe {
+ // Indicate that the complete set of stats is available
+ rb_hash_aset(hash, rust_str_to_sym("all_stats"), Qtrue);
+
+ // For each counter we track
+ for counter_name in COUNTER_NAMES {
+ // Get the counter value
+ let counter_ptr = get_counter_ptr(counter_name);
+ let counter_val = *counter_ptr;
+
+ // Put counter into hash
+ let key = rust_str_to_sym(counter_name);
+ let value = VALUE::fixnum_from_usize(counter_val as usize);
+ rb_hash_aset(hash, key, value);
+ }
+
+ // For each entry in exit_op_count, add a stats entry with key "exit_INSTRUCTION_NAME"
+ // and the value is the count of side exits for that instruction.
+ for op_idx in 0..VM_INSTRUCTION_SIZE {
+ let op_name = insn_name(op_idx);
+ let key_string = "exit_".to_owned() + &op_name;
+ let key = rust_str_to_sym(&key_string);
+ let value = VALUE::fixnum_from_usize(EXIT_OP_COUNT[op_idx] as usize);
+ rb_hash_aset(hash, key, value);
+ }
+ }
+
+ hash
+}
+
+/// Primitive called in yjit.rb. Zero out all the counters.
+#[no_mangle]
+pub extern "C" fn rb_yjit_reset_stats_bang(_ec: EcPtr, _ruby_self: VALUE) -> VALUE {
+ unsafe {
+ EXIT_OP_COUNT = [0; VM_INSTRUCTION_SIZE];
+ COUNTERS = Counters::default();
+ }
+
+ 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);
+}
+
+#[no_mangle]
+pub extern "C" fn rb_yjit_collect_binding_set() {
+ incr_counter!(binding_set);
+}
+
+#[no_mangle]
+pub extern "C" fn rb_yjit_count_side_exit_op(exit_pc: *const VALUE) -> *const VALUE {
+ #[cfg(not(test))]
+ unsafe {
+ // Get the opcode from the encoded insn handler at this PC
+ let opcode = rb_vm_insn_addr2opcode((*exit_pc).as_ptr());
+
+ // Increment the exit op count for this opcode
+ EXIT_OP_COUNT[opcode as usize] += 1;
+ };
+
+ // This function must return exit_pc!
+ return exit_pc;
+}