summaryrefslogtreecommitdiff
diff options
authorTakashi Kokubun <[email protected]>2025-07-16 09:53:10 -0700
committerGitHub <[email protected]>2025-07-16 09:53:10 -0700
commitacc317253043efc65e87b460de48dc4e50c87c59 (patch)
treee758e4909154184904a81f3dc8a5aa582098b1be
parentaf1ad78bffe78f70e649e60ed3ab016c8c6cef05 (diff)
ZJIT: Profile each instruction at most num_profiles times (#13903)HEADmaster
* ZJIT: Profile each instruction at most num_profiles times * Use saturating_add for num_profiles
-rw-r--r--zjit.c12
-rw-r--r--zjit/bindgen/src/main.rs1
-rw-r--r--zjit/src/cruby_bindings.inc.rs5
-rw-r--r--zjit/src/options.rs6
-rw-r--r--zjit/src/profile.rs58
5 files changed, 56 insertions, 26 deletions
diff --git a/zjit.c b/zjit.c
index 0c0c85d3a6..61c17d32c3 100644
--- a/zjit.c
+++ b/zjit.c
@@ -295,6 +295,18 @@ rb_zjit_profile_disable(const rb_iseq_t *iseq)
}
}
+// Update a YARV instruction to a given opcode (to disable ZJIT profiling).
+void
+rb_zjit_iseq_insn_set(const rb_iseq_t *iseq, unsigned int insn_idx, enum ruby_vminsn_type bare_insn)
+{
+#if RUBY_DEBUG
+ int insn = rb_vm_insn_addr2opcode((void *)iseq->body->iseq_encoded[insn_idx]);
+ RUBY_ASSERT(vm_zjit_insn_to_bare_insn(insn) == (int)bare_insn);
+#endif
+ const void *const *insn_table = rb_vm_get_insns_address_table();
+ iseq->body->iseq_encoded[insn_idx] = (VALUE)insn_table[bare_insn];
+}
+
// Get profiling information for ISEQ
void *
rb_iseq_get_zjit_payload(const rb_iseq_t *iseq)
diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs
index 91de6dcd8d..dd167e9eb0 100644
--- a/zjit/bindgen/src/main.rs
+++ b/zjit/bindgen/src/main.rs
@@ -335,6 +335,7 @@ fn main() {
.allowlist_function("rb_zjit_get_page_size")
.allowlist_function("rb_zjit_iseq_builtin_attrs")
.allowlist_function("rb_zjit_iseq_inspect")
+ .allowlist_function("rb_zjit_iseq_insn_set")
.allowlist_function("rb_set_cfp_(pc|sp)")
.allowlist_function("rb_c_method_tracing_currently_enabled")
.allowlist_function("rb_full_cfunc_return")
diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs
index 6ba13f9b27..44e544e972 100644
--- a/zjit/src/cruby_bindings.inc.rs
+++ b/zjit/src/cruby_bindings.inc.rs
@@ -929,6 +929,11 @@ unsafe extern "C" {
file: *const ::std::os::raw::c_char,
line: ::std::os::raw::c_int,
);
+ pub fn rb_zjit_iseq_insn_set(
+ iseq: *const rb_iseq_t,
+ insn_idx: ::std::os::raw::c_uint,
+ bare_insn: ruby_vminsn_type,
+ );
pub fn rb_iseq_get_zjit_payload(iseq: *const rb_iseq_t) -> *mut ::std::os::raw::c_void;
pub fn rb_iseq_set_zjit_payload(iseq: *const rb_iseq_t, payload: *mut ::std::os::raw::c_void);
pub fn rb_zjit_print_exception();
diff --git a/zjit/src/options.rs b/zjit/src/options.rs
index 476640d11f..68bbca07a4 100644
--- a/zjit/src/options.rs
+++ b/zjit/src/options.rs
@@ -17,7 +17,7 @@ pub static mut rb_zjit_call_threshold: u64 = 2;
#[derive(Clone, Copy, Debug)]
pub struct Options {
/// Number of times YARV instructions should be profiled.
- pub num_profiles: u64,
+ pub num_profiles: u8,
/// Enable debug logging
pub debug: bool,
@@ -51,7 +51,7 @@ pub fn init_options() -> Options {
/// Note that --help allows only 80 chars per line, including indentation. 80-char limit --> |
pub const ZJIT_OPTIONS: &'static [(&str, &str)] = &[
("--zjit-call-threshold=num", "Number of calls to trigger JIT (default: 2)."),
- ("--zjit-num-profiles=num", "Number of profiled calls before JIT (default: 1)."),
+ ("--zjit-num-profiles=num", "Number of profiled calls before JIT (default: 1, max: 255)."),
];
#[derive(Clone, Copy, Debug)]
@@ -158,7 +158,7 @@ fn update_profile_threshold(options: &Options) {
rb_zjit_profile_threshold = 0;
} else {
// Otherwise, profile instructions at least once.
- rb_zjit_profile_threshold = rb_zjit_call_threshold.saturating_sub(options.num_profiles).max(1);
+ rb_zjit_profile_threshold = rb_zjit_call_threshold.saturating_sub(options.num_profiles as u64).max(1);
}
}
}
diff --git a/zjit/src/profile.rs b/zjit/src/profile.rs
index de1455f788..2d0c7a4b9d 100644
--- a/zjit/src/profile.rs
+++ b/zjit/src/profile.rs
@@ -1,7 +1,7 @@
// We use the YARV bytecode constants which have a CRuby-style name
#![allow(non_upper_case_globals)]
-use crate::{cruby::*, gc::get_or_create_iseq_payload, hir_type::{types::{Empty, Fixnum}, Type}};
+use crate::{cruby::*, gc::get_or_create_iseq_payload, hir_type::{types::{Empty, Fixnum}, Type}, options::get_option};
/// Ephemeral state for profiling runtime information
struct Profiler {
@@ -38,43 +38,49 @@ impl Profiler {
/// API called from zjit_* instruction. opcode is the bare (non-zjit_*) instruction.
#[unsafe(no_mangle)]
-pub extern "C" fn rb_zjit_profile_insn(opcode: ruby_vminsn_type, ec: EcPtr) {
+pub extern "C" fn rb_zjit_profile_insn(bare_opcode: ruby_vminsn_type, ec: EcPtr) {
with_vm_lock(src_loc!(), || {
let mut profiler = Profiler::new(ec);
- profile_insn(&mut profiler, opcode);
+ profile_insn(&mut profiler, bare_opcode);
});
}
/// Profile a YARV instruction
-fn profile_insn(profiler: &mut Profiler, opcode: ruby_vminsn_type) {
- match opcode {
- YARVINSN_opt_nil_p => profile_operands(profiler, 1),
- YARVINSN_opt_plus => profile_operands(profiler, 2),
- YARVINSN_opt_minus => profile_operands(profiler, 2),
- YARVINSN_opt_mult => profile_operands(profiler, 2),
- YARVINSN_opt_div => profile_operands(profiler, 2),
- YARVINSN_opt_mod => profile_operands(profiler, 2),
- YARVINSN_opt_eq => profile_operands(profiler, 2),
- YARVINSN_opt_neq => profile_operands(profiler, 2),
- YARVINSN_opt_lt => profile_operands(profiler, 2),
- YARVINSN_opt_le => profile_operands(profiler, 2),
- YARVINSN_opt_gt => profile_operands(profiler, 2),
- YARVINSN_opt_ge => profile_operands(profiler, 2),
- YARVINSN_opt_and => profile_operands(profiler, 2),
- YARVINSN_opt_or => profile_operands(profiler, 2),
+fn profile_insn(profiler: &mut Profiler, bare_opcode: ruby_vminsn_type) {
+ let profile = &mut get_or_create_iseq_payload(profiler.iseq).profile;
+ match bare_opcode {
+ YARVINSN_opt_nil_p => profile_operands(profiler, profile, 1),
+ YARVINSN_opt_plus => profile_operands(profiler, profile, 2),
+ YARVINSN_opt_minus => profile_operands(profiler, profile, 2),
+ YARVINSN_opt_mult => profile_operands(profiler, profile, 2),
+ YARVINSN_opt_div => profile_operands(profiler, profile, 2),
+ YARVINSN_opt_mod => profile_operands(profiler, profile, 2),
+ YARVINSN_opt_eq => profile_operands(profiler, profile, 2),
+ YARVINSN_opt_neq => profile_operands(profiler, profile, 2),
+ YARVINSN_opt_lt => profile_operands(profiler, profile, 2),
+ YARVINSN_opt_le => profile_operands(profiler, profile, 2),
+ YARVINSN_opt_gt => profile_operands(profiler, profile, 2),
+ YARVINSN_opt_ge => profile_operands(profiler, profile, 2),
+ YARVINSN_opt_and => profile_operands(profiler, profile, 2),
+ YARVINSN_opt_or => profile_operands(profiler, profile, 2),
YARVINSN_opt_send_without_block => {
let cd: *const rb_call_data = profiler.insn_opnd(0).as_ptr();
let argc = unsafe { vm_ci_argc((*cd).ci) };
// Profile all the arguments and self (+1).
- profile_operands(profiler, (argc + 1) as usize);
+ profile_operands(profiler, profile, (argc + 1) as usize);
}
_ => {}
}
+
+ // Once we profile the instruction num_profiles times, we stop profiling it.
+ profile.num_profiles[profiler.insn_idx] = profile.num_profiles[profiler.insn_idx].saturating_add(1);
+ if profile.num_profiles[profiler.insn_idx] == get_option!(num_profiles) {
+ unsafe { rb_zjit_iseq_insn_set(profiler.iseq, profiler.insn_idx as u32, bare_opcode); }
+ }
}
/// Profile the Type of top-`n` stack operands
-fn profile_operands(profiler: &mut Profiler, n: usize) {
- let profile = &mut get_or_create_iseq_payload(profiler.iseq).profile;
+fn profile_operands(profiler: &mut Profiler, profile: &mut IseqProfile, n: usize) {
let types = &mut profile.opnd_types[profiler.insn_idx];
if types.len() <= n {
types.resize(n, Empty);
@@ -89,11 +95,17 @@ fn profile_operands(profiler: &mut Profiler, n: usize) {
pub struct IseqProfile {
/// Type information of YARV instruction operands, indexed by the instruction index
opnd_types: Vec<Vec<Type>>,
+
+ /// Number of profiled executions for each YARV instruction, indexed by the instruction index
+ num_profiles: Vec<u8>,
}
impl IseqProfile {
pub fn new(iseq_size: u32) -> Self {
- Self { opnd_types: vec![vec![]; iseq_size as usize] }
+ Self {
+ opnd_types: vec![vec![]; iseq_size as usize],
+ num_profiles: vec![0; iseq_size as usize],
+ }
}
/// Get profiled operand types for a given instruction index
close