diff options
author | Alon Zakai <alonzakai@gmail.com> | 2018-07-30 17:36:12 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-07-30 17:36:12 -0700 |
commit | 353e2c4fcb7662b8c73f5673fd0fc21bcb7287c6 (patch) | |
tree | eda3800dcd9751f29492fc5732f4af41c0e473c8 /src/pass.h | |
parent | 0483d81ad9a665d404f2e2182e718ccbf0592be7 (diff) | |
download | binaryen-353e2c4fcb7662b8c73f5673fd0fc21bcb7287c6.tar.gz binaryen-353e2c4fcb7662b8c73f5673fd0fc21bcb7287c6.tar.bz2 binaryen-353e2c4fcb7662b8c73f5673fd0fc21bcb7287c6.zip |
Stack IR (#1623)
This adds a new IR, "Stack IR". This represents wasm at a very low level, as a simple stream of instructions, basically the same as wasm's binary format. This is unlike Binaryen IR which is structured and in a tree format.
This gives some small wins on binary sizes, less than 1% in most cases, usually 0.25-0.50% or so. That's not much by itself, but looking forward this prepares us for multi-value, which we really need an IR like this to be able to optimize well. Also, it's possible there is more we can do already - currently there are just a few stack IR optimizations implemented,
DCE
local2stack - check if a set_local/get_local pair can be removed, which keeps the set's value on the stack, which if the stars align it can be popped instead of the get.
Block removal - remove any blocks with no branches, as they are valid in wasm binary format.
Implementation-wise, the IR is defined in wasm-stack.h. A new StackInst is defined, representing a single instruction. Most are simple reflections of Binaryen IR (an add, a load, etc.), and just pointers to them. Control flow constructs are expanded into multiple instructions, like a block turns into a block begin and end, and we may also emit extra unreachables to handle the fact Binaryen IR has unreachable blocks/ifs/loops but wasm does not. Overall, all the Binaryen IR differences with wasm vanish on the way to stack IR.
Where this IR lives: Each Function now has a unique_ptr to stack IR, that is, a function may have stack IR alongside the main IR. If the stack IR is present, we write it out during binary writing; if not, we do the same binaryen IR => wasm binary process as before (this PR should not affect speed there). This design lets us use normal Passes on stack IR, in particular this PR defines 3 passes:
Generate stack IR
Optimize stack IR (might be worth splitting out into separate passes eventually)
Print stack IR for debugging purposes
Having these as normal passes is convenient as then they can run in parallel across functions and all the other conveniences of our current Pass system. However, a downside of keeping the second IR as an option on Functions, and using normal Passes to operate on it, means that we may get out of sync: if you generate stack IR, then modify binaryen IR, then the stack IR may no longer be valid (for example, maybe you removed locals or modified instructions in place etc.). To avoid that, Passes now define if they modify Binaryen IR or not; if they do, we throw away the stack IR.
Miscellaneous notes:
Just writing Stack IR, then writing to binary - no optimizations - is 20% slower than going directly to binary, which is one reason why we still support direct writing. This does lead to some "fun" C++ template code to make that convenient: there is a single StackWriter class, templated over the "mode", which is either Binaryen2Binary (direct writing), Binaryen2Stack, or Stack2Binary. This avoids a lot of boilerplate as the 3 modes share a lot of code in overlapping ways.
Stack IR does not support source maps / debug info. We just don't use that IR if debug info is present.
A tiny text format comment (if emitting non-minified text) indicates stack IR is present, if it is ((; has Stack IR ;)). This may help with debugging, just in case people forget. There is also a pass to print out the stack IR for debug purposes, as mentioned above.
The sieve binaryen.js test was actually not validating all along - these new opts broke it in a more noticeable manner. Fixed.
Added extra checks in pass-debug mode, to verify that if stack IR should have been thrown out, it was. This should help avoid any confusion with the IR being invalid.
Added a comment about the possible future of stack IR as the main IR, depending on optimization results, following some discussion earlier today.
Diffstat (limited to 'src/pass.h')
-rw-r--r-- | src/pass.h | 24 |
1 files changed, 24 insertions, 0 deletions
diff --git a/src/pass.h b/src/pass.h index 30e16761d..384dcf791 100644 --- a/src/pass.h +++ b/src/pass.h @@ -76,6 +76,10 @@ struct PassOptions { ret.setDefaultOptimizationOptions(); return ret; } + + static PassOptions getWithoutOptimization() { + return PassOptions(); // defaults are to not optimize + } }; // @@ -137,6 +141,9 @@ struct PassRunner { // 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 @@ -174,7 +181,16 @@ protected: private: void doAdd(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); }; // @@ -223,6 +239,14 @@ public: // this will create the parent class. virtual Pass* create() { WASM_UNREACHABLE(); } + // 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: |