-
Notifications
You must be signed in to change notification settings - Fork 745
/
pass.h
360 lines (305 loc) · 12.2 KB
/
pass.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
/*
* Copyright 2015 WebAssembly Community Group participants
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef wasm_pass_h
#define wasm_pass_h
#include <functional>
#include "mixed_arena.h"
#include "support/utilities.h"
#include "wasm-traversal.h"
#include "wasm.h"
namespace wasm {
class Pass;
//
// Global registry of all passes in /passes/
//
struct PassRegistry {
PassRegistry();
static PassRegistry* get();
typedef std::function<Pass*()> Creator;
void registerPass(const char* name, const char* description, Creator create);
std::unique_ptr<Pass> createPass(std::string name);
std::vector<std::string> getRegisteredNames();
std::string getPassDescription(std::string name);
private:
void registerPasses();
struct PassInfo {
std::string description;
Creator create;
PassInfo() = default;
PassInfo(std::string description, Creator create)
: description(description), create(create) {}
};
std::map<std::string, PassInfo> passInfos;
};
struct InliningOptions {
// Function size at which we always inline.
// Typically a size so small that after optimizations, the inlined code will
// be smaller than the call instruction itself. 2 is a safe number because
// there is no risk of things like
// (func $reverse (param $x i32) (param $y i32)
// (call $something (local.get $y) (local.get $x))
// )
// in which case the reversing of the params means we'll possibly need
// a block and a temp local. But that takes at least 3 nodes, and 2 < 3.
// More generally, with 2 items we may have a local.get, but no way to
// require it to be saved instead of directly consumed.
Index alwaysInlineMaxSize = 2;
// Function size which we inline when functions are lightweight (no loops
// and calls) and we are doing aggressive optimisation for speed (-O3).
// In particular it's nice that with this limit we can inline the clamp
// functions (i32s-div, f64-to-int, etc.), that can affect perf.
Index flexibleInlineMaxSize = 20;
// Function size which we inline when there is only one caller.
// FIXME: this should logically be higher than flexibleInlineMaxSize.
Index oneCallerInlineMaxSize = 15;
// Loops usually mean the function does heavy work, so the call overhead
// is not significant and we do not inline such functions by default.
bool allowFunctionsWithLoops = false;
};
struct PassOptions {
// Run passes in debug mode, doing extra validation and timing checks.
bool debug = false;
// Whether to run the validator to check for errors.
bool validate = true;
// When validating validate globally and not just locally
bool validateGlobally = false;
// 0, 1, 2 correspond to -O0, -O1, -O2, etc.
int optimizeLevel = 0;
// 0, 1, 2 correspond to -O0, -Os, -Oz
int shrinkLevel = 0;
// Tweak thresholds for the Inlining pass.
InliningOptions inlining;
// Optimize assuming things like div by 0, bad load/store, will not trap.
bool ignoreImplicitTraps = false;
// Optimize assuming that the low 1K of memory is not valid memory for the
// application to use. In that case, we can optimize load/store offsets in
// many cases.
bool lowMemoryUnused = false;
enum { LowMemoryBound = 1024 };
// Whether to allow "loose" math semantics, ignoring corner cases with NaNs
// and assuming math follows the algebraic rules for associativity and so
// forth (which IEEE floats do not, strictly speaking). This is inspired by
// gcc/clang's -ffast-math flag.
bool fastMath = false;
// Whether to try to preserve debug info through, which are special calls.
bool debugInfo = false;
// Arbitrary string arguments from the commandline, which we forward to
// passes.
std::map<std::string, std::string> arguments;
void setDefaultOptimizationOptions() {
// -Os is our default
optimizeLevel = 2;
shrinkLevel = 1;
}
static PassOptions getWithDefaultOptimizationOptions() {
PassOptions ret;
ret.setDefaultOptimizationOptions();
return ret;
}
static PassOptions getWithoutOptimization() {
return PassOptions(); // defaults are to not optimize
}
std::string getArgument(std::string key, std::string errorTextIfMissing) {
if (arguments.count(key) == 0) {
Fatal() << errorTextIfMissing;
}
return arguments[key];
}
std::string getArgumentOrDefault(std::string key, std::string default_) {
if (arguments.count(key) == 0) {
return default_;
}
return arguments[key];
}
};
//
// Runs a set of passes, in order
//
struct PassRunner {
Module* wasm;
MixedArena* allocator;
std::vector<std::unique_ptr<Pass>> passes;
PassOptions options;
PassRunner(Module* wasm) : wasm(wasm), allocator(&wasm->allocator) {}
PassRunner(Module* wasm, PassOptions options)
: wasm(wasm), allocator(&wasm->allocator), options(options) {}
// no copying, we control |passes|
PassRunner(const PassRunner&) = delete;
PassRunner& operator=(const PassRunner&) = delete;
void setDebug(bool debug) {
options.debug = debug;
// validate everything by default if debugging
options.validateGlobally = debug;
}
void setDebugInfo(bool debugInfo) { options.debugInfo = debugInfo; }
void setValidateGlobally(bool validate) {
options.validateGlobally = validate;
}
// Add a pass using its name.
void add(std::string passName) {
auto pass = PassRegistry::get()->createPass(passName);
if (!pass) {
Fatal() << "Could not find pass: " << passName << "\n";
}
doAdd(std::move(pass));
}
// Add a pass given an instance.
template<class P> void add(std::unique_ptr<P> pass) {
doAdd(std::move(pass));
}
// Adds the default set of optimization passes; this is
// what -O does.
void addDefaultOptimizationPasses();
// Adds the default optimization passes that work on
// individual functions.
void addDefaultFunctionOptimizationPasses();
// Adds the default optimization passes that work on
// entire modules as a whole, and make sense to
// run before function passes.
void addDefaultGlobalOptimizationPrePasses();
// Adds the default optimization passes that work on
// entire modules as a whole, and make sense to
// run after function passes.
// This is run at the very end of the optimization
// process - you can assume no other opts will be run
// afterwards.
void addDefaultGlobalOptimizationPostPasses();
// Run the passes on the module
void run();
// Run the passes on a specific function
void runOnFunction(Function* func);
// Get the last pass that was already executed of a certain type.
template<class P> P* getLast();
// When running a pass runner within another pass runner, this
// flag should be set. This influences how pass debugging works,
// and may influence other things in the future too.
void setIsNested(bool nested) { isNested = nested; }
// BINARYEN_PASS_DEBUG is a convenient commandline way to log out the toplevel
// passes, their times, and validate between each pass.
// (we don't recurse pass debug into sub-passes, as it
// doesn't help anyhow and also is bad for e.g. printing
// which is a pass)
// this method returns whether we are in passDebug mode, and which value:
// 1: run pass by pass, validating in between
// 2: also save the last pass, so it breakage happens we can print the last
// one 3: also dump out byn-* files for each pass
static int getPassDebug();
protected:
bool isNested = false;
private:
void doAdd(std::unique_ptr<Pass> pass);
void runPass(Pass* pass);
void runPassOnFunction(Pass* pass, Function* func);
// After running a pass, handle any changes due to
// how the pass is defined, such as clearing away any
// temporary data structures that the pass declares it
// invalidates.
// If a function is passed, we operate just on that function;
// otherwise, the whole module.
void handleAfterEffects(Pass* pass, Function* func = nullptr);
};
//
// Core pass class
//
class Pass {
public:
virtual ~Pass() = default;
// Override this to perform preparation work before the pass runs.
// This will be called before the pass is run on a module.
virtual void prepareToRun(PassRunner* runner, Module* module) {}
// Implement this with code to run the pass on the whole module
virtual void run(PassRunner* runner, Module* module) {
WASM_UNREACHABLE("unimplemented");
}
// Implement this with code to run the pass on a single function, for
// a function-parallel pass
virtual void
runOnFunction(PassRunner* runner, Module* module, Function* function) {
WASM_UNREACHABLE("unimplemented");
}
// Function parallelism. By default, passes are not run in parallel, but you
// can override this method to say that functions are parallelizable. This
// should always be safe *unless* you do something in the pass that makes it
// not thread-safe; in other words, the Module and Function objects and
// so forth are set up so that Functions can be processed in parallel, so
// if you do not add global state that could be raced on, your pass could be
// function-parallel.
//
// Function-parallel passes create an instance of the Walker class per
// function. That means that you can't rely on Walker object properties to
// persist across your functions, and you can't expect a new object to be
// created for each function either (which could be very inefficient).
//
// It is valid for function-parallel passes to read (but not modify) global
// module state, like globals or imports. However, reading other functions'
// contents is invalid, as function-parallel tests can be run while still
// adding functions to the module.
virtual bool isFunctionParallel() { return false; }
// This method is used to create instances per function for a
// function-parallel pass. You may need to override this if you subclass a
// Walker, as otherwise this will create the parent class.
virtual Pass* create() { WASM_UNREACHABLE("unimplenented"); }
// Whether this pass modifies the Binaryen IR in the module. This is true for
// most passes, except for passes that have no side effects, or passes that
// only modify other things than Binaryen IR (for example, the Stack IR
// passes only modify that IR).
// This property is important as if Binaryen IR is modified, we need to throw
// out any Stack IR - it would need to be regenerated and optimized.
virtual bool modifiesBinaryenIR() { return true; }
std::string name;
protected:
Pass() = default;
Pass(Pass&) = default;
Pass& operator=(const Pass&) = delete;
};
//
// Core pass class that uses AST walking. This class can be parameterized by
// different types of AST walkers.
//
template<typename WalkerType>
class WalkerPass : public Pass, public WalkerType {
PassRunner* runner;
protected:
typedef WalkerPass<WalkerType> super;
public:
void run(PassRunner* runner, Module* module) override {
// Parallel pass running is implemented in the PassRunner.
if (isFunctionParallel()) {
PassRunner runner(module);
runner.setIsNested(true);
std::unique_ptr<Pass> copy;
copy.reset(create());
runner.add(std::move(copy));
runner.run();
return;
}
// Single-thread running just calls the walkModule traversal.
setPassRunner(runner);
WalkerType::setModule(module);
WalkerType::walkModule(module);
}
void
runOnFunction(PassRunner* runner, Module* module, Function* func) override {
setPassRunner(runner);
WalkerType::setModule(module);
WalkerType::walkFunction(func);
}
PassRunner* getPassRunner() { return runner; }
PassOptions& getPassOptions() { return runner->options; }
void setPassRunner(PassRunner* runner_) { runner = runner_; }
};
} // namespace wasm
#endif // wasm_pass_h