diff --git a/src/passes/GlobalEffects.cpp b/src/passes/GlobalEffects.cpp index 1625b551322..51c7bec59a5 100644 --- a/src/passes/GlobalEffects.cpp +++ b/src/passes/GlobalEffects.cpp @@ -22,6 +22,7 @@ #include #include "ir/effects.h" +#include "ir/element-utils.h" #include "ir/module-utils.h" #include "pass.h" #include "support/graph_traversal.h" @@ -47,6 +48,56 @@ struct FuncInfo { std::unordered_set indirectCalledTypes; }; +/* + Only funcs that are 'addressed' may be the target of an indirect call. A + function is addressed if: + - It appears in a ref.func expression + - It appears in an `elem` segment (note that we already ignore `elem declare` + statements in our IR, but we check separately for funcs that appear in + `ref.func`). + - It's exported, because it may flow back to us as a reference. + - It's imported, which implies it is `elem declare`d. + + If a function doesn't meet any of these criteria, it can't be the target of + an indirect call and we don't need to include its effects in indirect calls. +*/ +std::unordered_set getAddressedFuncs(Module& module) { + struct AddressedFuncsWalker : PostWalker { + std::unordered_set& addressedFuncs; + + AddressedFuncsWalker(std::unordered_set& addressedFuncs) + : addressedFuncs(addressedFuncs) {} + + void visitRefFunc(RefFunc* refFunc) { + addressedFuncs.insert(getModule()->getFunction(refFunc->func)); + } + }; + + std::unordered_set addressedFuncs; + AddressedFuncsWalker walker(addressedFuncs); + walker.walkModule(&module); + + ModuleUtils::iterImportedFunctions( + module, [&addressedFuncs, &module](Function* import) { + addressedFuncs.insert(module.getFunction(import->name)); + }); + + for (const auto& export_ : module.exports) { + if (export_->kind != ExternalKind::Function) { + continue; + } + + addressedFuncs.insert(module.getFunction(*export_->getInternalName())); + } + + ElementUtils::iterAllElementFunctionNames( + &module, [&addressedFuncs, &module](Name func) { + addressedFuncs.insert(module.getFunction(func)); + }); + + return addressedFuncs; +} + std::map analyzeFuncs(Module& module, const PassOptions& passOptions) { ModuleUtils::ParallelFunctionAnalysis analysis( @@ -146,6 +197,7 @@ using CallGraph = CallGraph buildCallGraph(const Module& module, const std::map& funcInfos, + const std::unordered_set& addressedFuncs, bool closedWorld) { CallGraph callGraph; if (!closedWorld) { @@ -181,7 +233,9 @@ CallGraph buildCallGraph(const Module& module, } // Type -> Function - callGraph[caller->type.getHeapType()].insert(caller); + if (addressedFuncs.contains(caller)) { + callGraph[caller->type.getHeapType()].insert(caller); + } } // Type -> Type @@ -345,8 +399,10 @@ struct GenerateGlobalEffects : public Pass { std::map funcInfos = analyzeFuncs(*module, getPassOptions()); - auto callGraph = - buildCallGraph(*module, funcInfos, getPassOptions().closedWorld); + auto addressedFuncs = getAddressedFuncs(*module); + + auto callGraph = buildCallGraph( + *module, funcInfos, addressedFuncs, getPassOptions().closedWorld); propagateEffects(*module, getPassOptions(), funcInfos, callGraph); diff --git a/test/lit/passes/global-effects-closed-world.wast b/test/lit/passes/global-effects-closed-world.wast index 77484c63d6d..586b0025538 100644 --- a/test/lit/passes/global-effects-closed-world.wast +++ b/test/lit/passes/global-effects-closed-world.wast @@ -329,15 +329,12 @@ ) ;; CHECK: (func $f (type $1) (param $ref (ref $only-has-effects-in-not-addressable-function)) - ;; CHECK-NEXT: (call $calls-type-with-effects-but-not-addressable - ;; CHECK-NEXT: (local.get $ref) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $f (param $ref (ref $only-has-effects-in-not-addressable-function)) ;; The type $has-effects-but-not-exported doesn't have an address because ;; it's not exported and it's never the target of a ref.func. - ;; We should be able to determine that $ref can only point to $nop. - ;; TODO: Only aggregate effects from functions that are addressed. + ;; So the call_ref has no potential targets and thus no effects. (call $calls-type-with-effects-but-not-addressable (local.get $ref)) ) )