diff options
Diffstat (limited to 'yjit/src/stats.rs')
-rw-r--r-- | yjit/src/stats.rs | 482 |
1 files changed, 357 insertions, 125 deletions
diff --git a/yjit/src/stats.rs b/yjit/src/stats.rs index 91ecc8209b..6ffe28f12a 100644 --- a/yjit/src/stats.rs +++ b/yjit/src/stats.rs @@ -3,6 +3,12 @@ #![allow(dead_code)] // Counters are only used with the stats features +use std::alloc::{GlobalAlloc, Layout, System}; +use std::ptr::addr_of_mut; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::time::Instant; +use std::collections::HashMap; + use crate::codegen::CodegenGlobals; use crate::core::Context; use crate::core::for_each_iseq_payload; @@ -10,13 +16,112 @@ use crate::cruby::*; use crate::options::*; use crate::yjit::yjit_enabled_p; -// stats_alloc is a middleware to instrument global allocations in Rust. -#[cfg(feature="stats")] +/// Running total of how many ISeqs are in the system. +#[no_mangle] +pub static mut rb_yjit_live_iseq_count: u64 = 0; + +/// Monotonically increasing total of how many ISEQs were allocated +#[no_mangle] +pub static mut rb_yjit_iseq_alloc_count: u64 = 0; + +/// A middleware to count Rust-allocated bytes as yjit_alloc_size. #[global_allocator] -static GLOBAL_ALLOCATOR: &stats_alloc::StatsAlloc<std::alloc::System> = &stats_alloc::INSTRUMENTED_SYSTEM; +static GLOBAL_ALLOCATOR: StatsAlloc = StatsAlloc { alloc_size: AtomicUsize::new(0) }; + +pub struct StatsAlloc { + alloc_size: AtomicUsize, +} + +unsafe impl GlobalAlloc for StatsAlloc { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + self.alloc_size.fetch_add(layout.size(), Ordering::SeqCst); + System.alloc(layout) + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + self.alloc_size.fetch_sub(layout.size(), Ordering::SeqCst); + System.dealloc(ptr, layout) + } + + unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { + self.alloc_size.fetch_add(layout.size(), Ordering::SeqCst); + System.alloc_zeroed(layout) + } + + unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { + if new_size > layout.size() { + self.alloc_size.fetch_add(new_size - layout.size(), Ordering::SeqCst); + } else if new_size < layout.size() { + self.alloc_size.fetch_sub(layout.size() - new_size, Ordering::SeqCst); + } + System.realloc(ptr, layout, new_size) + } +} + +/// Mapping of C function / ISEQ name to integer indices +/// This is accessed at compilation time only (protected by a lock) +static mut CFUNC_NAME_TO_IDX: Option<HashMap<String, usize>> = None; +static mut ISEQ_NAME_TO_IDX: Option<HashMap<String, usize>> = None; + +/// Vector of call counts for each C function / ISEQ index +/// This is modified (but not resized) by JITted code +static mut CFUNC_CALL_COUNT: Option<Vec<u64>> = None; +static mut ISEQ_CALL_COUNT: Option<Vec<u64>> = None; + +/// Assign an index to a given cfunc name string +pub fn get_cfunc_idx(name: &str) -> usize { + // SAFETY: We acquire a VM lock and don't create multiple &mut references to these static mut variables. + unsafe { get_method_idx(name, &mut *addr_of_mut!(CFUNC_NAME_TO_IDX), &mut *addr_of_mut!(CFUNC_CALL_COUNT)) } +} + +/// Assign an index to a given ISEQ name string +pub fn get_iseq_idx(name: &str) -> usize { + // SAFETY: We acquire a VM lock and don't create multiple &mut references to these static mut variables. + unsafe { get_method_idx(name, &mut *addr_of_mut!(ISEQ_NAME_TO_IDX), &mut *addr_of_mut!(ISEQ_CALL_COUNT)) } +} + +fn get_method_idx( + name: &str, + method_name_to_idx: &mut Option<HashMap<String, usize>>, + method_call_count: &mut Option<Vec<u64>>, +) -> usize { + //println!("{}", name); + + let name_to_idx = method_name_to_idx.get_or_insert_with(HashMap::default); + let call_count = method_call_count.get_or_insert_with(Vec::default); + + match name_to_idx.get(name) { + Some(idx) => *idx, + None => { + let idx = name_to_idx.len(); + name_to_idx.insert(name.to_string(), idx); + + // Resize the call count vector + if idx >= call_count.len() { + call_count.resize(idx + 1, 0); + } + + idx + } + } +} + +// Increment the counter for a C function +pub extern "C" fn incr_cfunc_counter(idx: usize) { + let cfunc_call_count = unsafe { CFUNC_CALL_COUNT.as_mut().unwrap() }; + assert!(idx < cfunc_call_count.len()); + cfunc_call_count[idx] += 1; +} + +// Increment the counter for an ISEQ +pub extern "C" fn incr_iseq_counter(idx: usize) { + let iseq_call_count = unsafe { ISEQ_CALL_COUNT.as_mut().unwrap() }; + assert!(idx < iseq_call_count.len()); + iseq_call_count[idx] += 1; +} // YJIT exit counts for each instruction type -const VM_INSTRUCTION_SIZE_USIZE:usize = VM_INSTRUCTION_SIZE as usize; +const VM_INSTRUCTION_SIZE_USIZE: usize = VM_INSTRUCTION_SIZE as usize; static mut EXIT_OP_COUNT: [u64; VM_INSTRUCTION_SIZE_USIZE] = [0; VM_INSTRUCTION_SIZE_USIZE]; /// Global state needed for collecting backtraces of exits @@ -37,13 +142,8 @@ static mut YJIT_EXIT_LOCATIONS: Option<YjitExitLocations> = None; impl YjitExitLocations { /// Initialize the yjit exit locations pub fn init() { - // Return if the stats feature is disabled - if !cfg!(feature = "stats") { - return; - } - // Return if --yjit-trace-exits isn't enabled - if !get_option!(gen_trace_exits) { + if get_option!(trace_exits).is_none() { return; } @@ -91,13 +191,8 @@ impl YjitExitLocations { return; } - // Return if the stats feature is disabled - if !cfg!(feature = "stats") { - return; - } - // Return if --yjit-trace-exits isn't enabled - if !get_option!(gen_trace_exits) { + if get_option!(trace_exits).is_none() { return; } @@ -139,6 +234,14 @@ macro_rules! make_counters { pub enum Counter { $($counter_name),+ } impl Counter { + /// Map a counter name string to a counter enum + pub fn get(name: &str) -> Option<Counter> { + match name { + $( stringify!($counter_name) => { Some(Counter::$counter_name) } ),+ + _ => None, + } + } + /// Get a counter name string pub fn get_name(&self) -> String { match self { @@ -163,6 +266,29 @@ macro_rules! make_counters { } } +/// The list of counters that are available without --yjit-stats. +/// They are incremented only by `incr_counter!` and don't use `gen_counter_incr`. +pub const DEFAULT_COUNTERS: [Counter; 17] = [ + Counter::code_gc_count, + Counter::compiled_iseq_entry, + Counter::cold_iseq_entry, + Counter::compiled_iseq_count, + Counter::compiled_blockid_count, + Counter::compiled_block_count, + Counter::compiled_branch_count, + Counter::compile_time_ns, + Counter::max_inline_versions, + + Counter::invalidation_count, + Counter::invalidate_method_lookup, + Counter::invalidate_bop_redefined, + Counter::invalidate_ractor_spawn, + Counter::invalidate_constant_state_bump, + Counter::invalidate_constant_ic_fill, + Counter::invalidate_no_singleton_class, + Counter::invalidate_ep_escape, +]; + /// Macro to increase a counter by name and count macro_rules! incr_counter_by { // Unsafe is ok here because options are initialized @@ -176,6 +302,24 @@ macro_rules! incr_counter_by { } pub(crate) use incr_counter_by; +/// Macro to increase a counter if the given value is larger +macro_rules! incr_counter_to { + // Unsafe is ok here because options are initialized + // once before any Ruby code executes + ($counter_name:ident, $count:expr) => { + #[allow(unused_unsafe)] + { + unsafe { + $crate::stats::COUNTERS.$counter_name = u64::max( + $crate::stats::COUNTERS.$counter_name, + $count as u64, + ) + } + } + }; +} +pub(crate) use incr_counter_to; + /// Macro to increment a counter by name macro_rules! incr_counter { // Unsafe is ok here because options are initialized @@ -202,123 +346,128 @@ pub(crate) use ptr_to_counter; // Declare all the counters we track make_counters! { - exec_instruction, - - send_keywords, - send_klass_megamorphic, - send_kw_splat, - send_args_splat_super, - send_iseq_zsuper, - send_block_arg, + yjit_insns_count, + + // Method calls that fallback to dynamic dispatch + send_singleton_class, send_ivar_set_method, send_zsuper_method, send_undef_method, - send_optimized_method, - send_optimized_method_call, send_optimized_method_block_call, send_call_block, send_call_kwarg, send_call_multi_ractor, + send_cme_not_found, + send_megamorphic, send_missing_method, send_refined_method, - send_cfunc_ruby_array_varg, + send_private_not_fcall, + send_cfunc_kw_splat_non_nil, + send_cfunc_splat_neg2, send_cfunc_argc_mismatch, + send_cfunc_block_arg, send_cfunc_toomany_args, send_cfunc_tracing, - send_cfunc_kwargs, send_cfunc_splat_with_kw, - send_cfunc_splat_send, + send_cfunc_splat_varg_ruby2_keywords, send_attrset_kwargs, + send_attrset_block_arg, send_iseq_tailcall, send_iseq_arity_error, - send_iseq_only_keywords, - send_iseq_kwargs_req_and_opt_missing, + send_iseq_block_arg_type, + send_iseq_clobbering_block_arg, + send_iseq_complex_discard_extras, + send_iseq_leaf_builtin_block_arg_block_param, + send_iseq_kw_splat_non_nil, send_iseq_kwargs_mismatch, send_iseq_has_post, - send_iseq_has_kwrest, send_iseq_has_no_kw, send_iseq_accepts_no_kwarg, send_iseq_materialized_block, - send_iseq_splat_with_opt, + send_iseq_splat_not_array, send_iseq_splat_with_kw, send_iseq_missing_optional_kw, send_iseq_too_many_kwargs, send_not_implemented_method, send_getter_arity, - send_se_cf_overflow, - send_se_protected_check_failed, - send_splatarray_length_not_equal, - send_splatarray_last_ruby_2_keywords, - send_splat_not_array, - send_args_splat_non_iseq, - send_args_splat_ivar, + send_getter_block_arg, send_args_splat_attrset, send_args_splat_bmethod, send_args_splat_aref, send_args_splat_aset, send_args_splat_opt_call, - send_args_splat_cfunc_var_args, - send_args_splat_cfunc_zuper, - send_args_splat_cfunc_ruby2_keywords, send_iseq_splat_arity_error, send_splat_too_long, - send_iseq_ruby2_keywords, - send_send_not_imm, send_send_wrong_args, send_send_null_mid, send_send_null_cme, send_send_nested, - send_send_chain, - send_send_chain_string, - send_send_chain_not_string, - send_send_chain_not_sym, - send_send_chain_not_string_or_sym, - send_send_getter, - send_send_builtin, + send_send_attr_reader, + send_send_attr_writer, send_iseq_has_rest_and_captured, - send_iseq_has_rest_and_send, + send_iseq_has_kwrest_and_captured, send_iseq_has_rest_and_kw_supplied, send_iseq_has_rest_opt_and_block, - send_iseq_has_rest_and_splat_not_equal, - send_is_a_class_mismatch, - send_instance_of_class_mismatch, - send_interrupted, - send_not_fixnums, - send_not_string, - send_mid_mismatch, - send_bmethod_ractor, send_bmethod_block_arg, - - traced_cfunc_return, - - invokesuper_me_changed, - invokesuper_block, - + send_optimized_block_arg, + + invokesuper_defined_class_mismatch, + invokesuper_kw_splat, + invokesuper_kwarg, + invokesuper_megamorphic, + invokesuper_no_cme, + invokesuper_no_me, + invokesuper_not_iseq_or_cfunc, + invokesuper_refinement, + invokesuper_singleton_class, + + invokeblock_megamorphic, invokeblock_none, - invokeblock_iseq_arg0_has_kw, + invokeblock_iseq_arg0_optional, invokeblock_iseq_arg0_args_splat, invokeblock_iseq_arg0_not_array, invokeblock_iseq_arg0_wrong_len, - invokeblock_iseq_block_changed, - invokeblock_tag_changed, + invokeblock_iseq_not_inlined, invokeblock_ifunc_args_splat, invokeblock_ifunc_kw_splat, invokeblock_proc, invokeblock_symbol, + // Method calls that exit to the interpreter + guard_send_block_arg_type, + guard_send_getter_splat_non_empty, + guard_send_klass_megamorphic, + guard_send_se_cf_overflow, + guard_send_se_protected_check_failed, + guard_send_splatarray_length_not_equal, + guard_send_splatarray_last_ruby2_keywords, + guard_send_splat_not_array, + guard_send_send_name_chain, + guard_send_iseq_has_rest_and_splat_too_few, + guard_send_is_a_class_mismatch, + guard_send_instance_of_class_mismatch, + guard_send_interrupted, + guard_send_not_fixnums, + guard_send_not_fixnum_or_flonum, + guard_send_not_string, + guard_send_respond_to_mid_mismatch, + + guard_send_cfunc_bad_splat_vargs, + + guard_invokesuper_me_changed, + + guard_invokeblock_tag_changed, + guard_invokeblock_iseq_block_changed, + + traced_cfunc_return, + leave_se_interrupt, leave_interp_return, - getivar_se_self_not_heap, - getivar_idx_out_of_range, getivar_megamorphic, getivar_not_heap, - setivar_se_self_not_heap, - setivar_idx_out_of_range, - setivar_val_heapobject, - setivar_name_not_mapped, setivar_not_heap, setivar_frozen, setivar_megamorphic, @@ -328,12 +477,23 @@ make_counters! { setlocal_wb_required, + invokebuiltin_too_many_args, + opt_plus_overflow, opt_minus_overflow, + opt_mult_overflow, + + opt_succ_not_fixnum, + opt_succ_overflow, opt_mod_zero, opt_div_zero, + lshift_amount_changed, + lshift_overflow, + + rshift_amount_changed, + opt_aref_argc_not_one, opt_aref_arg_not_fixnum, opt_aref_not_array, @@ -343,19 +503,28 @@ make_counters! { opt_aset_not_fixnum, opt_aset_not_hash, + opt_aref_with_qundef, + opt_case_dispatch_megamorphic, - opt_getinlinecache_miss, + opt_getconstant_path_ic_miss, + opt_getconstant_path_multi_ractor, expandarray_splat, expandarray_postarg, expandarray_not_array, - expandarray_rhs_too_small, + expandarray_to_ary, + expandarray_chain_max_depth, + // getblockparam gbp_wb_required, + + // getblockparamproxy + gbpp_unsupported_type, gbpp_block_param_modified, gbpp_block_handler_not_none, gbpp_block_handler_not_iseq, + gbpp_block_handler_not_proc, branchif_interrupted, branchunless_interrupted, @@ -364,20 +533,30 @@ make_counters! { objtostring_not_string, + getbyte_idx_not_fixnum, + getbyte_idx_negative, + getbyte_idx_out_of_bounds, + + splatkw_not_hash, + splatkw_not_nil, + binding_allocations, binding_set, - vm_insns_count, + compiled_iseq_entry, + cold_iseq_entry, compiled_iseq_count, compiled_blockid_count, compiled_block_count, compiled_branch_count, + compile_time_ns, compilation_failure, block_next_count, defer_count, defer_empty_count, branch_insn_count, branch_known_count, + max_inline_versions, freed_iseq_count, @@ -389,16 +568,15 @@ make_counters! { invalidate_ractor_spawn, invalidate_constant_state_bump, invalidate_constant_ic_fill, - - constant_state_bumps, - - // Not using "getivar_" to exclude this from exit reasons - get_ivar_max_depth, + invalidate_no_singleton_class, + invalidate_ep_escape, // Currently, it's out of the ordinary (might be impossible) for YJIT to leave gaps in // executable memory, so this should be 0. exec_mem_non_bump_alloc, + code_gc_count, + num_gc_obj_refs, num_send, @@ -406,6 +584,26 @@ make_counters! { num_send_polymorphic, num_send_x86_rel32, num_send_x86_reg, + num_send_dynamic, + num_send_cfunc, + num_send_cfunc_inline, + num_send_iseq, + num_send_iseq_leaf, + num_send_iseq_inline, + + num_getivar_megamorphic, + num_setivar_megamorphic, + num_opt_case_dispatch_megamorphic, + + num_throw, + num_throw_break, + num_throw_retry, + num_throw_return, + + num_lazy_frame_check, + num_lazy_frame_push, + lazy_frame_count, + lazy_frame_failure, iseq_stack_too_large, iseq_too_long, @@ -429,6 +627,17 @@ pub extern "C" fn rb_yjit_stats_enabled_p(_ec: EcPtr, _ruby_self: VALUE) -> VALU } } +/// Primitive called in yjit.rb +/// Check if stats generation should print at exit +#[no_mangle] +pub extern "C" fn rb_yjit_print_stats_p(_ec: EcPtr, _ruby_self: VALUE) -> VALUE { + if yjit_enabled_p() && get_option!(print_stats) { + return Qtrue; + } else { + return Qfalse; + } +} + /// Primitive called in yjit.rb. /// Export all YJIT statistics as a Ruby hash. #[no_mangle] @@ -442,8 +651,7 @@ pub extern "C" fn rb_yjit_get_stats(_ec: EcPtr, _ruby_self: VALUE, context: VALU /// to be enabled. #[no_mangle] pub extern "C" fn rb_yjit_trace_exit_locations_enabled_p(_ec: EcPtr, _ruby_self: VALUE) -> VALUE { - #[cfg(feature = "stats")] - if get_option!(gen_trace_exits) { + if get_option!(trace_exits).is_some() { return Qtrue; } @@ -459,13 +667,8 @@ pub extern "C" fn rb_yjit_get_exit_locations(_ec: EcPtr, _ruby_self: VALUE) -> V return Qnil; } - // Return if the stats feature is disabled - if !cfg!(feature = "stats") { - return Qnil; - } - // Return if --yjit-trace-exits isn't enabled - if !get_option!(gen_trace_exits) { + if get_option!(trace_exits).is_none() { return Qnil; } @@ -487,6 +690,16 @@ pub extern "C" fn rb_yjit_get_exit_locations(_ec: EcPtr, _ruby_self: VALUE) -> V } } +/// Increment a counter by name from the CRuby side +/// Warning: this is not fast because it requires a hash lookup, so don't use in tight loops +#[no_mangle] +pub extern "C" fn rb_yjit_incr_counter(counter_name: *const std::os::raw::c_char) { + use std::ffi::CStr; + let counter_name = unsafe { CStr::from_ptr(counter_name).to_str().unwrap() }; + let counter_ptr = get_counter_ptr(counter_name); + unsafe { *counter_ptr += 1 }; +} + /// Export all YJIT statistics as a Ruby hash. fn rb_yjit_gen_stats_dict(context: bool) -> VALUE { // If YJIT is not enabled, return Qnil @@ -504,7 +717,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(); @@ -526,15 +738,11 @@ fn rb_yjit_gen_stats_dict(context: bool) -> VALUE { // Live pages hash_aset_usize!(hash, "live_page_count", cb.num_mapped_pages() - freed_page_count); - // Code GC count - hash_aset_usize!(hash, "code_gc_count", CodegenGlobals::get_code_gc_count()); - // Size of memory region allocated for JIT code hash_aset_usize!(hash, "code_region_size", cb.mapped_region_size()); // Rust global allocations in bytes - #[cfg(feature="stats")] - hash_aset_usize!(hash, "yjit_alloc_size", global_allocation_size()); + hash_aset_usize!(hash, "yjit_alloc_size", GLOBAL_ALLOCATOR.alloc_size.load(Ordering::SeqCst)); // `context` is true at RubyVM::YJIT._print_stats for --yjit-stats. It's false by default // for RubyVM::YJIT.runtime_stats because counting all Contexts could be expensive. @@ -544,15 +752,30 @@ 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); + + hash_aset_usize!(hash, "live_iseq_count", rb_yjit_live_iseq_count as usize); + hash_aset_usize!(hash, "iseq_alloc_count", rb_yjit_iseq_alloc_count as usize); } - // If we're not generating stats, the hash is done + // If we're not generating stats, put only default counters if !get_option!(gen_stats) { + for counter in DEFAULT_COUNTERS { + // Get the counter value + let counter_ptr = get_counter_ptr(&counter.get_name()); + let counter_val = unsafe { *counter_ptr }; + + // Put counter into hash + let key = rust_str_to_sym(&counter.get_name()); + let value = VALUE::fixnum_from_usize(counter_val as usize); + unsafe { rb_hash_aset(hash, key, value); } + } + return hash; } - // If the stats feature is enabled - unsafe { // Indicate that the complete set of stats is available rb_hash_aset(hash, rust_str_to_sym("all_stats"), Qtrue); @@ -563,13 +786,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); @@ -585,6 +801,31 @@ fn rb_yjit_gen_stats_dict(context: bool) -> VALUE { let value = VALUE::fixnum_from_usize(EXIT_OP_COUNT[op_idx] as usize); rb_hash_aset(hash, key, value); } + + fn set_call_counts( + calls_hash: VALUE, + method_name_to_idx: &mut Option<HashMap<String, usize>>, + method_call_count: &mut Option<Vec<u64>>, + ) { + if let (Some(name_to_idx), Some(call_counts)) = (method_name_to_idx, method_call_count) { + for (name, idx) in name_to_idx { + let count = call_counts[*idx]; + let key = rust_str_to_sym(name); + let value = VALUE::fixnum_from_usize(count as usize); + unsafe { rb_hash_aset(calls_hash, key, value); } + } + } + } + + // Create a hash for the cfunc call counts + let cfunc_calls = rb_hash_new(); + rb_hash_aset(hash, rust_str_to_sym("cfunc_calls"), cfunc_calls); + set_call_counts(cfunc_calls, &mut *addr_of_mut!(CFUNC_NAME_TO_IDX), &mut *addr_of_mut!(CFUNC_CALL_COUNT)); + + // Create a hash for the ISEQ call counts + let iseq_calls = rb_hash_new(); + rb_hash_aset(hash, rust_str_to_sym("iseq_calls"), iseq_calls); + set_call_counts(iseq_calls, &mut *addr_of_mut!(ISEQ_NAME_TO_IDX), &mut *addr_of_mut!(ISEQ_CALL_COUNT)); } hash @@ -619,13 +860,8 @@ pub extern "C" fn rb_yjit_record_exit_stack(_exit_pc: *const VALUE) return; } - // Return if the stats feature is disabled - if !cfg!(feature = "stats") { - return; - } - // Return if --yjit-trace-exits isn't enabled - if !get_option!(gen_trace_exits) { + if get_option!(trace_exits).is_none() { return; } @@ -747,12 +983,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); @@ -778,9 +1008,11 @@ pub extern "C" fn rb_yjit_count_side_exit_op(exit_pc: *const VALUE) -> *const VA return exit_pc; } -// Get the size of global allocations in Rust. -#[cfg(feature="stats")] -fn global_allocation_size() -> usize { - let stats = GLOBAL_ALLOCATOR.stats(); - stats.bytes_allocated.saturating_sub(stats.bytes_deallocated) +/// Measure the time taken by func() and add that to yjit_compile_time. +pub fn with_compile_time<F, R>(func: F) -> R where F: FnOnce() -> R { + let start = Instant::now(); + let ret = func(); + let nanos = Instant::now().duration_since(start).as_nanos(); + incr_counter_by!(compile_time_ns, nanos); + ret } |