Skip to content
54 changes: 42 additions & 12 deletions src/passes/Asyncify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,13 @@
// 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-propagate-addlist
//
// The default behaviour of the addlist does not propagate instrumentation
// status. If this option is set then functions which call a function in
// the addlist will also be instrumented, and those that call them and so
// on.
//
// --pass-arg=asyncify-onlylist@name1,name2,name3
//
// If the "only-list" is provided, then *only* the functions in the list
Expand Down Expand Up @@ -529,6 +536,7 @@ class ModuleAnalyzer {
bool canIndirectChangeState,
const String::Split& removeListInput,
const String::Split& addListInput,
bool propagateAddList,
const String::Split& onlyListInput,
bool asserts,
bool verbose)
Expand Down Expand Up @@ -671,6 +679,34 @@ class ModuleAnalyzer {
module.removeFunction(name);
}

auto handleAddList = [&](ModuleAnalyzer::Map& map) {
if (!addListInput.empty()) {
for (auto& func : module.functions) {
if (addList.match(func->name) && removeList.match(func->name)) {
Fatal() << func->name
<< " is found in the add-list and in the remove-list";
}

if (!func->imported() && addList.match(func->name)) {
auto& info = map[func.get()];
if (verbose && !info.canChangeState) {
std::cout << "[asyncify] " << func->name
<< " is in the add-list, add\n";
}
info.canChangeState = true;
info.addedFromList = true;
}
}
}
};

// When propagateAddList is enabled, we should check a add-list before
// scannerpropagateBack so that callers of functions in add-list should also
// be instrumented.
if (propagateAddList) {
handleAddList(scanner.map);
}

scanner.propagateBack([](const Info& info) { return info.canChangeState; },
[](const Info& info) {
return !info.isBottomMostRuntime &&
Expand Down Expand Up @@ -707,18 +743,10 @@ class ModuleAnalyzer {
}
}

if (!addListInput.empty()) {
for (auto& func : module.functions) {
if (!func->imported() && addList.match(func->name)) {
auto& info = map[func.get()];
if (verbose && !info.canChangeState) {
std::cout << "[asyncify] " << func->name
<< " is in the add-list, add\n";
}
info.canChangeState = true;
info.addedFromList = true;
}
}
// When propagateAddList is disabled, which is default behavior,
// functions in add-list are just prepended to instrumented functions.
if (!propagateAddList) {
handleAddList(map);
}

removeList.checkPatternsMatches();
Expand Down Expand Up @@ -1645,6 +1673,7 @@ struct Asyncify : public Pass {
auto verbose = options.hasArgument("asyncify-verbose");
auto relocatable = options.hasArgument("asyncify-relocatable");
auto secondaryMemory = options.hasArgument("asyncify-in-secondary-memory");
auto propagateAddList = options.hasArgument("asyncify-propagate-addlist");

// Ensure there is a memory, as we need it.
if (secondaryMemory) {
Expand Down Expand Up @@ -1687,6 +1716,7 @@ struct Asyncify : public Pass {
canIndirectChangeState,
removeList,
addList,
propagateAddList,
onlyList,
asserts,
verbose);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.

;; RUN: foreach %s %t wasm-opt --asyncify --pass-arg=asyncify-addlist@foo -S --pass-arg=asyncify-propagate-addlist -o - | filecheck %s

(module
(memory 1 2)
;; CHECK: (type $0 (func))

;; CHECK: (type $1 (func (param i32)))

;; CHECK: (type $2 (func (result i32)))

;; CHECK: (import "env" "import" (func $import))
(import "env" "import" (func $import))
;; CHECK: (global $__asyncify_state (mut i32) (i32.const 0))

;; CHECK: (global $__asyncify_data (mut i32) (i32.const 0))

;; CHECK: (memory $0 1 2)

;; CHECK: (export "asyncify_start_unwind" (func $asyncify_start_unwind))

;; CHECK: (export "asyncify_stop_unwind" (func $asyncify_stop_unwind))

;; CHECK: (export "asyncify_start_rewind" (func $asyncify_start_rewind))

;; CHECK: (export "asyncify_stop_rewind" (func $asyncify_stop_rewind))

;; CHECK: (export "asyncify_get_state" (func $asyncify_get_state))

;; CHECK: (func $foo
;; CHECK-NEXT: (local $0 i32)
;; CHECK-NEXT: (local $1 i32)
;; CHECK-NEXT: (if
;; CHECK-NEXT: (i32.eq
;; CHECK-NEXT: (global.get $__asyncify_state)
;; CHECK-NEXT: (i32.const 2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.tee $0
;; CHECK-NEXT: (block $__asyncify_unwind
;; CHECK-NEXT: (block
;; CHECK-NEXT: (block
;; CHECK-NEXT: (if
;; CHECK-NEXT: (i32.eq
;; CHECK-NEXT: (global.get $__asyncify_state)
;; CHECK-NEXT: (i32.const 2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (block
;; CHECK-NEXT: (i32.store
;; CHECK-NEXT: (global.get $__asyncify_data)
;; CHECK-NEXT: (i32.add
;; CHECK-NEXT: (i32.load
;; CHECK-NEXT: (global.get $__asyncify_data)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const -4)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.set $1
;; CHECK-NEXT: (i32.load
;; CHECK-NEXT: (i32.load
;; CHECK-NEXT: (global.get $__asyncify_data)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (if
;; CHECK-NEXT: (i32.eq
;; CHECK-NEXT: (global.get $__asyncify_state)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call $nothing)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (return)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (block
;; CHECK-NEXT: (i32.store
;; CHECK-NEXT: (i32.load
;; CHECK-NEXT: (global.get $__asyncify_data)
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.get $0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.store
;; CHECK-NEXT: (global.get $__asyncify_data)
;; CHECK-NEXT: (i32.add
;; CHECK-NEXT: (i32.load
;; CHECK-NEXT: (global.get $__asyncify_data)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 4)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
(func $foo ;; doesn't look like it needs instrumentation, but in add list
(call $nothing)
)
;; CHECK: (func $bar
;; CHECK-NEXT: (call $nothing)
;; CHECK-NEXT: )
(func $bar ;; doesn't look like it needs instrumentation, and not in add list
(call $nothing)
)
;; CHECK: (func $nothing
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
(func $nothing
)
;; CHECK: (func $call_foo
;; CHECK-NEXT: (local $0 i32)
;; CHECK-NEXT: (local $1 i32)
;; CHECK-NEXT: (if
;; CHECK-NEXT: (i32.eq
;; CHECK-NEXT: (global.get $__asyncify_state)
;; CHECK-NEXT: (i32.const 2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.set $0
;; CHECK-NEXT: (block $__asyncify_unwind (result i32)
;; CHECK-NEXT: (block
;; CHECK-NEXT: (block
;; CHECK-NEXT: (if
;; CHECK-NEXT: (i32.eq
;; CHECK-NEXT: (global.get $__asyncify_state)
;; CHECK-NEXT: (i32.const 2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (block
;; CHECK-NEXT: (i32.store
;; CHECK-NEXT: (global.get $__asyncify_data)
;; CHECK-NEXT: (i32.add
;; CHECK-NEXT: (i32.load
;; CHECK-NEXT: (global.get $__asyncify_data)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const -4)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.set $1
;; CHECK-NEXT: (i32.load
;; CHECK-NEXT: (i32.load
;; CHECK-NEXT: (global.get $__asyncify_data)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (if
;; CHECK-NEXT: (if (result i32)
;; CHECK-NEXT: (i32.eq
;; CHECK-NEXT: (global.get $__asyncify_state)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: (i32.eq
;; CHECK-NEXT: (local.get $1)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (block
;; CHECK-NEXT: (call $foo)
;; CHECK-NEXT: (if
;; CHECK-NEXT: (i32.eq
;; CHECK-NEXT: (global.get $__asyncify_state)
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (br $__asyncify_unwind
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (return)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (block
;; CHECK-NEXT: (i32.store
;; CHECK-NEXT: (i32.load
;; CHECK-NEXT: (global.get $__asyncify_data)
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.get $0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.store
;; CHECK-NEXT: (global.get $__asyncify_data)
;; CHECK-NEXT: (i32.add
;; CHECK-NEXT: (i32.load
;; CHECK-NEXT: (global.get $__asyncify_data)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 4)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
(func $call_foo ;; doesn't look like it needs instrumentation, but propagated from add list
(call $foo)
)
)

;; CHECK: (func $asyncify_start_unwind (param $0 i32)
;; CHECK-NEXT: (global.set $__asyncify_state
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (global.set $__asyncify_data
;; CHECK-NEXT: (local.get $0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (if
;; CHECK-NEXT: (i32.gt_u
;; CHECK-NEXT: (i32.load
;; CHECK-NEXT: (global.get $__asyncify_data)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.load offset=4
;; CHECK-NEXT: (global.get $__asyncify_data)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )

;; CHECK: (func $asyncify_stop_unwind
;; CHECK-NEXT: (global.set $__asyncify_state
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (if
;; CHECK-NEXT: (i32.gt_u
;; CHECK-NEXT: (i32.load
;; CHECK-NEXT: (global.get $__asyncify_data)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.load offset=4
;; CHECK-NEXT: (global.get $__asyncify_data)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )

;; CHECK: (func $asyncify_start_rewind (param $0 i32)
;; CHECK-NEXT: (global.set $__asyncify_state
;; CHECK-NEXT: (i32.const 2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (global.set $__asyncify_data
;; CHECK-NEXT: (local.get $0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (if
;; CHECK-NEXT: (i32.gt_u
;; CHECK-NEXT: (i32.load
;; CHECK-NEXT: (global.get $__asyncify_data)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.load offset=4
;; CHECK-NEXT: (global.get $__asyncify_data)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )

;; CHECK: (func $asyncify_stop_rewind
;; CHECK-NEXT: (global.set $__asyncify_state
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (if
;; CHECK-NEXT: (i32.gt_u
;; CHECK-NEXT: (i32.load
;; CHECK-NEXT: (global.get $__asyncify_data)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.load offset=4
;; CHECK-NEXT: (global.get $__asyncify_data)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )

;; CHECK: (func $asyncify_get_state (result i32)
;; CHECK-NEXT: (global.get $__asyncify_state)
;; CHECK-NEXT: )
9 changes: 9 additions & 0 deletions test/unit/test_asyncify.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,15 @@ def test(list_name):
test('remove')
test('add')

def test_asyncify_addlist_and_removelist(self):
args = shared.WASM_OPT + [self.input_path('asyncify-pure.wat'),
'--asyncify',
'--pass-arg=asyncify-addlist@main',
'--pass-arg=asyncify-removelist@main']
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('main is found in the add-list and in the remove-list', proc.stdout)

def test_asyncify_imports(self):
def test(args):
return shared.run_process(shared.WASM_OPT + [self.input_path('asyncify-sleep.wat'), '--asyncify', '--print'] + args, stdout=subprocess.PIPE).stdout
Expand Down