Skip to content

Commit bb4526b

Browse files
author
Aiden Fox Ivey
authored
ZJIT: Add trace exit counter (ruby#14831)
1 parent dce202d commit bb4526b

4 files changed

Lines changed: 64 additions & 21 deletions

File tree

zjit/src/backend/lir.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ use std::mem::take;
44
use crate::codegen::local_size_and_idx_to_ep_offset;
55
use crate::cruby::{Qundef, RUBY_OFFSET_CFP_PC, RUBY_OFFSET_CFP_SP, SIZEOF_VALUE_I32, vm_stack_canary};
66
use crate::hir::SideExitReason;
7-
use crate::options::{debug, get_option};
7+
use crate::options::{debug, get_option, TraceExits};
88
use crate::cruby::VALUE;
9-
use crate::stats::{exit_counter_ptr, exit_counter_ptr_for_opcode, CompileError};
9+
use crate::stats::{exit_counter_ptr, exit_counter_ptr_for_opcode, side_exit_counter, CompileError};
1010
use crate::virtualmem::CodePtr;
1111
use crate::asm::{CodeBlock, Label};
1212
use crate::state::rb_zjit_record_exit_stack;
@@ -1644,8 +1644,22 @@ impl Assembler
16441644
}
16451645
}
16461646

1647-
if get_option!(trace_side_exits) {
1648-
asm_ccall!(self, rb_zjit_record_exit_stack, Opnd::const_ptr(pc as *const u8));
1647+
if get_option!(trace_side_exits).is_some() {
1648+
// Get the corresponding `Counter` for the current `SideExitReason`.
1649+
let side_exit_counter = side_exit_counter(reason);
1650+
1651+
// Only record the exit if `trace_side_exits` is defined and the counter is either the one specified
1652+
let should_record_exit = get_option!(trace_side_exits)
1653+
.map(|trace| match trace {
1654+
TraceExits::All => true,
1655+
TraceExits::Counter(counter) if counter == side_exit_counter => true,
1656+
_ => false,
1657+
})
1658+
.unwrap_or(false);
1659+
1660+
if should_record_exit {
1661+
asm_ccall!(self, rb_zjit_record_exit_stack, Opnd::const_ptr(pc as *const u8));
1662+
}
16491663
}
16501664

16511665
asm_comment!(self, "exit to the interpreter");

zjit/src/options.rs

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use std::{ffi::{CStr, CString}, ptr::null};
44
use std::os::raw::{c_char, c_int, c_uint};
55
use crate::cruby::*;
6+
use crate::stats::Counter;
67
use std::collections::HashSet;
78

89
/// Default --zjit-num-profiles
@@ -72,7 +73,7 @@ pub struct Options {
7273
pub dump_disasm: bool,
7374

7475
/// Trace and write side exit source maps to /tmp for stackprof.
75-
pub trace_side_exits: bool,
76+
pub trace_side_exits: Option<TraceExits>,
7677

7778
/// Frequency of tracing side exits.
7879
pub trace_side_exits_sample_interval: usize,
@@ -102,7 +103,7 @@ impl Default for Options {
102103
dump_hir_graphviz: None,
103104
dump_lir: false,
104105
dump_disasm: false,
105-
trace_side_exits: false,
106+
trace_side_exits: None,
106107
trace_side_exits_sample_interval: 0,
107108
perf: false,
108109
allowed_iseqs: None,
@@ -125,12 +126,20 @@ pub const ZJIT_OPTIONS: &[(&str, &str)] = &[
125126
("--zjit-perf", "Dump ISEQ symbols into /tmp/perf-{}.map for Linux perf."),
126127
("--zjit-log-compiled-iseqs=path",
127128
"Log compiled ISEQs to the file. The file will be truncated."),
128-
("--zjit-trace-exits",
129-
"Record Ruby source location when side-exiting."),
130-
("--zjit-trace-exits-sample-rate",
129+
("--zjit-trace-exits[=counter]",
130+
"Record source on side-exit. `Counter` picks specific counter."),
131+
("--zjit-trace-exits-sample-rate=num",
131132
"Frequency at which to record side exits. Must be `usize`.")
132133
];
133134

135+
#[derive(Copy, Clone, Debug)]
136+
pub enum TraceExits {
137+
// Trace all exits
138+
All,
139+
// Trace exits for a specific `Counter`
140+
Counter(Counter),
141+
}
142+
134143
#[derive(Clone, Copy, Debug)]
135144
pub enum DumpHIR {
136145
// Dump High-level IR without Snapshot
@@ -249,13 +258,18 @@ fn parse_option(str_ptr: *const std::os::raw::c_char) -> Option<()> {
249258
options.print_stats = false;
250259
}
251260

252-
("trace-exits", "") => {
253-
options.trace_side_exits = true;
261+
("trace-exits", exits) => {
262+
options.trace_side_exits = match exits {
263+
"" => Some(TraceExits::All),
264+
name => Counter::get(name).map(TraceExits::Counter),
265+
}
254266
}
255267

256268
("trace-exits-sample-rate", sample_interval) => {
257-
// Even if `trace_side_exits` is already set, set it.
258-
options.trace_side_exits = true;
269+
// If not already set, then set it to `TraceExits::All` by default.
270+
if options.trace_side_exits.is_none() {
271+
options.trace_side_exits = Some(TraceExits::All);
272+
}
259273
// `sample_interval ` must provide a string that can be validly parsed to a `usize`.
260274
options.trace_side_exits_sample_interval = sample_interval.parse::<usize>().ok()?;
261275
}

zjit/src/state.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ impl ZJITState {
8282
let exit_trampoline = gen_exit_trampoline(&mut cb).unwrap();
8383
let function_stub_hit_trampoline = gen_function_stub_hit_trampoline(&mut cb).unwrap();
8484

85-
let exit_locations = if get_option!(trace_side_exits) {
85+
let exit_locations = if get_option!(trace_side_exits).is_some() {
8686
Some(SideExitLocations::default())
8787
} else {
8888
None
@@ -369,7 +369,7 @@ fn try_increment_existing_stack(
369369
/// Record a backtrace with ZJIT side exits
370370
#[unsafe(no_mangle)]
371371
pub extern "C" fn rb_zjit_record_exit_stack(exit_pc: *const VALUE) {
372-
if !zjit_enabled_p() || !get_option!(trace_side_exits) {
372+
if !zjit_enabled_p() || get_option!(trace_side_exits).is_none() {
373373
return;
374374
}
375375

@@ -425,7 +425,7 @@ pub extern "C" fn rb_zjit_record_exit_stack(exit_pc: *const VALUE) {
425425
/// Mark `raw_samples` so they can be used by rb_zjit_add_frame.
426426
pub fn gc_mark_raw_samples() {
427427
// Return if ZJIT is not enabled
428-
if !zjit_enabled_p() || !get_option!(trace_side_exits) {
428+
if !zjit_enabled_p() || get_option!(trace_side_exits).is_none() {
429429
return;
430430
}
431431

zjit/src/stats.rs

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,17 @@ macro_rules! make_counters {
5757
$( Counter::$counter_name => stringify!($counter_name), )+
5858
}
5959
}
60+
61+
pub fn get(name: &str) -> Option<Counter> {
62+
match name {
63+
$( stringify!($default_counter_name) => Some(Counter::$default_counter_name), )+
64+
$( stringify!($exit_counter_name) => Some(Counter::$exit_counter_name), )+
65+
$( stringify!($dynamic_send_counter_name) => Some(Counter::$dynamic_send_counter_name), )+
66+
$( stringify!($optimized_send_counter_name) => Some(Counter::$optimized_send_counter_name), )+
67+
$( stringify!($counter_name) => Some(Counter::$counter_name), )+
68+
_ => None,
69+
}
70+
}
6071
}
6172

6273
/// Map a counter to a pointer
@@ -298,11 +309,11 @@ pub fn exit_counter_for_compile_error(compile_error: &CompileError) -> Counter {
298309
}
299310
}
300311

301-
pub fn exit_counter_ptr(reason: crate::hir::SideExitReason) -> *mut u64 {
312+
pub fn side_exit_counter(reason: crate::hir::SideExitReason) -> Counter {
302313
use crate::hir::SideExitReason::*;
303314
use crate::hir::CallType::*;
304315
use crate::stats::Counter::*;
305-
let counter = match reason {
316+
match reason {
306317
UnknownNewarraySend(_) => exit_unknown_newarray_send,
307318
UnhandledCallType(Tailcall) => exit_unhandled_tailcall,
308319
UnhandledCallType(Splat) => exit_unhandled_splat,
@@ -324,7 +335,11 @@ pub fn exit_counter_ptr(reason: crate::hir::SideExitReason) -> *mut u64 {
324335
StackOverflow => exit_stackoverflow,
325336
BlockParamProxyModified => exit_block_param_proxy_modified,
326337
BlockParamProxyNotIseqOrIfunc => exit_block_param_proxy_not_iseq_or_ifunc,
327-
};
338+
}
339+
}
340+
341+
pub fn exit_counter_ptr(reason: crate::hir::SideExitReason) -> *mut u64 {
342+
let counter = side_exit_counter(reason);
328343
counter_ptr(counter)
329344
}
330345

@@ -563,7 +578,7 @@ pub struct SideExitLocations {
563578
#[unsafe(no_mangle)]
564579
pub extern "C" fn rb_zjit_trace_exit_locations_enabled_p(_ec: EcPtr, _ruby_self: VALUE) -> VALUE {
565580
// Builtin zjit.rb calls this even if ZJIT is disabled, so OPTIONS may not be set.
566-
if unsafe { OPTIONS.as_ref() }.is_some_and(|opts| opts.trace_side_exits) {
581+
if unsafe { OPTIONS.as_ref() }.is_some_and(|opts| opts.trace_side_exits.is_some()) {
567582
Qtrue
568583
} else {
569584
Qfalse
@@ -574,7 +589,7 @@ pub extern "C" fn rb_zjit_trace_exit_locations_enabled_p(_ec: EcPtr, _ruby_self:
574589
/// into raw, lines, and frames hash for RubyVM::YJIT.exit_locations.
575590
#[unsafe(no_mangle)]
576591
pub extern "C" fn rb_zjit_get_exit_locations(_ec: EcPtr, _ruby_self: VALUE) -> VALUE {
577-
if !zjit_enabled_p() || !get_option!(trace_side_exits) {
592+
if !zjit_enabled_p() || get_option!(trace_side_exits).is_none() {
578593
return Qnil;
579594
}
580595

0 commit comments

Comments
 (0)