diff options
-rw-r--r-- | src/ir/iteration.h | 23 | ||||
-rw-r--r-- | src/passes/MergeBlocks.cpp | 189 | ||||
-rw-r--r-- | src/passes/ReorderFunctions.cpp | 2 | ||||
-rw-r--r-- | test/lit/passes/inlining-optimizing_optimize-level=3.wast | 46 | ||||
-rw-r--r-- | test/lit/passes/merge-blocks.wast | 207 | ||||
-rw-r--r-- | test/passes/converge_O3_metrics.bin.txt | 90 | ||||
-rw-r--r-- | test/passes/remove-unused-names_merge-blocks_all-features.txt | 106 |
7 files changed, 459 insertions, 204 deletions
diff --git a/src/ir/iteration.h b/src/ir/iteration.h index 29d183678..6574e5dff 100644 --- a/src/ir/iteration.h +++ b/src/ir/iteration.h @@ -58,15 +58,21 @@ template<class Specific> class AbstractChildIterator { void operator++() { index++; } Expression*& operator*() { - assert(index < parent.children.size()); - - // The vector of children is in reverse order, as that is how - // wasm-delegations-fields works. To get the order of execution, reverse - // things. - return *parent.children[parent.children.size() - 1 - index]; + return *parent.children[parent.mapIndex(index)]; } }; + friend struct Iterator; + + Index mapIndex(Index index) const { + assert(index < children.size()); + + // The vector of children is in reverse order, as that is how + // wasm-delegations-fields works. To get the order of execution, reverse + // things. + return children.size() - 1 - index; + } + public: // The vector of children in the order emitted by wasm-delegations-fields // (which is in reverse execution order). @@ -112,6 +118,11 @@ public: void addChild(Expression* parent, Expression** child) { children.push_back(child); } + + // API for accessing children in random order. + Expression*& getChild(Index index) { return *children[mapIndex(index)]; } + + Index getNumChildren() { return children.size(); } }; class ChildIterator : public AbstractChildIterator<ChildIterator> { diff --git a/src/passes/MergeBlocks.cpp b/src/passes/MergeBlocks.cpp index 6ba486717..225f4f05d 100644 --- a/src/passes/MergeBlocks.cpp +++ b/src/passes/MergeBlocks.cpp @@ -509,74 +509,155 @@ struct MergeBlocks return; } + // As we go through the children, to move things to the outside means + // moving them past the children before them: + // + // (parent + // (child1 + // (A) + // (B) + // ) + // (child2 + // + // If we move (A) out of parent, then that is fine (further things moved + // out would appear after it). But if we leave (B) in its current position + // then if we try to move anything from child2 out of parent then we must + // move those things past (B). We use a vector to track the effects of the + // children, where it contains the effects of what was left in the child + // after optimization. + std::vector<EffectAnalyzer> childEffects; + ChildIterator iterator(curr); - auto& children = iterator.children; - if (children.size() == 1) { - optimize(curr, *children[0]); - } else if (children.size() == 2) { - optimize(curr, *children[0], optimize(curr, *children[1]), children[1]); - } else if (children.size() == 3) { - optimizeTernary(curr, *children[2], *children[1], *children[0]); + auto numChildren = iterator.getNumChildren(); + + // Find the last block among the children, as all we are trying to do here + // is move the contents of blocks outwards. + Index lastBlock = -1; + for (Index i = 0; i < numChildren; i++) { + if (iterator.getChild(i)->is<Block>()) { + lastBlock = i; + } } - } - - void visitIf(If* curr) { - // We can move code out of the condition, but not any of the other children. - optimize(curr, curr->condition); - } - - void optimizeTernary(Expression* curr, - Expression*& first, - Expression*& second, - Expression*& third) { - Block* outer = nullptr; - outer = optimize(curr, first, outer); - // TODO: for now, just stop when we see any side effect after the first - // item, but we could handle them carefully like we do for binaries. - if (EffectAnalyzer(getPassOptions(), *getModule(), second) - .hasSideEffects()) { + if (lastBlock == Index(-1)) { + // There are no blocks at all, so there is nothing to optimize. return; } - outer = optimize(curr, second, outer); - if (EffectAnalyzer(getPassOptions(), *getModule(), third) - .hasSideEffects()) { - return; + + // We'll only compute effects up to the child before the last block, since + // we have nothing to optimize afterwards, which sets a maximum size on the + // vector. + if (lastBlock > 0) { + childEffects.reserve(lastBlock); } - optimize(curr, third, outer); - } - template<typename T> void handleCall(T* curr) { - Block* outer = nullptr; - for (Index i = 0; i < curr->operands.size(); i++) { - if (EffectAnalyzer(getPassOptions(), *getModule(), curr->operands[i]) - .hasSideEffects()) { - return; + // The outer block that will replace us, containing the contents moved out + // and then ourselves, assuming we manage to optimize. + Block* outerBlock = nullptr; + + for (Index i = 0; i <= lastBlock; i++) { + auto* child = iterator.getChild(i); + auto* block = child->dynCast<Block>(); + + auto continueEarly = [&]() { + // When we continue early, after failing to find anything to optimize, + // the effects we need to note for the child are simply those of the + // child in its original form. + childEffects.emplace_back(getPassOptions(), *getModule(), child); + }; + + // If there is no block, or it is one that might have branches, or it is + // too small for us to remove anything from (we cannot remove the last + // element), or if it has unreachable code (leave that for dce), then give + // up. + if (!block || block->name.is() || block->list.size() <= 1 || + hasUnreachableChild(block)) { + continueEarly(); + continue; } - outer = optimize(curr, curr->operands[i], outer); - } - } - void visitCall(Call* curr) { handleCall(curr); } + // Also give up if the block's last element has a different type than the + // block, as that would mean we would change the type received by the + // parent (which might cause its type to need to be updated, for example). + // Leave this alone, as other passes will simplify this anyhow (using + // refinalize). + auto* back = block->list.back(); + if (block->type != back->type) { + continueEarly(); + continue; + } - template<typename T> void handleNonDirectCall(T* curr) { - Block* outer = nullptr; - for (Index i = 0; i < curr->operands.size(); i++) { - if (EffectAnalyzer(getPassOptions(), *getModule(), curr->operands[i]) - .hasSideEffects()) { - return; + // The block seems to have the shape we want. Check for effects: we want + // to move all the items out but the last one, so they must all cross over + // anything we need to move past. + // + // In principle we could also handle the case where we can move out only + // some of the block items. However, that would be more complex (we'd need + // to allocate a new block sometimes), it is rare, and it may not always + // be helpful (we wouldn't actually be getting rid of the child block - + // although, in the binary format such blocks tend to vanish anyhow). + bool fail = false; + for (auto* blockChild : block->list) { + if (blockChild == back) { + break; + } + EffectAnalyzer blockChildEffects( + getPassOptions(), *getModule(), blockChild); + for (auto& effects : childEffects) { + if (blockChildEffects.invalidates(effects)) { + fail = true; + break; + } + } + if (fail) { + break; + } + } + if (fail) { + continueEarly(); + continue; + } + + // Wonderful, we can do this! Move our items to an outer block, reusing + // this one if there isn't one already. + if (!outerBlock) { + // Leave all the items there, just remove the last one which will remain + // where it was. + block->list.pop_back(); + outerBlock = block; + } else { + // Move the items to the existing outer block. + for (auto* blockChild : block->list) { + if (blockChild == back) { + break; + } + outerBlock->list.push_back(blockChild); + } + } + + // Set the back element as the new child, replacing the block that was + // there. + iterator.getChild(i) = back; + + // If there are further elements, we need to know what effects the + // remaining code has, as if they move they'll move past it. + if (i < lastBlock) { + childEffects.emplace_back(getPassOptions(), *getModule(), back); } - outer = optimize(curr, curr->operands[i], outer); } - if (EffectAnalyzer(getPassOptions(), *getModule(), curr->target) - .hasSideEffects()) { - return; + + if (outerBlock) { + // We moved items outside, which means we must replace ourselves with the + // block. + outerBlock->list.push_back(curr); + outerBlock->finalize(curr->type); + replaceCurrent(outerBlock); } - optimize(curr, curr->target, outer); } - void visitCallIndirect(CallIndirect* curr) { handleNonDirectCall(curr); } - - void visitCallRef(CallRef* curr) { handleNonDirectCall(curr); } + void visitIf(If* curr) { + // We can move code out of the condition, but not any of the other children. + optimize(curr, curr->condition); + } void visitThrow(Throw* curr) { Block* outer = nullptr; diff --git a/src/passes/ReorderFunctions.cpp b/src/passes/ReorderFunctions.cpp index 66b8275ef..326893951 100644 --- a/src/passes/ReorderFunctions.cpp +++ b/src/passes/ReorderFunctions.cpp @@ -24,7 +24,7 @@ // a less beneficial position for compression, that is, mutually-compressible // functions are no longer together (when they were before, in the original // order, the has some natural tendency one way or the other). TODO: investigate -// similarity ordering here. +// similarity ordering here (see #4322) // #include <memory> diff --git a/test/lit/passes/inlining-optimizing_optimize-level=3.wast b/test/lit/passes/inlining-optimizing_optimize-level=3.wast index 11eea40ff..980075d14 100644 --- a/test/lit/passes/inlining-optimizing_optimize-level=3.wast +++ b/test/lit/passes/inlining-optimizing_optimize-level=3.wast @@ -6478,34 +6478,32 @@ ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (loop $while-in66 - ;; CHECK-NEXT: (i32.store - ;; CHECK-NEXT: (local.get $11) - ;; CHECK-NEXT: (call $___uremdi3 - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (global.set $tempRet0 - ;; CHECK-NEXT: (i32.add - ;; CHECK-NEXT: (i32.gt_u - ;; CHECK-NEXT: (local.tee $20 - ;; CHECK-NEXT: (call $_bitshift64Shl - ;; CHECK-NEXT: (i32.load - ;; CHECK-NEXT: (local.get $11) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: (local.get $17) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.tee $12 - ;; CHECK-NEXT: (i32.add - ;; CHECK-NEXT: (local.get $12) - ;; CHECK-NEXT: (local.get $20) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $tempRet0 + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.gt_u + ;; CHECK-NEXT: (local.tee $20 + ;; CHECK-NEXT: (call $_bitshift64Shl + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (local.get $11) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (global.get $tempRet0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $17) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.tee $12 + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $12) + ;; CHECK-NEXT: (local.get $20) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $12) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.get $tempRet0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (local.get $11) + ;; CHECK-NEXT: (call $___uremdi3 + ;; CHECK-NEXT: (local.get $12) ;; CHECK-NEXT: (local.tee $20 ;; CHECK-NEXT: (global.get $tempRet0) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/merge-blocks.wast b/test/lit/passes/merge-blocks.wast index 3d931690b..d6ff8b3d8 100644 --- a/test/lit/passes/merge-blocks.wast +++ b/test/lit/passes/merge-blocks.wast @@ -127,15 +127,13 @@ ;; CHECK: (func $array.set-no-1 (param $foo (ref $array)) ;; CHECK-NEXT: (local $bar i32) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: (array.set $array ;; CHECK-NEXT: (local.get $foo) - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (local.tee $bar - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.tee $bar + ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 37) ;; CHECK-NEXT: ) @@ -159,16 +157,14 @@ ;; CHECK: (func $array.set-no-2 (param $foo (ref $array)) ;; CHECK-NEXT: (local $bar i32) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: (array.set $array ;; CHECK-NEXT: (local.get $foo) ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (local.tee $bar - ;; CHECK-NEXT: (i32.const 37) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.tee $bar + ;; CHECK-NEXT: (i32.const 37) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -226,4 +222,187 @@ ) ) ) + + ;; CHECK: (func $subsequent-children (param $x i32) (param $y i32) (param $z i32) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $helper + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $helper + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $subsequent-children + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $subsequent-children (param $x i32) (param $y i32) (param $z i32) (result i32) + ;; Both of the calls to helper can be moved outside. Those calls remain in + ;; order after doing so, so there is no problem, and none of them are moved + ;; across anything with side effects. This leaves only consts in the call to + ;; $subsequent-children. + (call $subsequent-children + (block (result i32) + (drop (call $helper (i32.const 0))) + (i32.const 1) + ) + (i32.const 2) + (block (result i32) + (drop (call $helper (i32.const 3))) + (i32.const 4) + ) + ) + ) + + ;; CHECK: (func $subsequent-children-1 (param $x i32) (param $y i32) (param $z i32) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $helper + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $subsequent-children-1 + ;; CHECK-NEXT: (call $helper + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $helper + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $subsequent-children-1 (param $x i32) (param $y i32) (param $z i32) (result i32) + (call $subsequent-children-1 + (block (result i32) + (drop (call $helper (i32.const 0))) + (call $helper (i32.const 1)) ;; Compared to before, this is now a call, so + ;; it has side effects, and the call with arg + ;; 3 cannot be moved past it. + ) + (i32.const 2) + (block (result i32) + (drop (call $helper (i32.const 3))) + (i32.const 4) + ) + ) + ) + + ;; CHECK: (func $subsequent-children-2 (param $x i32) (param $y i32) (param $z i32) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $helper + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $subsequent-children-2 + ;; CHECK-NEXT: (call $helper + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $helper + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $subsequent-children-2 (param $x i32) (param $y i32) (param $z i32) (result i32) + (call $subsequent-children-2 + (block (result i32) + (drop (call $helper (i32.const 0))) + (call $helper (i32.const 1)) + ) + ;; Similar to the above, but with the main call's last two arguments flipped. + ;; This should not have an effect on the output: we still can't pull out the + ;; call with arg 3. + (block (result i32) + (drop (call $helper (i32.const 3))) + (i32.const 4) + ) + (i32.const 2) + ) + ) + + ;; CHECK: (func $subsequent-children-3 (param $x i32) (param $y i32) (param $z i32) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $subsequent-children-3 + ;; CHECK-NEXT: (call $helper + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $helper + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $subsequent-children-3 (param $x i32) (param $y i32) (param $z i32) (result i32) + (call $subsequent-children-3 + (block (result i32) + (drop (i32.const 0)) ;; Similar to the above, but this is just a const now + ;; and not a call. We still can't pull out the call + ;; with arg 3, due to the call with arg 1. + (call $helper (i32.const 1)) + ) + (block (result i32) + (drop (call $helper (i32.const 3))) + (i32.const 4) + ) + (i32.const 2) + ) + ) + + ;; CHECK: (func $subsequent-children-4 (param $x i32) (param $y i32) (param $z i32) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $helper + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $subsequent-children-4 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $subsequent-children-4 (param $x i32) (param $y i32) (param $z i32) (result i32) + (call $subsequent-children-4 + (block (result i32) + (drop (i32.const 0)) + ;; Similar to the above, but remove the call on arg 1 as well. Now we *can* + ;; pull out the call with arg 3. + (i32.const 1) + ) + (block (result i32) + (drop (call $helper (i32.const 3))) + (i32.const 4) + ) + (i32.const 2) + ) + ) + + ;; CHECK: (func $helper (param $x i32) (result i32) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $helper (param $x i32) (result i32) + (unreachable) + ) ) diff --git a/test/passes/converge_O3_metrics.bin.txt b/test/passes/converge_O3_metrics.bin.txt index 48cc55541..8b17e64f5 100644 --- a/test/passes/converge_O3_metrics.bin.txt +++ b/test/passes/converge_O3_metrics.bin.txt @@ -54,21 +54,6 @@ total (func $_main (; has Stack IR ;) (result i32) (local $0 i32) (local $1 i32) - (local.set $1 - (i32.load offset=24 - (i32.add - (i32.load - (i32.sub - (i32.load - (i32.const 18100) - ) - (i32.const 12) - ) - ) - (i32.const 18100) - ) - ) - ) (local.set $0 (i32.const 10888) ) @@ -84,6 +69,21 @@ total ) ) ) + (local.set $1 + (i32.load offset=24 + (i32.add + (i32.load + (i32.sub + (i32.load + (i32.const 18100) + ) + (i32.const 12) + ) + ) + (i32.const 18100) + ) + ) + ) (if (local.tee $0 (i32.sub @@ -278,21 +278,6 @@ total (func $_main (; has Stack IR ;) (result i32) (local $0 i32) (local $1 i32) - (local.set $1 - (i32.load offset=24 - (i32.add - (i32.load - (i32.sub - (i32.load - (i32.const 18100) - ) - (i32.const 12) - ) - ) - (i32.const 18100) - ) - ) - ) (local.set $0 (i32.const 10888) ) @@ -308,6 +293,21 @@ total ) ) ) + (local.set $1 + (i32.load offset=24 + (i32.add + (i32.load + (i32.sub + (i32.load + (i32.const 18100) + ) + (i32.const 12) + ) + ) + (i32.const 18100) + ) + ) + ) (if (local.tee $0 (i32.sub @@ -497,21 +497,6 @@ total (func $_main (; has Stack IR ;) (result i32) (local $0 i32) (local $1 i32) - (local.set $1 - (i32.load offset=24 - (i32.add - (i32.load - (i32.sub - (i32.load - (i32.const 18100) - ) - (i32.const 12) - ) - ) - (i32.const 18100) - ) - ) - ) (local.set $0 (i32.const 10888) ) @@ -527,6 +512,21 @@ total ) ) ) + (local.set $1 + (i32.load offset=24 + (i32.add + (i32.load + (i32.sub + (i32.load + (i32.const 18100) + ) + (i32.const 12) + ) + ) + (i32.const 18100) + ) + ) + ) (if (local.tee $0 (i32.sub diff --git a/test/passes/remove-unused-names_merge-blocks_all-features.txt b/test/passes/remove-unused-names_merge-blocks_all-features.txt index ab1837ef7..e92d2ab9b 100644 --- a/test/passes/remove-unused-names_merge-blocks_all-features.txt +++ b/test/passes/remove-unused-names_merge-blocks_all-features.txt @@ -300,15 +300,15 @@ ) ) (drop - (block (result i32) - (unreachable) - (drop - (i32.const 20) - ) - (i32.add + (i32.const 20) + ) + (drop + (i32.add + (block (result i32) + (unreachable) (i32.const 10) - (i32.const 30) ) + (i32.const 30) ) ) ) @@ -417,19 +417,19 @@ ) ) (drop - (block (result i32) - (unreachable) - (drop - (i32.const 30) - ) - (drop - (i32.const 50) - ) - (select + (i32.const 30) + ) + (drop + (i32.const 50) + ) + (drop + (select + (block (result i32) + (unreachable) (i32.const 20) - (i32.const 40) - (i32.const 60) ) + (i32.const 40) + (i32.const 60) ) ) (drop @@ -454,24 +454,25 @@ (i32.const 10) ) (drop + (i32.const 50) + ) + (drop (select (i32.const 20) (block (result i32) (unreachable) (i32.const 40) ) - (block (result i32) - (drop - (i32.const 50) - ) - (i32.const 60) - ) + (i32.const 60) ) ) (drop (i32.const 10) ) (drop + (i32.const 50) + ) + (drop (select (i32.const 20) (block (result i32) @@ -480,12 +481,7 @@ ) (unreachable) ) - (block (result i32) - (drop - (i32.const 50) - ) - (i32.const 60) - ) + (i32.const 60) ) ) (drop @@ -604,17 +600,18 @@ (i32.const 20) (i32.const 40) ) + (drop + (i32.const 20) + ) (call $call-ii (block (result i32) (unreachable) (i32.const 10) ) - (block (result i32) - (drop - (i32.const 20) - ) - (i32.const 30) - ) + (i32.const 30) + ) + (drop + (i32.const 20) ) (call $call-ii (block (result i32) @@ -623,12 +620,7 @@ ) (unreachable) ) - (block (result i32) - (drop - (i32.const 20) - ) - (i32.const 30) - ) + (i32.const 30) ) (drop (i32.const 10) @@ -691,33 +683,27 @@ (i32.const 40) (i32.const 60) ) + (drop + (i32.const 30) + ) + (drop + (i32.const 50) + ) (call_indirect $0 (type $ii) (unreachable) - (block (result i32) - (drop - (i32.const 30) - ) - (i32.const 40) - ) - (block (result i32) - (drop - (i32.const 50) - ) - (i32.const 60) - ) + (i32.const 40) + (i32.const 60) ) (drop (i32.const 31) ) + (drop + (i32.const 51) + ) (call_indirect $0 (type $ii) (i32.const 41) (unreachable) - (block (result i32) - (drop - (i32.const 51) - ) - (i32.const 61) - ) + (i32.const 61) ) (drop (i32.const 32) |