Skip to content

Commit 0cf128d

Browse files
committed
ZJIT: Use worklist algorithm for type inference
Replace the RPO fixed-point iteration with a worklist-based approach. Instead of re-scanning every reachable block on each iteration, only re-process blocks whose incoming types actually changed. This reduces compile time on lobsters by ~11% (1,604ms vs 1,808ms).
1 parent 3d00e34 commit 0cf128d

File tree

2 files changed

+69
-45
lines changed

2 files changed

+69
-45
lines changed

zjit/src/bitset.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,16 @@ impl<T: Into<usize> + Copy> BitSet<T> {
3737
}
3838
}
3939

40+
/// Clear a bit. Returns whether the bit was previously set.
41+
pub fn remove(&mut self, idx: T) -> bool {
42+
debug_assert!(idx.into() < self.num_bits);
43+
let entry_idx = idx.into() / ENTRY_NUM_BITS;
44+
let bit_idx = idx.into() % ENTRY_NUM_BITS;
45+
let was_set = (self.entries[entry_idx] & (1 << bit_idx)) != 0;
46+
self.entries[entry_idx] &= !(1 << bit_idx);
47+
was_set
48+
}
49+
4050
pub fn get(&self, idx: T) -> bool {
4151
debug_assert!(idx.into() < self.num_bits);
4252
let entry_idx = idx.into() / ENTRY_NUM_BITS;

zjit/src/hir.rs

Lines changed: 59 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2603,60 +2603,74 @@ impl Function {
26032603
self.copy_param_types();
26042604

26052605
let mut reachable = BlockSet::with_capacity(self.blocks.len());
2606+
2607+
// Maintain both a worklist and a fast membership check to avoid linear search
2608+
let mut worklist: VecDeque<BlockId> = VecDeque::new();
2609+
let mut in_worklist = BlockSet::with_capacity(self.blocks.len());
2610+
macro_rules! worklist_add {
2611+
($block:expr) => {
2612+
if in_worklist.insert($block) {
2613+
worklist.push_back($block);
2614+
}
2615+
};
2616+
}
2617+
26062618
for entry_block in self.entry_blocks() {
26072619
reachable.insert(entry_block);
2620+
worklist_add!(entry_block);
2621+
}
2622+
2623+
// Helper to propagate types along a branch edge and enqueue the target if anything changed
2624+
macro_rules! enqueue {
2625+
($self:ident, $target:expr) => {
2626+
let newly_reachable = reachable.insert($target.target);
2627+
let mut target_changed = newly_reachable;
2628+
for (idx, arg) in $target.args.iter().enumerate() {
2629+
let param = $self.blocks[$target.target.0].params[idx];
2630+
let new = self.insn_types[param.0].union(self.insn_types[arg.0]);
2631+
if !self.insn_types[param.0].bit_equal(new) {
2632+
self.insn_types[param.0] = new;
2633+
target_changed = true;
2634+
}
2635+
}
2636+
if target_changed {
2637+
worklist_add!($target.target);
2638+
}
2639+
};
26082640
}
26092641

2610-
// Walk the graph, computing types until fixpoint
2611-
let rpo = self.rpo();
2612-
loop {
2613-
let mut changed = false;
2614-
for &block in &rpo {
2615-
if !reachable.get(block) { continue; }
2616-
for insn_id in &self.blocks[block.0].insns {
2617-
let insn_type = match self.find(*insn_id) {
2618-
Insn::IfTrue { val, target: BranchEdge { target, args } } => {
2619-
assert!(!self.type_of(val).bit_equal(types::Empty));
2620-
if self.type_of(val).could_be(Type::from_cbool(true)) {
2621-
reachable.insert(target);
2622-
for (idx, arg) in args.iter().enumerate() {
2623-
let param = self.blocks[target.0].params[idx];
2624-
self.insn_types[param.0] = self.type_of(param).union(self.type_of(*arg));
2625-
}
2626-
}
2627-
continue;
2628-
}
2629-
Insn::IfFalse { val, target: BranchEdge { target, args } } => {
2630-
assert!(!self.type_of(val).bit_equal(types::Empty));
2631-
if self.type_of(val).could_be(Type::from_cbool(false)) {
2632-
reachable.insert(target);
2633-
for (idx, arg) in args.iter().enumerate() {
2634-
let param = self.blocks[target.0].params[idx];
2635-
self.insn_types[param.0] = self.type_of(param).union(self.type_of(*arg));
2636-
}
2637-
}
2638-
continue;
2642+
// Walk the graph, computing types until worklist is empty
2643+
while let Some(block) = worklist.pop_front() {
2644+
in_worklist.remove(block);
2645+
if !reachable.get(block) { continue; }
2646+
2647+
for insn_id in &self.blocks[block.0].insns {
2648+
let insn_type = match self.find(*insn_id) {
2649+
Insn::IfTrue { val, target } => {
2650+
assert!(!self.type_of(val).bit_equal(types::Empty));
2651+
if self.type_of(val).could_be(Type::from_cbool(true)) {
2652+
enqueue!(self, target);
26392653
}
2640-
Insn::Jump(BranchEdge { target, args }) => {
2641-
reachable.insert(target);
2642-
for (idx, arg) in args.iter().enumerate() {
2643-
let param = self.blocks[target.0].params[idx];
2644-
self.insn_types[param.0] = self.type_of(param).union(self.type_of(*arg));
2645-
}
2646-
continue;
2654+
continue;
2655+
}
2656+
Insn::IfFalse { val, target } => {
2657+
assert!(!self.type_of(val).bit_equal(types::Empty));
2658+
if self.type_of(val).could_be(Type::from_cbool(false)) {
2659+
enqueue!(self, target);
26472660
}
2648-
insn if insn.has_output() => self.infer_type(*insn_id),
2649-
_ => continue,
2650-
};
2651-
if !self.type_of(*insn_id).bit_equal(insn_type) {
2652-
self.insn_types[insn_id.0] = insn_type;
2653-
changed = true;
2661+
continue;
26542662
}
2663+
Insn::Jump(target) => {
2664+
enqueue!(self, target);
2665+
continue;
2666+
}
2667+
insn if insn.has_output() => self.infer_type(*insn_id),
2668+
_ => continue,
2669+
};
2670+
if !self.type_of(*insn_id).bit_equal(insn_type) {
2671+
self.insn_types[insn_id.0] = insn_type;
26552672
}
26562673
}
2657-
if !changed {
2658-
break;
2659-
}
26602674
}
26612675
}
26622676

0 commit comments

Comments
 (0)