Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

YJIT: Allow tracing a counted exit #9890

Merged
merged 2 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 3 additions & 2 deletions doc/yjit/yjit.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,9 @@ YJIT supports all command-line options supported by upstream CRuby, but also add
It will cause all machine code to be discarded when the executable memory size limit is hit, meaning JIT compilation will then start over.
This can allow you to use a lower executable memory size limit, but may cause a slight drop in performance when the limit is hit.
- `--yjit-perf`: enable frame pointers and profiling with the `perf` tool
- `--yjit-trace-exits`: produce a Marshal dump of backtraces from specific exits. Automatically enables `--yjit-stats`
- `--yjit-trace-exits-sample-rate=N`: trace exit locations only every Nth occurrence
- `--yjit-trace-exits`: produce a Marshal dump of backtraces from all exits. Automatically enables `--yjit-stats`
- `--yjit-trace-exits=COUNTER`: produce a Marshal dump of backtraces from specified exits. Automatically enables `--yjit-stats`
- `--yjit-trace-exits-sample-rate=N`: trace exit locations only every Nth occurrence. Automatically enables `--yjit-trace-exits`

Note that there is also an environment variable `RUBY_YJIT_ENABLE` which can be used to enable YJIT.
This can be useful for some deployment scripts where specifying an extra command-line option to Ruby is not practical.
Expand Down
2 changes: 1 addition & 1 deletion yjit/src/backend/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1117,7 +1117,7 @@ impl Assembler
};

// Wrap a counter if needed
gen_counted_exit(side_exit, ocb, counter)
gen_counted_exit(side_exit_context.pc, side_exit, ocb, counter)
}

/// Create a new label instance that we can jump to
Expand Down
35 changes: 25 additions & 10 deletions yjit/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -532,9 +532,9 @@ fn gen_exit(exit_pc: *mut VALUE, asm: &mut Assembler) {
vec![Opnd::const_ptr(exit_pc as *const u8)]
);

// If --yjit-trace-exits option is enabled, record the exit stack
// while recording the side exits.
if get_option!(gen_trace_exits) {
// If --yjit-trace-exits is enabled, record the exit stack while recording
// the side exits. TraceExits::Counter is handled by gen_counted_exit().
if get_option!(trace_exits) == Some(TraceExits::All) {
asm.ccall(
rb_yjit_record_exit_stack as *const u8,
vec![Opnd::const_ptr(exit_pc as *const u8)]
Expand Down Expand Up @@ -575,7 +575,7 @@ pub fn gen_outlined_exit(exit_pc: *mut VALUE, ctx: &Context, ocb: &mut OutlinedC
}

/// Get a side exit. Increment a counter in it if --yjit-stats is enabled.
pub fn gen_counted_exit(side_exit: CodePtr, ocb: &mut OutlinedCb, counter: Option<Counter>) -> Option<CodePtr> {
pub fn gen_counted_exit(exit_pc: *mut VALUE, side_exit: CodePtr, ocb: &mut OutlinedCb, counter: Option<Counter>) -> Option<CodePtr> {
// The counter is only incremented when stats are enabled
if !get_option!(gen_stats) {
return Some(side_exit);
Expand All @@ -587,13 +587,16 @@ pub fn gen_counted_exit(side_exit: CodePtr, ocb: &mut OutlinedCb, counter: Optio

let mut asm = Assembler::new();

// Load the pointer into a register
asm_comment!(asm, "increment counter {}", counter.get_name());
let ptr_reg = asm.load(Opnd::const_ptr(get_counter_ptr(&counter.get_name()) as *const u8));
let counter_opnd = Opnd::mem(64, ptr_reg, 0);
// Increment a counter
gen_counter_incr(&mut asm, counter);

// Increment and store the updated value
asm.incr_counter(counter_opnd, Opnd::UImm(1));
// Trace a counted exit if --yjit-trace-exits=counter is given.
// TraceExits::All is handled by gen_exit().
if get_option!(trace_exits) == Some(TraceExits::CountedExit(counter)) {
with_caller_saved_temp_regs(&mut asm, |asm| {
asm.ccall(rb_yjit_record_exit_stack as *const u8, vec![Opnd::const_ptr(exit_pc as *const u8)]);
});
}

// Jump to the existing side exit
asm.jmp(Target::CodePtr(side_exit));
Expand All @@ -602,6 +605,18 @@ pub fn gen_counted_exit(side_exit: CodePtr, ocb: &mut OutlinedCb, counter: Optio
asm.compile(ocb, None).map(|(code_ptr, _)| code_ptr)
}

/// Preserve caller-saved stack temp registers during the call of a given block
fn with_caller_saved_temp_regs<F, R>(asm: &mut Assembler, block: F) -> R where F: FnOnce(&mut Assembler) -> R {
for &reg in caller_saved_temp_regs() {
asm.cpush(Opnd::Reg(reg)); // save stack temps
}
let ret = block(asm);
for &reg in caller_saved_temp_regs().rev() {
asm.cpop_into(Opnd::Reg(reg)); // restore stack temps
}
ret
}

// Ensure that there is an exit for the start of the block being compiled.
// Block invalidation uses this exit.
#[must_use]
Expand Down
2 changes: 1 addition & 1 deletion yjit/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2960,7 +2960,7 @@ pub fn gen_branch_stub_hit_trampoline(ocb: &mut OutlinedCb) -> Option<CodePtr> {
}

/// Return registers to be pushed and popped on branch_stub_hit.
fn caller_saved_temp_regs() -> impl Iterator<Item = &'static Reg> + DoubleEndedIterator {
pub fn caller_saved_temp_regs() -> impl Iterator<Item = &'static Reg> + DoubleEndedIterator {
let temp_regs = Assembler::get_temp_regs().iter();
let len = temp_regs.len();
// The return value gen_leave() leaves in C_RET_REG
Expand Down
33 changes: 28 additions & 5 deletions yjit/src/options.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::{ffi::{CStr, CString}, ptr::null, fs::File};
use crate::backend::current::TEMP_REGS;
use crate::{backend::current::TEMP_REGS, stats::Counter};
use std::os::raw::{c_char, c_int, c_uint};

// Call threshold for small deployments and command-line apps
Expand Down Expand Up @@ -48,7 +48,7 @@ pub struct Options {
pub print_stats: bool,

// Trace locations of exits
pub gen_trace_exits: bool,
pub trace_exits: Option<TraceExits>,

// how often to sample exit trace data
pub trace_exits_sample_rate: usize,
Expand Down Expand Up @@ -86,7 +86,7 @@ pub static mut OPTIONS: Options = Options {
max_versions: 4,
num_temp_regs: 5,
gen_stats: false,
gen_trace_exits: false,
trace_exits: None,
print_stats: true,
trace_exits_sample_rate: 0,
disable: false,
Expand All @@ -112,6 +112,14 @@ static YJIT_OPTIONS: [(&str, &str); 9] = [
("--yjit-trace-exits-sample-rate=num", "Trace exit locations only every Nth occurrence"),
];

#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum TraceExits {
// Trace all exits
All,
// Trace a specific counted exit
CountedExit(Counter),
}

#[derive(Clone, PartialEq, Eq, Debug)]
pub enum DumpDisasm {
// Dump to stdout
Expand Down Expand Up @@ -267,8 +275,23 @@ pub fn parse_option(str_ptr: *const std::os::raw::c_char) -> Option<()> {
return None;
}
},
("trace-exits", "") => unsafe { OPTIONS.gen_trace_exits = true; OPTIONS.gen_stats = true; OPTIONS.trace_exits_sample_rate = 0 },
("trace-exits-sample-rate", sample_rate) => unsafe { OPTIONS.gen_trace_exits = true; OPTIONS.gen_stats = true; OPTIONS.trace_exits_sample_rate = sample_rate.parse().unwrap(); },
("trace-exits", _) => unsafe {
OPTIONS.gen_stats = true;
OPTIONS.trace_exits = match opt_val {
"" => Some(TraceExits::All),
name => match Counter::get(name) {
Some(counter) => Some(TraceExits::CountedExit(counter)),
None => return None,
},
};
},
("trace-exits-sample-rate", sample_rate) => unsafe {
OPTIONS.gen_stats = true;
if OPTIONS.trace_exits.is_none() {
OPTIONS.trace_exits = Some(TraceExits::All);
}
OPTIONS.trace_exits_sample_rate = sample_rate.parse().unwrap();
},
("dump-insns", "") => unsafe { OPTIONS.dump_insns = true },
("verify-ctx", "") => unsafe { OPTIONS.verify_ctx = true },

Expand Down
18 changes: 13 additions & 5 deletions yjit/src/stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ impl YjitExitLocations {
/// Initialize the yjit exit locations
pub fn init() {
// Return if --yjit-trace-exits isn't enabled
if !get_option!(gen_trace_exits) {
if get_option!(trace_exits).is_none() {
return;
}

Expand Down Expand Up @@ -177,7 +177,7 @@ impl YjitExitLocations {
}

// Return if --yjit-trace-exits isn't enabled
if !get_option!(gen_trace_exits) {
if get_option!(trace_exits).is_none() {
return;
}

Expand Down Expand Up @@ -219,6 +219,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 {
Expand Down Expand Up @@ -636,7 +644,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 {
if get_option!(gen_trace_exits) {
if get_option!(trace_exits).is_some() {
return Qtrue;
}

Expand All @@ -653,7 +661,7 @@ pub extern "C" fn rb_yjit_get_exit_locations(_ec: EcPtr, _ruby_self: VALUE) -> V
}

// Return if --yjit-trace-exits isn't enabled
if !get_option!(gen_trace_exits) {
if get_option!(trace_exits).is_none() {
return Qnil;
}

Expand Down Expand Up @@ -834,7 +842,7 @@ pub extern "C" fn rb_yjit_record_exit_stack(_exit_pc: *const VALUE)
}

// Return if --yjit-trace-exits isn't enabled
if !get_option!(gen_trace_exits) {
if get_option!(trace_exits).is_none() {
return;
}

Expand Down