summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ast_utils.h28
-rw-r--r--src/passes/CMakeLists.txt1
-rw-r--r--src/passes/RemoveUnusedFunctions.cpp58
-rw-r--r--test/passes/remove-unused-functions.txt31
-rw-r--r--test/passes/remove-unused-functions.wast46
5 files changed, 164 insertions, 0 deletions
diff --git a/src/ast_utils.h b/src/ast_utils.h
index 730ef35a6..a43fc6b2f 100644
--- a/src/ast_utils.h
+++ b/src/ast_utils.h
@@ -46,6 +46,34 @@ struct BreakSeeker : public PostWalker<BreakSeeker, Visitor<BreakSeeker>> {
}
};
+// Finds all functions that are reachable via direct calls.
+
+struct DirectCallGraphAnalyzer : public PostWalker<DirectCallGraphAnalyzer, Visitor<DirectCallGraphAnalyzer>> {
+ Module *module;
+ std::vector<Function*> queue;
+ std::unordered_set<Function*> reachable;
+
+ DirectCallGraphAnalyzer(Module* module, const std::vector<Function*>& root) : module(module) {
+ for (auto* curr : root) {
+ queue.push_back(curr);
+ }
+ while (queue.size()) {
+ auto* curr = queue.back();
+ queue.pop_back();
+ if (reachable.count(curr) == 0) {
+ reachable.insert(curr);
+ walk(curr->body);
+ }
+ }
+ }
+ void visitCall(Call *curr) {
+ auto* target = module->getFunction(curr->target);
+ if (reachable.count(target) == 0) {
+ queue.push_back(target);
+ }
+ }
+};
+
// Look for side effects, including control flow
// TODO: optimize
diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt
index 2b70fb9d4..0ec2686da 100644
--- a/src/passes/CMakeLists.txt
+++ b/src/passes/CMakeLists.txt
@@ -13,6 +13,7 @@ SET(passes_SOURCES
RemoveImports.cpp
RemoveUnusedBrs.cpp
RemoveUnusedNames.cpp
+ RemoveUnusedFunctions.cpp
ReorderLocals.cpp
ReorderFunctions.cpp
SimplifyLocals.cpp
diff --git a/src/passes/RemoveUnusedFunctions.cpp b/src/passes/RemoveUnusedFunctions.cpp
new file mode 100644
index 000000000..1e06b428c
--- /dev/null
+++ b/src/passes/RemoveUnusedFunctions.cpp
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2016 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.
+ */
+
+//
+// Removes functions that are never used.
+//
+
+
+#include <memory>
+
+#include "wasm.h"
+#include "pass.h"
+#include "ast_utils.h"
+
+namespace wasm {
+
+struct RemoveUnusedFunctions : public Pass {
+ void run(PassRunner* runner, Module* module) override {
+ std::vector<Function*> root;
+ // Module start is a root.
+ if (module->start.is()) {
+ root.push_back(module->getFunction(module->start));
+ }
+ // Exports are roots.
+ for (auto& curr : module->exports) {
+ root.push_back(module->getFunction(curr->value));
+ }
+ // For now, all functions that can be called indirectly are marked as roots.
+ for (auto& curr : module->table.names) {
+ root.push_back(module->getFunction(curr));
+ }
+ // Compute function reachability starting from the root set.
+ DirectCallGraphAnalyzer analyzer(module, root);
+ // Remove unreachable functions.
+ auto& v = module->functions;
+ v.erase(std::remove_if(v.begin(), v.end(), [&](const std::unique_ptr<Function>& curr) {
+ return analyzer.reachable.count(curr.get()) == 0;
+ }), v.end());
+ assert(module->functions.size() == analyzer.reachable.size());
+ }
+};
+
+static RegisterPass<RemoveUnusedFunctions> registerPass("remove-unused-functions", "removes unused functions");
+
+} // namespace wasm
diff --git a/test/passes/remove-unused-functions.txt b/test/passes/remove-unused-functions.txt
new file mode 100644
index 000000000..171997e14
--- /dev/null
+++ b/test/passes/remove-unused-functions.txt
@@ -0,0 +1,31 @@
+(module
+ (memory 0)
+ (start $start)
+ (export "exported" $exported)
+ (table $called_indirect)
+ (func $start
+ (call $called0)
+ )
+ (func $called0
+ (call $called1)
+ )
+ (func $called1
+ (nop)
+ )
+ (func $called_indirect
+ (nop)
+ )
+ (func $exported
+ (call $called2)
+ )
+ (func $called2
+ (call $called2)
+ (call $called3)
+ )
+ (func $called3
+ (call $called4)
+ )
+ (func $called4
+ (call $called3)
+ )
+)
diff --git a/test/passes/remove-unused-functions.wast b/test/passes/remove-unused-functions.wast
new file mode 100644
index 000000000..4c89804bf
--- /dev/null
+++ b/test/passes/remove-unused-functions.wast
@@ -0,0 +1,46 @@
+(module
+ (start $start)
+ (export "exported" $exported)
+ (table $called_indirect)
+ (func $start
+ (call $called0)
+ )
+ (func $called0
+ (call $called1)
+ )
+ (func $called1
+ (nop)
+ )
+ (func $called_indirect
+ (nop)
+ )
+ (func $exported
+ (call $called2)
+ )
+ (func $called2
+ (call $called2)
+ (call $called3)
+ )
+ (func $called3
+ (call $called4)
+ )
+ (func $called4
+ (call $called3)
+ )
+ (func $remove0
+ (call $remove1)
+ )
+ (func $remove1
+ (nop)
+ )
+ (func $remove2
+ (call $remove2)
+ )
+ (func $remove3
+ (call $remove4)
+ )
+ (func $remove4
+ (call $remove3)
+ )
+)
+