Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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