|
17 | 17 | // |
18 | 18 | // Merges blocks to their parents. |
19 | 19 | // |
| 20 | +// We also restructure blocks in order to enable such merging. For |
| 21 | +// example, |
| 22 | +// |
| 23 | +// (i32.store |
| 24 | +// (block |
| 25 | +// (call $foo) |
| 26 | +// (i32.load (i32.const 100)) |
| 27 | +// ) |
| 28 | +// (i32.const 0) |
| 29 | +// ) |
| 30 | +// |
| 31 | +// can be transformed into |
| 32 | +// |
| 33 | +// (block |
| 34 | +// (call $foo) |
| 35 | +// (i32.store |
| 36 | +// (block |
| 37 | +// (i32.load (i32.const 100)) |
| 38 | +// ) |
| 39 | +// (i32.const 0) |
| 40 | +// ) |
| 41 | +// ) |
| 42 | +// |
| 43 | +// after which the internal block can go away, and |
| 44 | +// the new external block might be mergeable. This is always |
| 45 | +// worth it if the internal block ends up with 1 item. |
| 46 | +// For the second operand, |
| 47 | +// |
| 48 | +// (i32.store |
| 49 | +// (i32.const 100) |
| 50 | +// (block |
| 51 | +// (call $foo) |
| 52 | +// (i32.load (i32.const 200)) |
| 53 | +// ) |
| 54 | +// ) |
| 55 | +// |
| 56 | +// The order of operations requires that the first execute |
| 57 | +// before. We can do the same operation, but only if the |
| 58 | +// first has no side effects, or the code we are moving out |
| 59 | +// has no side effects. |
| 60 | +// If we can do this to both operands, we can generate a |
| 61 | +// single outside block. |
| 62 | +// |
20 | 63 |
|
21 | 64 | #include <wasm.h> |
22 | 65 | #include <pass.h> |
| 66 | +#include <ast_utils.h> |
23 | 67 |
|
24 | 68 | namespace wasm { |
25 | 69 |
|
@@ -50,6 +94,86 @@ struct MergeBlocks : public WalkerPass<PostWalker<MergeBlocks, Visitor<MergeBloc |
50 | 94 | } |
51 | 95 | } |
52 | 96 | } |
| 97 | + |
| 98 | + Block* optimize(Expression* curr, Expression*& child, Block* outer = nullptr, Expression** dependency1 = nullptr, Expression** dependency2 = nullptr) { |
| 99 | + if (!child) return outer; |
| 100 | + if (dependency1 && *dependency1 && EffectAnalyzer(*dependency1).hasSideEffects()) return outer; |
| 101 | + if (dependency2 && *dependency2 && EffectAnalyzer(*dependency2).hasSideEffects()) return outer; |
| 102 | + if (auto* block = child->dynCast<Block>()) { |
| 103 | + if (block->list.size() >= 2) { |
| 104 | + child = block->list.back(); |
| 105 | + if (outer == nullptr) { |
| 106 | + // reuse the block, move it out |
| 107 | + block->list.back() = curr; |
| 108 | + block->finalize(); // last block element was our input, and is now our output, which may differ TODO optimize |
| 109 | + replaceCurrent(block); |
| 110 | + return block; |
| 111 | + } else { |
| 112 | + // append to an existing outer block |
| 113 | + assert(outer->list.back() == curr); |
| 114 | + outer->list.pop_back(); |
| 115 | + for (Index i = 0; i < block->list.size() - 1; i++) { |
| 116 | + outer->list.push_back(block->list[i]); |
| 117 | + } |
| 118 | + outer->list.push_back(curr); |
| 119 | + } |
| 120 | + } |
| 121 | + } |
| 122 | + return outer; |
| 123 | + } |
| 124 | + |
| 125 | + void visitUnary(Unary* curr) { |
| 126 | + optimize(curr, curr->value); |
| 127 | + } |
| 128 | + void visitSetLocal(SetLocal* curr) { |
| 129 | + optimize(curr, curr->value); |
| 130 | + } |
| 131 | + void visitLoad(Load* curr) { |
| 132 | + optimize(curr, curr->ptr); |
| 133 | + } |
| 134 | + void visitReturn(Return* curr) { |
| 135 | + optimize(curr, curr->value); |
| 136 | + } |
| 137 | + |
| 138 | + void visitBinary(Binary* curr) { |
| 139 | + optimize(curr, curr->right, optimize(curr, curr->left), &curr->left); |
| 140 | + } |
| 141 | + void visitStore(Store* curr) { |
| 142 | + optimize(curr, curr->value, optimize(curr, curr->ptr), &curr->ptr); |
| 143 | + } |
| 144 | + |
| 145 | + void visitSelect(Select* curr) { |
| 146 | + optimize(curr, curr->condition, optimize(curr, curr->ifFalse, optimize(curr, curr->ifTrue), &curr->ifTrue), &curr->ifTrue, &curr->ifFalse); |
| 147 | + } |
| 148 | + |
| 149 | + void visitBreak(Break* curr) { |
| 150 | + optimize(curr, curr->condition, optimize(curr, curr->value), &curr->value); |
| 151 | + } |
| 152 | + void visitSwitch(Switch* curr) { |
| 153 | + optimize(curr, curr->condition, optimize(curr, curr->value), &curr->value); |
| 154 | + } |
| 155 | + |
| 156 | + template<typename T> |
| 157 | + void handleCall(T* curr, Block* outer = nullptr) { |
| 158 | + for (Index i = 0; i < curr->operands.size(); i++) { |
| 159 | + outer = optimize(curr, curr->operands[i], outer); |
| 160 | + if (EffectAnalyzer(curr->operands[i]).hasSideEffects()) return; |
| 161 | + } |
| 162 | + } |
| 163 | + |
| 164 | + void visitCall(Call* curr) { |
| 165 | + handleCall(curr); |
| 166 | + } |
| 167 | + |
| 168 | + void visitCallImport(CallImport* curr) { |
| 169 | + handleCall(curr); |
| 170 | + } |
| 171 | + |
| 172 | + void visitCallIndirect(CallIndirect* curr) { |
| 173 | + auto* outer = optimize(curr, curr->target); |
| 174 | + if (EffectAnalyzer(curr->target).hasSideEffects()) return; |
| 175 | + handleCall(curr, outer); |
| 176 | + } |
53 | 177 | }; |
54 | 178 |
|
55 | 179 | static RegisterPass<MergeBlocks> registerPass("merge-blocks", "merges blocks to their parents"); |
|
0 commit comments