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
63 changes: 63 additions & 0 deletions src/ir/module-utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1092,4 +1092,67 @@
return indexedTypes;
}

std::unordered_set<Function*> getReferencedFuncs(Module& module) {
struct AddressedFuncsWalker : WalkerPass<PostWalker<AddressedFuncsWalker>> {
// For each function, which functions are referenced in its body.
// The key for `nullptr` contains references that are not in a function
// (e.g. `elem` segments).
std::unordered_map<Function*, std::unordered_set<Function*>>&
allReferencedFuncs;
// Points to `allReferencedFuncs`.
std::unordered_set<Function*>* referencedFuncs = nullptr;

AddressedFuncsWalker(
std::unordered_map<Function*, std::unordered_set<Function*>>&
allReferencedFuncs)
: allReferencedFuncs(allReferencedFuncs),
referencedFuncs(&allReferencedFuncs.at(nullptr)) {}

std::unique_ptr<Pass> create() override {
return std::make_unique<AddressedFuncsWalker>(allReferencedFuncs);
}

bool isFunctionParallel() override { return true; }

bool modifiesBinaryenIR() override { return false; }

void doWalkFunction(Function* func) {
referencedFuncs = &allReferencedFuncs.at(func);
walk(func->body);
}

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

std::unordered_map<Function*, std::unordered_set<Function*>>
allReferencedFuncs;
allReferencedFuncs[nullptr];
for (auto& func : module.functions) {
allReferencedFuncs[func.get()];
}
Comment thread
stevenfontanella marked this conversation as resolved.
Resolved

PassRunner runner(&module);
AddressedFuncsWalker walker(allReferencedFuncs);
walker.run(&runner, &module);
walker.runOnModuleCode(&runner, &module);

std::unordered_set<Function*> mergedReferencedFuncs;
for (auto& [_, referencedFuncs] : allReferencedFuncs) {
mergedReferencedFuncs.merge(referencedFuncs);
}

for (const auto& export_ : module.exports) {
if (export_->kind != ExternalKind::Function) {

Check warning on line 1147 in src/ir/module-utils.cpp

View workflow job for this annotation

GitHub Actions / coverage

One-sided branch in diff

Branch `export_->kind != ExternalKind::Function` -> missing TRUE branch (True: 0x, False: 54x)
continue;
}

Check warning on line 1149 in src/ir/module-utils.cpp

View workflow job for this annotation

GitHub Actions / coverage

Uncovered diff lines

Lines 1148-1149 not executed by tests (0x)

mergedReferencedFuncs.insert(
module.getFunction(*export_->getInternalName()));
}

return mergedReferencedFuncs;
}

} // namespace wasm::ModuleUtils
8 changes: 8 additions & 0 deletions src/ir/module-utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,14 @@ struct IndexedHeapTypes {
// frequency of use to minimize code size.
IndexedHeapTypes getOptimizedIndexedHeapTypes(Module& wasm);

// Returns the set of functions that can potentially be the target of a call_ref
// or call_indirect. These are:
// * Functions that appear in a ref.func in the module.
// * Functions that are `elem declare`d (which is represented as ref.func in
// the IR).
// * Exported functions, since they can flow back to us as a reference.
std::unordered_set<Function*> getReferencedFuncs(Module& module);

} // namespace wasm::ModuleUtils

#endif // wasm_ir_module_h
72 changes: 1 addition & 71 deletions src/passes/GlobalEffects.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,76 +45,6 @@ struct FuncInfo {
std::unordered_set<HeapType> indirectCalledTypes;
};

// Only funcs that are referenced may be the target of an indirect call. A
// function is referenced if:
// - It appears in a ref.func expression (this includes `elem` statements
// because of how our IR is represented).
// - It's exported, because it may flow back to us as a reference.
//
// 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*> getReferencedFuncs(Module& module,
PassRunner& passRunner) {
struct AddressedFuncsWalker : WalkerPass<PostWalker<AddressedFuncsWalker>> {
// For each function, which functions are referenced in its body.
// The key for `nullptr` contains references that are not in a function
// (e.g. `elem` segments).
std::unordered_map<Function*, std::unordered_set<Function*>>&
allReferencedFuncs;
// Points to `allReferencedFuncs`.
std::unordered_set<Function*>* referencedFuncs = nullptr;

AddressedFuncsWalker(
std::unordered_map<Function*, std::unordered_set<Function*>>&
allReferencedFuncs)
: allReferencedFuncs(allReferencedFuncs),
referencedFuncs(&allReferencedFuncs[nullptr]) {}

std::unique_ptr<Pass> create() override {
return std::make_unique<AddressedFuncsWalker>(allReferencedFuncs);
}

bool isFunctionParallel() override { return true; }

bool modifiesBinaryenIR() override { return false; }

void doWalkFunction(Function* func) {
referencedFuncs = &allReferencedFuncs.at(func);
walk(func->body);
}

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

std::unordered_map<Function*, std::unordered_set<Function*>>
allReferencedFuncs;
for (auto& func : module.functions) {
allReferencedFuncs[func.get()];
}

AddressedFuncsWalker walker(allReferencedFuncs);
walker.run(&passRunner, &module);
walker.runOnModuleCode(&passRunner, &module);

std::unordered_set<Function*> mergedReferencedFuncs;
for (auto& [_, referencedFuncs] : allReferencedFuncs) {
mergedReferencedFuncs.merge(referencedFuncs);
}

for (const auto& export_ : module.exports) {
if (export_->kind != ExternalKind::Function) {
continue;
}

mergedReferencedFuncs.insert(
module.getFunction(*export_->getInternalName()));
}

return mergedReferencedFuncs;
}

std::map<Function*, FuncInfo> analyzeFuncs(Module& module,
const PassOptions& passOptions) {
ModuleUtils::ParallelFunctionAnalysis<FuncInfo> analysis(
Expand Down Expand Up @@ -456,7 +386,7 @@ struct GenerateGlobalEffects : public Pass {
std::map<Function*, FuncInfo> funcInfos =
analyzeFuncs(*module, getPassOptions());

auto referencedFuncs = getReferencedFuncs(*module, *getPassRunner());
auto referencedFuncs = ModuleUtils::getReferencedFuncs(*module);

auto callGraph = buildCallGraph(
*module, funcInfos, referencedFuncs, getPassOptions().worldMode);
Expand Down
1 change: 1 addition & 0 deletions test/gtest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ set(unittest_SOURCES
lattices.cpp
leaves.cpp
local-graph.cpp
module-utils.cpp
possible-contents.cpp
principal-type.cpp
printing.cpp
Expand Down
76 changes: 76 additions & 0 deletions test/gtest/module-utils.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright 2026 WebAssembly Community Group participants
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "gmock/gmock.h"
#include "gtest/gtest.h"

#include "ir/module-utils.h"
#include "parser/wat-parser.h"
#include "wasm.h"

using namespace wasm;
using namespace testing;

namespace {

class ModuleUtilsTest : public Test {
protected:
Module wasm;

[[nodiscard]] bool parse(std::string_view text) {
wasm.features = FeatureSet::All;
auto parsed = WATParser::parseModule(wasm, text);
if (auto* err = parsed.getErr()) {

Check warning on line 36 in test/gtest/module-utils.cpp

View workflow job for this annotation

GitHub Actions / coverage

One-sided branch in diff

Branch `err` -> missing TRUE branch (True: 0x, False: 1x)
EXPECT_TRUE(false) << "Failed to parse WAT: " << err->msg;
return false;
}

Check warning on line 39 in test/gtest/module-utils.cpp

View workflow job for this annotation

GitHub Actions / coverage

Uncovered diff lines

Lines 37-39 not executed by tests (0x)
return true;
}
};

TEST_F(ModuleUtilsTest, GetReferencedFuncs) {
auto moduleText = R"wasm(
(module
(import "env" "imported" (func $imported))

(table $table 1 funcref)
(elem (table $table) (i32.const 0) func $table_func)

(func $table_func)
(func $ref_func)
(func $unreferenced)

(func $exported
(drop (ref.func $ref_func))
)
(export "exported" (func $exported))
)
)wasm";

ASSERT_TRUE(parse(moduleText));

auto* tableFunc = wasm.getFunction("table_func");
auto* refFunc = wasm.getFunction("ref_func");
auto* exported = wasm.getFunction("exported");
ASSERT_NE(tableFunc, nullptr);
ASSERT_NE(refFunc, nullptr);
ASSERT_NE(exported, nullptr);

EXPECT_THAT(ModuleUtils::getReferencedFuncs(wasm),
UnorderedElementsAre(tableFunc, refFunc, exported));
}

} // namespace
Loading