|
56 | 56 | // |
57 | 57 |
|
58 | 58 | #include <iterator> |
| 59 | +#include <optional> |
59 | 60 | #include <unordered_map> |
60 | 61 | #include <unordered_set> |
61 | 62 |
|
@@ -301,75 +302,108 @@ struct CodeFolding |
301 | 302 | unoptimizables.clear(); |
302 | 303 | modifieds.clear(); |
303 | 304 | exitingBranchCache.clear(); |
304 | | - exitingBranchCachePopulated = false; |
305 | 305 | if (needEHFixups) { |
306 | 306 | EHUtils::handleBlockNestedPops(func, *getModule()); |
307 | 307 | } |
308 | 308 | } |
309 | 309 | } |
310 | 310 |
|
311 | 311 | private: |
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; |
| 312 | + // Cache of exiting branch names, populated on demand. Only queried roots |
| 313 | + // are stored. nullopt means no exiting branches; a set holds the names. |
| 314 | + std::unordered_map<Expression*, std::optional<std::unordered_set<Name>>> |
| 315 | + exitingBranchCache; |
316 | 316 |
|
317 | 317 | bool hasExitingBranches(Expression* expr) { |
318 | | - if (!exitingBranchCachePopulated) { |
319 | | - populateExitingBranchCache(getFunction()->body); |
320 | | - exitingBranchCachePopulated = true; |
| 318 | + auto it = exitingBranchCache.find(expr); |
| 319 | + if (it != exitingBranchCache.end()) { |
| 320 | + return it->second.has_value(); |
321 | 321 | } |
322 | | - return exitingBranchCache.count(expr); |
| 322 | + return populateExitingBranchCache(expr); |
323 | 323 | } |
324 | 324 |
|
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) { |
| 325 | + // Walk |root| bottom-up computing exiting branches. Name sets are kept |
| 326 | + // transiently (moved from children, erased after merge). Only the root's |
| 327 | + // name set is persisted. Already-cached subtrees are skipped via scan(), |
| 328 | + // and their cached names are merged in precisely. |
| 329 | + bool populateExitingBranchCache(Expression* root) { |
329 | 330 | struct CachePopulator |
330 | 331 | : public PostWalker<CachePopulator, |
331 | 332 | 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) {} |
| 333 | + std::unordered_map<Expression*, std::optional<std::unordered_set<Name>>>& |
| 334 | + resultCache; |
| 335 | + Expression* root; |
| 336 | + bool rootResult = false; |
| 337 | + std::unordered_map<Expression*, std::unordered_set<Name>> nameSets; |
| 338 | + |
| 339 | + CachePopulator(std::unordered_map< |
| 340 | + Expression*, |
| 341 | + std::optional<std::unordered_set<Name>>>& resultCache, |
| 342 | + Expression* root) |
| 343 | + : resultCache(resultCache), root(root) {} |
| 344 | + |
| 345 | + static void scan(CachePopulator* self, Expression** currp) { |
| 346 | + auto* curr = *currp; |
| 347 | + if (self->resultCache.count(curr)) { |
| 348 | + return; |
| 349 | + } |
| 350 | + PostWalker<CachePopulator, |
| 351 | + UnifiedExpressionVisitor<CachePopulator>>::scan(self, currp); |
| 352 | + } |
339 | 353 |
|
340 | 354 | void visitExpression(Expression* curr) { |
341 | 355 | std::unordered_set<Name> targets; |
342 | | - // Merge children's target sets into ours (move to avoid copies) |
| 356 | + |
343 | 357 | ChildIterator children(curr); |
344 | 358 | for (auto* child : children) { |
345 | | - auto it = targetSets.find(child); |
346 | | - if (it != targetSets.end()) { |
| 359 | + auto it = nameSets.find(child); |
| 360 | + if (it != nameSets.end()) { |
347 | 361 | if (targets.empty()) { |
348 | 362 | targets = std::move(it->second); |
349 | 363 | } else { |
350 | 364 | targets.merge(it->second); |
351 | 365 | } |
352 | | - targetSets.erase(it); |
| 366 | + nameSets.erase(it); |
| 367 | + } else { |
| 368 | + // Child was skipped by scan() — merge its cached names. |
| 369 | + auto cacheIt = resultCache.find(child); |
| 370 | + if (cacheIt != resultCache.end() && cacheIt->second) { |
| 371 | + if (targets.empty()) { |
| 372 | + targets = *cacheIt->second; |
| 373 | + } else { |
| 374 | + targets.insert(cacheIt->second->begin(), |
| 375 | + cacheIt->second->end()); |
| 376 | + } |
| 377 | + } |
353 | 378 | } |
354 | 379 | } |
355 | | - // Add branch uses (names this expression branches to) |
| 380 | + |
356 | 381 | BranchUtils::operateOnScopeNameUses( |
357 | 382 | curr, [&](Name& name) { targets.insert(name); }); |
358 | | - // Remove branch defs (names this expression defines as targets) |
| 383 | + |
359 | 384 | BranchUtils::operateOnScopeNameDefs(curr, [&](Name& name) { |
360 | 385 | if (name.is()) { |
361 | 386 | targets.erase(name); |
362 | 387 | } |
363 | 388 | }); |
364 | | - bool hasExiting = !targets.empty(); |
365 | | - if (hasExiting) { |
366 | | - cache.insert(curr); |
367 | | - targetSets[curr] = std::move(targets); |
| 389 | + |
| 390 | + if (!targets.empty()) { |
| 391 | + nameSets[curr] = std::move(targets); |
| 392 | + } |
| 393 | + if (curr == root) { |
| 394 | + auto it = nameSets.find(curr); |
| 395 | + if (it != nameSets.end()) { |
| 396 | + resultCache[curr] = std::move(it->second); |
| 397 | + rootResult = true; |
| 398 | + } else { |
| 399 | + resultCache[curr] = std::nullopt; |
| 400 | + } |
368 | 401 | } |
369 | 402 | } |
370 | 403 | }; |
371 | | - CachePopulator populator(exitingBranchCache); |
| 404 | + CachePopulator populator(exitingBranchCache, root); |
372 | 405 | populator.walk(root); |
| 406 | + return populator.rootResult; |
373 | 407 | } |
374 | 408 |
|
375 | 409 | // check if we can move a list of items out of another item. we can't do so |
|
0 commit comments