summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md5
-rw-r--r--src/passes/Asyncify.cpp135
-rw-r--r--test/passes/asyncify_pass-arg=asyncify-addlist@foo.txt162
-rw-r--r--test/passes/asyncify_pass-arg=asyncify-addlist@foo.wast13
-rw-r--r--test/passes/asyncify_pass-arg=asyncify-asserts_pass-arg=asyncify-onlylist@waka.txt (renamed from test/passes/asyncify_pass-arg=asyncify-asserts_pass-arg=asyncify-whitelist@waka.txt)0
-rw-r--r--test/passes/asyncify_pass-arg=asyncify-asserts_pass-arg=asyncify-onlylist@waka.wast (renamed from test/passes/asyncify_pass-arg=asyncify-asserts_pass-arg=asyncify-whitelist@waka.wast)0
-rw-r--r--test/passes/asyncify_pass-arg=asyncify-onlylist@foo,bar.txt (renamed from test/passes/asyncify_pass-arg=asyncify-whitelist@foo,bar.txt)0
-rw-r--r--test/passes/asyncify_pass-arg=asyncify-onlylist@foo,bar.wast (renamed from test/passes/asyncify_pass-arg=asyncify-whitelist@foo,bar.wast)2
-rw-r--r--test/unit/test_asyncify.py38
9 files changed, 291 insertions, 64 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0af5ed0f5..cc458ac19 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,11 @@ full changeset diff at the end of each section.
Current Trunk
-------------
+- Add Asyncify "add list" that adds to the list of functions to be instrumented.
+ Rename old lists to be clearer and more consistent with that, so now there is
+ "remove list" to remove, "add list" to add, and "only list" which if set means
+ that only those functions should be instrumented and nothing else.
+
v94
---
diff --git a/src/passes/Asyncify.cpp b/src/passes/Asyncify.cpp
index 62b14531a..8dcbb01bb 100644
--- a/src/passes/Asyncify.cpp
+++ b/src/passes/Asyncify.cpp
@@ -225,33 +225,45 @@
// 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
+// --pass-arg=asyncify-asserts
//
-// 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. '*' wildcard matching supported.
+// This enables extra asserts in the output, like checking if we in
+// an unwind/rewind in an invalid place (this can be helpful for manual
+// tweaking of the only-list / remove-list, see later).
//
-// As with --asyncify-imports, you can use a response file here.
+// For manual fine-tuning of the list of instrumented functions, there are lists
+// that you can set. These must be used carefully, as misuse can break your
+// application - for example, if a function is called that should be
+// instrumented but isn't because of these options, then bad things can happen.
+// Note that even if you test this locally then users running your code in
+// production may reach other code paths. Use these carefully!
//
-// --pass-arg=asyncify-whitelist@name1,name2,name3
+// Each of the lists can be used with a response file (@filename, which is then
+// loaded from the file). You can also use '*' wildcard matching in the lists.
//
-// 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. '*' wildcard matching supported.
+// --pass-arg=asyncify-removelist@name1,name2,name3
//
-// As with --asyncify-imports, you can use a response file here.
+// If the "remove-list" is provided, then the functions in it will be
+// *removed* from the list of instrumented functions. That is, they won't
+// be instrumented even if it looks like they need to be. This can be
+// useful if you know things the safe whole-program analysis doesn't, e.g.
+// that certain code paths will not be taken, where certain indirect calls
+// will go, etc.
//
-// --pass-arg=asyncify-asserts
+// --pass-arg=asyncify-addlist@name1,name2,name3
//
-// This enables extra asserts in the output, like checking if we in
-// an unwind/rewind in an invalid place (this can be helpful for manual
-// tweaking of the whitelist/blacklist).
+// If the "add-list" is provided, then the functions in the list will be
+// *added* to the list of instrumented functions, that is, they will be
+// instrumented even if otherwise we think they don't need to be. As by
+// default everything will be instrumented in the safest way possible,
+// this is only useful if you use ignore-indirect and use this to fix up
+// some indirect calls that *do* need to be instrumented, or if you will
+// do some later transform of the code that adds more call paths, etc.
+//
+// --pass-arg=asyncify-onlylist@name1,name2,name3
+//
+// If the "only-list" is provided, then *only* the functions in the list
+// will be instrumented, and nothing else.
//
// TODO When wasm has GC, extending the live ranges of locals can keep things
// alive unnecessarily. We may want to set locals to null at the end
@@ -465,7 +477,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;
- bool inBlacklist = false;
+ bool inRemoveList = false;
};
typedef std::map<Function*, Info> Map;
@@ -475,14 +487,16 @@ public:
ModuleAnalyzer(Module& module,
std::function<bool(Name, Name)> canImportChangeState,
bool canIndirectChangeState,
- const String::Split& blacklistInput,
- const String::Split& whitelistInput,
+ const String::Split& removeListInput,
+ const String::Split& addListInput,
+ const String::Split& onlyListInput,
bool asserts)
: module(module), canIndirectChangeState(canIndirectChangeState),
fakeGlobals(module), asserts(asserts) {
- PatternMatcher blacklist("black", module, blacklistInput);
- PatternMatcher whitelist("white", module, whitelistInput);
+ PatternMatcher removeList("remove", module, removeListInput);
+ PatternMatcher addList("add", module, addListInput);
+ PatternMatcher onlyList("only", module, onlyListInput);
// Rename the asyncify imports so their internal name matches the
// convention. This makes replacing them with the implementations
@@ -574,12 +588,12 @@ public:
}
});
- // Functions in the blacklist are assumed to not change the state.
+ // Functions in the remove-list are assumed to not change the state.
for (auto& pair : scanner.map) {
auto* func = pair.first;
auto& info = pair.second;
- if (blacklist.match(func->name)) {
- info.inBlacklist = true;
+ if (removeList.match(func->name)) {
+ info.inRemoveList = true;
info.canChangeState = false;
}
}
@@ -609,24 +623,33 @@ public:
scanner.propagateBack([](const Info& info) { return info.canChangeState; },
[](const Info& info) {
return !info.isBottomMostRuntime &&
- !info.inBlacklist;
+ !info.inRemoveList;
},
[](Info& info) { info.canChangeState = true; },
scanner.IgnoreIndirectCalls);
map.swap(scanner.map);
- if (!whitelistInput.empty()) {
- // Only the functions in the whitelist can change the state.
+ if (!onlyListInput.empty()) {
+ // Only the functions in the only-list can change the state.
for (auto& func : module.functions) {
if (!func->imported()) {
- map[func.get()].canChangeState = whitelist.match(func->name);
+ map[func.get()].canChangeState = onlyList.match(func->name);
}
}
}
- blacklist.checkPatternsMatches();
- whitelist.checkPatternsMatches();
+ if (!addListInput.empty()) {
+ for (auto& func : module.functions) {
+ if (!func->imported() && addList.match(func->name)) {
+ map[func.get()].canChangeState = true;
+ }
+ }
+ }
+
+ removeList.checkPatternsMatches();
+ addList.checkPatternsMatches();
+ onlyList.checkPatternsMatches();
}
bool needsInstrumentation(Function* func) {
@@ -977,7 +1000,7 @@ private:
}
// Given a function that is not instrumented - because we proved it doesn't
- // need it, or depending on the whitelist/blacklist - add assertions that
+ // need it, or depending on the only-list / remove-list - add assertions that
// verify that property at runtime.
// Note that it is ok to run code while sleeping (if you are careful not
// to break assumptions in the program!) - so what is actually
@@ -1266,23 +1289,38 @@ struct Asyncify : public Pass {
String::Split listedImports(stateChangingImports, ",");
auto ignoreIndirect = runner->options.getArgumentOrDefault(
"asyncify-ignore-indirect", "") == "";
- String::Split blacklist(
- String::trim(read_possible_response_file(
- runner->options.getArgumentOrDefault("asyncify-blacklist", ""))),
- ",");
- String::Split whitelist(
+ std::string removeListInput =
+ runner->options.getArgumentOrDefault("asyncify-removelist", "");
+ if (removeListInput.empty()) {
+ // Support old name for now to avoid immediate breakage TODO remove
+ removeListInput =
+ runner->options.getArgumentOrDefault("asyncify-blacklist", "");
+ }
+ String::Split removeList(
+ String::trim(read_possible_response_file(removeListInput)), ",");
+ String::Split addList(
String::trim(read_possible_response_file(
- runner->options.getArgumentOrDefault("asyncify-whitelist", ""))),
+ runner->options.getArgumentOrDefault("asyncify-addlist", ""))),
",");
+ std::string onlyListInput =
+ runner->options.getArgumentOrDefault("asyncify-onlylist", "");
+ if (onlyListInput.empty()) {
+ // Support old name for now to avoid immediate breakage TODO remove
+ onlyListInput =
+ runner->options.getArgumentOrDefault("asyncify-whitelist", "");
+ }
+ String::Split onlyList(
+ String::trim(read_possible_response_file(onlyListInput)), ",");
auto asserts =
runner->options.getArgumentOrDefault("asyncify-asserts", "") != "";
- blacklist = handleBracketingOperators(blacklist);
- whitelist = handleBracketingOperators(whitelist);
+ removeList = handleBracketingOperators(removeList);
+ addList = handleBracketingOperators(addList);
+ onlyList = handleBracketingOperators(onlyList);
- if (!blacklist.empty() && !whitelist.empty()) {
- Fatal() << "It makes no sense to use both a blacklist and a whitelist "
- "with asyncify.";
+ if (!onlyList.empty() && (!removeList.empty() || !addList.empty())) {
+ Fatal() << "It makes no sense to use both an asyncify only-list together "
+ "with another list.";
}
auto canImportChangeState = [&](Name module, Name base) {
@@ -1302,8 +1340,9 @@ struct Asyncify : public Pass {
ModuleAnalyzer analyzer(*module,
canImportChangeState,
ignoreIndirect,
- blacklist,
- whitelist,
+ removeList,
+ addList,
+ onlyList,
asserts);
// Add necessary globals before we emit code to use them.
diff --git a/test/passes/asyncify_pass-arg=asyncify-addlist@foo.txt b/test/passes/asyncify_pass-arg=asyncify-addlist@foo.txt
new file mode 100644
index 000000000..8119cfc6a
--- /dev/null
+++ b/test/passes/asyncify_pass-arg=asyncify-addlist@foo.txt
@@ -0,0 +1,162 @@
+(module
+ (type $none_=>_none (func))
+ (type $i32_=>_none (func (param i32)))
+ (type $none_=>_i32 (func (result i32)))
+ (import "env" "import" (func $import))
+ (memory $0 1 2)
+ (global $__asyncify_state (mut i32) (i32.const 0))
+ (global $__asyncify_data (mut i32) (i32.const 0))
+ (export "asyncify_start_unwind" (func $asyncify_start_unwind))
+ (export "asyncify_stop_unwind" (func $asyncify_stop_unwind))
+ (export "asyncify_start_rewind" (func $asyncify_start_rewind))
+ (export "asyncify_stop_rewind" (func $asyncify_stop_rewind))
+ (export "asyncify_get_state" (func $asyncify_get_state))
+ (func $foo
+ (local $0 i32)
+ (local $1 i32)
+ (if
+ (i32.eq
+ (global.get $__asyncify_state)
+ (i32.const 2)
+ )
+ (nop)
+ )
+ (local.tee $0
+ (block $__asyncify_unwind
+ (block
+ (block
+ (if
+ (i32.eq
+ (global.get $__asyncify_state)
+ (i32.const 2)
+ )
+ (block
+ (i32.store
+ (global.get $__asyncify_data)
+ (i32.add
+ (i32.load
+ (global.get $__asyncify_data)
+ )
+ (i32.const -4)
+ )
+ )
+ (local.set $1
+ (i32.load
+ (i32.load
+ (global.get $__asyncify_data)
+ )
+ )
+ )
+ )
+ )
+ (if
+ (i32.eq
+ (global.get $__asyncify_state)
+ (i32.const 0)
+ )
+ (call $nothing)
+ )
+ )
+ (return)
+ )
+ )
+ )
+ (block
+ (i32.store
+ (i32.load
+ (global.get $__asyncify_data)
+ )
+ (local.get $0)
+ )
+ (i32.store
+ (global.get $__asyncify_data)
+ (i32.add
+ (i32.load
+ (global.get $__asyncify_data)
+ )
+ (i32.const 4)
+ )
+ )
+ )
+ (nop)
+ )
+ (func $bar
+ (call $nothing)
+ )
+ (func $nothing
+ (nop)
+ )
+ (func $asyncify_start_unwind (param $0 i32)
+ (global.set $__asyncify_state
+ (i32.const 1)
+ )
+ (global.set $__asyncify_data
+ (local.get $0)
+ )
+ (if
+ (i32.gt_u
+ (i32.load
+ (global.get $__asyncify_data)
+ )
+ (i32.load offset=4
+ (global.get $__asyncify_data)
+ )
+ )
+ (unreachable)
+ )
+ )
+ (func $asyncify_stop_unwind
+ (global.set $__asyncify_state
+ (i32.const 0)
+ )
+ (if
+ (i32.gt_u
+ (i32.load
+ (global.get $__asyncify_data)
+ )
+ (i32.load offset=4
+ (global.get $__asyncify_data)
+ )
+ )
+ (unreachable)
+ )
+ )
+ (func $asyncify_start_rewind (param $0 i32)
+ (global.set $__asyncify_state
+ (i32.const 2)
+ )
+ (global.set $__asyncify_data
+ (local.get $0)
+ )
+ (if
+ (i32.gt_u
+ (i32.load
+ (global.get $__asyncify_data)
+ )
+ (i32.load offset=4
+ (global.get $__asyncify_data)
+ )
+ )
+ (unreachable)
+ )
+ )
+ (func $asyncify_stop_rewind
+ (global.set $__asyncify_state
+ (i32.const 0)
+ )
+ (if
+ (i32.gt_u
+ (i32.load
+ (global.get $__asyncify_data)
+ )
+ (i32.load offset=4
+ (global.get $__asyncify_data)
+ )
+ )
+ (unreachable)
+ )
+ )
+ (func $asyncify_get_state (result i32)
+ (global.get $__asyncify_state)
+ )
+)
diff --git a/test/passes/asyncify_pass-arg=asyncify-addlist@foo.wast b/test/passes/asyncify_pass-arg=asyncify-addlist@foo.wast
new file mode 100644
index 000000000..beb89a66a
--- /dev/null
+++ b/test/passes/asyncify_pass-arg=asyncify-addlist@foo.wast
@@ -0,0 +1,13 @@
+(module
+ (memory 1 2)
+ (import "env" "import" (func $import))
+ (func $foo ;; doesn't look like it needs instrumentation, but in add list
+ (call $nothing)
+ )
+ (func $bar ;; doesn't look like it needs instrumentation, and not in add list
+ (call $nothing)
+ )
+ (func $nothing
+ )
+)
+
diff --git a/test/passes/asyncify_pass-arg=asyncify-asserts_pass-arg=asyncify-whitelist@waka.txt b/test/passes/asyncify_pass-arg=asyncify-asserts_pass-arg=asyncify-onlylist@waka.txt
index 08eda7a63..08eda7a63 100644
--- a/test/passes/asyncify_pass-arg=asyncify-asserts_pass-arg=asyncify-whitelist@waka.txt
+++ b/test/passes/asyncify_pass-arg=asyncify-asserts_pass-arg=asyncify-onlylist@waka.txt
diff --git a/test/passes/asyncify_pass-arg=asyncify-asserts_pass-arg=asyncify-whitelist@waka.wast b/test/passes/asyncify_pass-arg=asyncify-asserts_pass-arg=asyncify-onlylist@waka.wast
index 7d67c89ed..7d67c89ed 100644
--- a/test/passes/asyncify_pass-arg=asyncify-asserts_pass-arg=asyncify-whitelist@waka.wast
+++ b/test/passes/asyncify_pass-arg=asyncify-asserts_pass-arg=asyncify-onlylist@waka.wast
diff --git a/test/passes/asyncify_pass-arg=asyncify-whitelist@foo,bar.txt b/test/passes/asyncify_pass-arg=asyncify-onlylist@foo,bar.txt
index 30d4f0f10..30d4f0f10 100644
--- a/test/passes/asyncify_pass-arg=asyncify-whitelist@foo,bar.txt
+++ b/test/passes/asyncify_pass-arg=asyncify-onlylist@foo,bar.txt
diff --git a/test/passes/asyncify_pass-arg=asyncify-whitelist@foo,bar.wast b/test/passes/asyncify_pass-arg=asyncify-onlylist@foo,bar.wast
index 355ca1b11..a152a5376 100644
--- a/test/passes/asyncify_pass-arg=asyncify-whitelist@foo,bar.wast
+++ b/test/passes/asyncify_pass-arg=asyncify-onlylist@foo,bar.wast
@@ -11,7 +11,7 @@
(call $import)
)
(func $other1
- (call $foo) ;; even though we call foo, we are not in the whitelist, so do not instrument us
+ (call $foo) ;; even though we call foo, we are not in the only list, so do not instrument us
)
(func $other2
(call $baz)
diff --git a/test/unit/test_asyncify.py b/test/unit/test_asyncify.py
index 331e1a9e2..55b198cbb 100644
--- a/test/unit/test_asyncify.py
+++ b/test/unit/test_asyncify.py
@@ -41,17 +41,17 @@ class AsyncifyTest(utils.BinaryenTestCase):
def test_asyncify_list_bad(self):
for arg, warning in [
- ('--pass-arg=asyncify-blacklist@nonexistent', 'nonexistent'),
- ('--pass-arg=asyncify-whitelist@nonexistent', 'nonexistent'),
- ('--pass-arg=asyncify-blacklist@main', None),
- ('--pass-arg=asyncify-whitelist@main', None),
- ('--pass-arg=asyncify-blacklist@m*n', None),
- ('--pass-arg=asyncify-whitelist@m*n', None),
- ('--pass-arg=asyncify-whitelist@main*', None),
- ('--pass-arg=asyncify-whitelist@*main', None),
- ('--pass-arg=asyncify-blacklist@non*existent', 'non*existent'),
- ('--pass-arg=asyncify-whitelist@non*existent', 'non*existent'),
- ('--pass-arg=asyncify-whitelist@DOS_ReadFile(unsigned short, unsigned char*, unsigned short*, bool)', None),
+ ('--pass-arg=asyncify-removelist@nonexistent', 'nonexistent'),
+ ('--pass-arg=asyncify-onlylist@nonexistent', 'nonexistent'),
+ ('--pass-arg=asyncify-removelist@main', None),
+ ('--pass-arg=asyncify-onlylist@main', None),
+ ('--pass-arg=asyncify-removelist@m*n', None),
+ ('--pass-arg=asyncify-onlylist@m*n', None),
+ ('--pass-arg=asyncify-onlylist@main*', None),
+ ('--pass-arg=asyncify-onlylist@*main', None),
+ ('--pass-arg=asyncify-removelist@non*existent', 'non*existent'),
+ ('--pass-arg=asyncify-onlylist@non*existent', 'non*existent'),
+ ('--pass-arg=asyncify-onlylist@DOS_ReadFile(unsigned short, unsigned char*, unsigned short*, bool)', None),
]:
print(arg, warning)
err = shared.run_process(shared.WASM_OPT + ['-q', self.input_path('asyncify-pure.wat'), '--asyncify', arg], stdout=subprocess.PIPE, stderr=subprocess.PIPE).stderr.strip()
@@ -61,10 +61,18 @@ class AsyncifyTest(utils.BinaryenTestCase):
else:
self.assertNotIn('warning', err)
- def test_asyncify_blacklist_and_whitelist(self):
- proc = shared.run_process(shared.WASM_OPT + [self.input_path('asyncify-pure.wat'), '--asyncify', '--pass-arg=asyncify-whitelist@main', '--pass-arg=asyncify-blacklist@main'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=False)
- self.assertNotEqual(proc.returncode, 0, 'must error on using both lists at once')
- self.assertIn('It makes no sense to use both a blacklist and a whitelist with asyncify', proc.stdout)
+ def test_asyncify_onlylist_and_other(self):
+ def test(list_name):
+ args = shared.WASM_OPT + [self.input_path('asyncify-pure.wat'),
+ '--asyncify',
+ '--pass-arg=asyncify-onlylist@main',
+ '--pass-arg=asyncify-%slist@main' % list_name]
+ proc = shared.run_process(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=False)
+ self.assertNotEqual(proc.returncode, 0, 'must error on using both lists at once')
+ self.assertIn('It makes no sense to use both an asyncify only-list together with another list', proc.stdout)
+
+ test('remove')
+ test('add')
def test_asyncify_imports(self):
def test(args):