Skip to content

Commit b2cf20f

Browse files
authored
ZJIT: Read block successors by reference instead of cloning the terminator (ruby#18016)
`Function::successors` went through `find()`, which clones the entire terminator `Insn` on every call just to read its `BlockId` targets. Reverse-post-order traversal queries every block's successors many times over the course of optimization, so that clone occurred rather frequently. The new iterator reads the targets from a borrowed terminator and does not allocate at all. Switching this to an iterator also avoids allocating a `Vec` for callers that just want to iterate through the successors.
1 parent f54e5b4 commit b2cf20f

1 file changed

Lines changed: 26 additions & 16 deletions

File tree

zjit/src/hir.rs

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3008,21 +3008,31 @@ impl Function {
30083008
self.blocks.pop();
30093009
}
30103010

3011-
fn successors(&self, block: BlockId) -> Vec<BlockId> {
3012-
let insns = &self.blocks[block.0].insns;
3013-
let last = self.find(*insns.last().unwrap());
3014-
match last {
3015-
Insn::CondBranch { if_true, if_false, .. } => vec![if_true.target, if_false.target],
3016-
Insn::Jump(edge) => vec![edge.target],
3017-
Insn::Entries { targets } => targets,
3018-
Insn::Unreachable | Insn::Return { .. } | Insn::SideExit { .. } | Insn::Throw { .. } => vec![],
3019-
// Blocks that don't end with terminators are technically errors,
3020-
// every block in the CFG should end with a terminator. But we
3021-
// want to be able to iterate over poorly constructed CFG when
3022-
// debugging, so we'll return an empty vec. The validation
3023-
// routines check for terminators, so we should catch CFG errors there.
3024-
_ => vec![]
3025-
}
3011+
/// Return an iterator over the successor blocks of `block`. NB: the iteration order is
3012+
/// intentionally undefined and the same BlockId may be yielded multiple times.
3013+
fn successors(&self, block: BlockId) -> impl Iterator<Item = BlockId> + '_ {
3014+
// Read the terminator directly rather than through `find`, which clones the whole `Insn`.
3015+
// `find` also resolves the id through union-find, but a terminator is never unioned: it
3016+
// produces no value, and `make_equal_to` asserts `has_output()`. So the instruction stored
3017+
// at this id is always the terminator itself, and reading it by reference matches what
3018+
// `find` would return.
3019+
let terminator = &self.insns[self.blocks[block.0].insns.last().unwrap().0];
3020+
3021+
let (first, second, rest): (Option<BlockId>, Option<BlockId>, &[BlockId]) = match terminator {
3022+
Insn::CondBranch { if_true, if_false, .. } => (Some(if_true.target), Some(if_false.target), &[]),
3023+
Insn::Jump(edge) => (Some(edge.target), None, &[]),
3024+
Insn::Entries { targets } => (None, None, targets.as_slice()),
3025+
3026+
// Terminators such as `Return`, `SideExit`, `Throw`, and
3027+
// `Unreachable` have no successors. A block that does not end in a
3028+
// terminator is malformed, but we still want to traverse a poorly
3029+
// constructed CFG when debugging, so we treat it as having no
3030+
// successors; the validation routines report the missing
3031+
// terminator separately.
3032+
_ => (None, None, &[]),
3033+
};
3034+
3035+
first.into_iter().chain(second).chain(rest.iter().copied())
30263036
}
30273037

30283038
/// Return a reference to the Block at the given index.
@@ -10064,7 +10074,7 @@ impl<'a> ControlFlowInfo<'a> {
1006410074
let mut predecessor_map: HashMap<BlockId, Vec<BlockId>> = HashMap::new();
1006510075

1006610076
for block_id in function.reverse_post_order() {
10067-
let mut successors = function.successors(block_id);
10077+
let mut successors: Vec<BlockId> = function.successors(block_id).collect();
1006810078
successors.dedup();
1006910079

1007010080
// Update predecessors for successor blocks.

0 commit comments

Comments
 (0)