summaryrefslogtreecommitdiff
path: root/zjit/src/stats.rs
diff options
context:
space:
mode:
Diffstat (limited to 'zjit/src/stats.rs')
-rw-r--r--zjit/src/stats.rs1194
1 files changed, 1173 insertions, 21 deletions
diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs
index ce185597c4..57320a02e7 100644
--- a/zjit/src/stats.rs
+++ b/zjit/src/stats.rs
@@ -1,18 +1,51 @@
+//! Counters and associated methods for events when ZJIT is run.
+
use std::time::Instant;
+use std::sync::atomic::Ordering;
+use crate::options::OPTIONS;
+
+// test binaries always bring it in as a cargo dependency
+#[cfg(all(feature = "stats_allocator", not(test)))]
+#[path = "../../jit/src/lib.rs"]
+mod jit;
-use crate::{cruby::*, options::get_option, state::{zjit_enabled_p, ZJITState}};
+use crate::{cruby::*, hir::ParseError, options::get_option, state::{zjit_enabled_p, ZJITState}};
macro_rules! make_counters {
(
default {
$($default_counter_name:ident,)+
}
+ exit {
+ $($exit_counter_name:ident,)+
+ }
+ dynamic_send {
+ $($dynamic_send_counter_name:ident,)+
+ }
+ optimized_send {
+ $($optimized_send_counter_name:ident,)+
+ }
+ dynamic_setivar {
+ $($dynamic_setivar_counter_name:ident,)+
+ }
+ dynamic_getivar {
+ $($dynamic_getivar_counter_name:ident,)+
+ }
+ dynamic_definedivar {
+ $($dynamic_definedivar_counter_name:ident,)+
+ }
$($counter_name:ident,)+
) => {
/// Struct containing the counter values
#[derive(Default, Debug)]
pub struct Counters {
$(pub $default_counter_name: u64,)+
+ $(pub $exit_counter_name: u64,)+
+ $(pub $dynamic_send_counter_name: u64,)+
+ $(pub $optimized_send_counter_name: u64,)+
+ $(pub $dynamic_setivar_counter_name: u64,)+
+ $(pub $dynamic_getivar_counter_name: u64,)+
+ $(pub $dynamic_definedivar_counter_name: u64,)+
$(pub $counter_name: u64,)+
}
@@ -21,14 +54,40 @@ macro_rules! make_counters {
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Counter {
$($default_counter_name,)+
+ $($exit_counter_name,)+
+ $($dynamic_send_counter_name,)+
+ $($optimized_send_counter_name,)+
+ $($dynamic_setivar_counter_name,)+
+ $($dynamic_getivar_counter_name,)+
+ $($dynamic_definedivar_counter_name,)+
$($counter_name,)+
}
impl Counter {
- pub fn name(&self) -> String {
+ pub fn name(&self) -> &'static str {
match self {
- $( Counter::$default_counter_name => stringify!($default_counter_name).to_string(), )+
- $( Counter::$counter_name => stringify!($counter_name).to_string(), )+
+ $( Counter::$default_counter_name => stringify!($default_counter_name), )+
+ $( Counter::$exit_counter_name => stringify!($exit_counter_name), )+
+ $( Counter::$dynamic_send_counter_name => stringify!($dynamic_send_counter_name), )+
+ $( Counter::$optimized_send_counter_name => stringify!($optimized_send_counter_name), )+
+ $( Counter::$dynamic_setivar_counter_name => stringify!($dynamic_setivar_counter_name), )+
+ $( Counter::$dynamic_getivar_counter_name => stringify!($dynamic_getivar_counter_name), )+
+ $( Counter::$dynamic_definedivar_counter_name => stringify!($dynamic_definedivar_counter_name), )+
+ $( Counter::$counter_name => stringify!($counter_name), )+
+ }
+ }
+
+ pub fn get(name: &str) -> Option<Counter> {
+ match name {
+ $( stringify!($default_counter_name) => Some(Counter::$default_counter_name), )+
+ $( stringify!($exit_counter_name) => Some(Counter::$exit_counter_name), )+
+ $( stringify!($dynamic_send_counter_name) => Some(Counter::$dynamic_send_counter_name), )+
+ $( stringify!($optimized_send_counter_name) => Some(Counter::$optimized_send_counter_name), )+
+ $( stringify!($dynamic_setivar_counter_name) => Some(Counter::$dynamic_setivar_counter_name), )+
+ $( stringify!($dynamic_getivar_counter_name) => Some(Counter::$dynamic_getivar_counter_name), )+
+ $( stringify!($dynamic_definedivar_counter_name) => Some(Counter::$dynamic_definedivar_counter_name), )+
+ $( stringify!($counter_name) => Some(Counter::$counter_name), )+
+ _ => None,
}
}
}
@@ -38,15 +97,56 @@ macro_rules! make_counters {
let counters = $crate::state::ZJITState::get_counters();
match counter {
$( Counter::$default_counter_name => std::ptr::addr_of_mut!(counters.$default_counter_name), )+
+ $( Counter::$exit_counter_name => std::ptr::addr_of_mut!(counters.$exit_counter_name), )+
+ $( Counter::$dynamic_send_counter_name => std::ptr::addr_of_mut!(counters.$dynamic_send_counter_name), )+
+ $( Counter::$dynamic_setivar_counter_name => std::ptr::addr_of_mut!(counters.$dynamic_setivar_counter_name), )+
+ $( Counter::$dynamic_getivar_counter_name => std::ptr::addr_of_mut!(counters.$dynamic_getivar_counter_name), )+
+ $( Counter::$dynamic_definedivar_counter_name => std::ptr::addr_of_mut!(counters.$dynamic_definedivar_counter_name), )+
+ $( Counter::$optimized_send_counter_name => std::ptr::addr_of_mut!(counters.$optimized_send_counter_name), )+
$( Counter::$counter_name => std::ptr::addr_of_mut!(counters.$counter_name), )+
}
}
- /// The list of counters that are available without --zjit-stats.
+ /// List of counters that are available without --zjit-stats.
/// They are incremented only by `incr_counter()` and don't use `gen_incr_counter()`.
pub const DEFAULT_COUNTERS: &'static [Counter] = &[
$( Counter::$default_counter_name, )+
];
+
+ /// List of other counters that are summed as side_exit_count.
+ pub const EXIT_COUNTERS: &'static [Counter] = &[
+ $( Counter::$exit_counter_name, )+
+ ];
+
+ /// List of other counters that are summed as dynamic_send_count.
+ pub const DYNAMIC_SEND_COUNTERS: &'static [Counter] = &[
+ $( Counter::$dynamic_send_counter_name, )+
+ ];
+
+ /// List of other counters that are summed as optimized_send_count.
+ pub const OPTIMIZED_SEND_COUNTERS: &'static [Counter] = &[
+ $( Counter::$optimized_send_counter_name, )+
+ ];
+
+ /// List of other counters that are summed as dynamic_setivar_count.
+ pub const DYNAMIC_SETIVAR_COUNTERS: &'static [Counter] = &[
+ $( Counter::$dynamic_setivar_counter_name, )+
+ ];
+
+ /// List of other counters that are summed as dynamic_getivar_count.
+ pub const DYNAMIC_GETIVAR_COUNTERS: &'static [Counter] = &[
+ $( Counter::$dynamic_getivar_counter_name, )+
+ ];
+
+ /// List of other counters that are summed as dynamic_definedivar_count.
+ pub const DYNAMIC_DEFINEDIVAR_COUNTERS: &'static [Counter] = &[
+ $( Counter::$dynamic_definedivar_counter_name, )+
+ ];
+
+ /// List of other counters that are available only for --zjit-stats.
+ pub const OTHER_COUNTERS: &'static [Counter] = &[
+ $( Counter::$counter_name, )+
+ ];
}
}
@@ -54,22 +154,660 @@ macro_rules! make_counters {
make_counters! {
// Default counters that are available without --zjit-stats
default {
+ compiled_iseq_count,
+ failed_iseq_count,
+ skipped_native_stack_full,
+
compile_time_ns,
profile_time_ns,
gc_time_ns,
invalidation_time_ns,
+
+ compiled_side_exit_count,
+ side_exit_size,
+ compile_side_exit_time_ns,
+
+ compile_hir_time_ns,
+ compile_hir_build_time_ns,
+ compile_hir_strength_reduce_time_ns,
+ compile_hir_optimize_load_store_time_ns,
+ compile_hir_canonicalize_time_ns,
+ compile_hir_fold_constants_time_ns,
+ compile_hir_clean_cfg_time_ns,
+ compile_hir_remove_redundant_patch_points_time_ns,
+ compile_hir_remove_duplicate_check_interrupts_time_ns,
+ compile_hir_eliminate_dead_code_time_ns,
+ compile_lir_time_ns,
}
+ // Exit counters that are summed as side_exit_count
+ exit {
+ // exit_: Side exits reasons
+ exit_compile_error,
+ exit_unhandled_newarray_send_min,
+ exit_unhandled_newarray_send_hash,
+ exit_unhandled_newarray_send_pack,
+ exit_unhandled_newarray_send_pack_buffer,
+ exit_unhandled_newarray_send_unknown,
+ exit_unhandled_duparray_send,
+ exit_unhandled_tailcall,
+ exit_unhandled_splat,
+ exit_unhandled_kwarg,
+ exit_unhandled_block_arg,
+ exit_unknown_special_variable,
+ exit_unhandled_hir_insn,
+ exit_unhandled_yarv_insn,
+ exit_fixnum_add_overflow,
+ exit_fixnum_sub_overflow,
+ exit_fixnum_mult_overflow,
+ exit_fixnum_lshift_overflow,
+ exit_fixnum_mod_by_zero,
+ exit_fixnum_div_by_zero,
+ exit_box_fixnum_overflow,
+ exit_guard_type_failure,
+ exit_guard_bit_equals_failure,
+ exit_guard_int_equals_failure,
+ exit_guard_shape_failure,
+ exit_expandarray_failure,
+ exit_guard_not_frozen_failure,
+ exit_guard_not_shared_failure,
+ exit_guard_less_failure,
+ exit_guard_greater_eq_failure,
+ exit_guard_super_method_entry,
+ exit_patchpoint_bop_redefined,
+ exit_patchpoint_method_redefined,
+ exit_patchpoint_stable_constant_names,
+ exit_patchpoint_no_tracepoint,
+ exit_patchpoint_no_ep_escape,
+ exit_patchpoint_single_ractor_mode,
+ exit_patchpoint_no_singleton_class,
+ exit_patchpoint_root_box_only,
+ exit_callee_side_exit,
+ exit_obj_to_string_fallback,
+ exit_interrupt,
+ exit_stackoverflow,
+ exit_block_param_proxy_not_iseq_or_ifunc,
+ exit_block_param_proxy_not_nil,
+ exit_block_param_proxy_fallback_miss,
+ exit_block_param_proxy_profile_not_covered,
+ exit_block_param_wb_required,
+ exit_too_many_keyword_parameters,
+ exit_no_profile_send,
+ exit_splatkw_not_nil_or_hash,
+ exit_splatkw_polymorphic,
+ exit_splatkw_not_profiled,
+ exit_directive_induced,
+ exit_send_while_tracing,
+ exit_invokeblock_not_ifunc,
+ }
+
+ // Send fallback counters that are summed as dynamic_send_count
+ dynamic_send {
+ // send_fallback_: Fallback reasons for send-ish instructions
+ send_fallback_send_without_block_polymorphic,
+ send_fallback_send_without_block_megamorphic,
+ send_fallback_send_without_block_no_profiles,
+ send_fallback_send_without_block_cfunc_not_variadic,
+ send_fallback_send_without_block_cfunc_array_variadic,
+ send_fallback_send_without_block_not_optimized_method_type,
+ send_fallback_send_without_block_not_optimized_method_type_optimized,
+ send_fallback_send_without_block_not_optimized_need_permission,
+ send_fallback_too_many_args_for_lir,
+ send_fallback_send_without_block_bop_redefined,
+ send_fallback_send_without_block_operands_not_fixnum,
+ send_fallback_send_without_block_polymorphic_fallback,
+ send_fallback_send_without_block_direct_keyword_mismatch,
+ send_fallback_send_without_block_direct_keyword_count_mismatch,
+ send_fallback_send_without_block_direct_missing_keyword,
+ send_fallback_send_without_block_direct_too_many_keywords,
+ send_fallback_send_polymorphic,
+ send_fallback_send_megamorphic,
+ send_fallback_send_no_profiles,
+ send_fallback_send_not_optimized_method_type,
+ send_fallback_send_not_optimized_need_permission,
+ send_fallback_ccall_with_frame_too_many_args,
+ send_fallback_argc_param_mismatch,
+ // The call has at least one feature on the caller or callee side
+ // that the optimizer does not support.
+ send_fallback_one_or_more_complex_arg_pass,
+ // Caller has keyword arguments but callee doesn't expect them.
+ send_fallback_unexpected_keyword_args,
+ // Singleton class previously created for receiver class.
+ send_fallback_singleton_class_seen,
+ send_fallback_bmethod_non_iseq_proc,
+ send_fallback_obj_to_string_not_string,
+ send_fallback_send_cfunc_variadic,
+ send_fallback_send_cfunc_array_variadic,
+ send_fallback_super_call_with_block,
+ send_fallback_super_from_block,
+ send_fallback_super_class_not_found,
+ send_fallback_super_complex_args_pass,
+ send_fallback_super_fallback_no_profile,
+ send_fallback_super_not_optimized_method_type,
+ send_fallback_super_polymorphic,
+ send_fallback_super_target_not_found,
+ send_fallback_super_target_complex_args_pass,
+ send_fallback_cannot_send_direct,
+ send_fallback_invokeblock_not_specialized,
+ send_fallback_sendforward_not_specialized,
+ send_fallback_invokesuperforward_not_specialized,
+ send_fallback_single_ractor_mode_required,
+ send_fallback_uncategorized,
+ }
+
+ // Optimized send counters that are summed as optimized_send_count
+ optimized_send {
+ iseq_optimized_send_count,
+ inline_cfunc_optimized_send_count,
+ inline_iseq_optimized_send_count,
+ non_variadic_cfunc_optimized_send_count,
+ variadic_cfunc_optimized_send_count,
+ }
+
+ // Ivar fallback counters that are summed as dynamic_setivar_count
+ dynamic_setivar {
+ // setivar_fallback_: Fallback reasons for dynamic setivar instructions
+ setivar_fallback_not_monomorphic,
+ setivar_fallback_immediate,
+ setivar_fallback_not_t_object,
+ setivar_fallback_complex,
+ setivar_fallback_frozen,
+ setivar_fallback_shape_transition,
+ setivar_fallback_new_shape_complex,
+ setivar_fallback_new_shape_needs_extension,
+ }
+
+ // Ivar fallback counters that are summed as dynamic_getivar_count
+ dynamic_getivar {
+ // getivar_fallback_: Fallback reasons for dynamic getivar instructions
+ getivar_fallback_not_monomorphic,
+ getivar_fallback_immediate,
+ getivar_fallback_not_t_object,
+ getivar_fallback_complex,
+ }
+
+ // Ivar fallback counters that are summed as dynamic_definedivar_count
+ dynamic_definedivar {
+ // definedivar_fallback_: Fallback reasons for dynamic definedivar instructions
+ definedivar_fallback_not_monomorphic,
+ definedivar_fallback_immediate,
+ definedivar_fallback_not_t_object,
+ definedivar_fallback_complex,
+ }
+
+ // compile_error_: Compile error reasons
+ compile_error_iseq_version_limit_reached,
+ compile_error_iseq_stack_too_large,
+ compile_error_native_stack_too_large,
+ compile_error_exception_handler,
+ compile_error_out_of_memory,
+ compile_error_label_linking_failure,
+ compile_error_jit_to_jit_optional,
+ compile_error_register_spill_on_ccall,
+ compile_error_register_spill_on_alloc,
+ compile_error_parse_stack_underflow,
+ compile_error_parse_malformed_iseq,
+ compile_error_parse_not_allowed,
+ compile_error_parse_directive_induced,
+ compile_error_validation_block_has_no_terminator,
+ compile_error_validation_terminator_not_at_end,
+ compile_error_validation_mismatched_block_arity,
+ compile_error_validation_jump_target_not_in_rpo,
+ compile_error_validation_operand_not_defined,
+ compile_error_validation_duplicate_instruction,
+ compile_error_validation_type_check_failure,
+ compile_error_validation_misc_validation_error,
+
+ // unhandled_hir_insn_: Unhandled HIR instructions
+ unhandled_hir_insn_array_max,
+ unhandled_hir_insn_fixnum_div,
+ unhandled_hir_insn_throw,
+ unhandled_hir_insn_invokebuiltin,
+ unhandled_hir_insn_unknown,
+
// The number of times YARV instructions are executed on JIT code
- zjit_insns_count,
+ zjit_insn_count,
+
+ // Method call def_type related to send without block fallback to dynamic dispatch
+ unspecialized_send_without_block_def_type_iseq,
+ unspecialized_send_without_block_def_type_cfunc,
+ unspecialized_send_without_block_def_type_attrset,
+ unspecialized_send_without_block_def_type_ivar,
+ unspecialized_send_without_block_def_type_bmethod,
+ unspecialized_send_without_block_def_type_zsuper,
+ unspecialized_send_without_block_def_type_alias,
+ unspecialized_send_without_block_def_type_undef,
+ unspecialized_send_without_block_def_type_not_implemented,
+ unspecialized_send_without_block_def_type_optimized,
+ unspecialized_send_without_block_def_type_missing,
+ unspecialized_send_without_block_def_type_refined,
+ unspecialized_send_without_block_def_type_null,
+
+ // Method call optimized_type related to send without block fallback to dynamic dispatch
+ unspecialized_send_without_block_def_type_optimized_send,
+ unspecialized_send_without_block_def_type_optimized_call,
+ unspecialized_send_without_block_def_type_optimized_block_call,
+ unspecialized_send_without_block_def_type_optimized_struct_aref,
+ unspecialized_send_without_block_def_type_optimized_struct_aset,
+
+ // Method call def_type related to send fallback to dynamic dispatch
+ unspecialized_send_def_type_iseq,
+ unspecialized_send_def_type_cfunc,
+ unspecialized_send_def_type_attrset,
+ unspecialized_send_def_type_ivar,
+ unspecialized_send_def_type_bmethod,
+ unspecialized_send_def_type_zsuper,
+ unspecialized_send_def_type_alias,
+ unspecialized_send_def_type_undef,
+ unspecialized_send_def_type_not_implemented,
+ unspecialized_send_def_type_optimized,
+ unspecialized_send_def_type_missing,
+ unspecialized_send_def_type_refined,
+ unspecialized_send_def_type_null,
+
+ // Super call def_type related to send fallback to dynamic dispatch
+ unspecialized_super_def_type_iseq,
+ unspecialized_super_def_type_cfunc,
+ unspecialized_super_def_type_attrset,
+ unspecialized_super_def_type_ivar,
+ unspecialized_super_def_type_bmethod,
+ unspecialized_super_def_type_zsuper,
+ unspecialized_super_def_type_alias,
+ unspecialized_super_def_type_undef,
+ unspecialized_super_def_type_not_implemented,
+ unspecialized_super_def_type_optimized,
+ unspecialized_super_def_type_missing,
+ unspecialized_super_def_type_refined,
+ unspecialized_super_def_type_null,
+
+ // Unsupported parameter features
+ complex_arg_pass_param_rest,
+ complex_arg_pass_param_post,
+ complex_arg_pass_param_kwrest,
+ complex_arg_pass_param_block,
+ complex_arg_pass_param_forwardable,
+ complex_arg_pass_accepts_no_block,
+ complex_arg_pass_does_not_use_block,
+
+ // Unsupported caller side features
+ complex_arg_pass_caller_splat,
+ complex_arg_pass_caller_blockarg,
+ complex_arg_pass_caller_kwarg,
+ complex_arg_pass_caller_kw_splat,
+ complex_arg_pass_caller_tailcall,
+ complex_arg_pass_caller_super,
+ complex_arg_pass_caller_zsuper,
+ complex_arg_pass_caller_forwarding,
+
+ // Writes to the VM frame
+ vm_write_jit_frame_count,
+ vm_write_sp_count,
+ vm_write_locals_count,
+ vm_write_stack_count,
+ vm_write_to_parent_iseq_local_count,
+ // TODO(max): Implement
+ // vm_reify_stack_count,
+
+ // The number of times we ran a dynamic check
+ guard_type_count,
+ guard_shape_count,
+
+ load_field_count,
+ store_field_count,
+
+ invokeblock_handler_monomorphic_iseq,
+ invokeblock_handler_monomorphic_ifunc,
+ invokeblock_handler_monomorphic_other,
+ invokeblock_handler_polymorphic,
+ invokeblock_handler_megamorphic,
+ invokeblock_handler_no_profiles,
+
+ getblockparamproxy_handler_iseq,
+ getblockparamproxy_handler_ifunc,
+ getblockparamproxy_handler_symbol,
+ getblockparamproxy_handler_proc,
+ getblockparamproxy_handler_nil,
+ getblockparamproxy_handler_polymorphic,
+ getblockparamproxy_handler_megamorphic,
+ getblockparamproxy_handler_no_profiles,
}
/// Increase a counter by a specified amount
-fn incr_counter(counter: Counter, amount: u64) {
+pub fn incr_counter_by(counter: Counter, amount: u64) {
let ptr = counter_ptr(counter);
unsafe { *ptr += amount; }
}
+/// Decrease a counter by a specified amount
+pub fn decr_counter_by(counter: Counter, amount: u64) {
+ let ptr = counter_ptr(counter);
+ unsafe { *ptr -= amount; }
+}
+
+/// Increment a counter by its identifier
+macro_rules! incr_counter {
+ ($counter_name:ident) => {
+ $crate::stats::incr_counter_by($crate::stats::Counter::$counter_name, 1)
+ }
+}
+pub(crate) use incr_counter;
+
+/// The number of side exits from each YARV instruction
+pub type InsnCounters = [u64; VM_INSTRUCTION_SIZE as usize];
+
+/// Return a raw pointer to the exit counter for a given YARV opcode
+pub fn exit_counter_ptr_for_opcode(opcode: u32) -> *mut u64 {
+ let exit_counters = ZJITState::get_exit_counters();
+ unsafe { exit_counters.get_unchecked_mut(opcode as usize) }
+}
+
+/// Return a raw pointer to the fallback counter for a given YARV opcode
+pub fn send_fallback_counter_ptr_for_opcode(opcode: u32) -> *mut u64 {
+ let fallback_counters = ZJITState::get_send_fallback_counters();
+ unsafe { fallback_counters.get_unchecked_mut(opcode as usize) }
+}
+
+/// Reason why ZJIT failed to produce any JIT code
+#[derive(Clone, Debug, PartialEq)]
+pub enum CompileError {
+ IseqVersionLimitReached,
+ IseqStackTooLarge,
+ NativeStackTooLarge,
+ ExceptionHandler,
+ OutOfMemory,
+ ParseError(ParseError),
+ /// When a ZJIT function is too large, the branches may have
+ /// offsets that don't fit in one instruction. We error in
+ /// error that case.
+ LabelLinkingFailure,
+}
+
+/// Return a raw pointer to the exit counter for a given CompileError
+pub fn exit_counter_for_compile_error(compile_error: &CompileError) -> Counter {
+ use crate::hir::ParseError::*;
+ use crate::hir::ValidationError::*;
+ use crate::stats::CompileError::*;
+ use crate::stats::Counter::*;
+ match compile_error {
+ IseqVersionLimitReached => compile_error_iseq_version_limit_reached,
+ IseqStackTooLarge => compile_error_iseq_stack_too_large,
+ NativeStackTooLarge => compile_error_native_stack_too_large,
+ ExceptionHandler => compile_error_exception_handler,
+ OutOfMemory => compile_error_out_of_memory,
+ LabelLinkingFailure => compile_error_label_linking_failure,
+ ParseError(parse_error) => match parse_error {
+ StackUnderflow(_) => compile_error_parse_stack_underflow,
+ MalformedIseq(_) => compile_error_parse_malformed_iseq,
+ NotAllowed => compile_error_parse_not_allowed,
+ DirectiveInduced => compile_error_parse_directive_induced,
+ Validation(validation) => match validation {
+ BlockHasNoTerminator(_) => compile_error_validation_block_has_no_terminator,
+ TerminatorNotAtEnd(_, _, _) => compile_error_validation_terminator_not_at_end,
+ MismatchedBlockArity(_, _, _) => compile_error_validation_mismatched_block_arity,
+ JumpTargetNotInRPO(_) => compile_error_validation_jump_target_not_in_rpo,
+ OperandNotDefined(_, _, _) => compile_error_validation_operand_not_defined,
+ DuplicateInstruction(_, _) => compile_error_validation_duplicate_instruction,
+ MismatchedOperandType(..) => compile_error_validation_type_check_failure,
+ MiscValidationError(..) => compile_error_validation_misc_validation_error,
+ },
+ }
+ }
+}
+
+pub fn exit_counter_for_unhandled_hir_insn(insn: &crate::hir::Insn) -> Counter {
+ use crate::hir::Insn::*;
+ use crate::stats::Counter::*;
+ match insn {
+ ArrayMax { .. } => unhandled_hir_insn_array_max,
+ FixnumDiv { .. } => unhandled_hir_insn_fixnum_div,
+ Throw { .. } => unhandled_hir_insn_throw,
+ InvokeBuiltin { .. } => unhandled_hir_insn_invokebuiltin,
+ _ => unhandled_hir_insn_unknown,
+ }
+}
+
+pub fn side_exit_counter(reason: crate::hir::SideExitReason) -> Counter {
+ use crate::hir::SideExitReason::*;
+ use crate::hir::CallType::*;
+ use crate::hir::Invariant;
+ use crate::stats::Counter::*;
+ match reason {
+ UnhandledNewarraySend(send_type) => match send_type {
+ VM_OPT_NEWARRAY_SEND_MIN => exit_unhandled_newarray_send_min,
+ VM_OPT_NEWARRAY_SEND_HASH => exit_unhandled_newarray_send_hash,
+ VM_OPT_NEWARRAY_SEND_PACK => exit_unhandled_newarray_send_pack,
+ VM_OPT_NEWARRAY_SEND_PACK_BUFFER => exit_unhandled_newarray_send_pack_buffer,
+ _ => exit_unhandled_newarray_send_unknown,
+ }
+ UnhandledDuparraySend(_) => exit_unhandled_duparray_send,
+ UnhandledCallType(Tailcall) => exit_unhandled_tailcall,
+ UnhandledCallType(Splat) => exit_unhandled_splat,
+ UnhandledCallType(Kwarg) => exit_unhandled_kwarg,
+ UnknownSpecialVariable(_) => exit_unknown_special_variable,
+ UnhandledHIRThrow => exit_unhandled_hir_insn,
+ UnhandledHIRInvokeBuiltin => exit_unhandled_hir_insn,
+ UnhandledHIRUnknown(_) => exit_unhandled_hir_insn,
+ UnhandledYARVInsn(_) => exit_unhandled_yarv_insn,
+ UnhandledBlockArg => exit_unhandled_block_arg,
+ FixnumAddOverflow => exit_fixnum_add_overflow,
+ FixnumSubOverflow => exit_fixnum_sub_overflow,
+ FixnumMultOverflow => exit_fixnum_mult_overflow,
+ FixnumLShiftOverflow => exit_fixnum_lshift_overflow,
+ FixnumModByZero => exit_fixnum_mod_by_zero,
+ FixnumDivByZero => exit_fixnum_div_by_zero,
+ BoxFixnumOverflow => exit_box_fixnum_overflow,
+ GuardType(_) => exit_guard_type_failure,
+ GuardShape(_) => exit_guard_shape_failure,
+ ExpandArray => exit_expandarray_failure,
+ GuardNotFrozen => exit_guard_not_frozen_failure,
+ GuardNotShared => exit_guard_not_shared_failure,
+ GuardLess => exit_guard_less_failure,
+ GuardGreaterEq => exit_guard_greater_eq_failure,
+ GuardSuperMethodEntry => exit_guard_super_method_entry,
+ CalleeSideExit => exit_callee_side_exit,
+ ObjToStringFallback => exit_obj_to_string_fallback,
+ Interrupt => exit_interrupt,
+ StackOverflow => exit_stackoverflow,
+ BlockParamProxyNotIseqOrIfunc => exit_block_param_proxy_not_iseq_or_ifunc,
+ BlockParamProxyNotNil => exit_block_param_proxy_not_nil,
+ BlockParamProxyFallbackMiss => exit_block_param_proxy_fallback_miss,
+ BlockParamProxyProfileNotCovered => exit_block_param_proxy_profile_not_covered,
+ BlockParamWbRequired => exit_block_param_wb_required,
+ TooManyKeywordParameters => exit_too_many_keyword_parameters,
+ SplatKwNotNilOrHash => exit_splatkw_not_nil_or_hash,
+ SplatKwPolymorphic => exit_splatkw_polymorphic,
+ SplatKwNotProfiled => exit_splatkw_not_profiled,
+ DirectiveInduced => exit_directive_induced,
+ PatchPoint(Invariant::BOPRedefined { .. })
+ => exit_patchpoint_bop_redefined,
+ PatchPoint(Invariant::MethodRedefined { .. })
+ => exit_patchpoint_method_redefined,
+ PatchPoint(Invariant::StableConstantNames { .. })
+ => exit_patchpoint_stable_constant_names,
+ PatchPoint(Invariant::NoTracePoint)
+ => exit_patchpoint_no_tracepoint,
+ PatchPoint(Invariant::NoEPEscape(_))
+ => exit_patchpoint_no_ep_escape,
+ PatchPoint(Invariant::SingleRactorMode)
+ => exit_patchpoint_single_ractor_mode,
+ PatchPoint(Invariant::NoSingletonClass { .. })
+ => exit_patchpoint_no_singleton_class,
+ PatchPoint(Invariant::RootBoxOnly)
+ => exit_patchpoint_root_box_only,
+ SendWhileTracing => exit_send_while_tracing,
+ NoProfileSend => exit_no_profile_send,
+ InvokeBlockNotIfunc => exit_invokeblock_not_ifunc,
+ }
+}
+
+pub fn exit_counter_ptr(reason: crate::hir::SideExitReason) -> *mut u64 {
+ let counter = side_exit_counter(reason);
+ counter_ptr(counter)
+}
+
+pub fn send_fallback_counter(reason: crate::hir::SendFallbackReason) -> Counter {
+ use crate::hir::SendFallbackReason::*;
+ use crate::stats::Counter::*;
+ match reason {
+ SendWithoutBlockPolymorphic => send_fallback_send_without_block_polymorphic,
+ SendWithoutBlockMegamorphic => send_fallback_send_without_block_megamorphic,
+ SendWithoutBlockNoProfiles => send_fallback_send_without_block_no_profiles,
+ SendWithoutBlockCfuncNotVariadic => send_fallback_send_without_block_cfunc_not_variadic,
+ SendWithoutBlockCfuncArrayVariadic => send_fallback_send_without_block_cfunc_array_variadic,
+ SendWithoutBlockNotOptimizedMethodType(_) => send_fallback_send_without_block_not_optimized_method_type,
+ SendWithoutBlockNotOptimizedMethodTypeOptimized(_)
+ => send_fallback_send_without_block_not_optimized_method_type_optimized,
+ SendWithoutBlockNotOptimizedNeedPermission
+ => send_fallback_send_without_block_not_optimized_need_permission,
+ TooManyArgsForLir => send_fallback_too_many_args_for_lir,
+ SendWithoutBlockBopRedefined => send_fallback_send_without_block_bop_redefined,
+ SendWithoutBlockOperandsNotFixnum => send_fallback_send_without_block_operands_not_fixnum,
+ SendWithoutBlockPolymorphicFallback => send_fallback_send_without_block_polymorphic_fallback,
+ SendDirectKeywordMismatch => send_fallback_send_without_block_direct_keyword_mismatch,
+ SendDirectKeywordCountMismatch => send_fallback_send_without_block_direct_keyword_count_mismatch,
+ SendDirectMissingKeyword => send_fallback_send_without_block_direct_missing_keyword,
+ SendDirectTooManyKeywords => send_fallback_send_without_block_direct_too_many_keywords,
+ SendPolymorphic => send_fallback_send_polymorphic,
+ SendMegamorphic => send_fallback_send_megamorphic,
+ SendNoProfiles => send_fallback_send_no_profiles,
+ SendCfuncVariadic => send_fallback_send_cfunc_variadic,
+ SendCfuncArrayVariadic => send_fallback_send_cfunc_array_variadic,
+ ComplexArgPass => send_fallback_one_or_more_complex_arg_pass,
+ UnexpectedKeywordArgs => send_fallback_unexpected_keyword_args,
+ SingletonClassSeen => send_fallback_singleton_class_seen,
+ ArgcParamMismatch => send_fallback_argc_param_mismatch,
+ BmethodNonIseqProc => send_fallback_bmethod_non_iseq_proc,
+ SendNotOptimizedMethodType(_) => send_fallback_send_not_optimized_method_type,
+ SendNotOptimizedNeedPermission => send_fallback_send_not_optimized_need_permission,
+ CCallWithFrameTooManyArgs => send_fallback_ccall_with_frame_too_many_args,
+ ObjToStringNotString => send_fallback_obj_to_string_not_string,
+ SuperCallWithBlock => send_fallback_super_call_with_block,
+ SuperFromBlock => send_fallback_super_from_block,
+ SuperClassNotFound => send_fallback_super_class_not_found,
+ SuperComplexArgsPass => send_fallback_super_complex_args_pass,
+ SuperNoProfiles => send_fallback_super_fallback_no_profile,
+ SuperNotOptimizedMethodType(_) => send_fallback_super_not_optimized_method_type,
+ SuperPolymorphic => send_fallback_super_polymorphic,
+ SuperTargetNotFound => send_fallback_super_target_not_found,
+ SuperTargetComplexArgsPass => send_fallback_super_target_complex_args_pass,
+ InvokeBlockNotSpecialized => send_fallback_invokeblock_not_specialized,
+ SendForwardNotSpecialized => send_fallback_sendforward_not_specialized,
+ InvokeSuperForwardNotSpecialized => send_fallback_invokesuperforward_not_specialized,
+ SingleRactorModeRequired => send_fallback_single_ractor_mode_required,
+ Uncategorized(_) => send_fallback_uncategorized,
+ }
+}
+
+pub fn send_without_block_fallback_counter_for_method_type(method_type: crate::hir::MethodType) -> Counter {
+ use crate::hir::MethodType::*;
+ use crate::stats::Counter::*;
+
+ match method_type {
+ Iseq => unspecialized_send_without_block_def_type_iseq,
+ Cfunc => unspecialized_send_without_block_def_type_cfunc,
+ Attrset => unspecialized_send_without_block_def_type_attrset,
+ Ivar => unspecialized_send_without_block_def_type_ivar,
+ Bmethod => unspecialized_send_without_block_def_type_bmethod,
+ Zsuper => unspecialized_send_without_block_def_type_zsuper,
+ Alias => unspecialized_send_without_block_def_type_alias,
+ Undefined => unspecialized_send_without_block_def_type_undef,
+ NotImplemented => unspecialized_send_without_block_def_type_not_implemented,
+ Optimized => unspecialized_send_without_block_def_type_optimized,
+ Missing => unspecialized_send_without_block_def_type_missing,
+ Refined => unspecialized_send_without_block_def_type_refined,
+ Null => unspecialized_send_without_block_def_type_null,
+ }
+}
+
+pub fn send_without_block_fallback_counter_for_optimized_method_type(method_type: crate::hir::OptimizedMethodType) -> Counter {
+ use crate::hir::OptimizedMethodType::*;
+ use crate::stats::Counter::*;
+
+ match method_type {
+ Send => unspecialized_send_without_block_def_type_optimized_send,
+ Call => unspecialized_send_without_block_def_type_optimized_call,
+ BlockCall => unspecialized_send_without_block_def_type_optimized_block_call,
+ StructAref => unspecialized_send_without_block_def_type_optimized_struct_aref,
+ StructAset => unspecialized_send_without_block_def_type_optimized_struct_aset,
+ }
+}
+
+pub fn send_fallback_counter_for_method_type(method_type: crate::hir::MethodType) -> Counter {
+ use crate::hir::MethodType::*;
+ use crate::stats::Counter::*;
+
+ match method_type {
+ Iseq => unspecialized_send_def_type_iseq,
+ Cfunc => unspecialized_send_def_type_cfunc,
+ Attrset => unspecialized_send_def_type_attrset,
+ Ivar => unspecialized_send_def_type_ivar,
+ Bmethod => unspecialized_send_def_type_bmethod,
+ Zsuper => unspecialized_send_def_type_zsuper,
+ Alias => unspecialized_send_def_type_alias,
+ Undefined => unspecialized_send_def_type_undef,
+ NotImplemented => unspecialized_send_def_type_not_implemented,
+ Optimized => unspecialized_send_def_type_optimized,
+ Missing => unspecialized_send_def_type_missing,
+ Refined => unspecialized_send_def_type_refined,
+ Null => unspecialized_send_def_type_null,
+ }
+}
+
+pub fn send_fallback_counter_for_super_method_type(method_type: crate::hir::MethodType) -> Counter {
+ use crate::hir::MethodType::*;
+ use crate::stats::Counter::*;
+
+ match method_type {
+ Iseq => unspecialized_super_def_type_iseq,
+ Cfunc => unspecialized_super_def_type_cfunc,
+ Attrset => unspecialized_super_def_type_attrset,
+ Ivar => unspecialized_super_def_type_ivar,
+ Bmethod => unspecialized_super_def_type_bmethod,
+ Zsuper => unspecialized_super_def_type_zsuper,
+ Alias => unspecialized_super_def_type_alias,
+ Undefined => unspecialized_super_def_type_undef,
+ NotImplemented => unspecialized_super_def_type_not_implemented,
+ Optimized => unspecialized_super_def_type_optimized,
+ Missing => unspecialized_super_def_type_missing,
+ Refined => unspecialized_super_def_type_refined,
+ Null => unspecialized_super_def_type_null,
+ }
+}
+
+/// Primitive called in zjit.rb. Zero out all the counters.
+#[unsafe(no_mangle)]
+pub extern "C" fn rb_zjit_reset_stats_bang(_ec: EcPtr, _self: VALUE) -> VALUE {
+ let counters = ZJITState::get_counters();
+ let exit_counters = ZJITState::get_exit_counters();
+
+ // Reset all counters to zero
+ *counters = Counters::default();
+
+ // Reset exit counters for YARV instructions
+ exit_counters.as_mut_slice().fill(0);
+
+ // Reset send fallback counters
+ ZJITState::get_send_fallback_counters().as_mut_slice().fill(0);
+
+ // Reset not-inlined counters
+ ZJITState::get_not_inlined_cfunc_counter_pointers().iter_mut()
+ .for_each(|b| { **(b.1) = 0; });
+
+ // Reset not-annotated counters
+ ZJITState::get_not_annotated_cfunc_counter_pointers().iter_mut()
+ .for_each(|b| { **(b.1) = 0; });
+
+ // Reset ccall counters
+ ZJITState::get_ccall_counter_pointers().iter_mut()
+ .for_each(|b| { **(b.1) = 0; });
+
+ // Reset iseq call counters
+ ZJITState::get_iseq_calls_count_pointers().iter_mut()
+ .for_each(|b| { **(b.1) = 0; });
+
+ Qnil
+}
+
/// Return a Hash object that contains ZJIT statistics
#[unsafe(no_mangle)]
pub extern "C" fn rb_zjit_stats(_ec: EcPtr, _self: VALUE, target_key: VALUE) -> VALUE {
@@ -80,13 +818,24 @@ pub extern "C" fn rb_zjit_stats(_ec: EcPtr, _self: VALUE, target_key: VALUE) ->
macro_rules! set_stat {
($hash:ident, $key:expr, $value:expr) => {
let key = rust_str_to_sym($key);
- // Evaluate $value only when it's needed
if key == target_key {
- return VALUE::fixnum_from_usize($value as usize);
+ return $value;
} else if $hash != Qnil {
#[allow(unused_unsafe)]
- unsafe { rb_hash_aset($hash, key, VALUE::fixnum_from_usize($value as usize)); }
+ unsafe { rb_hash_aset($hash, key, $value); }
}
+ };
+ }
+
+ macro_rules! set_stat_usize {
+ ($hash:ident, $key:expr, $value:expr) => {
+ set_stat!($hash, $key, VALUE::fixnum_from_usize($value as usize))
+ }
+ }
+
+ macro_rules! set_stat_f64 {
+ ($hash:ident, $key:expr, $value:expr) => {
+ set_stat!($hash, $key, unsafe { rb_float_new($value) })
}
}
@@ -95,34 +844,437 @@ pub extern "C" fn rb_zjit_stats(_ec: EcPtr, _self: VALUE, target_key: VALUE) ->
} else {
Qnil
};
- let counters = ZJITState::get_counters();
+ // Set default counters
for &counter in DEFAULT_COUNTERS {
- set_stat!(hash, &counter.name(), unsafe { *counter_ptr(counter) });
+ set_stat_usize!(hash, &counter.name(), unsafe { *counter_ptr(counter) });
}
- // Set counters that are enabled when --zjit-stats is enabled
- if get_option!(stats) {
- set_stat!(hash, "zjit_insns_count", counters.zjit_insns_count);
+ // Memory usage stats
+ let code_region_bytes = ZJITState::get_code_block().mapped_region_size();
+ set_stat_usize!(hash, "code_region_bytes", code_region_bytes);
+ set_stat_usize!(hash, "zjit_alloc_bytes", zjit_alloc_bytes());
+ set_stat_usize!(hash, "total_mem_bytes", code_region_bytes + zjit_alloc_bytes());
- if unsafe { rb_vm_insns_count } > 0 {
- set_stat!(hash, "vm_insns_count", unsafe { rb_vm_insns_count });
- }
+ // End of default stats. Every counter beyond this is provided only for --zjit-stats.
+ if !get_option!(stats) {
+ return hash;
+ }
+
+ // Set other stats-only counters
+ for &counter in OTHER_COUNTERS {
+ set_stat_usize!(hash, &counter.name(), unsafe { *counter_ptr(counter) });
+ }
+
+ // Set side-exit counters for each SideExitReason
+ let mut side_exit_count = 0;
+ for &counter in EXIT_COUNTERS {
+ let count = unsafe { *counter_ptr(counter) };
+ side_exit_count += count;
+ set_stat_usize!(hash, &counter.name(), count);
+ }
+ set_stat_usize!(hash, "side_exit_count", side_exit_count);
+
+ // Set side-exit counters for UnhandledYARVInsn
+ let exit_counters = ZJITState::get_exit_counters();
+ for (op_idx, count) in exit_counters.iter().enumerate().take(VM_INSTRUCTION_SIZE as usize) {
+ let op_name = insn_name(op_idx);
+ let key_string = "unhandled_yarv_insn_".to_owned() + &op_name;
+ set_stat_usize!(hash, &key_string, *count);
+ }
+
+ // Set send fallback counters for each DynamicSendReason
+ let mut dynamic_send_count = 0;
+ for &counter in DYNAMIC_SEND_COUNTERS {
+ let count = unsafe { *counter_ptr(counter) };
+ dynamic_send_count += count;
+ set_stat_usize!(hash, &counter.name(), count);
+ }
+ set_stat_usize!(hash, "dynamic_send_count", dynamic_send_count);
+
+ // Set optimized send counters
+ let mut optimized_send_count = 0;
+ for &counter in OPTIMIZED_SEND_COUNTERS {
+ let count = unsafe { *counter_ptr(counter) };
+ optimized_send_count += count;
+ set_stat_usize!(hash, &counter.name(), count);
+ }
+ set_stat_usize!(hash, "optimized_send_count", optimized_send_count);
+ set_stat_usize!(hash, "send_count", dynamic_send_count + optimized_send_count);
+
+ // Set send fallback counters for each setivar fallback reason
+ let mut dynamic_setivar_count = 0;
+ for &counter in DYNAMIC_SETIVAR_COUNTERS {
+ let count = unsafe { *counter_ptr(counter) };
+ dynamic_setivar_count += count;
+ set_stat_usize!(hash, &counter.name(), count);
+ }
+ set_stat_usize!(hash, "dynamic_setivar_count", dynamic_setivar_count);
+
+ // Set send fallback counters for each getivar fallback reason
+ let mut dynamic_getivar_count = 0;
+ for &counter in DYNAMIC_GETIVAR_COUNTERS {
+ let count = unsafe { *counter_ptr(counter) };
+ dynamic_getivar_count += count;
+ set_stat_usize!(hash, &counter.name(), count);
+ }
+ set_stat_usize!(hash, "dynamic_getivar_count", dynamic_getivar_count);
+
+ // Set send fallback counters for each definedivar fallback reason
+ let mut dynamic_definedivar_count = 0;
+ for &counter in DYNAMIC_DEFINEDIVAR_COUNTERS {
+ let count = unsafe { *counter_ptr(counter) };
+ dynamic_definedivar_count += count;
+ set_stat_usize!(hash, &counter.name(), count);
+ }
+ set_stat_usize!(hash, "dynamic_definedivar_count", dynamic_definedivar_count);
+
+ // Set send fallback counters for Uncategorized
+ let send_fallback_counters = ZJITState::get_send_fallback_counters();
+ for (op_idx, count) in send_fallback_counters.iter().enumerate().take(VM_INSTRUCTION_SIZE as usize) {
+ let op_name = insn_name(op_idx);
+ let key_string = "uncategorized_fallback_yarv_insn_".to_owned() + &op_name;
+ set_stat_usize!(hash, &key_string, *count);
+ }
+
+ // Only ZJIT_STATS builds support rb_vm_insn_count
+ if unsafe { rb_vm_insn_count } > 0 {
+ let vm_insn_count = unsafe { rb_vm_insn_count };
+ set_stat_usize!(hash, "vm_insn_count", vm_insn_count);
+
+ let zjit_insn_count = ZJITState::get_counters().zjit_insn_count;
+ let total_insn_count = vm_insn_count + zjit_insn_count;
+ set_stat_usize!(hash, "total_insn_count", total_insn_count);
+
+ set_stat_f64!(hash, "ratio_in_zjit", 100.0 * zjit_insn_count as f64 / total_insn_count as f64);
+ }
+
+ // Set not inlined cfunc counters
+ let not_inlined_cfuncs = ZJITState::get_not_inlined_cfunc_counter_pointers();
+ for (signature, counter) in not_inlined_cfuncs.iter() {
+ let key_string = format!("not_inlined_cfuncs_{signature}");
+ 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);
+ }
+
+ // Set ccall counters
+ let ccall = ZJITState::get_ccall_counter_pointers();
+ for (signature, counter) in ccall.iter() {
+ let key_string = format!("ccall_{signature}");
+ set_stat_usize!(hash, &key_string, **counter);
+ }
+
+ // Set iseq access counters
+ let iseq_access_counts = ZJITState::get_iseq_calls_count_pointers();
+ for (iseq_name, counter) in iseq_access_counts.iter() {
+ let key_string = format!("iseq_calls_count_{iseq_name}");
+ set_stat_usize!(hash, &key_string, **counter);
}
hash
}
+pub fn total_exit_count() -> u64 {
+ EXIT_COUNTERS.iter().fold(0, |sum, counter| sum + unsafe { *counter_ptr(*counter) })
+}
+
/// Measure the time taken by func() and add that to zjit_compile_time.
pub fn with_time_stat<F, R>(counter: Counter, 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(counter, nanos as u64);
+ incr_counter_by(counter, nanos as u64);
ret
}
/// The number of bytes ZJIT has allocated on the Rust heap.
-pub fn zjit_alloc_size() -> usize {
- 0 // TODO: report the actual memory usage to support --zjit-mem-size (Shopify/ruby#686)
+pub fn zjit_alloc_bytes() -> usize {
+ jit::GLOBAL_ALLOCATOR.alloc_size.load(Ordering::SeqCst)
+}
+
+/// Record a Perfetto duration event spanning the execution of `func`.
+/// Uses Begin/End pairs so nested calls produce properly nested slices.
+pub fn trace_compile_phase<F, R>(name: &str, func: F) -> R where F: FnOnce() -> R {
+ if !get_option!(trace_compiles) {
+ return func();
+ }
+ if let Some(tracer) = ZJITState::get_tracer() {
+ let ts = tracer.elapsed_ns();
+ tracer.write_duration_begin("compile", name, ts, &[]);
+ }
+ let result = func();
+ if let Some(tracer) = ZJITState::get_tracer() {
+ let ts = tracer.elapsed_ns();
+ tracer.write_duration_end("compile", name, ts);
+ }
+ result
+}
+
+/// Fuchsia Trace Format (FXT) binary writer for --zjit-trace-exits.
+/// Produces .fxt files that can be opened directly in Perfetto UI.
+/// Uses the string table for deduplication of repeated reason/frame strings.
+/// See: <https://fuchsia.dev/fuchsia-src/reference/tracing/trace-format>
+pub struct PerfettoTracer {
+ writer: std::io::BufWriter<std::fs::File>,
+ start_time: std::time::Instant,
+ event_count: usize,
+ pub skipped_samples: usize,
+ /// String table: string content -> interned index (1..32767)
+ string_table: std::collections::HashMap<String, u16>,
+ next_string_index: u16,
+ pid: u32,
+}
+
+impl PerfettoTracer {
+ /// Write a single 64-bit little-endian word.
+ fn write_word(&mut self, val: u64) {
+ use std::io::Write;
+ let _ = self.writer.write_all(&val.to_le_bytes());
+ }
+
+ /// Write bytes padded to 8-byte alignment.
+ fn write_padded_bytes(&mut self, bytes: &[u8]) {
+ use std::io::Write;
+ let _ = self.writer.write_all(bytes);
+ let remainder = bytes.len() % 8;
+ if remainder != 0 {
+ let _ = self.writer.write_all(&[0u8; 7][..8 - remainder]);
+ }
+ }
+
+ /// Number of 8-byte words needed for `len` bytes (rounded up).
+ fn word_count(len: usize) -> u64 {
+ ((len + 7) / 8) as u64
+ }
+
+ pub fn new() -> Self {
+ let pid = std::process::id();
+ let path = format!("/tmp/perfetto-{pid}.fxt");
+ let tracer = Self::create(&path, pid);
+ eprintln!("ZJIT: writing trace exits to {path}");
+ tracer
+ }
+
+ fn create(path: &str, pid: u32) -> Self {
+ let file = std::fs::File::create(path)
+ .unwrap_or_else(|e| panic!("ZJIT: failed to create {path}: {e}"));
+ let mut tracer = PerfettoTracer {
+ writer: std::io::BufWriter::new(file),
+ start_time: std::time::Instant::now(),
+ event_count: 0,
+ skipped_samples: 0,
+ string_table: std::collections::HashMap::new(),
+ next_string_index: 1, // index 0 = empty string
+ pid,
+ };
+
+ // Magic number record: metadata type=4 (trace info), trace info type=0,
+ // magic=0x16547846 at bits [24..55]
+ tracer.write_word((1u64 << 4) | (4u64 << 16) | (0x16547846u64 << 24));
+
+ // Initialization record: 1 tick = 1 nanosecond
+ tracer.write_word(1u64 | (2u64 << 4));
+ tracer.write_word(1_000_000_000u64);
+
+ // Register thread at index 1: (process_koid=pid, thread_koid=1)
+ tracer.write_word(3u64 | (3u64 << 4) | (1u64 << 16));
+ tracer.write_word(pid as u64);
+ tracer.write_word(1u64);
+
+ // Kernel object record for process: type=7, obj_type=1 (ZX_OBJ_TYPE_PROCESS), no args
+ let process_name_ref = tracer.intern_string("ruby");
+ let ko_process_header: u64 = 7u64
+ | (2u64 << 4) // size = 2 words
+ | (1u64 << 16) // obj_type = ZX_OBJ_TYPE_PROCESS
+ | ((process_name_ref as u64) << 24); // name
+ tracer.write_word(ko_process_header);
+ tracer.write_word(pid as u64); // koid = process id
+
+ // Kernel object record for thread: type=7, obj_type=2 (ZX_OBJ_TYPE_THREAD), 1 arg
+ let thread_name_ref = tracer.intern_string("main");
+ let process_arg_name_ref = tracer.intern_string("process");
+ let ko_thread_header: u64 = 7u64
+ | (4u64 << 4) // size = 4 words (header + koid + 2-word arg)
+ | (2u64 << 16) // obj_type = ZX_OBJ_TYPE_THREAD
+ | ((thread_name_ref as u64) << 24) // name
+ | (1u64 << 40); // n_args = 1
+ tracer.write_word(ko_thread_header);
+ tracer.write_word(1u64); // koid = thread id (matches thread record)
+ // Koid argument: type=8, size=2, name="process", value=pid
+ let arg_header: u64 = 8u64 | (2u64 << 4) | ((process_arg_name_ref as u64) << 16);
+ tracer.write_word(arg_header);
+ tracer.write_word(pid as u64);
+
+ // Pre-intern common strings
+ tracer.intern_string("side_exit");
+ tracer.intern_string("compile");
+ tracer.intern_string("invalidation");
+ // Pre-intern argument names "0".."14" for per-frame arguments
+ for i in 0..15u32 {
+ tracer.intern_string(&i.to_string());
+ }
+
+ // Flush header immediately so something is written even if process exits abruptly
+ {
+ use std::io::Write;
+ let _ = tracer.writer.flush();
+ }
+
+ tracer
+ }
+
+ /// Intern a string into the string table, writing a string record if new.
+ /// Returns the string table index (1..32767). Returns 0 for empty strings
+ /// or if the table is full.
+ fn intern_string(&mut self, s: &str) -> u16 {
+ if s.is_empty() {
+ return 0;
+ }
+ if let Some(&idx) = self.string_table.get(s) {
+ return idx;
+ }
+ if self.next_string_index >= 0x8000 {
+ return 0; // table full
+ }
+
+ let idx = self.next_string_index;
+ let bytes = s.as_bytes();
+ let len = bytes.len().min(0x7FFF); // 15-bit max length
+ let record_words = 1 + Self::word_count(len);
+
+ // String record: type=2, index in [16..30], length in [32..46]
+ let header: u64 = 2u64
+ | (record_words << 4)
+ | ((idx as u64) << 16)
+ | ((len as u64) << 32);
+ self.write_word(header);
+ self.write_padded_bytes(&bytes[..len]);
+
+ self.string_table.insert(s.to_string(), idx);
+ self.next_string_index += 1;
+ idx
+ }
+
+ /// Return nanoseconds elapsed since tracer creation.
+ pub fn elapsed_ns(&self) -> u64 {
+ self.start_time.elapsed().as_nanos() as u64
+ }
+
+ /// Write a Duration Begin event (FXT event type 2) with optional frame arguments.
+ pub fn write_duration_begin(&mut self, category: &str, name: &str, ts_ns: u64, frames: &[String]) {
+ self.write_duration_event(2, category, name, ts_ns, frames);
+ }
+
+ /// Write a Duration End event (FXT event type 3).
+ pub fn write_duration_end(&mut self, category: &str, name: &str, ts_ns: u64) {
+ self.write_duration_event(3, category, name, ts_ns, &[]);
+ }
+
+ /// Write a Duration Begin or End event with optional frame arguments.
+ fn write_duration_event(&mut self, event_type: u64, category: &str, name: &str, ts_ns: u64, frames: &[String]) {
+ let category_ref = self.intern_string(category);
+ let name_ref = self.intern_string(name);
+
+ let n_args = frames.len().min(15) as u64;
+ let mut frame_refs: Vec<(u16, u16)> = Vec::with_capacity(n_args as usize);
+ for (i, frame) in frames.iter().take(15).enumerate() {
+ let fname_ref = self.intern_string(&i.to_string());
+ let value_ref = self.intern_string(frame);
+ frame_refs.push((fname_ref, value_ref));
+ }
+
+ let event_words: u64 = 2 + n_args;
+ let header: u64 = 4u64 // record type = event
+ | (event_words << 4) // record size
+ | (event_type << 16) // event type = begin or end
+ | (n_args << 20) // argument count
+ | (1u64 << 24) // thread_ref = 1
+ | ((category_ref as u64) << 32)
+ | ((name_ref as u64) << 48);
+ self.write_word(header);
+ self.write_word(ts_ns);
+
+ for (fname_ref, value_ref) in frame_refs {
+ let arg_header: u64 = 6u64
+ | (1u64 << 4)
+ | ((fname_ref as u64) << 16)
+ | ((value_ref as u64) << 32);
+ self.write_word(arg_header);
+ }
+
+ self.event_count += 1;
+
+ use std::io::Write;
+ let _ = self.writer.flush();
+ }
+
+ pub fn write_event(&mut self, category: &str, reason: &str, frames: &[String]) {
+ let ts_nanos = self.start_time.elapsed().as_nanos() as u64;
+
+ // Intern event metadata strings (may emit string records first)
+ let category_ref = self.intern_string(category);
+ let name_ref = self.intern_string(reason);
+
+ // Intern each frame label and collect refs (max 15 due to 4-bit n_args)
+ let n_args = frames.len().min(15) as u64;
+ let mut frame_refs: Vec<(u16, u16)> = Vec::with_capacity(n_args as usize);
+ for (i, frame) in frames.iter().take(15).enumerate() {
+ let name_ref = self.intern_string(&i.to_string());
+ let value_ref = self.intern_string(frame);
+ frame_refs.push((name_ref, value_ref));
+ }
+
+ // Each fully-interned string argument is exactly 1 word
+ let event_words = 2 + n_args;
+ let header: u64 = 4u64
+ | (event_words << 4)
+ | (n_args << 20) // argument count
+ | (1u64 << 24) // thread_ref = 1
+ | ((category_ref as u64) << 32)
+ | ((name_ref as u64) << 48);
+ self.write_word(header);
+ self.write_word(ts_nanos);
+
+ // One 1-word string argument per frame: type=6, size=1, indexed name, indexed value
+ for (name_ref, value_ref) in frame_refs {
+ let arg_header: u64 = 6u64
+ | (1u64 << 4)
+ | ((name_ref as u64) << 16)
+ | ((value_ref as u64) << 32);
+ self.write_word(arg_header);
+ }
+
+ self.event_count += 1;
+
+ // Flush to ensure data reaches disk. Static globals may not be
+ // dropped on process exit, so we can't rely on Drop for flushing.
+ use std::io::Write;
+ let _ = self.writer.flush();
+ }
+}
+
+impl Drop for PerfettoTracer {
+ fn drop(&mut self) {
+ use std::io::Write;
+ let _ = self.writer.flush();
+ }
+}
+
+/// Primitive called in zjit.rb
+///
+/// Check if trace_exits generation is enabled.
+#[unsafe(no_mangle)]
+pub extern "C" fn rb_zjit_trace_exit_locations_enabled_p(_ec: EcPtr, _ruby_self: VALUE) -> VALUE {
+ // Builtin zjit.rb calls this even if ZJIT is disabled, so OPTIONS may not be set.
+ if unsafe { OPTIONS.as_ref() }.is_some_and(|opts| opts.trace_side_exits.is_some()) {
+ Qtrue
+ } else {
+ Qfalse
+ }
}