summaryrefslogtreecommitdiff
path: root/src/passes/Asyncify.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/passes/Asyncify.cpp')
-rw-r--r--src/passes/Asyncify.cpp113
1 files changed, 97 insertions, 16 deletions
diff --git a/src/passes/Asyncify.cpp b/src/passes/Asyncify.cpp
index 98b4425b1..7d6dd9f76 100644
--- a/src/passes/Asyncify.cpp
+++ b/src/passes/Asyncify.cpp
@@ -212,6 +212,24 @@
// If that is true for your codebase, then this can be extremely useful
// as otherwise it looks like any indirect call can go to a lot of places.
//
+// --pass-arg=asyncify-blacklist@name1,name2,name3
+//
+// If the blacklist is provided, then the functions in it will not be
+// instrumented even if it looks like they need to. This can be useful
+// if you know things the whole-program analysis doesn't, like if you
+// know certain indirect calls are safe and won't unwind. But if you
+// get the list wrong things will break (and in a production build user
+// input might reach code paths you missed during testing, so it's hard
+// to know you got this right), so this is not recommended unless you
+// really know what are doing, and need to optimize every bit of speed
+// and size.
+//
+// --pass-arg=asyncify-whitelist@name1,name2,name3
+//
+// If the whitelist is provided, then only the functions in the list
+// will be instrumented. Like the blacklist, getting this wrong will
+// break your application.
+//
#include "ir/effects.h"
#include "ir/literal-utils.h"
@@ -327,9 +345,15 @@ class ModuleAnalyzer {
public:
ModuleAnalyzer(Module& module,
std::function<bool(Name, Name)> canImportChangeState,
- bool canIndirectChangeState)
+ bool canIndirectChangeState,
+ const String::Split& blacklistInput,
+ const String::Split& whitelistInput)
: module(module), canIndirectChangeState(canIndirectChangeState),
globals(module) {
+
+ blacklist.insert(blacklistInput.begin(), blacklistInput.end());
+ whitelist.insert(whitelistInput.begin(), whitelistInput.end());
+
// Scan to see which functions can directly change the state.
// Also handle the asyncify imports, removing them (as we will implement
// them later), and replace calls to them with calls to the later proper
@@ -388,11 +412,13 @@ public:
}
Info* info;
Module* module;
+ ModuleAnalyzer* analyzer;
bool canIndirectChangeState;
};
Walker walker;
walker.info = &info;
walker.module = &module;
+ walker.analyzer = this;
walker.canIndirectChangeState = canIndirectChangeState;
walker.walk(func->body);
@@ -406,6 +432,13 @@ public:
map.swap(scanner.map);
+ // Functions in the blacklist are assumed to not change the state.
+ for (auto& name : blacklist) {
+ if (auto* func = module.getFunctionOrNull(name)) {
+ map[func].canChangeState = false;
+ }
+ }
+
// Remove the asyncify imports, if any.
for (auto& pair : map) {
auto* func = pair.first;
@@ -435,12 +468,22 @@ public:
while (!work.empty()) {
auto* func = work.pop();
for (auto* caller : map[func].calledBy) {
- if (!map[caller].canChangeState && !map[caller].isBottomMostRuntime) {
+ if (!map[caller].canChangeState && !map[caller].isBottomMostRuntime &&
+ !blacklist.count(caller->name)) {
map[caller].canChangeState = true;
work.push(caller);
}
}
}
+
+ if (!whitelist.empty()) {
+ // Only the functions in the whitelist can change the state.
+ for (auto& func : module.functions) {
+ if (!func->imported()) {
+ map[func.get()].canChangeState = whitelist.count(func->name) > 0;
+ }
+ }
+ }
}
bool needsInstrumentation(Function* func) {
@@ -481,6 +524,7 @@ public:
// TODO optimize the other case, at least by type
}
Module* module;
+ ModuleAnalyzer* analyzer;
Map* map;
bool canIndirectChangeState;
bool canChangeState = false;
@@ -488,6 +532,7 @@ public:
};
Walker walker;
walker.module = &module;
+ walker.analyzer = this;
walker.map = &map;
walker.canIndirectChangeState = canIndirectChangeState;
walker.walk(curr);
@@ -498,6 +543,8 @@ public:
}
GlobalHelper globals;
+ std::set<Name> blacklist;
+ std::set<Name> whitelist;
};
// Checks if something performs a call: either a direct or indirect call,
@@ -987,23 +1034,57 @@ struct Asyncify : public Pass {
String::Split listedImports(stateChangingImports, ",");
auto ignoreIndirect =
runner->options.getArgumentOrDefault("asyncify-ignore-indirect", "");
+ String::Split blacklist(
+ runner->options.getArgumentOrDefault("asyncify-blacklist", ""), ",");
+ String::Split whitelist(
+ runner->options.getArgumentOrDefault("asyncify-whitelist", ""), ",");
+
+ // The lists contain human-readable strings. Turn them into the internal
+ // escaped names for later comparisons
+ auto processList = [module](String::Split& list, const std::string& which) {
+ for (auto& name : list) {
+ auto escaped = WasmBinaryBuilder::escape(name);
+ auto* func = module->getFunctionOrNull(escaped);
+ if (!func) {
+ std::cerr << "warning: Asyncify " << which
+ << "list contained a non-existing function name: " << name
+ << " (" << escaped << ")\n";
+ } else if (func->imported()) {
+ Fatal() << "Asyncify " << which
+ << "list contained an imported function name (use the import "
+ "list for imports): "
+ << name << '\n';
+ }
+ name = escaped.str;
+ }
+ };
+ processList(blacklist, "black");
+ processList(whitelist, "white");
+
+ if (!blacklist.empty() && !whitelist.empty()) {
+ Fatal() << "It makes no sense to use both a blacklist and a whitelist "
+ "with asyncify.";
+ }
+
+ auto canImportChangeState = [&](Name module, Name base) {
+ if (allImportsCanChangeState) {
+ return true;
+ }
+ std::string full = std::string(module.str) + '.' + base.str;
+ for (auto& listedImport : listedImports) {
+ if (String::wildcardMatch(listedImport, full)) {
+ return true;
+ }
+ }
+ return false;
+ };
// Scan the module.
ModuleAnalyzer analyzer(*module,
- [&](Name module, Name base) {
- if (allImportsCanChangeState) {
- return true;
- }
- std::string full =
- std::string(module.str) + '.' + base.str;
- for (auto& listedImport : listedImports) {
- if (String::wildcardMatch(listedImport, full)) {
- return true;
- }
- }
- return false;
- },
- ignoreIndirect == "");
+ canImportChangeState,
+ ignoreIndirect == "",
+ blacklist,
+ whitelist);
// Add necessary globals before we emit code to use them.
addGlobals(module);