From 9bc667fd325ae68106b40d9593448f2c17315aee Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Wed, 15 Apr 2026 22:17:49 +0000 Subject: [PATCH 1/3] Cherrypick SCC changes --- src/passes/GlobalEffects.cpp | 182 +++++++++++++++++++++++++---------- 1 file changed, 130 insertions(+), 52 deletions(-) diff --git a/src/passes/GlobalEffects.cpp b/src/passes/GlobalEffects.cpp index ac17037902b..bdb62b106b0 100644 --- a/src/passes/GlobalEffects.cpp +++ b/src/passes/GlobalEffects.cpp @@ -22,7 +22,7 @@ #include "ir/effects.h" #include "ir/module-utils.h" #include "pass.h" -#include "support/unique_deferring_queue.h" +#include "support/strongly_connected_components.h" #include "wasm.h" namespace wasm { @@ -107,89 +107,167 @@ std::map analyzeFuncs(Module& module, return std::move(analysis.map); } +std::unordered_map> +buildCallGraph(const Module& module, + const std::map& funcInfos) { + std::unordered_map> callGraph; + for (const auto& [func, info] : funcInfos) { + for (Name callee : info.calledFunctions) { + callGraph[func].insert(module.getFunction(callee)); + } + } + + return callGraph; +} + // Propagate effects from callees to callers transitively // e.g. if A -> B -> C (A calls B which calls C) // Then B inherits effects from C and A inherits effects from both B and C. +// +// Generate SCC for the call graph, then traverse it in reverse topological +// order processing each callee before its callers. When traversing: +// - Merge all of the effects of functions within the CC +// - Also merge the (already computed) effects of each callee CC +// - Add trap effects for potentially recursive call chains void propagateEffects( const Module& module, - const std::unordered_map>& reverseCallGraph, - std::map& funcInfos) { - - UniqueNonrepeatingDeferredQueue> work; + const PassOptions& passOptions, + std::map& funcInfos, + const std::unordered_map> + callGraph) { + struct CallGraphSCCs + : SCCs::const_iterator, CallGraphSCCs> { + const std::map& funcInfos; + const std::unordered_map>& + callGraph; + const Module& module; + + CallGraphSCCs( + const std::vector& funcs, + const std::map& funcInfos, + const std::unordered_map>& + callGraph, + const Module& module) + : SCCs::const_iterator, CallGraphSCCs>( + funcs.begin(), funcs.end()), + funcInfos(funcInfos), callGraph(callGraph), module(module) {} + + void pushChildren(Function* f) { + auto callees = callGraph.find(f); + if (callees == callGraph.end()) { + return; + } - for (const auto& [callee, callers] : reverseCallGraph) { - for (const auto& caller : callers) { - work.push(std::pair(callee, caller)); + for (auto* callee : callees->second) { + push(callee); + } } + }; + + std::vector allFuncs; + for (auto& [func, info] : funcInfos) { + allFuncs.push_back(func); } + CallGraphSCCs sccs(allFuncs, funcInfos, callGraph, module); + + std::unordered_map sccMembers; + std::unordered_map> componentEffects; + + int ccIndex = 0; + for (auto ccIterator : sccs) { + ccIndex++; + std::optional& ccEffects = componentEffects[ccIndex]; + std::vector ccFuncs(ccIterator.begin(), ccIterator.end()); + + ccEffects.emplace(passOptions, module); - auto propagate = [&](Name callee, Name caller) { - auto& callerEffects = funcInfos.at(module.getFunction(caller)).effects; - const auto& calleeEffects = - funcInfos.at(module.getFunction(callee)).effects; - if (!callerEffects) { - return; + for (Function* f : ccFuncs) { + sccMembers.emplace(f, ccIndex); } - if (!calleeEffects) { - callerEffects = UnknownEffects; - return; + std::unordered_set calleeSccs; + for (Function* caller : ccFuncs) { + auto callees = callGraph.find(caller); + if (callees == callGraph.end()) { + continue; + } + for (auto* callee : callees->second) { + calleeSccs.insert(sccMembers.at(callee)); + } } - callerEffects->mergeIn(*calleeEffects); - }; + // Merge in effects from callees + for (int calleeScc : calleeSccs) { + const auto& calleeComponentEffects = componentEffects.at(calleeScc); + if (calleeComponentEffects == UnknownEffects) { + ccEffects = UnknownEffects; + break; + } - while (!work.empty()) { - auto [callee, caller] = work.pop(); + else if (ccEffects != UnknownEffects) { + ccEffects->mergeIn(*calleeComponentEffects); + } + } - if (callee == caller) { - auto& callerEffects = funcInfos.at(module.getFunction(caller)).effects; - if (callerEffects) { - callerEffects->trap = true; + // Add trap effects for potential cycles. + if (ccFuncs.size() > 1) { + if (ccEffects != UnknownEffects) { + ccEffects->trap = true; + } + } else { + auto* func = ccFuncs[0]; + if (funcInfos.at(func).calledFunctions.contains(func->name)) { + if (ccEffects != UnknownEffects) { + ccEffects->trap = true; + } } } - // Even if nothing changed, we still need to keep traversing the callers - // to look for a potential cycle which adds a trap affect on the above - // lines. - propagate(callee, caller); + // Aggregate effects within this CC + if (ccEffects) { + for (Function* f : ccFuncs) { + const auto& effects = funcInfos.at(f).effects; + if (effects == UnknownEffects) { + ccEffects = UnknownEffects; + break; + } - const auto& callerCallers = reverseCallGraph.find(caller); - if (callerCallers == reverseCallGraph.end()) { - continue; + ccEffects->mergeIn(*effects); + } } - for (const Name& callerCaller : callerCallers->second) { - work.push(std::pair(callee, callerCaller)); + // Assign each function's effects to its CC effects. + for (Function* f : ccFuncs) { + if (!ccEffects) { + funcInfos.at(f).effects = UnknownEffects; + } else { + funcInfos.at(f).effects.emplace(*ccEffects); + } } } } +void copyEffectsToFunctions(const std::map funcInfos) { + for (auto& [func, info] : funcInfos) { + func->effects.reset(); + if (!info.effects) { + continue; + } + + func->effects = std::make_shared(*info.effects); + } +} + struct GenerateGlobalEffects : public Pass { void run(Module* module) override { std::map funcInfos = analyzeFuncs(*module, getPassOptions()); - // callee : caller - std::unordered_map> callers; - for (const auto& [func, info] : funcInfos) { - for (const auto& callee : info.calledFunctions) { - callers[callee].insert(func->name); - } - } + auto callGraph = buildCallGraph(*module, funcInfos); - propagateEffects(*module, callers, funcInfos); + propagateEffects(*module, getPassOptions(), funcInfos, callGraph); - // Generate the final data, starting from a blank slate where nothing is - // known. - for (auto& [func, info] : funcInfos) { - func->effects.reset(); - if (!info.effects) { - continue; - } - - func->effects = std::make_shared(*info.effects); - } + copyEffectsToFunctions(funcInfos); } }; From a6b62725923e6b169e2a7dde0e7e651ebb69c642 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Thu, 16 Apr 2026 19:57:38 +0000 Subject: [PATCH 2/3] PR updates --- src/passes/GlobalEffects.cpp | 67 ++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/src/passes/GlobalEffects.cpp b/src/passes/GlobalEffects.cpp index bdb62b106b0..cc3e81dfb6a 100644 --- a/src/passes/GlobalEffects.cpp +++ b/src/passes/GlobalEffects.cpp @@ -107,10 +107,11 @@ std::map analyzeFuncs(Module& module, return std::move(analysis.map); } -std::unordered_map> -buildCallGraph(const Module& module, - const std::map& funcInfos) { - std::unordered_map> callGraph; +using CallGraph = std::unordered_map>; + +CallGraph buildCallGraph(const Module& module, + const std::map& funcInfos) { + CallGraph callGraph; for (const auto& [func, info] : funcInfos) { for (Name callee : info.calledFunctions) { callGraph[func].insert(module.getFunction(callee)); @@ -120,6 +121,19 @@ buildCallGraph(const Module& module, return callGraph; } +void mergeMaybeEffects(std::optional& dest, + const std::optional& src) { + if (dest == UnknownEffects) { + return; + } + if (src == UnknownEffects) { + dest = UnknownEffects; + return; + } + + dest->mergeIn(*src); +} + // Propagate effects from callees to callers transitively // e.g. if A -> B -> C (A calls B which calls C) // Then B inherits effects from C and A inherits effects from both B and C. @@ -129,12 +143,10 @@ buildCallGraph(const Module& module, // - Merge all of the effects of functions within the CC // - Also merge the (already computed) effects of each callee CC // - Add trap effects for potentially recursive call chains -void propagateEffects( - const Module& module, - const PassOptions& passOptions, - std::map& funcInfos, - const std::unordered_map> - callGraph) { +void propagateEffects(const Module& module, + const PassOptions& passOptions, + std::map& funcInfos, + const CallGraph& callGraph) { struct CallGraphSCCs : SCCs::const_iterator, CallGraphSCCs> { const std::map& funcInfos; @@ -170,19 +182,18 @@ void propagateEffects( } CallGraphSCCs sccs(allFuncs, funcInfos, callGraph, module); - std::unordered_map sccMembers; - std::unordered_map> componentEffects; + std::vector> componentEffects; + // Points to an index in componentEffects + std::unordered_map funcComponents; - int ccIndex = 0; for (auto ccIterator : sccs) { - ccIndex++; - std::optional& ccEffects = componentEffects[ccIndex]; - std::vector ccFuncs(ccIterator.begin(), ccIterator.end()); + std::optional& ccEffects = + componentEffects.emplace_back(std::in_place, passOptions, module); - ccEffects.emplace(passOptions, module); + std::vector ccFuncs(ccIterator.begin(), ccIterator.end()); for (Function* f : ccFuncs) { - sccMembers.emplace(f, ccIndex); + funcComponents.emplace(f, componentEffects.size() - 1); } std::unordered_set calleeSccs; @@ -192,21 +203,14 @@ void propagateEffects( continue; } for (auto* callee : callees->second) { - calleeSccs.insert(sccMembers.at(callee)); + calleeSccs.insert(funcComponents.at(callee)); } } // Merge in effects from callees for (int calleeScc : calleeSccs) { const auto& calleeComponentEffects = componentEffects.at(calleeScc); - if (calleeComponentEffects == UnknownEffects) { - ccEffects = UnknownEffects; - break; - } - - else if (ccEffects != UnknownEffects) { - ccEffects->mergeIn(*calleeComponentEffects); - } + mergeMaybeEffects(ccEffects, calleeComponentEffects); } // Add trap effects for potential cycles. @@ -227,12 +231,7 @@ void propagateEffects( if (ccEffects) { for (Function* f : ccFuncs) { const auto& effects = funcInfos.at(f).effects; - if (effects == UnknownEffects) { - ccEffects = UnknownEffects; - break; - } - - ccEffects->mergeIn(*effects); + mergeMaybeEffects(ccEffects, effects); } } @@ -247,7 +246,7 @@ void propagateEffects( } } -void copyEffectsToFunctions(const std::map funcInfos) { +void copyEffectsToFunctions(const std::map& funcInfos) { for (auto& [func, info] : funcInfos) { func->effects.reset(); if (!info.effects) { From 0a2f0ddd6abb3abe3bacba30a88c31f37350a37e Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Thu, 16 Apr 2026 22:11:17 +0000 Subject: [PATCH 3/3] Avoid repeated lookup --- src/passes/GlobalEffects.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/passes/GlobalEffects.cpp b/src/passes/GlobalEffects.cpp index cc3e81dfb6a..7f47cf108eb 100644 --- a/src/passes/GlobalEffects.cpp +++ b/src/passes/GlobalEffects.cpp @@ -113,8 +113,13 @@ CallGraph buildCallGraph(const Module& module, const std::map& funcInfos) { CallGraph callGraph; for (const auto& [func, info] : funcInfos) { + if (info.calledFunctions.empty()) { + continue; + } + + auto& callees = callGraph[func]; for (Name callee : info.calledFunctions) { - callGraph[func].insert(module.getFunction(callee)); + callees.insert(module.getFunction(callee)); } }