Skip to content

Commit 3f797c8

Browse files
authored
Bysyncify: add ignore-imports and ignore-indirect options (#2178)
ignore-imports makes it not assume that any import may unwind/rewind the stack. ignore-indirect makes it not assume any indirect call can reach an unwind/rewind (which means, it assumes there is not an indirect call on the stack while unwinding).
1 parent 06698d7 commit 3f797c8

4 files changed

Lines changed: 317 additions & 35 deletions

File tree

src/passes/Bysyncify.cpp

Lines changed: 59 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -167,21 +167,30 @@
167167
// we are recreating the call stack. At that point you should call
168168
// bysyncify_stop_rewind and then execution can resume normally.
169169
//
170-
// By default this pass assumes that any import may call any of the
171-
// exported bysyncify methods, that is, any import may start an unwind/rewind.
172-
// To customize this, you can provide an argument to wasm-opt (or another
173-
// tool that can run this pass),
170+
// By default this pass is very carefuly: it assumes that any import and
171+
// any indirect call may start an unwind/rewind operation. If you know more
172+
// specific information you can inform bysyncify about that, which can reduce
173+
// a great deal of overhead, as it can instrument less code. The relevant
174+
// options to wasm-opt etc. are:
174175
//
175176
// --pass-arg=bysyncify-imports@module1.base1,module2.base2,module3.base3
176177
//
177-
// Each module.base in that comma-separated list will be considered to
178-
// be an import that can unwind/rewind, and all others are assumed not to
179-
// (aside from the bysyncify.* imports, which are always assumed to). To
180-
// say that no import (aside from bysyncify.*) can do so (that is, the
181-
// opposite of the default behavior), you can simply provide an import
182-
// that doesn't exist, for example:
183-
184-
// --pass-arg=bysyncify-imports@no.imports
178+
// Each module.base in that comma-separated list will be considered to
179+
// be an import that can unwind/rewind, and all others are assumed not to
180+
// (aside from the bysyncify.* imports, which are always assumed to).
181+
//
182+
// --pass-arg=bysyncify-ignore-imports
183+
//
184+
// Ignore all imports (except for bynsyncify.*), that is, assume none of
185+
// them can start an unwind/rewind. (This is effectively the same as
186+
// providing bysyncify-imports with a list of non-existent imports.)
187+
//
188+
// --pass-arg=bysyncify-ignore-indirect
189+
//
190+
// Ignore all indirect calls. This implies that you know an call stack
191+
// will never need to be unwound with an indirect call somewhere in it.
192+
// If that is true for your codebase, then this can be extremely useful
193+
// as otherwise it looks like any indirect call can go to a lot of places.
185194
//
186195

187196
#include "ir/effects.h"
@@ -225,6 +234,7 @@ const auto STACK_ALIGN = 4;
225234
// by it.
226235
class ModuleAnalyzer {
227236
Module& module;
237+
bool canIndirectChangeState;
228238

229239
struct Info {
230240
bool canChangeState = false;
@@ -237,8 +247,9 @@ class ModuleAnalyzer {
237247

238248
public:
239249
ModuleAnalyzer(Module& module,
240-
std::function<bool(Name, Name)> canImportChangeState)
241-
: module(module) {
250+
std::function<bool(Name, Name)> canImportChangeState,
251+
bool canIndirectChangeState)
252+
: module(module), canIndirectChangeState(canIndirectChangeState) {
242253
// Scan to see which functions can directly change the state.
243254
// Also handle the bysyncify imports, removing them (as we will implement
244255
// them later), and replace calls to them with calls to the later proper
@@ -281,15 +292,19 @@ class ModuleAnalyzer {
281292
info->callsTo.insert(target);
282293
}
283294
void visitCallIndirect(CallIndirect* curr) {
284-
// TODO optimize
285-
info->canChangeState = true;
295+
if (canIndirectChangeState) {
296+
info->canChangeState = true;
297+
}
298+
// TODO optimize the other case, at least by type
286299
}
287300
Info* info;
288301
Module* module;
302+
bool canIndirectChangeState;
289303
};
290304
Walker walker;
291305
walker.info = &info;
292306
walker.module = &module;
307+
walker.canIndirectChangeState = canIndirectChangeState;
293308
walker.walk(func->body);
294309
});
295310
map.swap(scanner.map);
@@ -357,16 +372,20 @@ class ModuleAnalyzer {
357372
}
358373
}
359374
void visitCallIndirect(CallIndirect* curr) {
360-
// TODO optimize
361-
canChangeState = true;
375+
if (canIndirectChangeState) {
376+
canChangeState = true;
377+
}
378+
// TODO optimize the other case, at least by type
362379
}
363380
Module* module;
364381
Map* map;
382+
bool canIndirectChangeState;
365383
bool canChangeState = false;
366384
};
367385
Walker walker;
368386
walker.module = &module;
369387
walker.map = &map;
388+
walker.canIndirectChangeState = canIndirectChangeState;
370389
walker.walk(curr);
371390
return walker.canChangeState;
372391
}
@@ -788,23 +807,31 @@ struct BysyncifyLocals : public WalkerPass<PostWalker<BysyncifyLocals>> {
788807
struct Bysyncify : public Pass {
789808
void run(PassRunner* runner, Module* module) override {
790809
bool optimize = runner->options.optimizeLevel > 0;
791-
// Find which imports can change the state.
792-
const char* ALL_IMPORTS_CAN_CHANGE_STATE = "__bysyncify_all_imports";
793-
auto stateChangingImports = runner->options.getArgumentOrDefault(
794-
"bysyncify-imports", ALL_IMPORTS_CAN_CHANGE_STATE);
795-
bool allImportsCanChangeState =
796-
stateChangingImports == ALL_IMPORTS_CAN_CHANGE_STATE;
810+
// Find which things can change the state.
811+
auto stateChangingImports =
812+
runner->options.getArgumentOrDefault("bysyncify-imports", "");
797813
std::string separator = ",";
798-
stateChangingImports = separator + stateChangingImports + separator;
814+
auto ignoreImports =
815+
runner->options.getArgumentOrDefault("bysyncify-ignore-imports", "");
816+
bool allImportsCanChangeState =
817+
stateChangingImports == "" && ignoreImports == "";
818+
if (!allImportsCanChangeState) {
819+
stateChangingImports = separator + stateChangingImports + separator;
820+
}
821+
auto ignoreIndirect =
822+
runner->options.getArgumentOrDefault("bysyncify-ignore-indirect", "");
799823

800824
// Scan the module.
801-
ModuleAnalyzer analyzer(*module, [&](Name module, Name base) {
802-
if (allImportsCanChangeState) {
803-
return true;
804-
}
805-
std::string full = separator + module.str + '.' + base.str + separator;
806-
return stateChangingImports.find(full) != std::string::npos;
807-
});
825+
ModuleAnalyzer analyzer(
826+
*module,
827+
[&](Name module, Name base) {
828+
if (allImportsCanChangeState) {
829+
return true;
830+
}
831+
std::string full = separator + module.str + '.' + base.str + separator;
832+
return stateChangingImports.find(full) != std::string::npos;
833+
},
834+
ignoreIndirect == "");
808835

809836
// Add necessary globals before we emit code to use them.
810837
addGlobals(module);

src/tools/optimization-options.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -187,12 +187,15 @@ struct OptimizationOptions : public ToolOptions {
187187
"in the form KEY@VALUE",
188188
Options::Arguments::N,
189189
[this](Options*, const std::string& argument) {
190+
std::string key, value;
190191
auto colon = argument.find('@');
191192
if (colon == std::string::npos) {
192-
Fatal() << "--pass-arg value must be in the form of KEY@VALUE";
193+
key = argument;
194+
value = "1";
195+
} else {
196+
key = argument.substr(0, colon);
197+
value = argument.substr(colon + 1);
193198
}
194-
auto key = argument.substr(0, colon);
195-
auto value = argument.substr(colon + 1);
196199
passOptions.arguments[key] = value;
197200
});
198201
// add passes in registry
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
(module
2+
(type $f (func))
3+
(type $FUNCSIG$i (func (result i32)))
4+
(type $FUNCSIG$vi (func (param i32)))
5+
(import "env" "import" (func $import))
6+
(import "env" "import2" (func $import2 (result i32)))
7+
(import "env" "import3" (func $import3 (param i32)))
8+
(memory $0 1 2)
9+
(table $0 2 2 funcref)
10+
(elem (i32.const 0) $calls-import2-drop $calls-import2-drop)
11+
(global $__bysyncify_state (mut i32) (i32.const 0))
12+
(global $__bysyncify_data (mut i32) (i32.const 0))
13+
(export "bysyncify_start_unwind" (func $bysyncify_start_unwind))
14+
(export "bysyncify_stop_unwind" (func $bysyncify_stop_unwind))
15+
(export "bysyncify_start_rewind" (func $bysyncify_start_rewind))
16+
(export "bysyncify_stop_rewind" (func $bysyncify_stop_rewind))
17+
(func $calls-import (; 3 ;) (type $f)
18+
(call $import)
19+
)
20+
(func $calls-import2-drop (; 4 ;) (type $f)
21+
(local $0 i32)
22+
(local.set $0
23+
(call $import2)
24+
)
25+
)
26+
(func $calls-import2-if-else (; 5 ;) (type $FUNCSIG$vi) (param $x i32)
27+
(local $1 i32)
28+
(if
29+
(local.get $x)
30+
(call $import3
31+
(i32.const 1)
32+
)
33+
(call $import3
34+
(i32.const 2)
35+
)
36+
)
37+
)
38+
(func $calls-indirect (; 6 ;) (type $FUNCSIG$vi) (param $x i32)
39+
(local $1 i32)
40+
(local $2 i32)
41+
(local $3 i32)
42+
(local $4 i32)
43+
(local $5 i32)
44+
(if
45+
(i32.eq
46+
(global.get $__bysyncify_state)
47+
(i32.const 2)
48+
)
49+
(block
50+
(i32.store
51+
(global.get $__bysyncify_data)
52+
(i32.add
53+
(i32.load
54+
(global.get $__bysyncify_data)
55+
)
56+
(i32.const -8)
57+
)
58+
)
59+
(local.set $4
60+
(i32.load
61+
(global.get $__bysyncify_data)
62+
)
63+
)
64+
(local.set $x
65+
(i32.load
66+
(local.get $4)
67+
)
68+
)
69+
(local.set $1
70+
(i32.load offset=4
71+
(local.get $4)
72+
)
73+
)
74+
)
75+
)
76+
(local.set $2
77+
(block $__bysyncify_unwind (result i32)
78+
(block
79+
(block
80+
(if
81+
(i32.eq
82+
(global.get $__bysyncify_state)
83+
(i32.const 2)
84+
)
85+
(block
86+
(i32.store
87+
(global.get $__bysyncify_data)
88+
(i32.add
89+
(i32.load
90+
(global.get $__bysyncify_data)
91+
)
92+
(i32.const -4)
93+
)
94+
)
95+
(local.set $3
96+
(i32.load
97+
(i32.load
98+
(global.get $__bysyncify_data)
99+
)
100+
)
101+
)
102+
)
103+
)
104+
(if
105+
(if (result i32)
106+
(i32.eq
107+
(global.get $__bysyncify_state)
108+
(i32.const 0)
109+
)
110+
(i32.const 1)
111+
(i32.eq
112+
(local.get $3)
113+
(i32.const 0)
114+
)
115+
)
116+
(block
117+
(call_indirect (type $f)
118+
(local.get $x)
119+
)
120+
(if
121+
(i32.eq
122+
(global.get $__bysyncify_state)
123+
(i32.const 1)
124+
)
125+
(br $__bysyncify_unwind
126+
(i32.const 0)
127+
)
128+
)
129+
)
130+
)
131+
)
132+
(return)
133+
)
134+
)
135+
)
136+
(block
137+
(i32.store
138+
(i32.load
139+
(global.get $__bysyncify_data)
140+
)
141+
(local.get $2)
142+
)
143+
(i32.store
144+
(global.get $__bysyncify_data)
145+
(i32.add
146+
(i32.load
147+
(global.get $__bysyncify_data)
148+
)
149+
(i32.const 4)
150+
)
151+
)
152+
)
153+
(block
154+
(local.set $5
155+
(i32.load
156+
(global.get $__bysyncify_data)
157+
)
158+
)
159+
(i32.store
160+
(local.get $5)
161+
(local.get $x)
162+
)
163+
(i32.store offset=4
164+
(local.get $5)
165+
(local.get $1)
166+
)
167+
(i32.store
168+
(global.get $__bysyncify_data)
169+
(i32.add
170+
(i32.load
171+
(global.get $__bysyncify_data)
172+
)
173+
(i32.const 8)
174+
)
175+
)
176+
)
177+
)
178+
(func $bysyncify_start_unwind (; 7 ;) (param $0 i32)
179+
(if
180+
(i32.gt_u
181+
(i32.load
182+
(local.get $0)
183+
)
184+
(i32.load offset=4
185+
(local.get $0)
186+
)
187+
)
188+
(unreachable)
189+
)
190+
(global.set $__bysyncify_state
191+
(i32.const 1)
192+
)
193+
(global.set $__bysyncify_data
194+
(local.get $0)
195+
)
196+
)
197+
(func $bysyncify_stop_unwind (; 8 ;)
198+
(global.set $__bysyncify_state
199+
(i32.const 0)
200+
)
201+
)
202+
(func $bysyncify_start_rewind (; 9 ;) (param $0 i32)
203+
(if
204+
(i32.gt_u
205+
(i32.load
206+
(local.get $0)
207+
)
208+
(i32.load offset=4
209+
(local.get $0)
210+
)
211+
)
212+
(unreachable)
213+
)
214+
(global.set $__bysyncify_state
215+
(i32.const 2)
216+
)
217+
(global.set $__bysyncify_data
218+
(local.get $0)
219+
)
220+
)
221+
(func $bysyncify_stop_rewind (; 10 ;)
222+
(global.set $__bysyncify_state
223+
(i32.const 0)
224+
)
225+
)
226+
)

0 commit comments

Comments
 (0)