@@ -300,76 +300,96 @@ struct CodeFolding
300300 returnTails.clear ();
301301 unoptimizables.clear ();
302302 modifieds.clear ();
303- exitingBranchCache.clear ();
304- exitingBranchCachePopulated = false ;
303+ hasExitingBranchCache.clear ();
305304 if (needEHFixups) {
306305 EHUtils::handleBlockNestedPops (func, *getModule ());
307306 }
308307 }
309308 }
310309
311310private:
312- // Cache of expressions that have branches exiting to targets defined
313- // outside them. Populated lazily on first access via hasExitingBranches().
314- std::unordered_set<Expression*> exitingBranchCache;
315- bool exitingBranchCachePopulated = false ;
311+ // Cache of hasExitingBranches() bool results, populated on demand.
312+ std::unordered_map<Expression*, bool > hasExitingBranchCache;
316313
317314 bool hasExitingBranches (Expression* expr) {
318- if (!exitingBranchCachePopulated) {
319- populateExitingBranchCache ( getFunction ()-> body );
320- exitingBranchCachePopulated = true ;
315+ auto it = hasExitingBranchCache. find (expr);
316+ if (it != hasExitingBranchCache. end ()) {
317+ return it-> second ;
321318 }
322- return exitingBranchCache. count (expr);
319+ return populateExitingBranchCache (expr);
323320 }
324321
325- // Pre-populate the exiting branch cache for all sub-expressions of root
326- // in a single O(N) bottom-up walk. After this, exitingBranchCache
327- // lookups are O(1).
328- void populateExitingBranchCache (Expression* root) {
322+ // Walk |root| bottom-up computing exiting branches. Name sets are kept
323+ // transiently (moved from children, erased after merge). Only the root's
324+ // bool result is persisted to keep the cache small. Already-cached
325+ // subtrees are skipped via scan().
326+ bool populateExitingBranchCache (Expression* root) {
329327 struct CachePopulator
330328 : public PostWalker<CachePopulator,
331329 UnifiedExpressionVisitor<CachePopulator>> {
332- std::unordered_set<Expression*>& cache;
333- // Track unresolved branch targets at each node. We propagate children's
334- // targets upward: add uses, remove defs. If any remain, the expression
335- // has exiting branches.
336- std::unordered_map<Expression*, std::unordered_set<Name>> targetSets;
337-
338- CachePopulator (std::unordered_set<Expression*>& cache) : cache(cache) {}
330+ std::unordered_map<Expression*, bool >& resultCache;
331+ Expression* root;
332+ bool rootResult = false ;
333+ std::unordered_map<Expression*, std::unordered_set<Name>> nameSets;
334+
335+ CachePopulator (std::unordered_map<Expression*, bool >& resultCache,
336+ Expression* root)
337+ : resultCache(resultCache), root(root) {}
338+
339+ static void scan (CachePopulator* self, Expression** currp) {
340+ auto * curr = *currp;
341+ if (self->resultCache .count (curr)) {
342+ return ;
343+ }
344+ PostWalker<CachePopulator,
345+ UnifiedExpressionVisitor<CachePopulator>>::scan (self, currp);
346+ }
339347
340348 void visitExpression (Expression* curr) {
341349 std::unordered_set<Name> targets;
342- // Merge children's target sets into ours (move to avoid copies)
350+
351+ bool childFromPriorWalkHasExiting = false ;
343352 ChildIterator children (curr);
344353 for (auto * child : children) {
345- auto it = targetSets .find (child);
346- if (it != targetSets .end ()) {
354+ auto it = nameSets .find (child);
355+ if (it != nameSets .end ()) {
347356 if (targets.empty ()) {
348357 targets = std::move (it->second );
349358 } else {
350359 targets.merge (it->second );
351360 }
352- targetSets.erase (it);
361+ nameSets.erase (it);
362+ } else {
363+ // Conservatively propagate prior-cached children's results.
364+ auto cacheIt = resultCache.find (child);
365+ if (cacheIt != resultCache.end () && cacheIt->second ) {
366+ childFromPriorWalkHasExiting = true ;
367+ }
353368 }
354369 }
355- // Add branch uses (names this expression branches to)
370+
356371 BranchUtils::operateOnScopeNameUses (
357372 curr, [&](Name& name) { targets.insert (name); });
358- // Remove branch defs (names this expression defines as targets)
373+
359374 BranchUtils::operateOnScopeNameDefs (curr, [&](Name& name) {
360375 if (name.is ()) {
361376 targets.erase (name);
362377 }
363378 });
364- bool hasExiting = !targets.empty ();
365- if (hasExiting) {
366- cache.insert (curr);
367- targetSets[curr] = std::move (targets);
379+
380+ bool hasExiting = !targets.empty () || childFromPriorWalkHasExiting;
381+ if (!targets.empty ()) {
382+ nameSets[curr] = std::move (targets);
383+ }
384+ if (curr == root) {
385+ resultCache[curr] = hasExiting;
386+ rootResult = hasExiting;
368387 }
369388 }
370389 };
371- CachePopulator populator (exitingBranchCache );
390+ CachePopulator populator (hasExitingBranchCache, root );
372391 populator.walk (root);
392+ return populator.rootResult ;
373393 }
374394
375395 // check if we can move a list of items out of another item. we can't do so
0 commit comments