summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/cfg/cfg-traversal.h24
-rw-r--r--test/lit/passes/rse-eh.wast358
2 files changed, 381 insertions, 1 deletions
diff --git a/src/cfg/cfg-traversal.h b/src/cfg/cfg-traversal.h
index 1464b4eb7..9b2027c42 100644
--- a/src/cfg/cfg-traversal.h
+++ b/src/cfg/cfg-traversal.h
@@ -258,8 +258,29 @@ struct CFGWalker : public ControlFlowWalker<SubType, VisitorType> {
// ...
// end
assert(self->unwindExprStack.size() == self->throwingInstsStack.size());
- for (int i = self->throwingInstsStack.size() - 1; i >= 0; i--) {
+ for (int i = self->throwingInstsStack.size() - 1; i >= 0;) {
auto* tryy = self->unwindExprStack[i]->template cast<Try>();
+ if (tryy->isDelegate()) {
+ // If this delegates to the caller, there is no possibility that this
+ // instruction can throw to outer catches.
+ if (tryy->delegateTarget == DELEGATE_CALLER_TARGET) {
+ break;
+ }
+ // If this delegates to an outer try, we skip catches between this try
+ // and the target try.
+ bool found = false;
+ for (int j = i - 1; j >= 0; j--) {
+ if (self->unwindExprStack[j]->template cast<Try>()->name ==
+ tryy->delegateTarget) {
+ i = j;
+ found = true;
+ break;
+ }
+ }
+ assert(found);
+ continue;
+ }
+
// Exception thrown. Note outselves so that we will create a link to each
// catch within the try when we get there.
self->throwingInstsStack[i].push_back(self->currBasicBlock);
@@ -269,6 +290,7 @@ struct CFGWalker : public ControlFlowWalker<SubType, VisitorType> {
if (tryy->hasCatchAll()) {
break;
}
+ i--;
}
}
diff --git a/test/lit/passes/rse-eh.wast b/test/lit/passes/rse-eh.wast
index e8b9570ed..8be0cfa82 100644
--- a/test/lit/passes/rse-eh.wast
+++ b/test/lit/passes/rse-eh.wast
@@ -452,4 +452,362 @@
;; by one of the catches and it will set the local to 1.
(local.set $x (i32.const 1))
)
+
+ ;; CHECK: (func $catchless-try
+ ;; CHECK-NEXT: (local $x i32)
+ ;; CHECK-NEXT: (try $try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $catchless-try
+ (local $x i32)
+ (try
+ (do
+ (call $foo)
+ (local.set $x (i32.const 1))
+ )
+ )
+ ;; The only way we end up here is when (call $foo) does not throw, because
+ ;; if (call $foo) throws, it will throw to the caller because it is within
+ ;; a catchless try. In that case the local.set after (call $foo) would have
+ ;; run before this, so this can be dropped.
+ (local.set $x (i32.const 1))
+ )
+
+ ;; CHECK: (func $try-delegate0
+ ;; CHECK-NEXT: (local $x i32)
+ ;; CHECK-NEXT: (try $l0
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (try $try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (try $try2
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (throw $e
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (delegate $l0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch_all
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch_all
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-delegate0
+ (local $x i32)
+ (try $l0
+ (do
+ (try
+ (do
+ (try
+ (do
+ (throw $e (i32.const 0))
+ )
+ (delegate $l0)
+ )
+ )
+ (catch_all)
+ )
+ )
+ (catch_all
+ (local.set $x (i32.const 1))
+ )
+ )
+ ;; The innermost try has a delegate, which delegates to the outermost try's
+ ;; catch_all, which has the same local.set. So this can be dropped.
+ (local.set $x (i32.const 1))
+ )
+
+ ;; CHECK: (func $try-delegate1
+ ;; CHECK-NEXT: (local $x i32)
+ ;; CHECK-NEXT: (try $l0
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (try $try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (try $try3
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (throw $e
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (delegate $l0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch_all
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch_all
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-delegate1
+ (local $x i32)
+ (try $l0
+ (do
+ (try
+ (do
+ (try
+ (do
+ (throw $e (i32.const 0))
+ )
+ (delegate $l0)
+ )
+ )
+ (catch_all
+ (local.set $x (i32.const 1))
+ )
+ )
+ )
+ (catch_all)
+ )
+ ;; The middle try's catch_all has the same local.set, but it is skipped
+ ;; because the innermost try-delegate delegates to the outer try while
+ ;; skipping the middle try-catch_all. So this should NOT be dropped.
+ (local.set $x (i32.const 1))
+ )
+
+ ;; CHECK: (func $try-delegate2
+ ;; CHECK-NEXT: (local $x i32)
+ ;; CHECK-NEXT: (try $l0
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (try $try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (try $try4
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (throw $e
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (delegate 2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch_all
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch_all
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-delegate2
+ (local $x i32)
+ (try $l0
+ (do
+ (try
+ (do
+ (try
+ (do
+ (throw $e (i32.const 0))
+ )
+ (delegate 2) ;; delegate to caller
+ )
+ )
+ (catch_all
+ (local.set $x (i32.const 1))
+ )
+ )
+ )
+ (catch_all
+ (local.set $x (i32.const 1))
+ )
+ )
+ ;; The innermost try-delegate delegates to the caller, bypassing all
+ ;; local.sets in the middle and the outermost try-catch_alls. So this should
+ ;; NOT be dropped. (Instead this is unreachable, but that's DCE's work)
+ (local.set $x (i32.const 1))
+ )
+
+ ;; CHECK: (func $try-delegate3
+ ;; CHECK-NEXT: (local $x i32)
+ ;; CHECK-NEXT: (try $l0
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (try $try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (try $l1
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (try $try5
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (try $try6
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (throw $e
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (delegate $l1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch_all
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (delegate $l0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch_all
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch_all
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-delegate3
+ (local $x i32)
+ (try $l0
+ (do
+ (try
+ (do
+ (try $l1
+ (do
+ (try
+ (do
+ (try
+ (do
+ (throw $e (i32.const 0))
+ )
+ (delegate $l1)
+ )
+ )
+ (catch_all
+ (local.set $x (i32.const 1))
+ )
+ )
+ )
+ (delegate $l0)
+ )
+ )
+ (catch_all
+ (local.set $x (i32.const 1))
+ )
+ )
+ )
+ (catch_all)
+ )
+ ;; The innermost try delegates to $l1, which in turn delegates to $l0,
+ ;; skipping all local.sets in between. So this should NOT be dropped.
+ (local.set $x (i32.const 1))
+ )
+
+ ;; CHECK: (func $try-delegate4
+ ;; CHECK-NEXT: (local $x i32)
+ ;; CHECK-NEXT: (try $l0
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (try $try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (try $l1
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (try $try7
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (try $try8
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (throw $e
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (delegate $l1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch_all
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (delegate $l0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch_all
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch_all
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-delegate4
+ (local $x i32)
+ (try $l0
+ (do
+ (try
+ (do
+ (try $l1
+ (do
+ (try
+ (do
+ (try
+ (do
+ (throw $e (i32.const 0))
+ )
+ (delegate $l1)
+ )
+ )
+ (catch_all)
+ )
+ )
+ (delegate $l0)
+ )
+ )
+ (catch_all)
+ )
+ )
+ (catch_all
+ (local.set $x (i32.const 1))
+ )
+ )
+ ;; The innermost try delegates to $l1, which in turn delgates to $l0, whose
+ ;; catch_all runs the same local.set. So this can be dropped.
+ (local.set $x (i32.const 1))
+ )
)