Skip to content

Commit daf81f7

Browse files
1 parent 66dff99 commit daf81f7

File tree

1 file changed

+44
-57
lines changed

1 file changed

+44
-57
lines changed

src/passes/CodeFolding.cpp

Lines changed: 44 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,10 @@ struct CodeFolding
136136
modifieds; // modified code should not be processed
137137
// again, wait for next pass
138138

139-
// Cache for hasExitingBranches results. Populated by a single bottom-up
140-
// walk to avoid O(N^2) repeated tree traversals on nested blocks.
141-
std::unordered_map<Expression*, bool> exitingBranchCache_;
139+
// Lazy cache for exiting branch checks. Key absent = not yet computed;
140+
// key present with empty set = no exiting branches; non-empty set = has
141+
// exiting branches (and stores the unresolved target names for propagation).
142+
std::unordered_map<Expression*, std::unordered_set<Name>> exitingBranchCache_;
142143

143144
// walking
144145

@@ -312,55 +313,46 @@ struct CodeFolding
312313
}
313314

314315
private:
315-
// Pre-populate the exiting branch cache for all sub-expressions of root
316-
// in a single O(N) bottom-up walk. After this, exitingBranchCache_
317-
// lookups are O(1).
318-
void populateExitingBranchCache(Expression* root) {
319-
struct CachePopulator
320-
: public PostWalker<CachePopulator,
321-
UnifiedExpressionVisitor<CachePopulator>> {
322-
std::unordered_map<Expression*, bool>& cache;
323-
// Track unresolved branch targets at each node. We propagate children's
324-
// targets upward: add uses, remove defs. If any remain, the expression
325-
// has exiting branches.
326-
std::unordered_map<Expression*, std::unordered_set<Name>> targetSets;
327-
328-
CachePopulator(std::unordered_map<Expression*, bool>& cache)
329-
: cache(cache) {}
330-
331-
void visitExpression(Expression* curr) {
332-
std::unordered_set<Name> targets;
333-
// Merge children's target sets into ours (move to avoid copies)
334-
ChildIterator children(curr);
335-
for (auto* child : children) {
336-
auto it = targetSets.find(child);
337-
if (it != targetSets.end()) {
338-
if (targets.empty()) {
339-
targets = std::move(it->second);
340-
} else {
341-
targets.merge(it->second);
342-
}
343-
targetSets.erase(it);
344-
}
345-
}
346-
// Add branch uses (names this expression branches to)
347-
BranchUtils::operateOnScopeNameUses(
348-
curr, [&](Name& name) { targets.insert(name); });
349-
// Remove branch defs (names this expression defines as targets)
350-
BranchUtils::operateOnScopeNameDefs(curr, [&](Name& name) {
351-
if (name.is()) {
352-
targets.erase(name);
353-
}
354-
});
355-
bool hasExiting = !targets.empty();
356-
cache[curr] = hasExiting;
357-
if (hasExiting) {
358-
targetSets[curr] = std::move(targets);
359-
}
316+
// Lazily check if an expression has branches that exit to targets defined
317+
// outside it. On the first call for a given expression, this walks the
318+
// expression and all its unvisited children, caching results so that
319+
// subsequent queries are O(1).
320+
bool hasExitingBranches(Expression* expr) {
321+
auto it = exitingBranchCache_.find(expr);
322+
if (it != exitingBranchCache_.end()) {
323+
return !it->second.empty();
324+
}
325+
computeExitingBranches(expr);
326+
return !exitingBranchCache_[expr].empty();
327+
}
328+
329+
// Recursively compute and cache exiting-branch info for expr and all
330+
// unvisited children.
331+
void computeExitingBranches(Expression* expr) {
332+
if (exitingBranchCache_.count(expr)) {
333+
return;
334+
}
335+
// Compute children first (bottom-up)
336+
ChildIterator children(expr);
337+
for (auto* child : children) {
338+
computeExitingBranches(child);
339+
}
340+
// Merge children's unresolved targets
341+
std::unordered_set<Name> targets;
342+
for (auto* child : children) {
343+
auto& childTargets = exitingBranchCache_[child];
344+
targets.insert(childTargets.begin(), childTargets.end());
345+
}
346+
// Add branch uses (names this expression branches to)
347+
BranchUtils::operateOnScopeNameUses(
348+
expr, [&](Name& name) { targets.insert(name); });
349+
// Remove branch defs (names this expression defines as targets)
350+
BranchUtils::operateOnScopeNameDefs(expr, [&](Name& name) {
351+
if (name.is()) {
352+
targets.erase(name);
360353
}
361-
};
362-
CachePopulator populator(exitingBranchCache_);
363-
populator.walk(root);
354+
});
355+
exitingBranchCache_[expr] = std::move(targets);
364356
}
365357

366358
// check if we can move a list of items out of another item. we can't do so
@@ -606,11 +598,6 @@ struct CodeFolding
606598
if (tails.size() < 2) {
607599
return false;
608600
}
609-
// Pre-populate the cache once at the top level so all subsequent
610-
// exitingBranchCache_ lookups are O(1).
611-
if (num == 0) {
612-
populateExitingBranchCache(getFunction()->body);
613-
}
614601
// remove things that are untoward and cannot be optimized
615602
tails.erase(
616603
std::remove_if(tails.begin(),
@@ -699,7 +686,7 @@ struct CodeFolding
699686
// TODO: this should not be a problem in
700687
// *non*-terminating tails, but
701688
// double-verify that
702-
if (exitingBranchCache_[newItem]) {
689+
if (hasExitingBranches(newItem)) {
703690
return true;
704691
}
705692
return false;

0 commit comments

Comments
 (0)