Skip to content

Commit 0d27eab

Browse files
committed
ZJIT: Expose --zjit-recompile-cap option
Replace the hardcoded MAX_GLOBAL_RECOMPILATIONS constant with a configurable --zjit-recompile-cap CLI option (default: 50, 0=unlimited). This limits how many ISEQs can be recompiled per process, preventing code size inflation on large workloads where many ISEQs hit the side-exit threshold.
1 parent d8d0484 commit 0d27eab

2 files changed

Lines changed: 20 additions & 4 deletions

File tree

zjit/src/codegen.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ pub extern "C" fn rb_zjit_count_side_exit(payload_raw: *mut std::ffi::c_void) {
5151
}
5252

5353
static GLOBAL_RECOMPILE_COUNT: AtomicU64 = AtomicU64::new(0);
54-
const MAX_GLOBAL_RECOMPILATIONS: u64 = 50;
5554

5655
/// Escalating threshold for deferred re-profiling. Higher deferral levels
5756
/// give cold branches progressively more time to warm up.
@@ -66,10 +65,13 @@ fn deferred_threshold(defer_count: u32) -> u32 {
6665
/// When `preserve_profiles` is true, only counters are reset (type distributions survive).
6766
/// When false, both counters and type distributions are cleared.
6867
fn trigger_recompilation(payload_raw: *mut std::ffi::c_void, iseq: IseqPtr, preserve_profiles: bool) {
69-
if MAX_GLOBAL_RECOMPILATIONS > 0 {
68+
// Global recompilation cap: prevent code size inflation and overhead from
69+
// too many ISEQs recompiling. Configurable via --zjit-recompile-cap=num.
70+
let cap = get_option!(recompile_cap);
71+
if cap > 0 {
7072
let prev = GLOBAL_RECOMPILE_COUNT.fetch_add(1, Ordering::Relaxed);
71-
if prev >= MAX_GLOBAL_RECOMPILATIONS {
72-
GLOBAL_RECOMPILE_COUNT.fetch_sub(1, Ordering::Relaxed);
73+
if prev >= cap {
74+
GLOBAL_RECOMPILE_COUNT.fetch_sub(1, Ordering::Relaxed); // undo increment
7375
return;
7476
}
7577
}

zjit/src/options.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ pub struct Options {
103103
/// Number of side exits before triggering recompilation of an ISEQ.
104104
/// Set to 0 to disable recompilation.
105105
pub recompile_threshold: u64,
106+
107+
/// Maximum number of ISEQs that can be recompiled per process.
108+
/// Limits code size inflation for large workloads. Set to 0 for unlimited.
109+
pub recompile_cap: u64,
106110
}
107111

108112
impl Default for Options {
@@ -129,6 +133,7 @@ impl Default for Options {
129133
allowed_iseqs: None,
130134
log_compiled_iseqs: None,
131135
recompile_threshold: DEFAULT_RECOMPILE_THRESHOLD,
136+
recompile_cap: 50,
132137
}
133138
}
134139
}
@@ -154,6 +159,8 @@ pub const ZJIT_OPTIONS: &[(&str, &str)] = &[
154159
"Log compiled ISEQs to the file. The file will be truncated."),
155160
("--zjit-recompile-threshold=num",
156161
"Side exits to trigger recompile (default: 100, 0=off)."),
162+
("--zjit-recompile-cap=num",
163+
"Max ISEQs to recompile (default: 50, 0=unlimited)."),
157164
("--zjit-trace-exits[=counter]",
158165
"Record source on side-exit. `Counter` picks specific counter."),
159166
("--zjit-trace-exits-sample-rate=num",
@@ -348,6 +355,13 @@ fn parse_option(str_ptr: *const std::os::raw::c_char) -> Option<()> {
348355
Err(_) => return None,
349356
},
350357

358+
("recompile-cap", _) => match opt_val.parse() {
359+
Ok(n) => {
360+
options.recompile_cap = n;
361+
}
362+
Err(_) => return None,
363+
},
364+
351365
("stats-quiet", _) => {
352366
options.stats = true;
353367
options.print_stats = false;

0 commit comments

Comments
 (0)