Skip to content

Commit 4433454

Browse files
authored
Add an Asyncify option to propagate the addList (#5935)
The new asyncify flag --pass-arg=asyncify-propagate-addlist changes the behavior of --pass-arg=asyncify-addlist : with it, callers of functions in the asyncify-addlist will be also instrumented.
1 parent f984324 commit 4433454

File tree

3 files changed

+334
-12
lines changed

3 files changed

+334
-12
lines changed

src/passes/Asyncify.cpp

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,13 @@
273273
// some indirect calls that *do* need to be instrumented, or if you will
274274
// do some later transform of the code that adds more call paths, etc.
275275
//
276+
// --pass-arg=asyncify-propagate-addlist
277+
//
278+
// The default behaviour of the addlist does not propagate instrumentation
279+
// status. If this option is set then functions which call a function in
280+
// the addlist will also be instrumented, and those that call them and so
281+
// on.
282+
//
276283
// --pass-arg=asyncify-onlylist@name1,name2,name3
277284
//
278285
// If the "only-list" is provided, then *only* the functions in the list
@@ -534,6 +541,7 @@ class ModuleAnalyzer {
534541
bool canIndirectChangeState,
535542
const String::Split& removeListInput,
536543
const String::Split& addListInput,
544+
bool propagateAddList,
537545
const String::Split& onlyListInput,
538546
bool verbose)
539547
: module(module), canIndirectChangeState(canIndirectChangeState),
@@ -675,6 +683,34 @@ class ModuleAnalyzer {
675683
module.removeFunction(name);
676684
}
677685

686+
auto handleAddList = [&](ModuleAnalyzer::Map& map) {
687+
if (!addListInput.empty()) {
688+
for (auto& func : module.functions) {
689+
if (addList.match(func->name) && removeList.match(func->name)) {
690+
Fatal() << func->name
691+
<< " is found in the add-list and in the remove-list";
692+
}
693+
694+
if (!func->imported() && addList.match(func->name)) {
695+
auto& info = map[func.get()];
696+
if (verbose && !info.canChangeState) {
697+
std::cout << "[asyncify] " << func->name
698+
<< " is in the add-list, add\n";
699+
}
700+
info.canChangeState = true;
701+
info.addedFromList = true;
702+
}
703+
}
704+
}
705+
};
706+
707+
// When propagateAddList is enabled, we should check a add-list before
708+
// scannerpropagateBack so that callers of functions in add-list should also
709+
// be instrumented.
710+
if (propagateAddList) {
711+
handleAddList(scanner.map);
712+
}
713+
678714
scanner.propagateBack([](const Info& info) { return info.canChangeState; },
679715
[](const Info& info) {
680716
return !info.isBottomMostRuntime &&
@@ -711,18 +747,10 @@ class ModuleAnalyzer {
711747
}
712748
}
713749

714-
if (!addListInput.empty()) {
715-
for (auto& func : module.functions) {
716-
if (!func->imported() && addList.match(func->name)) {
717-
auto& info = map[func.get()];
718-
if (verbose && !info.canChangeState) {
719-
std::cout << "[asyncify] " << func->name
720-
<< " is in the add-list, add\n";
721-
}
722-
info.canChangeState = true;
723-
info.addedFromList = true;
724-
}
725-
}
750+
// When propagateAddList is disabled, which is default behavior,
751+
// functions in add-list are just prepended to instrumented functions.
752+
if (!propagateAddList) {
753+
handleAddList(map);
726754
}
727755

728756
removeList.checkPatternsMatches();
@@ -1609,6 +1637,7 @@ struct Asyncify : public Pass {
16091637
auto verbose = options.hasArgument("asyncify-verbose");
16101638
auto relocatable = options.hasArgument("asyncify-relocatable");
16111639
auto secondaryMemory = options.hasArgument("asyncify-in-secondary-memory");
1640+
auto propagateAddList = options.hasArgument("asyncify-propagate-addlist");
16121641

16131642
// Ensure there is a memory, as we need it.
16141643
if (secondaryMemory) {
@@ -1651,6 +1680,7 @@ struct Asyncify : public Pass {
16511680
canIndirectChangeState,
16521681
removeList,
16531682
addList,
1683+
propagateAddList,
16541684
onlyList,
16551685
verbose);
16561686

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
2+
;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
3+
4+
;; RUN: foreach %s %t wasm-opt --asyncify --pass-arg=asyncify-addlist@foo -S --pass-arg=asyncify-propagate-addlist -o - | filecheck %s
5+
6+
(module
7+
(memory 1 2)
8+
;; CHECK: (type $0 (func))
9+
10+
;; CHECK: (type $1 (func (param i32)))
11+
12+
;; CHECK: (type $2 (func (result i32)))
13+
14+
;; CHECK: (import "env" "import" (func $import))
15+
(import "env" "import" (func $import))
16+
;; CHECK: (global $__asyncify_state (mut i32) (i32.const 0))
17+
18+
;; CHECK: (global $__asyncify_data (mut i32) (i32.const 0))
19+
20+
;; CHECK: (memory $0 1 2)
21+
22+
;; CHECK: (export "asyncify_start_unwind" (func $asyncify_start_unwind))
23+
24+
;; CHECK: (export "asyncify_stop_unwind" (func $asyncify_stop_unwind))
25+
26+
;; CHECK: (export "asyncify_start_rewind" (func $asyncify_start_rewind))
27+
28+
;; CHECK: (export "asyncify_stop_rewind" (func $asyncify_stop_rewind))
29+
30+
;; CHECK: (export "asyncify_get_state" (func $asyncify_get_state))
31+
32+
;; CHECK: (func $foo
33+
;; CHECK-NEXT: (local $0 i32)
34+
;; CHECK-NEXT: (local $1 i32)
35+
;; CHECK-NEXT: (if
36+
;; CHECK-NEXT: (i32.eq
37+
;; CHECK-NEXT: (global.get $__asyncify_state)
38+
;; CHECK-NEXT: (i32.const 2)
39+
;; CHECK-NEXT: )
40+
;; CHECK-NEXT: (nop)
41+
;; CHECK-NEXT: )
42+
;; CHECK-NEXT: (local.tee $0
43+
;; CHECK-NEXT: (block $__asyncify_unwind
44+
;; CHECK-NEXT: (block
45+
;; CHECK-NEXT: (block
46+
;; CHECK-NEXT: (if
47+
;; CHECK-NEXT: (i32.eq
48+
;; CHECK-NEXT: (global.get $__asyncify_state)
49+
;; CHECK-NEXT: (i32.const 2)
50+
;; CHECK-NEXT: )
51+
;; CHECK-NEXT: (block
52+
;; CHECK-NEXT: (i32.store
53+
;; CHECK-NEXT: (global.get $__asyncify_data)
54+
;; CHECK-NEXT: (i32.add
55+
;; CHECK-NEXT: (i32.load
56+
;; CHECK-NEXT: (global.get $__asyncify_data)
57+
;; CHECK-NEXT: )
58+
;; CHECK-NEXT: (i32.const -4)
59+
;; CHECK-NEXT: )
60+
;; CHECK-NEXT: )
61+
;; CHECK-NEXT: (local.set $1
62+
;; CHECK-NEXT: (i32.load
63+
;; CHECK-NEXT: (i32.load
64+
;; CHECK-NEXT: (global.get $__asyncify_data)
65+
;; CHECK-NEXT: )
66+
;; CHECK-NEXT: )
67+
;; CHECK-NEXT: )
68+
;; CHECK-NEXT: )
69+
;; CHECK-NEXT: )
70+
;; CHECK-NEXT: (if
71+
;; CHECK-NEXT: (i32.eq
72+
;; CHECK-NEXT: (global.get $__asyncify_state)
73+
;; CHECK-NEXT: (i32.const 0)
74+
;; CHECK-NEXT: )
75+
;; CHECK-NEXT: (call $nothing)
76+
;; CHECK-NEXT: )
77+
;; CHECK-NEXT: )
78+
;; CHECK-NEXT: (return)
79+
;; CHECK-NEXT: )
80+
;; CHECK-NEXT: )
81+
;; CHECK-NEXT: )
82+
;; CHECK-NEXT: (block
83+
;; CHECK-NEXT: (i32.store
84+
;; CHECK-NEXT: (i32.load
85+
;; CHECK-NEXT: (global.get $__asyncify_data)
86+
;; CHECK-NEXT: )
87+
;; CHECK-NEXT: (local.get $0)
88+
;; CHECK-NEXT: )
89+
;; CHECK-NEXT: (i32.store
90+
;; CHECK-NEXT: (global.get $__asyncify_data)
91+
;; CHECK-NEXT: (i32.add
92+
;; CHECK-NEXT: (i32.load
93+
;; CHECK-NEXT: (global.get $__asyncify_data)
94+
;; CHECK-NEXT: )
95+
;; CHECK-NEXT: (i32.const 4)
96+
;; CHECK-NEXT: )
97+
;; CHECK-NEXT: )
98+
;; CHECK-NEXT: )
99+
;; CHECK-NEXT: (nop)
100+
;; CHECK-NEXT: )
101+
(func $foo ;; doesn't look like it needs instrumentation, but in add list
102+
(call $nothing)
103+
)
104+
;; CHECK: (func $bar
105+
;; CHECK-NEXT: (call $nothing)
106+
;; CHECK-NEXT: )
107+
(func $bar ;; doesn't look like it needs instrumentation, and not in add list
108+
(call $nothing)
109+
)
110+
;; CHECK: (func $nothing
111+
;; CHECK-NEXT: (nop)
112+
;; CHECK-NEXT: )
113+
(func $nothing
114+
)
115+
;; CHECK: (func $call_foo
116+
;; CHECK-NEXT: (local $0 i32)
117+
;; CHECK-NEXT: (local $1 i32)
118+
;; CHECK-NEXT: (if
119+
;; CHECK-NEXT: (i32.eq
120+
;; CHECK-NEXT: (global.get $__asyncify_state)
121+
;; CHECK-NEXT: (i32.const 2)
122+
;; CHECK-NEXT: )
123+
;; CHECK-NEXT: (nop)
124+
;; CHECK-NEXT: )
125+
;; CHECK-NEXT: (local.set $0
126+
;; CHECK-NEXT: (block $__asyncify_unwind (result i32)
127+
;; CHECK-NEXT: (block
128+
;; CHECK-NEXT: (block
129+
;; CHECK-NEXT: (if
130+
;; CHECK-NEXT: (i32.eq
131+
;; CHECK-NEXT: (global.get $__asyncify_state)
132+
;; CHECK-NEXT: (i32.const 2)
133+
;; CHECK-NEXT: )
134+
;; CHECK-NEXT: (block
135+
;; CHECK-NEXT: (i32.store
136+
;; CHECK-NEXT: (global.get $__asyncify_data)
137+
;; CHECK-NEXT: (i32.add
138+
;; CHECK-NEXT: (i32.load
139+
;; CHECK-NEXT: (global.get $__asyncify_data)
140+
;; CHECK-NEXT: )
141+
;; CHECK-NEXT: (i32.const -4)
142+
;; CHECK-NEXT: )
143+
;; CHECK-NEXT: )
144+
;; CHECK-NEXT: (local.set $1
145+
;; CHECK-NEXT: (i32.load
146+
;; CHECK-NEXT: (i32.load
147+
;; CHECK-NEXT: (global.get $__asyncify_data)
148+
;; CHECK-NEXT: )
149+
;; CHECK-NEXT: )
150+
;; CHECK-NEXT: )
151+
;; CHECK-NEXT: )
152+
;; CHECK-NEXT: )
153+
;; CHECK-NEXT: (if
154+
;; CHECK-NEXT: (if (result i32)
155+
;; CHECK-NEXT: (i32.eq
156+
;; CHECK-NEXT: (global.get $__asyncify_state)
157+
;; CHECK-NEXT: (i32.const 0)
158+
;; CHECK-NEXT: )
159+
;; CHECK-NEXT: (i32.const 1)
160+
;; CHECK-NEXT: (i32.eq
161+
;; CHECK-NEXT: (local.get $1)
162+
;; CHECK-NEXT: (i32.const 0)
163+
;; CHECK-NEXT: )
164+
;; CHECK-NEXT: )
165+
;; CHECK-NEXT: (block
166+
;; CHECK-NEXT: (call $foo)
167+
;; CHECK-NEXT: (if
168+
;; CHECK-NEXT: (i32.eq
169+
;; CHECK-NEXT: (global.get $__asyncify_state)
170+
;; CHECK-NEXT: (i32.const 1)
171+
;; CHECK-NEXT: )
172+
;; CHECK-NEXT: (br $__asyncify_unwind
173+
;; CHECK-NEXT: (i32.const 0)
174+
;; CHECK-NEXT: )
175+
;; CHECK-NEXT: )
176+
;; CHECK-NEXT: )
177+
;; CHECK-NEXT: )
178+
;; CHECK-NEXT: )
179+
;; CHECK-NEXT: (return)
180+
;; CHECK-NEXT: )
181+
;; CHECK-NEXT: )
182+
;; CHECK-NEXT: )
183+
;; CHECK-NEXT: (block
184+
;; CHECK-NEXT: (i32.store
185+
;; CHECK-NEXT: (i32.load
186+
;; CHECK-NEXT: (global.get $__asyncify_data)
187+
;; CHECK-NEXT: )
188+
;; CHECK-NEXT: (local.get $0)
189+
;; CHECK-NEXT: )
190+
;; CHECK-NEXT: (i32.store
191+
;; CHECK-NEXT: (global.get $__asyncify_data)
192+
;; CHECK-NEXT: (i32.add
193+
;; CHECK-NEXT: (i32.load
194+
;; CHECK-NEXT: (global.get $__asyncify_data)
195+
;; CHECK-NEXT: )
196+
;; CHECK-NEXT: (i32.const 4)
197+
;; CHECK-NEXT: )
198+
;; CHECK-NEXT: )
199+
;; CHECK-NEXT: )
200+
;; CHECK-NEXT: (nop)
201+
;; CHECK-NEXT: )
202+
(func $call_foo ;; doesn't look like it needs instrumentation, but propagated from add list
203+
(call $foo)
204+
)
205+
)
206+
207+
;; CHECK: (func $asyncify_start_unwind (param $0 i32)
208+
;; CHECK-NEXT: (global.set $__asyncify_state
209+
;; CHECK-NEXT: (i32.const 1)
210+
;; CHECK-NEXT: )
211+
;; CHECK-NEXT: (global.set $__asyncify_data
212+
;; CHECK-NEXT: (local.get $0)
213+
;; CHECK-NEXT: )
214+
;; CHECK-NEXT: (if
215+
;; CHECK-NEXT: (i32.gt_u
216+
;; CHECK-NEXT: (i32.load
217+
;; CHECK-NEXT: (global.get $__asyncify_data)
218+
;; CHECK-NEXT: )
219+
;; CHECK-NEXT: (i32.load offset=4
220+
;; CHECK-NEXT: (global.get $__asyncify_data)
221+
;; CHECK-NEXT: )
222+
;; CHECK-NEXT: )
223+
;; CHECK-NEXT: (unreachable)
224+
;; CHECK-NEXT: )
225+
;; CHECK-NEXT: )
226+
227+
;; CHECK: (func $asyncify_stop_unwind
228+
;; CHECK-NEXT: (global.set $__asyncify_state
229+
;; CHECK-NEXT: (i32.const 0)
230+
;; CHECK-NEXT: )
231+
;; CHECK-NEXT: (if
232+
;; CHECK-NEXT: (i32.gt_u
233+
;; CHECK-NEXT: (i32.load
234+
;; CHECK-NEXT: (global.get $__asyncify_data)
235+
;; CHECK-NEXT: )
236+
;; CHECK-NEXT: (i32.load offset=4
237+
;; CHECK-NEXT: (global.get $__asyncify_data)
238+
;; CHECK-NEXT: )
239+
;; CHECK-NEXT: )
240+
;; CHECK-NEXT: (unreachable)
241+
;; CHECK-NEXT: )
242+
;; CHECK-NEXT: )
243+
244+
;; CHECK: (func $asyncify_start_rewind (param $0 i32)
245+
;; CHECK-NEXT: (global.set $__asyncify_state
246+
;; CHECK-NEXT: (i32.const 2)
247+
;; CHECK-NEXT: )
248+
;; CHECK-NEXT: (global.set $__asyncify_data
249+
;; CHECK-NEXT: (local.get $0)
250+
;; CHECK-NEXT: )
251+
;; CHECK-NEXT: (if
252+
;; CHECK-NEXT: (i32.gt_u
253+
;; CHECK-NEXT: (i32.load
254+
;; CHECK-NEXT: (global.get $__asyncify_data)
255+
;; CHECK-NEXT: )
256+
;; CHECK-NEXT: (i32.load offset=4
257+
;; CHECK-NEXT: (global.get $__asyncify_data)
258+
;; CHECK-NEXT: )
259+
;; CHECK-NEXT: )
260+
;; CHECK-NEXT: (unreachable)
261+
;; CHECK-NEXT: )
262+
;; CHECK-NEXT: )
263+
264+
;; CHECK: (func $asyncify_stop_rewind
265+
;; CHECK-NEXT: (global.set $__asyncify_state
266+
;; CHECK-NEXT: (i32.const 0)
267+
;; CHECK-NEXT: )
268+
;; CHECK-NEXT: (if
269+
;; CHECK-NEXT: (i32.gt_u
270+
;; CHECK-NEXT: (i32.load
271+
;; CHECK-NEXT: (global.get $__asyncify_data)
272+
;; CHECK-NEXT: )
273+
;; CHECK-NEXT: (i32.load offset=4
274+
;; CHECK-NEXT: (global.get $__asyncify_data)
275+
;; CHECK-NEXT: )
276+
;; CHECK-NEXT: )
277+
;; CHECK-NEXT: (unreachable)
278+
;; CHECK-NEXT: )
279+
;; CHECK-NEXT: )
280+
281+
;; CHECK: (func $asyncify_get_state (result i32)
282+
;; CHECK-NEXT: (global.get $__asyncify_state)
283+
;; CHECK-NEXT: )

test/unit/test_asyncify.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,15 @@ def test(list_name):
7575
test('remove')
7676
test('add')
7777

78+
def test_asyncify_addlist_and_removelist(self):
79+
args = shared.WASM_OPT + [self.input_path('asyncify-pure.wat'),
80+
'--asyncify',
81+
'--pass-arg=asyncify-addlist@main',
82+
'--pass-arg=asyncify-removelist@main']
83+
proc = shared.run_process(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=False)
84+
self.assertNotEqual(proc.returncode, 0, 'must error on using both lists at once')
85+
self.assertIn('main is found in the add-list and in the remove-list', proc.stdout)
86+
7887
def test_asyncify_imports(self):
7988
def test(args):
8089
return shared.run_process(shared.WASM_OPT + [self.input_path('asyncify-sleep.wat'), '--asyncify', '--print'] + args, stdout=subprocess.PIPE).stdout

0 commit comments

Comments
 (0)