Skip to content

Commit 98e55e7

Browse files
authored
ZJIT: Add --zjit-max-versions option (ruby#16607)
Allow configuring the maximum number of compiled versions per ISEQ via --zjit-max-versions=num (default: 2). Like YJIT's equivalent, this option is hidden from --help output.
1 parent d368d42 commit 98e55e7

4 files changed

Lines changed: 25 additions & 9 deletions

File tree

zjit/src/codegen.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,15 @@ use crate::hir_type::{types, Type};
2626
use crate::options::{get_option, PerfMap};
2727
use crate::cast::IntoUsize;
2828

29-
/// At the moment, we support recompiling each ISEQ only once.
30-
pub const MAX_ISEQ_VERSIONS: usize = 2;
29+
/// Default maximum number of compiled versions per ISEQ.
30+
const DEFAULT_MAX_VERSIONS: usize = 2;
31+
32+
/// Maximum number of compiled versions per ISEQ.
33+
/// Configurable via --zjit-max-versions (default: 2).
34+
pub fn max_iseq_versions() -> usize {
35+
unsafe { crate::options::OPTIONS.as_ref() }
36+
.map_or(DEFAULT_MAX_VERSIONS, |opts| opts.max_versions)
37+
}
3138

3239
/// Sentinel program counter stored in C frames when runtime checks are enabled.
3340
const PC_POISON: Option<*const VALUE> = if cfg!(feature = "runtime_checks") {
@@ -216,7 +223,7 @@ fn gen_iseq_entry_point(cb: &mut CodeBlock, iseq: IseqPtr, jit_exception: bool)
216223
pub fn invalidate_iseq_version(cb: &mut CodeBlock, iseq: IseqPtr, version: &mut IseqVersionRef) {
217224
let payload = get_or_create_iseq_payload(iseq);
218225
if unsafe { version.as_ref() }.status != IseqStatus::Invalidated
219-
&& payload.versions.len() < MAX_ISEQ_VERSIONS
226+
&& payload.versions.len() < max_iseq_versions()
220227
{
221228
unsafe { version.as_mut() }.status = IseqStatus::Invalidated;
222229
unsafe { rb_iseq_reset_jit_func(iseq) };
@@ -300,8 +307,8 @@ fn gen_iseq(cb: &mut CodeBlock, iseq: IseqPtr, function: Option<&Function>) -> R
300307
Some(IseqStatus::CantCompile(err)) => return Err(err.clone()),
301308
_ => {},
302309
}
303-
// If the ISEQ already hax MAX_ISEQ_VERSIONS, do not compile a new version.
304-
if payload.versions.len() == MAX_ISEQ_VERSIONS {
310+
// If the ISEQ already has max versions, do not compile a new version.
311+
if payload.versions.len() >= max_iseq_versions() {
305312
return Err(CompileError::IseqVersionLimitReached);
306313
}
307314

zjit/src/codegen_tests.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use super::{gen_insn, JITState};
44
use crate::asm::CodeBlock;
55
use crate::backend::lir::Assembler;
6-
use crate::codegen::MAX_ISEQ_VERSIONS;
6+
use crate::codegen::max_iseq_versions;
77
use crate::cruby::*;
88
use crate::hir::{Insn, iseq_to_hir};
99
use crate::options::{rb_zjit_prepare_options, set_call_threshold};
@@ -4937,13 +4937,14 @@ fn test_invokesuper_with_local_written_by_blockiseq() {
49374937

49384938
#[test]
49394939
fn test_max_iseq_versions() {
4940+
let max_versions = max_iseq_versions();
49404941
eval(&format!("
49414942
TEST = -1
49424943
def test = TEST
49434944
49444945
# compile and invalidate MAX+1 times
49454946
i = 0
4946-
while i < {MAX_ISEQ_VERSIONS} + 1
4947+
while i < {max_versions} + 1
49474948
test; test # compile a version
49484949
49494950
Object.send(:remove_const, :TEST)
@@ -4956,7 +4957,7 @@ fn test_max_iseq_versions() {
49564957
// It should not exceed MAX_ISEQ_VERSIONS
49574958
let iseq = get_method_iseq("self", "test");
49584959
let payload = get_or_create_iseq_payload(iseq);
4959-
assert_eq!(payload.versions.len(), MAX_ISEQ_VERSIONS);
4960+
assert_eq!(payload.versions.len(), max_iseq_versions());
49604961

49614962
// The last call should not discard the JIT code
49624963
assert!(matches!(unsafe { payload.versions.last().unwrap().as_ref() }.status, IseqStatus::Compiled(_)));

zjit/src/hir.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5129,7 +5129,7 @@ impl Function {
51295129
// SideExits would just add overhead (the exit fires every time without benefit).
51305130
// Keep them as Send fallbacks so the interpreter handles them directly.
51315131
let payload = get_or_create_iseq_payload(self.iseq);
5132-
if payload.versions.len() + 1 >= crate::codegen::MAX_ISEQ_VERSIONS {
5132+
if payload.versions.len() + 1 >= crate::codegen::max_iseq_versions() {
51335133
return;
51345134
}
51355135
for block in self.rpo() {

zjit/src/options.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ pub struct Options {
105105

106106
/// Path to a file where compiled ISEQs will be saved.
107107
pub log_compiled_iseqs: Option<std::path::PathBuf>,
108+
109+
/// Maximum number of versions per ISEQ
110+
pub max_versions: usize,
108111
}
109112

110113
impl Default for Options {
@@ -130,6 +133,7 @@ impl Default for Options {
130133
perf: None,
131134
allowed_iseqs: None,
132135
log_compiled_iseqs: None,
136+
max_versions: 2,
133137
}
134138
}
135139
}
@@ -340,6 +344,10 @@ fn parse_option(str_ptr: *const std::os::raw::c_char) -> Option<()> {
340344
Err(_) => return None,
341345
},
342346

347+
("max-versions", _) => match opt_val.parse() {
348+
Ok(n) => options.max_versions = n,
349+
Err(_) => return None,
350+
},
343351

344352
("stats-quiet", _) => {
345353
options.stats = true;

0 commit comments

Comments
 (0)