diff --git a/src/passes/CodeFolding.cpp b/src/passes/CodeFolding.cpp index e53cd4f880d..945c7ac8857 100644 --- a/src/passes/CodeFolding.cpp +++ b/src/passes/CodeFolding.cpp @@ -56,6 +56,7 @@ // #include +#include #include #include @@ -63,6 +64,7 @@ #include "ir/effects.h" #include "ir/eh-utils.h" #include "ir/find_all.h" +#include "ir/iteration.h" #include "ir/label-utils.h" #include "ir/utils.h" #include "pass.h" @@ -299,6 +301,7 @@ struct CodeFolding returnTails.clear(); unoptimizables.clear(); modifieds.clear(); + exitingBranchCache.clear(); if (needEHFixups) { EHUtils::handleBlockNestedPops(func, *getModule()); } @@ -306,6 +309,103 @@ struct CodeFolding } private: + // Cache of exiting branch names, populated on demand. Only queried roots + // are stored. nullopt means no exiting branches; a set holds the names. + std::unordered_map>> + exitingBranchCache; + + bool hasExitingBranches(Expression* expr) { + auto it = exitingBranchCache.find(expr); + if (it != exitingBranchCache.end()) { + return it->second.has_value(); + } + return populateExitingBranchCache(expr); + } + + // Walk |root| bottom-up computing exiting branches. Name sets are kept + // transiently (moved from children, erased after merge). Only the root's + // name set is persisted. Already-cached subtrees are skipped via scan(), + // and their cached names are merged in precisely. + bool populateExitingBranchCache(Expression* root) { + struct CachePopulator + : public PostWalker> { + std::unordered_map>>& + resultCache; + Expression* root; + bool rootResult = false; + std::unordered_map> nameSets; + + CachePopulator(std::unordered_map< + Expression*, + std::optional>>& resultCache, + Expression* root) + : resultCache(resultCache), root(root) {} + + static void scan(CachePopulator* self, Expression** currp) { + auto* curr = *currp; + if (self->resultCache.count(curr)) { + return; + } + PostWalker>::scan(self, currp); + } + + void visitExpression(Expression* curr) { + std::unordered_set targets; + + ChildIterator children(curr); + for (auto* child : children) { + auto it = nameSets.find(child); + if (it != nameSets.end()) { + if (targets.empty()) { + targets = std::move(it->second); + } else { + targets.merge(it->second); + } + nameSets.erase(it); + } else { + // Child was skipped by scan() — merge its cached names. + auto cacheIt = resultCache.find(child); + if (cacheIt != resultCache.end() && cacheIt->second) { + if (targets.empty()) { + targets = *cacheIt->second; + } else { + targets.insert(cacheIt->second->begin(), + cacheIt->second->end()); + } + } + } + } + + BranchUtils::operateOnScopeNameUses( + curr, [&](Name& name) { targets.insert(name); }); + + BranchUtils::operateOnScopeNameDefs(curr, [&](Name& name) { + if (name.is()) { + targets.erase(name); + } + }); + + if (!targets.empty()) { + nameSets[curr] = std::move(targets); + } + if (curr == root) { + auto it = nameSets.find(curr); + if (it != nameSets.end()) { + resultCache[curr] = std::move(it->second); + rootResult = true; + } else { + resultCache[curr] = std::nullopt; + } + } + } + }; + CachePopulator populator(exitingBranchCache, root); + populator.walk(root); + return populator.rootResult; + } + // check if we can move a list of items out of another item. we can't do so // if one of the items has a branch to something inside outOf that is not // inside that item @@ -637,9 +737,7 @@ struct CodeFolding // TODO: this should not be a problem in // *non*-terminating tails, but // double-verify that - if (EffectAnalyzer( - getPassOptions(), *getModule(), newItem) - .hasExternalBreakTargets()) { + if (hasExitingBranches(newItem)) { return true; } return false;