diff options
| author | Max Bernstein <rubybugs@bernsteinbear.com> | 2026-02-10 13:47:52 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-02-10 13:47:52 -0500 |
| commit | c272297e8a9f2b8034739b915707910b4e568479 (patch) | |
| tree | 0a04bf0177809211946d246111abffc79dcac588 | |
| parent | 09ae95332f34791c68c841f8ad41b3f3ff9e8609 (diff) | |
## Summary
- Replace the RPO fixed-point iteration in `infer_types` with a worklist-based approach
- Instead of re-scanning every reachable block on each iteration, only re-process blocks whose incoming types actually changed
- Add `BitSet::remove` helper for worklist membership tracking
## Benchmark
On lobsters (release+stats build, 10 iterations):
| Stat | Branch | Master | Delta |
|------|--------|--------|-------|
| compile_time | 1,604ms | 1,808ms | -204ms (-11.3%) |
| Avg iter time | 766ms | 807ms | -41ms (-5.1%) |
| -rw-r--r-- | zjit/src/bitset.rs | 10 | ||||
| -rw-r--r-- | zjit/src/hir.rs | 103 |
2 files changed, 68 insertions, 45 deletions
diff --git a/zjit/src/bitset.rs b/zjit/src/bitset.rs index b5b69abdee..349cc84a30 100644 --- a/zjit/src/bitset.rs +++ b/zjit/src/bitset.rs @@ -37,6 +37,16 @@ impl<T: Into<usize> + Copy> BitSet<T> { } } + /// Clear a bit. Returns whether the bit was previously set. + pub fn remove(&mut self, idx: T) -> bool { + debug_assert!(idx.into() < self.num_bits); + let entry_idx = idx.into() / ENTRY_NUM_BITS; + let bit_idx = idx.into() % ENTRY_NUM_BITS; + let was_set = (self.entries[entry_idx] & (1 << bit_idx)) != 0; + self.entries[entry_idx] &= !(1 << bit_idx); + was_set + } + pub fn get(&self, idx: T) -> bool { debug_assert!(idx.into() < self.num_bits); let entry_idx = idx.into() / ENTRY_NUM_BITS; diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index b6dcbd67c2..579c327d44 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2610,60 +2610,73 @@ impl Function { self.copy_param_types(); let mut reachable = BlockSet::with_capacity(self.blocks.len()); + + // Maintain both a worklist and a fast membership check to avoid linear search + let mut worklist: VecDeque<BlockId> = VecDeque::with_capacity(self.blocks.len()); + let mut in_worklist = BlockSet::with_capacity(self.blocks.len()); + macro_rules! worklist_add { + ($block:expr) => { + if in_worklist.insert($block) { + worklist.push_back($block); + } + }; + } + for entry_block in self.entry_blocks() { reachable.insert(entry_block); + worklist_add!(entry_block); + } + + // Helper to propagate types along a branch edge and enqueue the target if anything changed + macro_rules! enqueue { + ($self:ident, $target:expr) => { + let newly_reachable = reachable.insert($target.target); + let mut target_changed = newly_reachable; + for (idx, arg) in $target.args.iter().enumerate() { + let param = $self.blocks[$target.target.0].params[idx]; + let new = self.insn_types[param.0].union(self.insn_types[arg.0]); + if !self.insn_types[param.0].bit_equal(new) { + self.insn_types[param.0] = new; + target_changed = true; + } + } + if target_changed { + worklist_add!($target.target); + } + }; } - // Walk the graph, computing types until fixpoint - let rpo = self.rpo(); - loop { - let mut changed = false; - for &block in &rpo { - if !reachable.get(block) { continue; } - for insn_id in &self.blocks[block.0].insns { - let insn_type = match self.find(*insn_id) { - Insn::IfTrue { val, target: BranchEdge { target, args } } => { - assert!(!self.type_of(val).bit_equal(types::Empty)); - if self.type_of(val).could_be(Type::from_cbool(true)) { - reachable.insert(target); - for (idx, arg) in args.iter().enumerate() { - let param = self.blocks[target.0].params[idx]; - self.insn_types[param.0] = self.type_of(param).union(self.type_of(*arg)); - } - } - continue; - } - Insn::IfFalse { val, target: BranchEdge { target, args } } => { - assert!(!self.type_of(val).bit_equal(types::Empty)); - if self.type_of(val).could_be(Type::from_cbool(false)) { - reachable.insert(target); - for (idx, arg) in args.iter().enumerate() { - let param = self.blocks[target.0].params[idx]; - self.insn_types[param.0] = self.type_of(param).union(self.type_of(*arg)); - } - } - continue; + // Walk the graph, computing types until worklist is empty + while let Some(block) = worklist.pop_front() { + in_worklist.remove(block); + if !reachable.get(block) { continue; } + for insn_id in &self.blocks[block.0].insns { + let insn_type = match self.find(*insn_id) { + Insn::IfTrue { val, target } => { + assert!(!self.type_of(val).bit_equal(types::Empty)); + if self.type_of(val).could_be(Type::from_cbool(true)) { + enqueue!(self, target); } - Insn::Jump(BranchEdge { target, args }) => { - reachable.insert(target); - for (idx, arg) in args.iter().enumerate() { - let param = self.blocks[target.0].params[idx]; - self.insn_types[param.0] = self.type_of(param).union(self.type_of(*arg)); - } - continue; + continue; + } + Insn::IfFalse { val, target } => { + assert!(!self.type_of(val).bit_equal(types::Empty)); + if self.type_of(val).could_be(Type::from_cbool(false)) { + enqueue!(self, target); } - insn if insn.has_output() => self.infer_type(*insn_id), - _ => continue, - }; - if !self.type_of(*insn_id).bit_equal(insn_type) { - self.insn_types[insn_id.0] = insn_type; - changed = true; + continue; } + Insn::Jump(target) => { + enqueue!(self, target); + continue; + } + insn if insn.has_output() => self.infer_type(*insn_id), + _ => continue, + }; + if !self.type_of(*insn_id).bit_equal(insn_type) { + self.insn_types[insn_id.0] = insn_type; } } - if !changed { - break; - } } } |
