summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlon Zakai <azakai@google.com>2022-09-13 15:10:00 -0700
committerGitHub <noreply@github.com>2022-09-13 22:10:00 +0000
commit795a70b02032f57f14d97d7555af97f0e527b4b0 (patch)
treeec3f0625ddfef2b831b1801990a39eafb912d328
parentf1a3e682e864fcb827a01d3c5725d847053fe149 (diff)
downloadbinaryen-795a70b02032f57f14d97d7555af97f0e527b4b0.tar.gz
binaryen-795a70b02032f57f14d97d7555af97f0e527b4b0.tar.bz2
binaryen-795a70b02032f57f14d97d7555af97f0e527b4b0.zip
[Exceptions] Optimize in CodePushing even with exceptions thrown (#5028)
We had some concerns about this not working in the past, but thinking about it now, I believe it is safe to do. Specifically, a throw is either like a break or a return - either it jumps out to an outer scope (like a break) or it jumps out of the function (like a return), and both breaks and returns have already been handled here. This change has some nice effects on J2Wasm output, where there are quite a lot of throws, which we can now optimize around.
-rw-r--r--src/ir/effects.h12
-rw-r--r--src/passes/CodePushing.cpp20
-rw-r--r--test/lit/passes/code-pushing-eh.wast71
3 files changed, 96 insertions, 7 deletions
diff --git a/src/ir/effects.h b/src/ir/effects.h
index d3ae9ba20..ad3ab235d 100644
--- a/src/ir/effects.h
+++ b/src/ir/effects.h
@@ -881,9 +881,19 @@ public:
return effects;
}
- void ignoreBranches() {
+ // Ignores all forms of control flow transfers: breaks, returns, and
+ // exceptions. (Note that traps are not considered relevant here - a trap does
+ // not just transfer control flow, but can be seen as halting the entire
+ // program.)
+ //
+ // This function matches transfersControlFlow(), that is, after calling this
+ // method transfersControlFlow() will always return false.
+ void ignoreControlFlowTransfers() {
branchesOut = false;
breakTargets.clear();
+ throws_ = false;
+ delegateTargets.clear();
+ assert(!transfersControlFlow());
}
private:
diff --git a/src/passes/CodePushing.cpp b/src/passes/CodePushing.cpp
index 682f025c8..5026773d4 100644
--- a/src/passes/CodePushing.cpp
+++ b/src/passes/CodePushing.cpp
@@ -161,11 +161,21 @@ private:
// everything that matters if you want to be pushed past the pushPoint
EffectAnalyzer cumulativeEffects(passOptions, module);
cumulativeEffects.walk(list[pushPoint]);
- // it is ok to ignore the branching here, that is the crucial point of this
- // opt
- // TODO: it would be ok to ignore thrown exceptions here, if we know they
- // could not be caught and must go outside of the function
- cumulativeEffects.ignoreBranches();
+ // It is ok to ignore branching out of the block here, that is the crucial
+ // point of this optimization. That is, we are in a situation like this:
+ //
+ // {
+ // x = value;
+ // if (..) break;
+ // foo(x);
+ // }
+ //
+ // If the branch is taken, then that's fine, it will jump out of this block
+ // and reach some outer scope, and in that case we never need x at all
+ // (since we've proven before that x is not used outside of this block, see
+ // numGetsSoFar which we use for that). Similarly, control flow could
+ // transfer away via a return or an exception and that would be ok as well.
+ cumulativeEffects.ignoreControlFlowTransfers();
std::vector<LocalSet*> toPush;
Index i = pushPoint - 1;
while (1) {
diff --git a/test/lit/passes/code-pushing-eh.wast b/test/lit/passes/code-pushing-eh.wast
index c428d757d..294c76898 100644
--- a/test/lit/passes/code-pushing-eh.wast
+++ b/test/lit/passes/code-pushing-eh.wast
@@ -60,7 +60,8 @@
(func $cannot-push-past-throw
(local $x i32)
(block $out
- ;; This local.set cannot be pushed down, because there is 'throw' below
+ ;; This local.set cannot be pushed down, because there is 'throw' below.
+ ;; This pass only pushes past conditional control flow atm.
(local.set $x (i32.const 1))
(throw $e (i32.const 0))
(drop (i32.const 1))
@@ -322,4 +323,72 @@
(drop (local.get $x))
)
)
+
+ ;; CHECK: (func $can-push-past-conditional-throw (param $param i32)
+ ;; CHECK-NEXT: (local $x i32)
+ ;; CHECK-NEXT: (block $block
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (local.get $param)
+ ;; CHECK-NEXT: (throw $e
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $can-push-past-conditional-throw (param $param i32)
+ (local $x i32)
+ (block $block
+ ;; We can push past an if containing a throw. The if is conditional
+ ;; control flow, which is what we look for in this optimization, and a
+ ;; throw is like a break - it will jump out of the current block - so we
+ ;; can push the set past it, as the set is only needed in this block.
+ (local.set $x (i32.const 1))
+ (if
+ (local.get $param)
+ (throw $e (i32.const 0))
+ )
+ (drop (local.get $x))
+ )
+ )
+
+ ;; CHECK: (func $cannot-push-past-conditional-throw-extra-use (param $param i32)
+ ;; CHECK-NEXT: (local $x i32)
+ ;; CHECK-NEXT: (block $block
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (local.get $param)
+ ;; CHECK-NEXT: (throw $e
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $cannot-push-past-conditional-throw-extra-use (param $param i32)
+ (local $x i32)
+ ;; As above, but now there is another local.get outside of the block. That
+ ;; means the local.set cannot be pushed to a place it might not execute.
+ (block $block
+ (local.set $x (i32.const 1))
+ (if
+ (local.get $param)
+ (throw $e (i32.const 0))
+ )
+ (drop (local.get $x))
+ )
+ (drop (local.get $x))
+ )
)