Skip to content

Commit 7a35c83

Browse files
[NFC] Extract out referenced functions utility (#9347)
Move out the existing logic in `GlobalEffects` for use in the future `RelaxNullability` pass. The `PassRunner` param isn't needed because we don't actually inherit anything from it. The code is moved verbatim, except for the fix in [this commit](eb5f475) and the `PassRunner` param is removed. For use in #9349.
1 parent c79cd77 commit 7a35c83

5 files changed

Lines changed: 149 additions & 71 deletions

File tree

‎src/ir/module-utils.cpp‎

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1092,4 +1092,67 @@ IndexedHeapTypes getOptimizedIndexedHeapTypes(Module& wasm) {
10921092
return indexedTypes;
10931093
}
10941094

1095+
std::unordered_set<Function*> getReferencedFuncs(Module& module) {
1096+
struct AddressedFuncsWalker : WalkerPass<PostWalker<AddressedFuncsWalker>> {
1097+
// For each function, which functions are referenced in its body.
1098+
// The key for `nullptr` contains references that are not in a function
1099+
// (e.g. `elem` segments).
1100+
std::unordered_map<Function*, std::unordered_set<Function*>>&
1101+
allReferencedFuncs;
1102+
// Points to `allReferencedFuncs`.
1103+
std::unordered_set<Function*>* referencedFuncs = nullptr;
1104+
1105+
AddressedFuncsWalker(
1106+
std::unordered_map<Function*, std::unordered_set<Function*>>&
1107+
allReferencedFuncs)
1108+
: allReferencedFuncs(allReferencedFuncs),
1109+
referencedFuncs(&allReferencedFuncs.at(nullptr)) {}
1110+
1111+
std::unique_ptr<Pass> create() override {
1112+
return std::make_unique<AddressedFuncsWalker>(allReferencedFuncs);
1113+
}
1114+
1115+
bool isFunctionParallel() override { return true; }
1116+
1117+
bool modifiesBinaryenIR() override { return false; }
1118+
1119+
void doWalkFunction(Function* func) {
1120+
referencedFuncs = &allReferencedFuncs.at(func);
1121+
walk(func->body);
1122+
}
1123+
1124+
void visitRefFunc(RefFunc* refFunc) {
1125+
referencedFuncs->insert(getModule()->getFunction(refFunc->func));
1126+
}
1127+
};
1128+
1129+
std::unordered_map<Function*, std::unordered_set<Function*>>
1130+
allReferencedFuncs;
1131+
allReferencedFuncs[nullptr];
1132+
for (auto& func : module.functions) {
1133+
allReferencedFuncs[func.get()];
1134+
}
1135+
1136+
PassRunner runner(&module);
1137+
AddressedFuncsWalker walker(allReferencedFuncs);
1138+
walker.run(&runner, &module);
1139+
walker.runOnModuleCode(&runner, &module);
1140+
1141+
std::unordered_set<Function*> mergedReferencedFuncs;
1142+
for (auto& [_, referencedFuncs] : allReferencedFuncs) {
1143+
mergedReferencedFuncs.merge(referencedFuncs);
1144+
}
1145+
1146+
for (const auto& export_ : module.exports) {
1147+
if (export_->kind != ExternalKind::Function) {
1148+
continue;
1149+
}
1150+
1151+
mergedReferencedFuncs.insert(
1152+
module.getFunction(*export_->getInternalName()));
1153+
}
1154+
1155+
return mergedReferencedFuncs;
1156+
}
1157+
10951158
} // namespace wasm::ModuleUtils

‎src/ir/module-utils.h‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,14 @@ struct IndexedHeapTypes {
505505
// frequency of use to minimize code size.
506506
IndexedHeapTypes getOptimizedIndexedHeapTypes(Module& wasm);
507507

508+
// Returns the set of functions that can potentially be the target of a call_ref
509+
// or call_indirect. These are:
510+
// * Functions that appear in a ref.func in the module.
511+
// * Functions that are `elem declare`d (which is represented as ref.func in
512+
// the IR).
513+
// * Exported functions, since they can flow back to us as a reference.
514+
std::unordered_set<Function*> getReferencedFuncs(Module& module);
515+
508516
} // namespace wasm::ModuleUtils
509517

510518
#endif // wasm_ir_module_h

‎src/passes/GlobalEffects.cpp‎

Lines changed: 1 addition & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -45,76 +45,6 @@ struct FuncInfo {
4545
std::unordered_set<HeapType> indirectCalledTypes;
4646
};
4747

48-
// Only funcs that are referenced may be the target of an indirect call. A
49-
// function is referenced if:
50-
// - It appears in a ref.func expression (this includes `elem` statements
51-
// because of how our IR is represented).
52-
// - It's exported, because it may flow back to us as a reference.
53-
//
54-
// If a function doesn't meet any of these criteria, it can't be the target of
55-
// an indirect call and we don't need to include its effects in indirect calls.
56-
std::unordered_set<Function*> getReferencedFuncs(Module& module,
57-
PassRunner& passRunner) {
58-
struct AddressedFuncsWalker : WalkerPass<PostWalker<AddressedFuncsWalker>> {
59-
// For each function, which functions are referenced in its body.
60-
// The key for `nullptr` contains references that are not in a function
61-
// (e.g. `elem` segments).
62-
std::unordered_map<Function*, std::unordered_set<Function*>>&
63-
allReferencedFuncs;
64-
// Points to `allReferencedFuncs`.
65-
std::unordered_set<Function*>* referencedFuncs = nullptr;
66-
67-
AddressedFuncsWalker(
68-
std::unordered_map<Function*, std::unordered_set<Function*>>&
69-
allReferencedFuncs)
70-
: allReferencedFuncs(allReferencedFuncs),
71-
referencedFuncs(&allReferencedFuncs[nullptr]) {}
72-
73-
std::unique_ptr<Pass> create() override {
74-
return std::make_unique<AddressedFuncsWalker>(allReferencedFuncs);
75-
}
76-
77-
bool isFunctionParallel() override { return true; }
78-
79-
bool modifiesBinaryenIR() override { return false; }
80-
81-
void doWalkFunction(Function* func) {
82-
referencedFuncs = &allReferencedFuncs.at(func);
83-
walk(func->body);
84-
}
85-
86-
void visitRefFunc(RefFunc* refFunc) {
87-
referencedFuncs->insert(getModule()->getFunction(refFunc->func));
88-
}
89-
};
90-
91-
std::unordered_map<Function*, std::unordered_set<Function*>>
92-
allReferencedFuncs;
93-
for (auto& func : module.functions) {
94-
allReferencedFuncs[func.get()];
95-
}
96-
97-
AddressedFuncsWalker walker(allReferencedFuncs);
98-
walker.run(&passRunner, &module);
99-
walker.runOnModuleCode(&passRunner, &module);
100-
101-
std::unordered_set<Function*> mergedReferencedFuncs;
102-
for (auto& [_, referencedFuncs] : allReferencedFuncs) {
103-
mergedReferencedFuncs.merge(referencedFuncs);
104-
}
105-
106-
for (const auto& export_ : module.exports) {
107-
if (export_->kind != ExternalKind::Function) {
108-
continue;
109-
}
110-
111-
mergedReferencedFuncs.insert(
112-
module.getFunction(*export_->getInternalName()));
113-
}
114-
115-
return mergedReferencedFuncs;
116-
}
117-
11848
std::map<Function*, FuncInfo> analyzeFuncs(Module& module,
11949
const PassOptions& passOptions) {
12050
ModuleUtils::ParallelFunctionAnalysis<FuncInfo> analysis(
@@ -456,7 +386,7 @@ struct GenerateGlobalEffects : public Pass {
456386
std::map<Function*, FuncInfo> funcInfos =
457387
analyzeFuncs(*module, getPassOptions());
458388

459-
auto referencedFuncs = getReferencedFuncs(*module, *getPassRunner());
389+
auto referencedFuncs = ModuleUtils::getReferencedFuncs(*module);
460390

461391
auto callGraph = buildCallGraph(
462392
*module, funcInfos, referencedFuncs, getPassOptions().worldMode);

‎test/gtest/CMakeLists.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ set(unittest_SOURCES
2929
lattices.cpp
3030
leaves.cpp
3131
local-graph.cpp
32+
module-utils.cpp
3233
possible-contents.cpp
3334
principal-type.cpp
3435
printing.cpp

‎test/gtest/module-utils.cpp‎

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright 2026 WebAssembly Community Group participants
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "gmock/gmock.h"
18+
#include "gtest/gtest.h"
19+
20+
#include "ir/module-utils.h"
21+
#include "parser/wat-parser.h"
22+
#include "wasm.h"
23+
24+
using namespace wasm;
25+
using namespace testing;
26+
27+
namespace {
28+
29+
class ModuleUtilsTest : public Test {
30+
protected:
31+
Module wasm;
32+
33+
[[nodiscard]] bool parse(std::string_view text) {
34+
wasm.features = FeatureSet::All;
35+
auto parsed = WATParser::parseModule(wasm, text);
36+
if (auto* err = parsed.getErr()) {
37+
EXPECT_TRUE(false) << "Failed to parse WAT: " << err->msg;
38+
return false;
39+
}
40+
return true;
41+
}
42+
};
43+
44+
TEST_F(ModuleUtilsTest, GetReferencedFuncs) {
45+
auto moduleText = R"wasm(
46+
(module
47+
(import "env" "imported" (func $imported))
48+
49+
(table $table 1 funcref)
50+
(elem (table $table) (i32.const 0) func $table_func)
51+
52+
(func $table_func)
53+
(func $ref_func)
54+
(func $unreferenced)
55+
56+
(func $exported
57+
(drop (ref.func $ref_func))
58+
)
59+
(export "exported" (func $exported))
60+
)
61+
)wasm";
62+
63+
ASSERT_TRUE(parse(moduleText));
64+
65+
auto* tableFunc = wasm.getFunction("table_func");
66+
auto* refFunc = wasm.getFunction("ref_func");
67+
auto* exported = wasm.getFunction("exported");
68+
ASSERT_NE(tableFunc, nullptr);
69+
ASSERT_NE(refFunc, nullptr);
70+
ASSERT_NE(exported, nullptr);
71+
72+
EXPECT_THAT(ModuleUtils::getReferencedFuncs(wasm),
73+
UnorderedElementsAre(tableFunc, refFunc, exported));
74+
}
75+
76+
} // namespace

0 commit comments

Comments
 (0)