@@ -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
314315private:
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