Skip to content

Commit 85bc316

Browse files
committed
ZJIT: globally remove duplicate CheckInterrupts
This also required updating the effects of Jump and CondBranch, since `effects::Any` includes `InterruptFlag` (meaning the optimization would never actually work across blocks). ``` ruby-master-zjit: ruby 4.1.0dev (2026-09-17T20:29:33Z master aca98ee) +ZJIT +PRISM [arm64-darwin25] ruby-dev-zjit: ruby 4.1.0dev (2026-09-17T21:09:40Z hm-ukpqtuorkpnknmos 203f9cdd52) +ZJIT +PRISM [arm64-darwin25] last_commit=ZJIT: globally remove duplicate CheckInterrupts ---------- --------------------- ------------------ --------------------- ------------------------------ bench ruby-master-zjit (ms) ruby-dev-zjit (ms) ruby-dev-zjit 1st itr ruby-master-zjit/ruby-dev-zjit 30k_ifelse 44.7 ± 2.8% 42.5 ± 2.7% 1.045 1.050 ---------- --------------------- ------------------ --------------------- ------------------------------ ```
1 parent 7525ed0 commit 85bc316

2 files changed

Lines changed: 60 additions & 29 deletions

File tree

‎zjit/src/hir.rs‎

Lines changed: 60 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1846,8 +1846,8 @@ impl Insn {
18461846
Insn::IsBlockParamModified { .. } => effects::Empty,
18471847
Insn::GetBlockParam { .. } => effects::Any,
18481848
Insn::Snapshot { .. } => effects::Empty,
1849-
Insn::Jump(_) => effects::Any,
1850-
Insn::CondBranch { .. } => effects::Any,
1849+
Insn::Jump(_) => effects::Control,
1850+
Insn::CondBranch { .. } => effects::Control,
18511851
Insn::CCall { elidable, .. } => {
18521852
if *elidable {
18531853
Effect::write(abstract_heaps::Allocator)
@@ -7132,26 +7132,67 @@ impl Function {
71327132
}
71337133
}
71347134

7135-
/// Remove duplicate CheckInterrupts instructions within each basic block.
7136-
/// Only the last CheckInterrupts in a block is needed unless an intervening
7137-
/// instruction writes to InterruptFlag (e.g. a call), which resets tracking.
7135+
/// Remove duplicate CheckInterrupts instructions globally.
71387136
fn remove_duplicate_check_interrupts(&mut self) {
7139-
for block_id in self.reverse_post_order() {
7140-
let mut seen = false;
7141-
let insns = std::mem::take(&mut self.blocks[block_id].insns);
7142-
let mut new_insns = Vec::with_capacity(insns.len());
7143-
for insn_id in insns.into_iter().rev() {
7144-
let insn = &self.insns[insn_id];
7145-
if matches!(insn, Insn::CheckInterrupts { .. }) {
7146-
if seen { continue; }
7147-
seen = true;
7148-
} else if insn.effects_of().write_bits().overlaps(abstract_heaps::InterruptFlag) {
7149-
seen = false;
7137+
let cfi = ControlFlowInfo::new(self);
7138+
let rpo = cfi.reverse_post_order();
7139+
7140+
let rpo_index = {
7141+
let mut order = vec![usize::MAX; self.blocks.len()];
7142+
for (i, &block_id) in rpo.iter().enumerate() {
7143+
order[block_id] = i;
7144+
}
7145+
order
7146+
};
7147+
7148+
// Start by assuming all blocks are unchecked.
7149+
let mut checked = vec![false; self.blocks.len()];
7150+
7151+
// Iterate until fixpoint: when no CheckInterrupts instruction is removed, and the checked
7152+
// status of all blocks stays the same.
7153+
let mut changed = true;
7154+
while changed {
7155+
changed = false;
7156+
7157+
for &block_id in rpo.iter().rev() {
7158+
let successors = cfi.successors(block_id);
7159+
7160+
// A block can initially be considered checked if
7161+
// - it has successors
7162+
// - all successors have been checked
7163+
// - none of the successors are back edges (i.e. have a lower RPO index than the current block)
7164+
let mut block_checked = !successors.is_empty()
7165+
&& successors.iter().all(|&succ| checked[succ] && !(rpo_index[succ] < rpo_index[block_id]));
7166+
7167+
let insns = std::mem::take(&mut self.blocks[block_id].insns);
7168+
let mut new_insns = Vec::with_capacity(insns.len());
7169+
7170+
// Only the last CheckInterrupts in a block is needed unless an intervening
7171+
// instruction writes to InterruptFlag (e.g. a call), which resets tracking.
7172+
// Checking as late as possible keeps fewer values live in the check's frame state,
7173+
// which lets other passes elide more.
7174+
for insn_id in insns.into_iter().rev() {
7175+
let insn = &self.insns[insn_id];
7176+
if matches!(insn, Insn::CheckInterrupts { .. }) {
7177+
if block_checked {
7178+
changed = true;
7179+
continue;
7180+
}
7181+
block_checked = true;
7182+
} else if insn.effects_of().write_bits().overlaps(abstract_heaps::InterruptFlag) {
7183+
block_checked = false;
7184+
}
7185+
new_insns.push(insn_id);
7186+
}
7187+
7188+
new_insns.reverse();
7189+
self.blocks[block_id].insns = new_insns;
7190+
7191+
if checked[block_id] != block_checked {
7192+
changed = true;
7193+
checked[block_id] = block_checked;
71507194
}
7151-
new_insns.push(insn_id);
71527195
}
7153-
new_insns.reverse();
7154-
self.blocks[block_id].insns = new_insns;
71557196
}
71567197
}
71577198

‎zjit/src/hir/opt_tests.rs‎

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11580,11 +11580,9 @@ mod hir_opt_tests {
1158011580
Jump bb13(v54, v27)
1158111581
bb13(v44:BasicObject, v45:BasicObject):
1158211582
v57:BasicObject = Send v44, :call # SendFallbackReason: Send: no profile data available
11583-
CheckInterrupts
1158411583
Jump bb4(v57)
1158511584
bb6():
1158611585
v66:Fixnum[42] = Const Value(42)
11587-
CheckInterrupts
1158811586
Jump bb4(v66)
1158911587
bb4(v72:BasicObject):
1159011588
PopInlineFrame
@@ -15246,7 +15244,6 @@ mod hir_opt_tests {
1524615244
v48:BasicObject = ArrayAref v19, v47
1524715245
v49:ArrayExact = NewArray v48
1524815246
PushInlineFrame :target, v25 (0x1040), num_args=1
15249-
CheckInterrupts
1525015247
PopInlineFrame
1525115248
Jump bb4(v49)
1525215249
bb6():
@@ -15264,7 +15261,6 @@ mod hir_opt_tests {
1526415261
v59:BasicObject = ArrayAref v19, v58
1526515262
v60:ArrayExact = NewArray v59
1526615263
PushInlineFrame :target, v31 (0x1090), num_args=1
15267-
CheckInterrupts
1526815264
PopInlineFrame
1526915265
Jump bb4(v60)
1527015266
bb8():
@@ -21611,10 +21607,8 @@ mod hir_opt_tests {
2161121607
CondBranch v37, bb7(), bb6()
2161221608
bb7():
2161321609
v42:Fixnum[0] = Const Value(0)
21614-
CheckInterrupts
2161521610
Jump bb4(v42)
2161621611
bb6():
21617-
CheckInterrupts
2161821612
Jump bb4(v62)
2161921613
bb4(v56:Fixnum):
2162021614
PopInlineFrame
@@ -22950,7 +22944,6 @@ mod hir_opt_tests {
2295022944
StoreField v95, :@y@0x108c, v49
2295122945
v172:CShape[0x108d] = Const CShape(0x108d)
2295222946
StoreField v95, :shape_id@0x1088, v172
22953-
CheckInterrupts
2295422947
PopInlineFrame
2295522948
PatchPoint MethodRedefined(Point@0x1008, ==@0x1098, cme:0x10a0)
2295622949
PushInlineFrame :==, v85 (0x10c8), num_args=1
@@ -23371,7 +23364,6 @@ mod hir_opt_tests {
2337123364
v59:CInt64[-4] = Const CInt64(-4)
2337223365
v60:CInt64 = IntAnd v58, v59
2337323366
v61:BasicObject = InvokeBlockIseqDirect (0x1068), v60
23374-
CheckInterrupts
2337523367
PopInlineFrame
2337623368
Jump bb4(v61)
2337723369
bb6():
@@ -23387,7 +23379,6 @@ mod hir_opt_tests {
2338723379
v77:CInt64[-4] = Const CInt64(-4)
2338823380
v78:CInt64 = IntAnd v76, v77
2338923381
v79:BasicObject = InvokeBlockIseqDirect (0x1068), v78
23390-
CheckInterrupts
2339123382
PopInlineFrame
2339223383
Jump bb4(v79)
2339323384
bb8():
@@ -23403,7 +23394,6 @@ mod hir_opt_tests {
2340323394
v95:CInt64[-4] = Const CInt64(-4)
2340423395
v96:CInt64 = IntAnd v94, v95
2340523396
v97:BasicObject = InvokeBlockIseqDirect (0x1068), v96
23406-
CheckInterrupts
2340723397
PopInlineFrame
2340823398
Jump bb4(v97)
2340923399
bb10():

0 commit comments

Comments
 (0)