summaryrefslogtreecommitdiff
path: root/zjit/src/state.rs
diff options
context:
space:
mode:
Diffstat (limited to 'zjit/src/state.rs')
-rw-r--r--zjit/src/state.rs232
1 files changed, 53 insertions, 179 deletions
diff --git a/zjit/src/state.rs b/zjit/src/state.rs
index b8dcd70358..b9f8033e7f 100644
--- a/zjit/src/state.rs
+++ b/zjit/src/state.rs
@@ -1,14 +1,14 @@
//! Runtime state of ZJIT.
use crate::codegen::{gen_entry_trampoline, gen_exit_trampoline, gen_exit_trampoline_with_counter, gen_function_stub_hit_trampoline};
-use crate::cruby::{self, rb_bug_panic_hook, rb_vm_insn_count, src_loc, EcPtr, Qnil, Qtrue, rb_vm_insn_addr2opcode, rb_profile_frames, VALUE, VM_INSTRUCTION_SIZE, size_t, rb_gc_mark, with_vm_lock, rust_str_to_id, rb_funcallv, rb_const_get, rb_cRubyVM};
+use crate::cruby::{self, rb_bug_panic_hook, rb_vm_insn_count, src_loc, EcPtr, Qnil, Qtrue, rb_profile_frames, rb_profile_frame_full_label, rb_profile_frame_absolute_path, rb_profile_frame_path, VALUE, VM_INSTRUCTION_SIZE, with_vm_lock, rust_str_to_id, rb_funcallv, rb_const_get, rb_cRubyVM};
use crate::cruby_methods;
-use cruby::{ID, rb_callable_method_entry, get_def_method_serial, rb_gc_register_mark_object};
+use cruby::{ID, rb_callable_method_entry, get_def_method_serial, rb_gc_register_mark_object, ruby_str_to_rust_string_result};
use std::sync::atomic::Ordering;
use crate::invariants::Invariants;
use crate::asm::CodeBlock;
use crate::options::{get_option, rb_zjit_prepare_options};
-use crate::stats::{Counters, InsnCounters, SideExitLocations};
+use crate::stats::{Counters, InsnCounters, PerfettoTracer};
use crate::virtualmem::CodePtr;
use std::sync::atomic::AtomicUsize;
use std::collections::HashMap;
@@ -68,8 +68,8 @@ pub struct ZJITState {
/// Counter pointers for access counts of ISEQs accessed by JIT code
iseq_calls_count_pointers: HashMap<String, Box<u64>>,
- /// Locations of side exists within generated code
- exit_locations: Option<SideExitLocations>,
+ /// Perfetto tracer for --zjit-trace-exits
+ perfetto_tracer: Option<PerfettoTracer>,
}
/// Tracks the initialization progress
@@ -124,8 +124,8 @@ impl ZJITState {
let exit_trampoline = gen_exit_trampoline(&mut cb).unwrap();
let function_stub_hit_trampoline = gen_function_stub_hit_trampoline(&mut cb).unwrap();
- let exit_locations = if get_option!(trace_side_exits).is_some() {
- Some(SideExitLocations::default())
+ let perfetto_tracer = if get_option!(trace_side_exits).is_some() {
+ Some(PerfettoTracer::new())
} else {
None
};
@@ -146,7 +146,7 @@ impl ZJITState {
not_annotated_frame_cfunc_counter_pointers: HashMap::new(),
ccall_counter_pointers: HashMap::new(),
iseq_calls_count_pointers: HashMap::new(),
- exit_locations,
+ perfetto_tracer,
};
unsafe { ZJIT_STATE = Enabled(zjit_state); }
@@ -283,24 +283,9 @@ impl ZJITState {
ZJITState::get_instance().function_stub_hit_trampoline
}
- /// Get a mutable reference to the ZJIT raw samples Vec
- pub fn get_raw_samples() -> Option<&'static mut Vec<VALUE>> {
- ZJITState::get_instance().exit_locations.as_mut().map(|el| &mut el.raw_samples)
- }
-
- /// Get a mutable reference to the ZJIT line samples Vec.
- pub fn get_line_samples() -> Option<&'static mut Vec<i32>> {
- ZJITState::get_instance().exit_locations.as_mut().map(|el| &mut el.line_samples)
- }
-
- /// Get number of skipped samples.
- pub fn get_skipped_samples() -> Option<&'static mut usize> {
- ZJITState::get_instance().exit_locations.as_mut().map(|el| &mut el.skipped_samples)
- }
-
- /// Get number of skipped samples.
- pub fn set_skipped_samples(n: usize) -> Option<()> {
- ZJITState::get_instance().exit_locations.as_mut().map(|el| el.skipped_samples = n)
+ /// Get a mutable reference to the Perfetto tracer
+ pub fn get_tracer() -> Option<&'static mut PerfettoTracer> {
+ ZJITState::get_instance().perfetto_tracer.as_mut()
}
}
@@ -437,177 +422,66 @@ pub extern "C" fn rb_zjit_assert_compiles(_ec: EcPtr, _self: VALUE) -> VALUE {
Qnil
}
-/// Call `rb_profile_frames` and write the result into buffers to be consumed by `rb_zjit_record_exit_stack`.
-fn record_profiling_frames() -> (i32, Vec<VALUE>, Vec<i32>) {
- // Stackprof uses a buffer of length 2048 when collating the frames into statistics.
- // Since eventually the collected information will be used by Stackprof, collect only
- // 2048 frames at a time.
- // https://github.com/tmm1/stackprof/blob/5d832832e4afcb88521292d6dfad4a9af760ef7c/ext/stackprof/stackprof.c#L21
- const BUFF_LEN: usize = 2048;
-
- let mut frames_buffer = vec![VALUE(0_usize); BUFF_LEN];
- let mut lines_buffer = vec![0; BUFF_LEN];
-
- let stack_length = unsafe {
- rb_profile_frames(
- 0,
- BUFF_LEN as i32,
- frames_buffer.as_mut_ptr(),
- lines_buffer.as_mut_ptr(),
- )
- };
-
- // Trim at `stack_length` since anything past it is redundant
- frames_buffer.truncate(stack_length as usize);
- lines_buffer.truncate(stack_length as usize);
-
- (stack_length, frames_buffer, lines_buffer)
-}
-
-/// Write samples in `frames_buffer` and `lines_buffer` from profiling into
-/// `raw_samples` and `line_samples`. Also write opcode, number of frames,
-/// and stack size to be consumed by Stackprof.
-fn write_exit_stack_samples(
- raw_samples: &'static mut Vec<VALUE>,
- line_samples: &'static mut Vec<i32>,
- frames_buffer: &[VALUE],
- lines_buffer: &[i32],
- stack_length: i32,
- exit_pc: *const VALUE,
-) {
- raw_samples.push(VALUE(stack_length as usize));
- line_samples.push(stack_length);
-
- // Push frames and their lines in reverse order.
- for i in (0..stack_length as usize).rev() {
- raw_samples.push(frames_buffer[i]);
- line_samples.push(lines_buffer[i]);
- }
-
- // Get the opcode from instruction handler at exit PC.
- let exit_opcode = unsafe { rb_vm_insn_addr2opcode((*exit_pc).as_ptr()) };
- raw_samples.push(VALUE(exit_opcode as usize));
- // Push a dummy line number since we don't know where this insn is from.
- line_samples.push(0);
+/// Resolve a profile frame VALUE to a human-readable "label (path)" string.
+fn resolve_frame_label(frame: VALUE) -> String {
+ unsafe {
+ let label_str = ruby_str_to_rust_string_result(rb_profile_frame_full_label(frame)).unwrap_or("<unknown>".into());
- // Push number of times seen onto the stack.
- raw_samples.push(VALUE(1usize));
- line_samples.push(1);
-}
+ let path = rb_profile_frame_absolute_path(frame);
+ let path = if path.nil_p() { rb_profile_frame_path(frame) } else { path };
+ let path_str = ruby_str_to_rust_string_result(path).unwrap_or("<unknown>".into());
-fn try_increment_existing_stack(
- raw_samples: &mut [VALUE],
- line_samples: &mut [i32],
- frames_buffer: &[VALUE],
- stack_length: i32,
- samples_length: usize,
-) -> bool {
- let prev_stack_len_index = raw_samples.len() - samples_length;
- let prev_stack_len = i64::from(raw_samples[prev_stack_len_index]);
-
- if prev_stack_len == stack_length as i64 {
- // Check if all stack lengths match and all frames are identical
- let frames_match = (0..stack_length).all(|i| {
- let current_frame = frames_buffer[stack_length as usize - 1 - i as usize];
- let prev_frame = raw_samples[prev_stack_len_index + i as usize + 1];
- current_frame == prev_frame
- });
-
- if frames_match {
- let counter_idx = raw_samples.len() - 1;
- let new_count = i64::from(raw_samples[counter_idx]) + 1;
-
- raw_samples[counter_idx] = VALUE(new_count as usize);
- line_samples[counter_idx] = new_count as i32;
- return true;
- }
+ format!("{label_str} ({path_str})")
}
- false
}
-/// Record a backtrace with ZJIT side exits
+/// Record a backtrace with ZJIT side exits as a Perfetto trace event
#[unsafe(no_mangle)]
-pub extern "C" fn rb_zjit_record_exit_stack(exit_pc: *const VALUE) {
+pub extern "C" fn rb_zjit_record_exit_stack(reason: *const std::ffi::c_char) {
if !zjit_enabled_p() || get_option!(trace_side_exits).is_none() {
return;
}
- // When `trace_side_exits_sample_interval` is zero, then the feature is disabled.
+ let tracer = match ZJITState::get_tracer() {
+ Some(t) => t,
+ None => return,
+ };
+
+ // When `trace_side_exits_sample_interval` is non-zero, apply sampling.
if get_option!(trace_side_exits_sample_interval) != 0 {
- // If `trace_side_exits_sample_interval` is set, then can safely unwrap
- // both `get_skipped_samples` and `set_skipped_samples`.
- let skipped_samples = *ZJITState::get_skipped_samples().unwrap();
- if skipped_samples < get_option!(trace_side_exits_sample_interval) {
- // Skip sample and increment counter.
- ZJITState::set_skipped_samples(skipped_samples + 1).unwrap();
+ if tracer.skipped_samples < get_option!(trace_side_exits_sample_interval) {
+ tracer.skipped_samples += 1;
return;
} else {
- ZJITState::set_skipped_samples(0).unwrap();
+ tracer.skipped_samples = 0;
}
}
- let (stack_length, frames_buffer, lines_buffer) = record_profiling_frames();
-
- // Can safely unwrap since `trace_side_exits` must be true at this point
- let zjit_raw_samples = ZJITState::get_raw_samples().unwrap();
- let zjit_line_samples = ZJITState::get_line_samples().unwrap();
- assert_eq!(zjit_raw_samples.len(), zjit_line_samples.len());
-
- // Represents pushing the stack length, the instruction opcode, and the sample count.
- const SAMPLE_METADATA_SIZE: usize = 3;
- let samples_length = (stack_length as usize) + SAMPLE_METADATA_SIZE;
-
- // If zjit_raw_samples is greater than or equal to the current length of the samples
- // we might have seen this stack trace previously.
- if zjit_raw_samples.len() >= samples_length
- && try_increment_existing_stack(
- zjit_raw_samples,
- zjit_line_samples,
- &frames_buffer,
- stack_length,
- samples_length,
- )
- {
- return;
- }
-
- write_exit_stack_samples(
- zjit_raw_samples,
- zjit_line_samples,
- &frames_buffer,
- &lines_buffer,
- stack_length,
- exit_pc,
- );
-}
-
-/// Mark `raw_samples` so they can be used by rb_zjit_add_frame.
-pub fn gc_mark_raw_samples() {
- // Return if ZJIT is not enabled
- if !zjit_enabled_p() || get_option!(trace_side_exits).is_none() {
- return;
- }
+ // Collect profile frames
+ const BUFF_LEN: usize = 2048;
+ let mut frames_buffer = vec![VALUE(0_usize); BUFF_LEN];
+ let mut lines_buffer = vec![0i32; BUFF_LEN];
- let mut idx: size_t = 0;
- let zjit_raw_samples = ZJITState::get_raw_samples().unwrap();
+ let stack_length = unsafe {
+ rb_profile_frames(
+ 0,
+ BUFF_LEN as i32,
+ frames_buffer.as_mut_ptr(),
+ lines_buffer.as_mut_ptr(),
+ )
+ };
- while idx < zjit_raw_samples.len() as size_t {
- let num = zjit_raw_samples[idx as usize];
- let mut i = 0;
- idx += 1;
+ // Resolve each frame to a human-readable string (top frame first)
+ let frames: Vec<String> = (0..stack_length as usize)
+ .map(|i| resolve_frame_label(frames_buffer[i]))
+ .collect();
- // Mark the zjit_raw_samples at the given index. These represent
- // the data that needs to be GC'd which are the current frames.
- while i < i32::from(num) {
- unsafe { rb_gc_mark(zjit_raw_samples[idx as usize]); }
- i += 1;
- idx += 1;
- }
+ // Get the reason string
+ let reason_str = if reason.is_null() {
+ "unknown"
+ } else {
+ unsafe { std::ffi::CStr::from_ptr(reason).to_str().unwrap_or("unknown") }
+ };
- // Increase index for exit instruction.
- idx += 1;
- // Increase index for bookeeping value (number of times we've seen this
- // row in a stack).
- idx += 1;
- }
+ tracer.write_event(reason_str, &frames);
}