diff options
author | Takashi Kokubun <[email protected]> | 2025-07-16 09:53:10 -0700 |
---|---|---|
committer | GitHub <[email protected]> | 2025-07-16 09:53:10 -0700 |
commit | acc317253043efc65e87b460de48dc4e50c87c59 (patch) | |
tree | e758e4909154184904a81f3dc8a5aa582098b1be | |
parent | af1ad78bffe78f70e649e60ed3ab016c8c6cef05 (diff) |
* ZJIT: Profile each instruction at most num_profiles times
* Use saturating_add for num_profiles
-rw-r--r-- | zjit.c | 12 | ||||
-rw-r--r-- | zjit/bindgen/src/main.rs | 1 | ||||
-rw-r--r-- | zjit/src/cruby_bindings.inc.rs | 5 | ||||
-rw-r--r-- | zjit/src/options.rs | 6 | ||||
-rw-r--r-- | zjit/src/profile.rs | 58 |
5 files changed, 56 insertions, 26 deletions
@@ -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 |