Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 59 additions & 3 deletions src/passes/GlobalEffects.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <ranges>

#include "ir/effects.h"
#include "ir/element-utils.h"
#include "ir/module-utils.h"
#include "pass.h"
#include "support/graph_traversal.h"
Expand All @@ -47,6 +48,56 @@ struct FuncInfo {
std::unordered_set<HeapType> 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<Function*> getAddressedFuncs(Module& module) {
struct AddressedFuncsWalker : PostWalker<AddressedFuncsWalker> {
std::unordered_set<Function*>& addressedFuncs;

AddressedFuncsWalker(std::unordered_set<Function*>& addressedFuncs)
: addressedFuncs(addressedFuncs) {}

void visitRefFunc(RefFunc* refFunc) {
addressedFuncs.insert(getModule()->getFunction(refFunc->func));
}
};

std::unordered_set<Function*> 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<Function*, FuncInfo> analyzeFuncs(Module& module,
const PassOptions& passOptions) {
ModuleUtils::ParallelFunctionAnalysis<FuncInfo> analysis(
Expand Down Expand Up @@ -147,6 +198,7 @@ using CallGraph =

CallGraph buildCallGraph(const Module& module,
const std::map<Function*, FuncInfo>& funcInfos,
const std::unordered_set<Function*>& addressedFuncs,
bool closedWorld) {
CallGraph callGraph;
if (!closedWorld) {
Expand Down Expand Up @@ -182,7 +234,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
Expand Down Expand Up @@ -346,8 +400,10 @@ struct GenerateGlobalEffects : public Pass {
std::map<Function*, FuncInfo> 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);

Expand Down
7 changes: 2 additions & 5 deletions test/lit/passes/global-effects-closed-world.wast
Original file line number Diff line number Diff line change
Expand Up @@ -348,15 +348,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))
)
)
Expand Down
Loading