Skip to content

Commit 893d7a5

Browse files
add exitingBranchCache_
1 parent ce7f869 commit 893d7a5

File tree

1 file changed

+58
-3
lines changed

1 file changed

+58
-3
lines changed

src/passes/CodeFolding.cpp

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
#include "ir/effects.h"
6464
#include "ir/eh-utils.h"
6565
#include "ir/find_all.h"
66+
#include "ir/iteration.h"
6667
#include "ir/label-utils.h"
6768
#include "ir/utils.h"
6869
#include "pass.h"
@@ -135,6 +136,10 @@ struct CodeFolding
135136
modifieds; // modified code should not be processed
136137
// again, wait for next pass
137138

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_;
142+
138143
// walking
139144

140145
void visitExpression(Expression* curr) {
@@ -299,13 +304,60 @@ struct CodeFolding
299304
returnTails.clear();
300305
unoptimizables.clear();
301306
modifieds.clear();
307+
exitingBranchCache_.clear();
302308
if (needEHFixups) {
303309
EHUtils::handleBlockNestedPops(func, *getModule());
304310
}
305311
}
306312
}
307313

308314
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+
targets.merge(it->second);
339+
targetSets.erase(it);
340+
}
341+
}
342+
// Add branch uses (names this expression branches to)
343+
BranchUtils::operateOnScopeNameUses(
344+
curr, [&](Name& name) { targets.insert(name); });
345+
// Remove branch defs (names this expression defines as targets)
346+
BranchUtils::operateOnScopeNameDefs(curr, [&](Name& name) {
347+
if (name.is())
348+
targets.erase(name);
349+
});
350+
bool hasExiting = !targets.empty();
351+
cache[curr] = hasExiting;
352+
if (hasExiting) {
353+
targetSets[curr] = std::move(targets);
354+
}
355+
}
356+
};
357+
CachePopulator populator(exitingBranchCache_);
358+
populator.walk(root);
359+
}
360+
309361
// check if we can move a list of items out of another item. we can't do so
310362
// if one of the items has a branch to something inside outOf that is not
311363
// inside that item
@@ -549,6 +601,11 @@ struct CodeFolding
549601
if (tails.size() < 2) {
550602
return false;
551603
}
604+
// Pre-populate the cache once at the top level so all subsequent
605+
// exitingBranchCache_ lookups are O(1).
606+
if (num == 0) {
607+
populateExitingBranchCache(getFunction()->body);
608+
}
552609
// remove things that are untoward and cannot be optimized
553610
tails.erase(
554611
std::remove_if(tails.begin(),
@@ -637,9 +694,7 @@ struct CodeFolding
637694
// TODO: this should not be a problem in
638695
// *non*-terminating tails, but
639696
// double-verify that
640-
if (EffectAnalyzer(
641-
getPassOptions(), *getModule(), newItem)
642-
.hasExternalBreakTargets()) {
697+
if (exitingBranchCache_[newItem]) {
643698
return true;
644699
}
645700
return false;

0 commit comments

Comments
 (0)