summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ir/effects.h52
-rw-r--r--src/ir/properties.h2
-rw-r--r--src/passes/CodeFolding.cpp2
-rw-r--r--src/passes/LoopInvariantCodeMotion.cpp2
-rw-r--r--src/passes/SimplifyLocals.cpp2
-rw-r--r--src/passes/Vacuum.cpp2
-rw-r--r--test/lit/passes/code-pushing-eh.wast132
-rw-r--r--test/lit/passes/vacuum-eh.wast51
8 files changed, 216 insertions, 29 deletions
diff --git a/src/ir/effects.h b/src/ir/effects.h
index ced8e63cb..808693e95 100644
--- a/src/ir/effects.h
+++ b/src/ir/effects.h
@@ -111,9 +111,9 @@ public:
// An atomic load/store/RMW/Cmpxchg or an operator that has a defined ordering
// wrt atomics (e.g. memory.grow)
bool isAtomic = false;
- bool throws = false;
+ bool throws_ = false;
// The nested depth of try-catch_all. If an instruction that may throw is
- // inside an inner try-catch_all, we don't mark it as 'throws', because it
+ // inside an inner try-catch_all, we don't mark it as 'throws_', because it
// will be caught by an inner catch_all. We only count 'try's with a
// 'catch_all' because instructions within a 'try' without a 'catch_all' can
// still throw outside of the try.
@@ -144,6 +144,7 @@ public:
return accessesMutableStruct() || readsImmutableStruct;
}
bool accessesArray() const { return calls || readsArray || writesArray; }
+ bool throws() const { return throws_ || !delegateTargets.empty(); }
// Check whether this may transfer control flow to somewhere outside of this
// expression (aside from just flowing out normally). That includes a break
// or a throw (if the throw is not known to be caught inside this expression;
@@ -152,7 +153,7 @@ public:
// caught in the function at all, which would mean control flow cannot be
// transferred inside the function, but this expression does not know that).
bool transfersControlFlow() const {
- return branchesOut || throws || hasExternalBreakTargets();
+ return branchesOut || throws() || hasExternalBreakTargets();
}
// Changes something in globally-stored state.
@@ -167,7 +168,7 @@ public:
bool hasNonTrapSideEffects() const {
return localsWritten.size() > 0 || danglingPop || writesGlobalState() ||
- throws || transfersControlFlow();
+ throws() || transfersControlFlow();
}
bool hasSideEffects() const { return trap || hasNonTrapSideEffects(); }
@@ -261,7 +262,7 @@ public:
// function, so transfersControlFlow would be true) - while we allow the
// reordering of traps with each other, we do not reorder exceptions with
// anything.
- assert(!((trap && other.throws) || (throws && other.trap)));
+ assert(!((trap && other.throws()) || (throws() && other.trap)));
// We can't reorder an implicit trap in a way that could alter what global
// state is modified.
if ((trap && other.writesGlobalState()) ||
@@ -287,7 +288,7 @@ public:
implicitTrap = implicitTrap || other.implicitTrap;
trapsNeverHappen = trapsNeverHappen || other.trapsNeverHappen;
isAtomic = isAtomic || other.isAtomic;
- throws = throws || other.throws;
+ throws_ = throws_ || other.throws_;
danglingPop = danglingPop || other.danglingPop;
for (auto i : other.localsRead) {
localsRead.insert(i);
@@ -307,6 +308,9 @@ public:
for (auto i : other.breakTargets) {
breakTargets.insert(i);
}
+ for (auto i : other.delegateTargets) {
+ delegateTargets.insert(i);
+ }
}
// the checks above happen after the node's children were processed, in the
@@ -329,6 +333,7 @@ public:
}
std::set<Name> breakTargets;
+ std::set<Name> delegateTargets;
private:
struct InternalAnalyzer
@@ -369,6 +374,18 @@ private:
static void doStartCatch(InternalAnalyzer* self, Expression** currp) {
Try* curr = (*currp)->cast<Try>();
+ // This is conservative. When an inner try-delegate targets the current
+ // expression, even if the try-delegate's body can't throw, we consider
+ // the current expression can throw for simplicity, unless the current
+ // expression is not inside a try-catch_all. It is hard to figure out
+ // whether the original try-delegate's body throws or not at this point.
+ if (curr->name.is()) {
+ if (self->parent.delegateTargets.count(curr->name) &&
+ self->parent.tryDepth == 0) {
+ self->parent.throws_ = true;
+ }
+ self->parent.delegateTargets.erase(curr->name);
+ }
// We only count 'try's with a 'catch_all' because instructions within a
// 'try' without a 'catch_all' can still throw outside of the try.
if (curr->hasCatchAll()) {
@@ -423,7 +440,7 @@ private:
parent.calls = true;
// When EH is enabled, any call can throw.
if (parent.features.hasExceptionHandling() && parent.tryDepth == 0) {
- parent.throws = true;
+ parent.throws_ = true;
}
if (curr->isReturn) {
parent.branchesOut = true;
@@ -438,7 +455,7 @@ private:
void visitCallIndirect(CallIndirect* curr) {
parent.calls = true;
if (parent.features.hasExceptionHandling() && parent.tryDepth == 0) {
- parent.throws = true;
+ parent.throws_ = true;
}
if (curr->isReturn) {
parent.branchesOut = true;
@@ -631,15 +648,19 @@ private:
parent.readsTable = true;
parent.writesTable = true;
}
- void visitTry(Try* curr) {}
+ void visitTry(Try* curr) {
+ if (curr->delegateTarget.is()) {
+ parent.delegateTargets.insert(curr->delegateTarget);
+ }
+ }
void visitThrow(Throw* curr) {
if (parent.tryDepth == 0) {
- parent.throws = true;
+ parent.throws_ = true;
}
}
void visitRethrow(Rethrow* curr) {
if (parent.tryDepth == 0) {
- parent.throws = true;
+ parent.throws_ = true;
}
// traps when the arg is null
parent.implicitTrap = true;
@@ -658,7 +679,7 @@ private:
void visitCallRef(CallRef* curr) {
parent.calls = true;
if (parent.features.hasExceptionHandling() && parent.tryDepth == 0) {
- parent.throws = true;
+ parent.throws_ = true;
}
if (curr->isReturn) {
parent.branchesOut = true;
@@ -811,7 +832,7 @@ public:
if (isAtomic) {
effects |= SideEffects::IsAtomic;
}
- if (throws) {
+ if (throws_) {
effects |= SideEffects::Throws;
}
if (danglingPop) {
@@ -826,7 +847,10 @@ public:
}
private:
- void pre() { breakTargets.clear(); }
+ void pre() {
+ breakTargets.clear();
+ delegateTargets.clear();
+ }
void post() {
assert(tryDepth == 0);
diff --git a/src/ir/properties.h b/src/ir/properties.h
index 8800f1049..d2c5affd2 100644
--- a/src/ir/properties.h
+++ b/src/ir/properties.h
@@ -283,7 +283,7 @@ inline Expression* getImmediateFallthrough(Expression* curr,
return br->value;
}
} else if (auto* tryy = curr->dynCast<Try>()) {
- if (!EffectAnalyzer(passOptions, module, tryy->body).throws) {
+ if (!EffectAnalyzer(passOptions, module, tryy->body).throws()) {
return tryy->body;
}
} else if (auto* as = curr->dynCast<RefCast>()) {
diff --git a/src/passes/CodeFolding.cpp b/src/passes/CodeFolding.cpp
index 7a492dbea..0323223e1 100644
--- a/src/passes/CodeFolding.cpp
+++ b/src/passes/CodeFolding.cpp
@@ -319,7 +319,7 @@ private:
// conservative approximation because there can be cases that 'try' is
// within the expression that may throw so it is safe to take the
// expression out.
- if (effects.throws && !FindAll<Try>(outOf).list.empty()) {
+ if (effects.throws() && !FindAll<Try>(outOf).list.empty()) {
return false;
}
}
diff --git a/src/passes/LoopInvariantCodeMotion.cpp b/src/passes/LoopInvariantCodeMotion.cpp
index f339f8126..7fbd3ef55 100644
--- a/src/passes/LoopInvariantCodeMotion.cpp
+++ b/src/passes/LoopInvariantCodeMotion.cpp
@@ -125,7 +125,7 @@ struct LoopInvariantCodeMotion
(effects.readsMutableGlobalState() &&
loopEffects.writesGlobalState());
// TODO: look into optimizing this with exceptions. for now, disallow
- if (effects.throws || loopEffects.throws) {
+ if (effects.throws() || loopEffects.throws()) {
unsafeToMove = true;
}
if (!unsafeToMove) {
diff --git a/src/passes/SimplifyLocals.cpp b/src/passes/SimplifyLocals.cpp
index 8ed822774..35980f503 100644
--- a/src/passes/SimplifyLocals.cpp
+++ b/src/passes/SimplifyLocals.cpp
@@ -304,7 +304,7 @@ struct SimplifyLocals
if (curr->is<Try>()) {
std::vector<Index> invalidated;
for (auto& [index, info] : self->sinkables) {
- if (info.effects.throws) {
+ if (info.effects.throws()) {
invalidated.push_back(index);
}
}
diff --git a/src/passes/Vacuum.cpp b/src/passes/Vacuum.cpp
index abd54f334..6adab04e9 100644
--- a/src/passes/Vacuum.cpp
+++ b/src/passes/Vacuum.cpp
@@ -360,7 +360,7 @@ struct Vacuum : public WalkerPass<ExpressionStackWalker<Vacuum>> {
void visitTry(Try* curr) {
// If try's body does not throw, the whole try-catch can be replaced with
// the try's body.
- if (!EffectAnalyzer(getPassOptions(), *getModule(), curr->body).throws) {
+ if (!EffectAnalyzer(getPassOptions(), *getModule(), curr->body).throws()) {
replaceCurrent(curr->body);
for (auto* catchBody : curr->catchBodies) {
typeUpdater.noteRecursiveRemoval(catchBody);
diff --git a/test/lit/passes/code-pushing-eh.wast b/test/lit/passes/code-pushing-eh.wast
index a18b10dae..c428d757d 100644
--- a/test/lit/passes/code-pushing-eh.wast
+++ b/test/lit/passes/code-pushing-eh.wast
@@ -1,17 +1,19 @@
;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
;; RUN: wasm-opt %s --code-pushing -all -S -o - | filecheck %s
+;; The tests in this file test EffectAnalyzer, which is used by CodePushing.
+
(module
;; CHECK: (tag $e (param i32))
(tag $e (param i32))
- ;; CHECK: (func $cant-push-past-call
+ ;; CHECK: (func $cannot-push-past-call
;; CHECK-NEXT: (local $x i32)
;; CHECK-NEXT: (block $out
;; CHECK-NEXT: (local.set $x
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (call $cant-push-past-call)
+ ;; CHECK-NEXT: (call $cannot-push-past-call)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
@@ -23,19 +25,19 @@
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
- (func $cant-push-past-call
+ (func $cannot-push-past-call
(local $x i32)
(block $out
;; This local.set cannot be pushed down, because the call below can throw
(local.set $x (i32.const 1))
- (call $cant-push-past-call)
+ (call $cannot-push-past-call)
(drop (i32.const 1))
(br_if $out (i32.const 2))
(drop (local.get $x))
)
)
- ;; CHECK: (func $cant-push-past-throw
+ ;; CHECK: (func $cannot-push-past-throw
;; CHECK-NEXT: (local $x i32)
;; CHECK-NEXT: (block $out
;; CHECK-NEXT: (local.set $x
@@ -55,7 +57,7 @@
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
- (func $cant-push-past-throw
+ (func $cannot-push-past-throw
(local $x i32)
(block $out
;; This local.set cannot be pushed down, because there is 'throw' below
@@ -117,7 +119,7 @@
;; CHECK-NEXT: )
(func $foo)
- ;; CHECK: (func $cant-push-past-try
+ ;; CHECK: (func $cannot-push-past-try
;; CHECK-NEXT: (local $x i32)
;; CHECK-NEXT: (block $out
;; CHECK-NEXT: (local.set $x
@@ -144,7 +146,7 @@
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
- (func $cant-push-past-try
+ (func $cannot-push-past-try
(local $x i32)
(block $out
;; This local.set cannot be pushed down, because the exception thrown by
@@ -164,7 +166,7 @@
)
)
- ;; CHECK: (func $cant-push-past-rethrow-within-catch
+ ;; CHECK: (func $cannot-push-past-rethrow-within-catch
;; CHECK-NEXT: (local $x i32)
;; CHECK-NEXT: (block $out
;; CHECK-NEXT: (local.set $x
@@ -191,7 +193,7 @@
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
- (func $cant-push-past-rethrow-within-catch
+ (func $cannot-push-past-rethrow-within-catch
(local $x i32)
(block $out
;; This local.set cannot be pushed down, because there is 'rethrow' within
@@ -210,4 +212,114 @@
(drop (local.get $x))
)
)
+
+ ;; CHECK: (func $can-push-past-try-delegate
+ ;; CHECK-NEXT: (local $x i32)
+ ;; CHECK-NEXT: (block $out
+ ;; CHECK-NEXT: (try $l
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (try $try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (throw $e
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (delegate $l)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch_all
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br_if $out
+ ;; CHECK-NEXT: (i32.const 2)
+ ;; 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-try-delegate
+ (local $x i32)
+ (block $out
+ ;; This local.set can be pushed down, because the 'throw' below is going
+ ;; to be caught by the catch_all
+ (local.set $x (i32.const 1))
+ (try $l
+ (do
+ (try
+ (do
+ (throw $e (i32.const 0))
+ )
+ (delegate $l)
+ )
+ )
+ (catch_all)
+ )
+ (drop (i32.const 1))
+ (br_if $out (i32.const 2))
+ (drop (local.get $x))
+ )
+ )
+
+ ;; CHECK: (func $cannot-push-past-try-delegate
+ ;; CHECK-NEXT: (local $x i32)
+ ;; CHECK-NEXT: (block $out
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (try $l
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (try $try
+ ;; 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: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br_if $out
+ ;; CHECK-NEXT: (i32.const 2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $cannot-push-past-try-delegate
+ (local $x i32)
+ (block $out
+ ;; This local.set cannot be pushed down, because the 'delegate' bypasses
+ ;; the catch_all, making the whole 'try' throwable.
+ (local.set $x (i32.const 1))
+ (try $l
+ (do
+ (try
+ (do
+ (throw $e (i32.const 0))
+ )
+ (delegate 2)
+ )
+ )
+ (catch_all)
+ )
+ (drop (i32.const 1))
+ (br_if $out (i32.const 2))
+ (drop (local.get $x))
+ )
+ )
)
diff --git a/test/lit/passes/vacuum-eh.wast b/test/lit/passes/vacuum-eh.wast
index ef6b6ff5b..574a525bc 100644
--- a/test/lit/passes/vacuum-eh.wast
+++ b/test/lit/passes/vacuum-eh.wast
@@ -125,4 +125,55 @@
)
)
)
+
+ ;; CHECK: (func $try-delegate-outer-target
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (try $label$0
+ ;; 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 $label$0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch_all
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-delegate-outer-target
+ (local $0 i32)
+ (try $label$0 ;; outer try
+ (do
+ ;; If it were not for the inner (delegate $label0), this middle try
+ ;; cannot throw even if there is a throw in the inner try, because this
+ ;; try has a catch_all. And Vacuum can replace the outer try-catch with
+ ;; the try's body if the body doesn't throw.
+ ;;
+ ;; But because the inner try has a delegate that targets the outer try,
+ ;; this middle try can throw, and we can't do the optimization for
+ ;; the outer try.
+ (try ;; middle try
+ (do
+ (try ;; inner try
+ (do
+ (throw $e
+ (i32.const 0)
+ )
+ )
+ (delegate $label$0)
+ )
+ )
+ (catch_all)
+ )
+ )
+ )
+ )
)