Skip to content

Commit ffe2600

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 9720769 commit ffe2600

2 files changed

Lines changed: 61 additions & 29 deletions

File tree

‎zjit/src/hir.rs‎

Lines changed: 61 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1845,8 +1845,8 @@ impl Insn {
18451845
Insn::GetBlockParam { .. } => effects::Any,
18461846
Insn::SymToProc { .. } => effects::Any,
18471847
Insn::Snapshot { .. } => effects::Empty,
1848-
Insn::Jump(_) => effects::Any,
1849-
Insn::CondBranch { .. } => effects::Any,
1848+
Insn::Jump(_) => effects::Control,
1849+
Insn::CondBranch { .. } => effects::Control,
18501850
Insn::CCall { elidable, .. } => {
18511851
if *elidable {
18521852
Effect::write(abstract_heaps::Allocator)
@@ -7138,26 +7138,68 @@ impl Function {
71387138
}
71397139
}
71407140

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

‎zjit/src/hir/opt_tests.rs‎

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11963,11 +11963,9 @@ mod hir_opt_tests {
1196311963
Jump bb20(v100, v27)
1196411964
bb20(v67:BasicObject, v68:BasicObject):
1196511965
v103:BasicObject = Send v67, :call # SendFallbackReason: Send: no profile data available
11966-
CheckInterrupts
1196711966
Jump bb4(v103)
1196811967
bb6():
1196911968
v112:Fixnum[42] = Const Value(42)
11970-
CheckInterrupts
1197111969
Jump bb4(v112)
1197211970
bb4(v118:BasicObject):
1197311971
PopInlineFrame
@@ -15659,7 +15657,6 @@ mod hir_opt_tests {
1565915657
v48:BasicObject = ArrayAref v19, v47
1566015658
v49:ArrayExact = NewArray v48
1566115659
PushInlineFrame :target, v25 (0x1040), num_args=1
15662-
CheckInterrupts
1566315660
PopInlineFrame
1566415661
Jump bb4(v49)
1566515662
bb6():
@@ -15677,7 +15674,6 @@ mod hir_opt_tests {
1567715674
v59:BasicObject = ArrayAref v19, v58
1567815675
v60:ArrayExact = NewArray v59
1567915676
PushInlineFrame :target, v31 (0x1090), num_args=1
15680-
CheckInterrupts
1568115677
PopInlineFrame
1568215678
Jump bb4(v60)
1568315679
bb8():
@@ -22054,10 +22050,8 @@ mod hir_opt_tests {
2205422050
CondBranch v37, bb7(), bb6()
2205522051
bb7():
2205622052
v42:Fixnum[0] = Const Value(0)
22057-
CheckInterrupts
2205822053
Jump bb4(v42)
2205922054
bb6():
22060-
CheckInterrupts
2206122055
Jump bb4(v62)
2206222056
bb4(v56:Fixnum):
2206322057
PopInlineFrame
@@ -23453,7 +23447,6 @@ mod hir_opt_tests {
2345323447
StoreField v95, :@y@0x108c, v49
2345423448
v172:CShape[0x108d] = Const CShape(0x108d)
2345523449
StoreField v95, :shape_id@0x1088, v172
23456-
CheckInterrupts
2345723450
PopInlineFrame
2345823451
PatchPoint MethodRedefined(Point@0x1008, ==@0x1098, cme:0x10a0)
2345923452
PushInlineFrame :==, v85 (0x10c8), num_args=1
@@ -23874,7 +23867,6 @@ mod hir_opt_tests {
2387423867
v59:CInt64[-4] = Const CInt64(-4)
2387523868
v60:CInt64 = IntAnd v58, v59
2387623869
v61:BasicObject = InvokeBlockIseqDirect (0x1068), v60
23877-
CheckInterrupts
2387823870
PopInlineFrame
2387923871
Jump bb4(v61)
2388023872
bb6():
@@ -23890,7 +23882,6 @@ mod hir_opt_tests {
2389023882
v77:CInt64[-4] = Const CInt64(-4)
2389123883
v78:CInt64 = IntAnd v76, v77
2389223884
v79:BasicObject = InvokeBlockIseqDirect (0x1068), v78
23893-
CheckInterrupts
2389423885
PopInlineFrame
2389523886
Jump bb4(v79)
2389623887
bb8():
@@ -23906,7 +23897,6 @@ mod hir_opt_tests {
2390623897
v95:CInt64[-4] = Const CInt64(-4)
2390723898
v96:CInt64 = IntAnd v94, v95
2390823899
v97:BasicObject = InvokeBlockIseqDirect (0x1068), v96
23909-
CheckInterrupts
2391023900
PopInlineFrame
2391123901
Jump bb4(v97)
2391223902
bb10():

0 commit comments

Comments
 (0)