summaryrefslogtreecommitdiff
path: root/src/passes/Asyncify.cpp
diff options
context:
space:
mode:
authorAlon Zakai <azakai@google.com>2019-11-18 19:03:39 -0800
committerGitHub <noreply@github.com>2019-11-18 19:03:39 -0800
commit9d21a951dfc60a0fed861763f50f4130dd0a42b6 (patch)
treee6f0e840ca0d4db7f18b381a279c32b1e1c2feac /src/passes/Asyncify.cpp
parentee3022fd3be7ba0b9e7a12e426a8246134134cc4 (diff)
downloadbinaryen-9d21a951dfc60a0fed861763f50f4130dd0a42b6.tar.gz
binaryen-9d21a951dfc60a0fed861763f50f4130dd0a42b6.tar.bz2
binaryen-9d21a951dfc60a0fed861763f50f4130dd0a42b6.zip
Refactor a CallGraphPropertyAnalysis helper [NFC] (#2441)
This moves code out of Asyncify into a general helper class. The class automates scanning the functions for a property, then propagating it to functions that call them. In Asyncify, the property is "may call something that leads to sleep", and we propagate backwards to callers, to find all those that may sleep. This will be useful in a future exceptions-optimizing pass I want to write, where the property will be "may throw". We will then be able to remove exceptions overhead in cases that definitely do not throw.
Diffstat (limited to 'src/passes/Asyncify.cpp')
-rw-r--r--src/passes/Asyncify.cpp74
1 files changed, 33 insertions, 41 deletions
diff --git a/src/passes/Asyncify.cpp b/src/passes/Asyncify.cpp
index c4e1c58bd..dd8845577 100644
--- a/src/passes/Asyncify.cpp
+++ b/src/passes/Asyncify.cpp
@@ -261,7 +261,6 @@
#include "pass.h"
#include "support/file.h"
#include "support/string.h"
-#include "support/unique_deferring_queue.h"
#include "wasm-builder.h"
#include "wasm.h"
@@ -405,7 +404,8 @@ class ModuleAnalyzer {
Module& module;
bool canIndirectChangeState;
- struct Info {
+ struct Info
+ : public ModuleUtils::CallGraphPropertyAnalysis<Info>::FunctionInfo {
// If this function can start an unwind/rewind.
bool canChangeState = false;
// If this function is part of the runtime that receives an unwinding
@@ -419,8 +419,7 @@ class ModuleAnalyzer {
// the top-most part is still marked as changing the state; so things
// that call it are instrumented. This is not done for the bottom.
bool isTopMostRuntime = false;
- std::set<Function*> callsTo;
- std::set<Function*> calledBy;
+ bool inBlacklist = false;
};
typedef std::map<Function*, Info> Map;
@@ -443,7 +442,7 @@ public:
// Also handle the asyncify imports, removing them (as we will implement
// them later), and replace calls to them with calls to the later proper
// name.
- ModuleUtils::ParallelFunctionMap<Info> scanner(
+ ModuleUtils::CallGraphPropertyAnalysis<Info> scanner(
module, [&](Function* func, Info& info) {
if (func->imported()) {
// The relevant asyncify imports can definitely change the state.
@@ -482,9 +481,7 @@ public:
Fatal() << "call to unidenfied asyncify import: "
<< target->base;
}
- return;
}
- info->callsTo.insert(target);
}
void visitCallIndirect(CallIndirect* curr) {
if (curr->isReturn) {
@@ -515,52 +512,47 @@ public:
}
});
- map.swap(scanner.map);
-
// Functions in the blacklist are assumed to not change the state.
- for (auto& name : blacklist.names) {
- if (auto* func = module.getFunctionOrNull(name)) {
- map[func].canChangeState = false;
+ for (auto& pair : scanner.map) {
+ auto* func = pair.first;
+ auto& info = pair.second;
+ if (blacklist.match(func->name)) {
+ info.inBlacklist = true;
+ info.canChangeState = false;
}
}
- // Remove the asyncify imports, if any.
- for (auto& pair : map) {
+ // Remove the asyncify imports, if any, and any calls to them.
+ std::vector<Name> funcsToDelete;
+ for (auto& pair : scanner.map) {
auto* func = pair.first;
+ auto& callsTo = pair.second.callsTo;
if (func->imported() && func->module == ASYNCIFY) {
- module.removeFunction(func->name);
+ funcsToDelete.push_back(func->name);
}
- }
-
- // Find what is called by what.
- for (auto& pair : map) {
- auto* func = pair.first;
- auto& info = pair.second;
- for (auto* target : info.callsTo) {
- map[target].calledBy.insert(func);
+ std::vector<Function*> callersToDelete;
+ for (auto* target : callsTo) {
+ if (target->imported() && target->module == ASYNCIFY) {
+ callersToDelete.push_back(target);
+ }
}
- }
-
- // Close over, finding all functions that can reach something that can
- // change the state.
- // The work queue contains an item we just learned can change the state.
- UniqueDeferredQueue<Function*> work;
- for (auto& func : module.functions) {
- if (map[func.get()].canChangeState) {
- work.push(func.get());
+ for (auto* target : callersToDelete) {
+ callsTo.erase(target);
}
}
- while (!work.empty()) {
- auto* func = work.pop();
- for (auto* caller : map[func].calledBy) {
- if (!map[caller].canChangeState && !map[caller].isBottomMostRuntime &&
- !blacklist.match(caller->name)) {
- map[caller].canChangeState = true;
- work.push(caller);
- }
- }
+ for (auto name : funcsToDelete) {
+ module.removeFunction(name);
}
+ scanner.propagateBack([](const Info& info) { return info.canChangeState; },
+ [](const Info& info) {
+ return !info.isBottomMostRuntime &&
+ !info.inBlacklist;
+ },
+ [](Info& info) { info.canChangeState = true; });
+
+ map.swap(scanner.map);
+
if (!whitelistInput.empty()) {
// Only the functions in the whitelist can change the state.
for (auto& func : module.functions) {