summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xscripts/fuzz_opt.py13
-rw-r--r--src/ir/ReFinalize.cpp7
-rw-r--r--src/ir/effects.h26
-rw-r--r--src/ir/linear-execution.h6
-rw-r--r--src/ir/possible-contents.cpp34
-rw-r--r--src/ir/possible-contents.h16
-rw-r--r--src/literal.h19
-rw-r--r--src/passes/CodeFolding.cpp21
-rw-r--r--src/passes/DeadCodeElimination.cpp6
-rw-r--r--src/passes/SimplifyLocals.cpp6
-rw-r--r--src/passes/Vacuum.cpp11
-rw-r--r--src/wasm-interpreter.h70
-rw-r--r--src/wasm-type.h2
-rw-r--r--src/wasm/literal.cpp18
-rw-r--r--src/wasm/wasm-interpreter.cpp3
-rw-r--r--src/wasm/wasm-type.cpp2
-rw-r--r--test/lit/basic/reference-types.wast228
-rw-r--r--test/lit/exec/eh-gc.wast27
-rw-r--r--test/lit/exec/eh-print.wast (renamed from test/lit/exec/eh-legacy-print.wast)0
-rw-r--r--test/lit/exec/eh.wast45
-rw-r--r--test/lit/merge/renamings.wat48
-rw-r--r--test/lit/merge/renamings.wat.second16
-rw-r--r--test/lit/passes/coalesce-locals-eh-legacy.wast6
-rw-r--r--test/lit/passes/coalesce-locals-eh.wast84
-rw-r--r--test/lit/passes/code-folding-eh-legacy.wast5
-rw-r--r--test/lit/passes/code-folding-eh.wast296
-rw-r--r--test/lit/passes/code-pushing-eh-legacy.wast139
-rw-r--r--test/lit/passes/code-pushing-eh.wast300
-rw-r--r--test/lit/passes/dce-eh-legacy.wast26
-rw-r--r--test/lit/passes/dce-eh.wast145
-rw-r--r--test/lit/passes/global-effects-eh-legacy.wast507
-rw-r--r--test/lit/passes/global-effects.wast157
-rw-r--r--test/lit/passes/gufa-eh.wast60
-rw-r--r--test/lit/passes/local-subtyping.wast52
-rw-r--r--test/lit/passes/simplify-locals-eh-legacy.wast105
-rw-r--r--test/lit/passes/simplify-locals-eh.wast226
-rw-r--r--test/lit/passes/vacuum-eh-legacy.wast3
-rw-r--r--test/lit/passes/vacuum-eh.wast235
-rw-r--r--test/spec/exception-handling.wast150
-rw-r--r--test/spec/return_call_eh-legacy.wast35
-rw-r--r--test/spec/return_call_eh.wast29
41 files changed, 2764 insertions, 420 deletions
diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py
index 28426f773..e06e13b72 100755
--- a/scripts/fuzz_opt.py
+++ b/scripts/fuzz_opt.py
@@ -352,10 +352,6 @@ INITIAL_CONTENTS_IGNORE = [
'typed_continuations_contnew.wast',
'typed_continuations_contbind.wast',
'typed_continuations_suspend.wast',
- # New EH implementation is in progress
- 'exception-handling.wast',
- 'translate-to-new-eh.wast',
- 'rse-eh.wast',
]
@@ -841,7 +837,9 @@ class CompareVMs(TestCaseHandler):
# V8 does not support shared memories when running with
# shared-everything enabled, so do not fuzz shared-everything
# for now.
- return all_disallowed(['shared-everything'])
+ # Due to the V8 bug https://issues.chromium.org/issues/332931390
+ # we do not fuzz exception-handling either.
+ return all_disallowed(['shared-everything', 'exception-handling'])
def can_compare_to_self(self):
# With nans, VM differences can confuse us, so only very simple VMs
@@ -1649,6 +1647,9 @@ def get_random_opts():
print('avoiding --flatten due to multivalue + reference types not supporting it (spilling of non-nullable tuples)')
print('TODO: Resolving https://github.com/WebAssembly/binaryen/issues/4824 may fix this')
continue
+ if '--enable-exception-handling' in FEATURE_OPTS:
+ print('avoiding --flatten due to exception-handling not supporting it (requires blocks with results)')
+ continue
if '--gc' not in FEATURE_OPTS:
print('avoiding --flatten due to GC not supporting it (spilling of non-nullable locals)')
continue
@@ -1707,7 +1708,7 @@ print('FEATURE_DISABLE_FLAGS:', FEATURE_DISABLE_FLAGS)
# some features depend on other features, so if a required feature is
# disabled, its dependent features need to be disabled as well.
IMPLIED_FEATURE_OPTS = {
- '--disable-reference-types': ['--disable-gc', '--disable-strings'],
+ '--disable-reference-types': ['--disable-gc', '--disable-exception-handling', '--disable-strings'],
'--disable-gc': ['--disable-strings'],
}
diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp
index 0f78d37b7..c32d6efbf 100644
--- a/src/ir/ReFinalize.cpp
+++ b/src/ir/ReFinalize.cpp
@@ -128,7 +128,12 @@ void ReFinalize::visitTableFill(TableFill* curr) { curr->finalize(); }
void ReFinalize::visitTableCopy(TableCopy* curr) { curr->finalize(); }
void ReFinalize::visitTableInit(TableInit* curr) { curr->finalize(); }
void ReFinalize::visitTry(Try* curr) { curr->finalize(); }
-void ReFinalize::visitTryTable(TryTable* curr) { curr->finalize(); }
+void ReFinalize::visitTryTable(TryTable* curr) {
+ curr->finalize();
+ for (size_t i = 0; i < curr->catchDests.size(); i++) {
+ updateBreakValueType(curr->catchDests[i], curr->sentTypes[i]);
+ }
+}
void ReFinalize::visitThrow(Throw* curr) { curr->finalize(); }
void ReFinalize::visitRethrow(Rethrow* curr) { curr->finalize(); }
void ReFinalize::visitThrowRef(ThrowRef* curr) { curr->finalize(); }
diff --git a/src/ir/effects.h b/src/ir/effects.h
index fee8b3441..716624d64 100644
--- a/src/ir/effects.h
+++ b/src/ir/effects.h
@@ -431,6 +431,14 @@ private:
self->pushTask(doStartTry, currp);
return;
}
+ if (auto* tryTable = curr->dynCast<TryTable>()) {
+ // We need to increment try depth before starting.
+ self->pushTask(doEndTryTable, currp);
+ self->pushTask(doVisitTryTable, currp);
+ self->pushTask(scan, &tryTable->body);
+ self->pushTask(doStartTryTable, currp);
+ return;
+ }
PostWalker<InternalAnalyzer, OverriddenVisitor<InternalAnalyzer>>::scan(
self, currp);
}
@@ -472,6 +480,24 @@ private:
self->parent.catchDepth--;
}
+ static void doStartTryTable(InternalAnalyzer* self, Expression** currp) {
+ auto* curr = (*currp)->cast<TryTable>();
+ // We only count 'try_table's with a 'catch_all' because instructions
+ // within a 'try_table' without a 'catch_all' can still throw outside of
+ // the try.
+ if (curr->hasCatchAll()) {
+ self->parent.tryDepth++;
+ }
+ }
+
+ static void doEndTryTable(InternalAnalyzer* self, Expression** currp) {
+ auto* curr = (*currp)->cast<TryTable>();
+ if (curr->hasCatchAll()) {
+ assert(self->parent.tryDepth > 0 && "try depth cannot be negative");
+ self->parent.tryDepth--;
+ }
+ }
+
void visitBlock(Block* curr) {
if (curr->name.is()) {
parent.breakTargets.erase(curr->name); // these were internal breaks
diff --git a/src/ir/linear-execution.h b/src/ir/linear-execution.h
index c6593bd64..e8b1923aa 100644
--- a/src/ir/linear-execution.h
+++ b/src/ir/linear-execution.h
@@ -171,6 +171,12 @@ struct LinearExecutionWalker : public PostWalker<SubType, VisitorType> {
self->pushTask(SubType::scan, &curr->cast<Try>()->body);
break;
}
+ case Expression::Id::TryTableId: {
+ self->pushTask(SubType::doVisitTryTable, currp);
+ self->pushTask(SubType::doNoteNonLinear, currp);
+ self->pushTask(SubType::scan, &curr->cast<TryTable>()->body);
+ break;
+ }
case Expression::Id::ThrowId: {
self->pushTask(SubType::doVisitThrow, currp);
self->pushTask(SubType::doNoteNonLinear, currp);
diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp
index e7454c7c6..e5e6cf659 100644
--- a/src/ir/possible-contents.cpp
+++ b/src/ir/possible-contents.cpp
@@ -1135,8 +1135,38 @@ struct InfoCollector
}
}
void visitTryTable(TryTable* curr) {
- // TODO: optimize when possible
- addRoot(curr);
+ receiveChildValue(curr->body, curr);
+
+ // Connect caught tags with their branch targets, and materialize non-null
+ // exnref values.
+ auto numTags = curr->catchTags.size();
+ for (Index tagIndex = 0; tagIndex < numTags; tagIndex++) {
+ auto tag = curr->catchTags[tagIndex];
+ auto target = curr->catchDests[tagIndex];
+
+ Index exnrefIndex = 0;
+ if (tag.is()) {
+ auto params = getModule()->getTag(tag)->sig.params;
+
+ for (Index i = 0; i < params.size(); i++) {
+ if (isRelevant(params[i])) {
+ info.links.push_back(
+ {TagLocation{tag, i},
+ BreakTargetLocation{getFunction(), target, i}});
+ }
+ }
+
+ exnrefIndex = params.size();
+ }
+
+ if (curr->catchRefs[tagIndex]) {
+ auto location = CaughtExnRefLocation{};
+ addRoot(location,
+ PossibleContents::fromType(Type(HeapType::exn, NonNullable)));
+ info.links.push_back(
+ {location, BreakTargetLocation{getFunction(), target, exnrefIndex}});
+ }
+ }
}
void visitThrow(Throw* curr) {
auto& operands = curr->operands;
diff --git a/src/ir/possible-contents.h b/src/ir/possible-contents.h
index 5ec4f758f..7b88483cf 100644
--- a/src/ir/possible-contents.h
+++ b/src/ir/possible-contents.h
@@ -473,6 +473,15 @@ struct TagLocation {
}
};
+// The location of an exnref materialized by a catch_ref or catch_all_ref clause
+// of a try_table. No data is stored here. exnrefs contain a tag and a payload
+// at run-time, as well as potential metadata such as stack traces, but we don't
+// track that. So this is the same as NullLocation in a way: we just need *a*
+// source of contents for places that receive an exnref.
+struct CaughtExnRefLocation {
+ bool operator==(const CaughtExnRefLocation& other) const { return true; }
+};
+
// A null value. This is used as the location of the default value of a var in a
// function, a null written to a struct field in struct.new_with_default, etc.
struct NullLocation {
@@ -520,6 +529,7 @@ using Location = std::variant<ExpressionLocation,
SignatureResultLocation,
DataLocation,
TagLocation,
+ CaughtExnRefLocation,
NullLocation,
ConeReadLocation>;
@@ -608,6 +618,12 @@ template<> struct hash<wasm::TagLocation> {
}
};
+template<> struct hash<wasm::CaughtExnRefLocation> {
+ size_t operator()(const wasm::CaughtExnRefLocation& loc) const {
+ return std::hash<const char*>()("caught-exnref-location");
+ }
+};
+
template<> struct hash<wasm::NullLocation> {
size_t operator()(const wasm::NullLocation& loc) const {
return std::hash<wasm::Type>{}(loc.type);
diff --git a/src/literal.h b/src/literal.h
index d247b0c84..dd6247d00 100644
--- a/src/literal.h
+++ b/src/literal.h
@@ -32,6 +32,7 @@ namespace wasm {
class Literals;
struct GCData;
+struct ExnData;
class Literal {
// store only integers, whose bits are deterministic. floats
@@ -44,6 +45,7 @@ class Literal {
int64_t i64;
uint8_t v128[16];
// funcref function name. `isNull()` indicates a `null` value.
+ // TODO: handle cross-module calls using something other than a Name here.
Name func;
// A reference to GC data, either a Struct or an Array. For both of those we
// store the referred data as a Literals object (which is natural for an
@@ -56,6 +58,8 @@ class Literal {
// reference as its sole value even though internal i31 references do not
// have a gcData.
std::shared_ptr<GCData> gcData;
+ // A reference to Exn data.
+ std::shared_ptr<ExnData> exnData;
};
public:
@@ -85,6 +89,7 @@ public:
assert(type.isSignature());
}
explicit Literal(std::shared_ptr<GCData> gcData, HeapType type);
+ explicit Literal(std::shared_ptr<ExnData> exnData);
explicit Literal(std::string_view string);
Literal(const Literal& other);
Literal& operator=(const Literal& other);
@@ -96,6 +101,7 @@ public:
// Whether this is GC data, that is, something stored on the heap (aside from
// a null or i31). This includes structs, arrays, and also strings.
bool isData() const { return type.isData(); }
+ bool isExn() const { return type.isExn(); }
bool isString() const { return type.isString(); }
bool isNull() const { return type.isNull(); }
@@ -303,6 +309,7 @@ public:
return func;
}
std::shared_ptr<GCData> getGCData() const;
+ std::shared_ptr<ExnData> getExnData() const;
// careful!
int32_t* geti32Ptr() {
@@ -742,6 +749,18 @@ struct GCData {
GCData(HeapType type, Literals values) : type(type), values(values) {}
};
+// The data of a (ref exn) literal.
+struct ExnData {
+ // The tag of this exn data.
+ // TODO: handle cross-module calls using something other than a Name here.
+ Name tag;
+
+ // The payload of this exn data.
+ Literals payload;
+
+ ExnData(Name tag, Literals payload) : tag(tag), payload(payload) {}
+};
+
} // namespace wasm
namespace std {
diff --git a/src/passes/CodeFolding.cpp b/src/passes/CodeFolding.cpp
index dc2301f9c..2d17f6d31 100644
--- a/src/passes/CodeFolding.cpp
+++ b/src/passes/CodeFolding.cpp
@@ -343,15 +343,18 @@ private:
if (effects.danglingPop) {
return false;
}
- // When an expression can throw and it is within a try scope, taking it
- // out of the try scope changes the program's behavior, because the
- // expression that would otherwise have been caught by the try now
- // throws up to the next try scope or even up to the caller. We restrict
- // the move if 'outOf' contains a 'try' anywhere in it. This is a
- // 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()) {
+ // When an expression can throw and it is within a try/try_table scope,
+ // taking it out of the try/try_table scope changes the program's
+ // behavior, because the expression that would otherwise have been
+ // caught by the try/try_table now throws up to the next try/try_table
+ // scope or even up to the caller. We restrict the move if 'outOf'
+ // contains a 'try' or 'try_table' anywhere in it. This is a
+ // conservative approximation because there can be cases that
+ // 'try'/'try_table' is within the expression that may throw so it is
+ // safe to take the expression out.
+ // TODO: optimize this check to avoid two FindAlls.
+ if (effects.throws() &&
+ (FindAll<Try>(outOf).has() || FindAll<TryTable>(outOf).has())) {
return false;
}
}
diff --git a/src/passes/DeadCodeElimination.cpp b/src/passes/DeadCodeElimination.cpp
index 996133ad5..f8acde7b0 100644
--- a/src/passes/DeadCodeElimination.cpp
+++ b/src/passes/DeadCodeElimination.cpp
@@ -185,6 +185,12 @@ struct DeadCodeElimination
tryy->body->type == Type::unreachable && allCatchesUnreachable) {
typeUpdater.changeType(tryy, Type::unreachable);
}
+ } else if (auto* tryTable = curr->dynCast<TryTable>()) {
+ // try_table can finish normally only if its body finishes normally.
+ if (tryTable->type != Type::unreachable &&
+ tryTable->body->type == Type::unreachable) {
+ typeUpdater.changeType(tryTable, Type::unreachable);
+ }
} else {
WASM_UNREACHABLE("unimplemented DCE control flow structure");
}
diff --git a/src/passes/SimplifyLocals.cpp b/src/passes/SimplifyLocals.cpp
index 28b13980b..eca503ad4 100644
--- a/src/passes/SimplifyLocals.cpp
+++ b/src/passes/SimplifyLocals.cpp
@@ -321,9 +321,9 @@ struct SimplifyLocals
Expression** currp) {
Expression* curr = *currp;
- // Certain expressions cannot be sinked into 'try', and so at the start of
- // 'try' we forget about them.
- if (curr->is<Try>()) {
+ // Certain expressions cannot be sinked into 'try'/'try_table', and so at
+ // the start of 'try'/'try_table' we forget about them.
+ if (curr->is<Try>() || curr->is<TryTable>()) {
std::vector<Index> invalidated;
for (auto& [index, info] : self->sinkables) {
// Expressions that may throw cannot be moved into a try (which might
diff --git a/src/passes/Vacuum.cpp b/src/passes/Vacuum.cpp
index 4a4963291..0e49f19c8 100644
--- a/src/passes/Vacuum.cpp
+++ b/src/passes/Vacuum.cpp
@@ -87,7 +87,7 @@ struct Vacuum : public WalkerPass<ExpressionStackWalker<Vacuum>> {
// Some instructions have special handling in visit*, and we should do
// nothing for them here.
if (curr->is<Drop>() || curr->is<Block>() || curr->is<If>() ||
- curr->is<Loop>() || curr->is<Try>()) {
+ curr->is<Loop>() || curr->is<Try>() || curr->is<TryTable>()) {
return curr;
}
// Check if this expression itself has side effects, ignoring children.
@@ -435,6 +435,15 @@ struct Vacuum : public WalkerPass<ExpressionStackWalker<Vacuum>> {
}
}
+ void visitTryTable(TryTable* curr) {
+ // If try_table's body does not throw, the whole try_table can be replaced
+ // with the try_table's body.
+ if (!EffectAnalyzer(getPassOptions(), *getModule(), curr->body).throws()) {
+ replaceCurrent(curr->body);
+ return;
+ }
+ }
+
void visitFunction(Function* curr) {
auto* optimized =
optimize(curr->body, curr->getResults() != Type::none, true);
diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h
index 644a141a2..1bf135c40 100644
--- a/src/wasm-interpreter.h
+++ b/src/wasm-interpreter.h
@@ -47,8 +47,7 @@
namespace wasm {
struct WasmException {
- Name tag;
- Literals values;
+ Literal exn;
};
std::ostream& operator<<(std::ostream& o, const WasmException& exn);
@@ -204,6 +203,15 @@ protected:
return Literal(allocation, type.getHeapType());
}
+ // Same as makeGCData but for ExnData.
+ Literal makeExnData(Name tag, const Literals& payload) {
+ auto allocation = std::make_shared<ExnData>(tag, payload);
+#if __has_feature(leak_sanitizer) || __has_feature(address_sanitizer)
+ __lsan_ignore_object(allocation.get());
+#endif
+ return Literal(allocation);
+ }
+
public:
// Indicates no limit of maxDepth or maxLoopIterations.
static const Index NO_LIMIT = 0;
@@ -1428,16 +1436,25 @@ public:
return flow;
}
NOTE_EVAL1(curr->tag);
- WasmException exn;
- exn.tag = curr->tag;
- for (auto item : arguments) {
- exn.values.push_back(item);
- }
- throwException(exn);
+ throwException(WasmException{makeExnData(curr->tag, arguments)});
WASM_UNREACHABLE("throw");
}
Flow visitRethrow(Rethrow* curr) { WASM_UNREACHABLE("unimp"); }
- Flow visitThrowRef(ThrowRef* curr) { WASM_UNREACHABLE("unimp"); }
+ Flow visitThrowRef(ThrowRef* curr) {
+ NOTE_ENTER("ThrowRef");
+ Flow flow = visit(curr->exnref);
+ if (flow.breaking()) {
+ return flow;
+ }
+ const auto& exnref = flow.getSingleValue();
+ NOTE_EVAL1(exnref);
+ if (exnref.isNull()) {
+ trap("null ref");
+ }
+ assert(exnref.isExn());
+ throwException(WasmException{exnref});
+ WASM_UNREACHABLE("throw");
+ }
Flow visitRefI31(RefI31* curr) {
NOTE_ENTER("RefI31");
Flow flow = visit(curr->value);
@@ -2455,6 +2472,10 @@ public:
NOTE_ENTER("Try");
return Flow(NONCONSTANT_FLOW);
}
+ Flow visitTryTable(TryTable* curr) {
+ NOTE_ENTER("TryTable");
+ return Flow(NONCONSTANT_FLOW);
+ }
Flow visitRethrow(Rethrow* curr) {
NOTE_ENTER("Rethrow");
return Flow(NONCONSTANT_FLOW);
@@ -4103,9 +4124,10 @@ public:
return ret;
};
+ auto exnData = e.exn.getExnData();
for (size_t i = 0; i < curr->catchTags.size(); i++) {
- if (curr->catchTags[i] == e.tag) {
- multiValues.push_back(e.values);
+ if (curr->catchTags[i] == exnData->tag) {
+ multiValues.push_back(exnData->payload);
return processCatchBody(curr->catchBodies[i]);
}
}
@@ -4119,6 +4141,32 @@ public:
throw;
}
}
+ Flow visitTryTable(TryTable* curr) {
+ NOTE_ENTER("TryTable");
+ try {
+ return self()->visit(curr->body);
+ } catch (const WasmException& e) {
+ auto exnData = e.exn.getExnData();
+ for (size_t i = 0; i < curr->catchTags.size(); i++) {
+ auto catchTag = curr->catchTags[i];
+ if (!catchTag.is() || catchTag == exnData->tag) {
+ Flow ret;
+ ret.breakTo = curr->catchDests[i];
+ if (catchTag.is()) {
+ for (auto item : exnData->payload) {
+ ret.values.push_back(item);
+ }
+ }
+ if (curr->catchRefs[i]) {
+ ret.values.push_back(e.exn);
+ }
+ return ret;
+ }
+ }
+ // This exception is not caught by this try_table. Rethrow it.
+ throw;
+ }
+ }
Flow visitRethrow(Rethrow* curr) {
for (int i = exceptionStack.size() - 1; i >= 0; i--) {
if (exceptionStack[i].second == curr->target) {
diff --git a/src/wasm-type.h b/src/wasm-type.h
index ff1358a12..03c4f8e77 100644
--- a/src/wasm-type.h
+++ b/src/wasm-type.h
@@ -170,6 +170,7 @@ public:
bool isSignature() const;
bool isStruct() const;
bool isArray() const;
+ bool isExn() const;
bool isString() const;
bool isDefaultable() const;
@@ -388,6 +389,7 @@ public:
bool isContinuation() const { return getKind() == HeapTypeKind::Cont; }
bool isStruct() const { return getKind() == HeapTypeKind::Struct; }
bool isArray() const { return getKind() == HeapTypeKind::Array; }
+ bool isExn() const { return isMaybeShared(HeapType::exn); }
bool isString() const { return isMaybeShared(HeapType::string); }
bool isBottom() const;
bool isOpen() const;
diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp
index 9d659f753..650318be3 100644
--- a/src/wasm/literal.cpp
+++ b/src/wasm/literal.cpp
@@ -80,6 +80,12 @@ Literal::Literal(std::shared_ptr<GCData> gcData, HeapType type)
(type.isBottom() && !gcData));
}
+Literal::Literal(std::shared_ptr<ExnData> exnData)
+ : exnData(exnData), type(HeapType::exn, NonNullable) {
+ // The data must not be null.
+ assert(exnData);
+}
+
Literal::Literal(std::string_view string)
: gcData(nullptr), type(Type(HeapType::string, NonNullable)) {
// TODO: we could in theory internalize strings
@@ -133,6 +139,9 @@ Literal::Literal(const Literal& other) : type(other.type) {
case HeapType::i31:
i32 = other.i32;
return;
+ case HeapType::exn:
+ new (&exnData) std::shared_ptr<ExnData>(other.exnData);
+ return;
case HeapType::ext:
WASM_UNREACHABLE("handled above with isData()");
case HeapType::none:
@@ -147,7 +156,6 @@ Literal::Literal(const Literal& other) : type(other.type) {
case HeapType::cont:
case HeapType::struct_:
case HeapType::array:
- case HeapType::exn:
WASM_UNREACHABLE("invalid type");
case HeapType::string:
WASM_UNREACHABLE("TODO: string literals");
@@ -161,6 +169,8 @@ Literal::~Literal() {
}
if (isNull() || isData() || type.getHeapType().isMaybeShared(HeapType::ext)) {
gcData.~shared_ptr();
+ } else if (isExn()) {
+ exnData.~shared_ptr();
}
}
@@ -328,6 +338,12 @@ std::shared_ptr<GCData> Literal::getGCData() const {
return gcData;
}
+std::shared_ptr<ExnData> Literal::getExnData() const {
+ assert(isExn());
+ assert(exnData);
+ return exnData;
+}
+
Literal Literal::castToF32() {
assert(type == Type::i32);
Literal ret(Type::f32);
diff --git a/src/wasm/wasm-interpreter.cpp b/src/wasm/wasm-interpreter.cpp
index b49eeef4b..cd6c232b5 100644
--- a/src/wasm/wasm-interpreter.cpp
+++ b/src/wasm/wasm-interpreter.cpp
@@ -20,7 +20,8 @@ void Indenter::print() {
#endif // WASM_INTERPRETER_DEBUG
std::ostream& operator<<(std::ostream& o, const WasmException& exn) {
- return o << exn.tag << " " << exn.values;
+ auto exnData = exn.exn.getExnData();
+ return o << exnData->tag << " " << exnData->payload;
}
} // namespace wasm
diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp
index 84ae51521..1730f1b85 100644
--- a/src/wasm/wasm-type.cpp
+++ b/src/wasm/wasm-type.cpp
@@ -814,6 +814,8 @@ bool Type::isStruct() const { return isRef() && getHeapType().isStruct(); }
bool Type::isArray() const { return isRef() && getHeapType().isArray(); }
+bool Type::isExn() const { return isRef() && getHeapType().isExn(); }
+
bool Type::isString() const { return isRef() && getHeapType().isString(); }
bool Type::isDefaultable() const {
diff --git a/test/lit/basic/reference-types.wast b/test/lit/basic/reference-types.wast
index 7f9ba4219..335939573 100644
--- a/test/lit/basic/reference-types.wast
+++ b/test/lit/basic/reference-types.wast
@@ -642,6 +642,62 @@
;; CHECK-TEXT-NEXT: )
;; CHECK-TEXT-NEXT: )
;; CHECK-TEXT-NEXT: (drop
+ ;; CHECK-TEXT-NEXT: (block $tryend (result eqref)
+ ;; CHECK-TEXT-NEXT: (drop
+ ;; CHECK-TEXT-NEXT: (block $catch (result i32)
+ ;; CHECK-TEXT-NEXT: (br $tryend
+ ;; CHECK-TEXT-NEXT: (try_table (result eqref) (catch $e-i32 $catch)
+ ;; CHECK-TEXT-NEXT: (local.get $local_eqref)
+ ;; CHECK-TEXT-NEXT: )
+ ;; CHECK-TEXT-NEXT: )
+ ;; CHECK-TEXT-NEXT: )
+ ;; CHECK-TEXT-NEXT: )
+ ;; CHECK-TEXT-NEXT: (ref.null none)
+ ;; CHECK-TEXT-NEXT: )
+ ;; CHECK-TEXT-NEXT: )
+ ;; CHECK-TEXT-NEXT: (drop
+ ;; CHECK-TEXT-NEXT: (block $tryend0 (result funcref)
+ ;; CHECK-TEXT-NEXT: (drop
+ ;; CHECK-TEXT-NEXT: (block $catch0 (result i32)
+ ;; CHECK-TEXT-NEXT: (br $tryend0
+ ;; CHECK-TEXT-NEXT: (try_table (result funcref) (catch $e-i32 $catch0)
+ ;; CHECK-TEXT-NEXT: (ref.func $foo)
+ ;; CHECK-TEXT-NEXT: )
+ ;; CHECK-TEXT-NEXT: )
+ ;; CHECK-TEXT-NEXT: )
+ ;; CHECK-TEXT-NEXT: )
+ ;; CHECK-TEXT-NEXT: (ref.null nofunc)
+ ;; CHECK-TEXT-NEXT: )
+ ;; CHECK-TEXT-NEXT: )
+ ;; CHECK-TEXT-NEXT: (drop
+ ;; CHECK-TEXT-NEXT: (block $tryend1 (result anyref)
+ ;; CHECK-TEXT-NEXT: (drop
+ ;; CHECK-TEXT-NEXT: (block $catch1 (result i32)
+ ;; CHECK-TEXT-NEXT: (br $tryend1
+ ;; CHECK-TEXT-NEXT: (try_table (result anyref) (catch $e-i32 $catch1)
+ ;; CHECK-TEXT-NEXT: (local.get $local_eqref)
+ ;; CHECK-TEXT-NEXT: )
+ ;; CHECK-TEXT-NEXT: )
+ ;; CHECK-TEXT-NEXT: )
+ ;; CHECK-TEXT-NEXT: )
+ ;; CHECK-TEXT-NEXT: (ref.null none)
+ ;; CHECK-TEXT-NEXT: )
+ ;; CHECK-TEXT-NEXT: )
+ ;; CHECK-TEXT-NEXT: (drop
+ ;; CHECK-TEXT-NEXT: (block $tryend2 (result anyref)
+ ;; CHECK-TEXT-NEXT: (drop
+ ;; CHECK-TEXT-NEXT: (block $catch2 (result i32)
+ ;; CHECK-TEXT-NEXT: (br $tryend2
+ ;; CHECK-TEXT-NEXT: (try_table (result anyref) (catch $e-i32 $catch2)
+ ;; CHECK-TEXT-NEXT: (ref.null none)
+ ;; CHECK-TEXT-NEXT: )
+ ;; CHECK-TEXT-NEXT: )
+ ;; CHECK-TEXT-NEXT: )
+ ;; CHECK-TEXT-NEXT: )
+ ;; CHECK-TEXT-NEXT: (local.get $local_eqref)
+ ;; CHECK-TEXT-NEXT: )
+ ;; CHECK-TEXT-NEXT: )
+ ;; CHECK-TEXT-NEXT: (drop
;; CHECK-TEXT-NEXT: (select (result eqref)
;; CHECK-TEXT-NEXT: (local.get $local_eqref)
;; CHECK-TEXT-NEXT: (ref.null none)
@@ -1189,6 +1245,62 @@
;; CHECK-BIN-NEXT: )
;; CHECK-BIN-NEXT: )
;; CHECK-BIN-NEXT: (drop
+ ;; CHECK-BIN-NEXT: (block $label$50 (result eqref)
+ ;; CHECK-BIN-NEXT: (drop
+ ;; CHECK-BIN-NEXT: (block $label$51 (result i32)
+ ;; CHECK-BIN-NEXT: (br $label$50
+ ;; CHECK-BIN-NEXT: (try_table (result eqref) (catch $e-i32 $label$51)
+ ;; CHECK-BIN-NEXT: (local.get $local_eqref)
+ ;; CHECK-BIN-NEXT: )
+ ;; CHECK-BIN-NEXT: )
+ ;; CHECK-BIN-NEXT: )
+ ;; CHECK-BIN-NEXT: )
+ ;; CHECK-BIN-NEXT: (ref.null none)
+ ;; CHECK-BIN-NEXT: )
+ ;; CHECK-BIN-NEXT: )
+ ;; CHECK-BIN-NEXT: (drop
+ ;; CHECK-BIN-NEXT: (block $label$53 (result funcref)
+ ;; CHECK-BIN-NEXT: (drop
+ ;; CHECK-BIN-NEXT: (block $label$54 (result i32)
+ ;; CHECK-BIN-NEXT: (br $label$53
+ ;; CHECK-BIN-NEXT: (try_table (result funcref) (catch $e-i32 $label$54)
+ ;; CHECK-BIN-NEXT: (ref.func $foo)
+ ;; CHECK-BIN-NEXT: )
+ ;; CHECK-BIN-NEXT: )
+ ;; CHECK-BIN-NEXT: )
+ ;; CHECK-BIN-NEXT: )
+ ;; CHECK-BIN-NEXT: (ref.null nofunc)
+ ;; CHECK-BIN-NEXT: )
+ ;; CHECK-BIN-NEXT: )
+ ;; CHECK-BIN-NEXT: (drop
+ ;; CHECK-BIN-NEXT: (block $label$56 (result anyref)
+ ;; CHECK-BIN-NEXT: (drop
+ ;; CHECK-BIN-NEXT: (block $label$57 (result i32)
+ ;; CHECK-BIN-NEXT: (br $label$56
+ ;; CHECK-BIN-NEXT: (try_table (result anyref) (catch $e-i32 $label$57)
+ ;; CHECK-BIN-NEXT: (local.get $local_eqref)
+ ;; CHECK-BIN-NEXT: )
+ ;; CHECK-BIN-NEXT: )
+ ;; CHECK-BIN-NEXT: )
+ ;; CHECK-BIN-NEXT: )
+ ;; CHECK-BIN-NEXT: (ref.null none)
+ ;; CHECK-BIN-NEXT: )
+ ;; CHECK-BIN-NEXT: )
+ ;; CHECK-BIN-NEXT: (drop
+ ;; CHECK-BIN-NEXT: (block $label$59 (result anyref)
+ ;; CHECK-BIN-NEXT: (drop
+ ;; CHECK-BIN-NEXT: (block $label$60 (result i32)
+ ;; CHECK-BIN-NEXT: (br $label$59
+ ;; CHECK-BIN-NEXT: (try_table (result anyref) (catch $e-i32 $label$60)
+ ;; CHECK-BIN-NEXT: (ref.null none)
+ ;; CHECK-BIN-NEXT: )
+ ;; CHECK-BIN-NEXT: )
+ ;; CHECK-BIN-NEXT: )
+ ;; CHECK-BIN-NEXT: )
+ ;; CHECK-BIN-NEXT: (local.get $local_eqref)
+ ;; CHECK-BIN-NEXT: )
+ ;; CHECK-BIN-NEXT: )
+ ;; CHECK-BIN-NEXT: (drop
;; CHECK-BIN-NEXT: (select (result eqref)
;; CHECK-BIN-NEXT: (local.get $local_eqref)
;; CHECK-BIN-NEXT: (ref.null none)
@@ -1597,6 +1709,66 @@
)
)
+ ;; Test try_table return type
+ (drop
+ (block $tryend (result eqref)
+ (drop
+ (block $catch (result i32)
+ (br $tryend
+ (try_table (result eqref) (catch $e-i32 $catch)
+ (local.get $local_eqref)
+ )
+ )
+ )
+ )
+ (ref.null eq)
+ )
+ )
+ (drop
+ (block $tryend (result funcref)
+ (drop
+ (block $catch (result i32)
+ (br $tryend
+ (try_table (result funcref) (catch $e-i32 $catch)
+ (ref.func $foo)
+ )
+ )
+ )
+ )
+ (ref.null func)
+ )
+ )
+
+ ;; Test subtype relationship for try_table return type
+ (drop
+ (block $tryend (result anyref)
+ (drop
+ (block $catch (result i32)
+ (br $tryend
+ (try_table (result anyref) (catch $e-i32 $catch)
+ (local.get $local_eqref)
+ )
+ )
+ )
+ )
+ (ref.null any)
+ )
+ )
+ (drop
+ (block $tryend (result anyref)
+ (drop
+ (block $catch (result i32)
+ (br $tryend
+ (try_table (result anyref) (catch $e-i32 $catch)
+ (ref.null eq)
+ )
+ )
+ )
+ )
+ (local.get $local_eqref)
+ )
+ )
+
;; Test typed select
(drop
(select (result eqref)
@@ -2416,6 +2588,62 @@
;; CHECK-BIN-NODEBUG-NEXT: )
;; CHECK-BIN-NODEBUG-NEXT: )
;; CHECK-BIN-NODEBUG-NEXT: (drop
+;; CHECK-BIN-NODEBUG-NEXT: (block $label$50 (result eqref)
+;; CHECK-BIN-NODEBUG-NEXT: (drop
+;; CHECK-BIN-NODEBUG-NEXT: (block $label$51 (result i32)
+;; CHECK-BIN-NODEBUG-NEXT: (br $label$50
+;; CHECK-BIN-NODEBUG-NEXT: (try_table (result eqref) (catch $tag$0 $label$51)
+;; CHECK-BIN-NODEBUG-NEXT: (local.get $0)
+;; CHECK-BIN-NODEBUG-NEXT: )
+;; CHECK-BIN-NODEBUG-NEXT: )
+;; CHECK-BIN-NODEBUG-NEXT: )
+;; CHECK-BIN-NODEBUG-NEXT: )
+;; CHECK-BIN-NODEBUG-NEXT: (ref.null none)
+;; CHECK-BIN-NODEBUG-NEXT: )
+;; CHECK-BIN-NODEBUG-NEXT: )
+;; CHECK-BIN-NODEBUG-NEXT: (drop
+;; CHECK-BIN-NODEBUG-NEXT: (block $label$53 (result funcref)
+;; CHECK-BIN-NODEBUG-NEXT: (drop
+;; CHECK-BIN-NODEBUG-NEXT: (block $label$54 (result i32)
+;; CHECK-BIN-NODEBUG-NEXT: (br $label$53
+;; CHECK-BIN-NODEBUG-NEXT: (try_table (result funcref) (catch $tag$0 $label$54)
+;; CHECK-BIN-NODEBUG-NEXT: (ref.func $3)
+;; CHECK-BIN-NODEBUG-NEXT: )
+;; CHECK-BIN-NODEBUG-NEXT: )
+;; CHECK-BIN-NODEBUG-NEXT: )
+;; CHECK-BIN-NODEBUG-NEXT: )
+;; CHECK-BIN-NODEBUG-NEXT: (ref.null nofunc)
+;; CHECK-BIN-NODEBUG-NEXT: )
+;; CHECK-BIN-NODEBUG-NEXT: )
+;; CHECK-BIN-NODEBUG-NEXT: (drop
+;; CHECK-BIN-NODEBUG-NEXT: (block $label$56 (result anyref)
+;; CHECK-BIN-NODEBUG-NEXT: (drop
+;; CHECK-BIN-NODEBUG-NEXT: (block $label$57 (result i32)
+;; CHECK-BIN-NODEBUG-NEXT: (br $label$56
+;; CHECK-BIN-NODEBUG-NEXT: (try_table (result anyref) (catch $tag$0 $label$57)
+;; CHECK-BIN-NODEBUG-NEXT: (local.get $0)
+;; CHECK-BIN-NODEBUG-NEXT: )
+;; CHECK-BIN-NODEBUG-NEXT: )
+;; CHECK-BIN-NODEBUG-NEXT: )
+;; CHECK-BIN-NODEBUG-NEXT: )
+;; CHECK-BIN-NODEBUG-NEXT: (ref.null none)
+;; CHECK-BIN-NODEBUG-NEXT: )
+;; CHECK-BIN-NODEBUG-NEXT: )
+;; CHECK-BIN-NODEBUG-NEXT: (drop
+;; CHECK-BIN-NODEBUG-NEXT: (block $label$59 (result anyref)
+;; CHECK-BIN-NODEBUG-NEXT: (drop
+;; CHECK-BIN-NODEBUG-NEXT: (block $label$60 (result i32)
+;; CHECK-BIN-NODEBUG-NEXT: (br $label$59
+;; CHECK-BIN-NODEBUG-NEXT: (try_table (result anyref) (catch $tag$0 $label$60)
+;; CHECK-BIN-NODEBUG-NEXT: (ref.null none)
+;; CHECK-BIN-NODEBUG-NEXT: )
+;; CHECK-BIN-NODEBUG-NEXT: )
+;; CHECK-BIN-NODEBUG-NEXT: )
+;; CHECK-BIN-NODEBUG-NEXT: )
+;; CHECK-BIN-NODEBUG-NEXT: (local.get $0)
+;; CHECK-BIN-NODEBUG-NEXT: )
+;; CHECK-BIN-NODEBUG-NEXT: )
+;; CHECK-BIN-NODEBUG-NEXT: (drop
;; CHECK-BIN-NODEBUG-NEXT: (select (result eqref)
;; CHECK-BIN-NODEBUG-NEXT: (local.get $0)
;; CHECK-BIN-NODEBUG-NEXT: (ref.null none)
diff --git a/test/lit/exec/eh-gc.wast b/test/lit/exec/eh-gc.wast
new file mode 100644
index 000000000..6f97f44f8
--- /dev/null
+++ b/test/lit/exec/eh-gc.wast
@@ -0,0 +1,27 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited.
+
+;; RUN: wasm-opt %s -all --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s
+
+(module
+ (tag $tag (param externref))
+
+ ;; CHECK: [fuzz-exec] calling catch-null
+ (func $catch-null (export "catch-null")
+ (block $tryend
+ ;; The actual resulting value type is more refined than externref (it is a
+ ;; bottom type) which we should not error on.
+ (drop
+ (block $catch (result externref)
+ (try_table (catch $tag $catch)
+ (throw $tag
+ (ref.null noextern)
+ )
+ )
+ (br $tryend)
+ )
+ )
+ )
+ )
+)
+;; CHECK: [fuzz-exec] calling catch-null
+;; CHECK-NEXT: [fuzz-exec] comparing catch-null
diff --git a/test/lit/exec/eh-legacy-print.wast b/test/lit/exec/eh-print.wast
index f50164631..f50164631 100644
--- a/test/lit/exec/eh-legacy-print.wast
+++ b/test/lit/exec/eh-print.wast
diff --git a/test/lit/exec/eh.wast b/test/lit/exec/eh.wast
new file mode 100644
index 000000000..45215b048
--- /dev/null
+++ b/test/lit/exec/eh.wast
@@ -0,0 +1,45 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited.
+
+;; RUN: wasm-opt %s -all --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s
+
+(module
+ (tag $e-i32 (param i32))
+
+ ;; CHECK: [fuzz-exec] calling throw
+ ;; CHECK-NEXT: [exception thrown: e-i32 1]
+ (func $throw (export "throw")
+ (throw $e-i32 (i32.const 1))
+ )
+
+ ;; CHECK: [fuzz-exec] calling try_table-catch
+ (func $try_table-catch (export "try_table-catch")
+ (block $tryend
+ (drop
+ (block $catch (result i32)
+ (try_table (catch $e-i32 $catch)
+ (throw $e-i32 (i32.const 2))
+ )
+ (br $tryend)
+ )
+ )
+ )
+ )
+
+ ;; CHECK: [fuzz-exec] calling catchless-try_table
+ ;; CHECK-NEXT: [exception thrown: e-i32 3]
+ (func $catchless-try_table (export "catchless-try_table")
+ (try_table
+ (throw $e-i32 (i32.const 3))
+ )
+ )
+)
+;; CHECK: [fuzz-exec] calling throw
+;; CHECK-NEXT: [exception thrown: e-i32 1]
+
+;; CHECK: [fuzz-exec] calling try_table-catch
+
+;; CHECK: [fuzz-exec] calling catchless-try_table
+;; CHECK-NEXT: [exception thrown: e-i32 3]
+;; CHECK-NEXT: [fuzz-exec] comparing catchless-try_table
+;; CHECK-NEXT: [fuzz-exec] comparing throw
+;; CHECK-NEXT: [fuzz-exec] comparing try_table-catch
diff --git a/test/lit/merge/renamings.wat b/test/lit/merge/renamings.wat
index c6a22542a..9c54f3514 100644
--- a/test/lit/merge/renamings.wat
+++ b/test/lit/merge/renamings.wat
@@ -160,6 +160,22 @@
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block $catch (result i32)
+ ;; CHECK-NEXT: (try_table (catch $foo $catch)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block $catch0 (result i64)
+ ;; CHECK-NEXT: (try_table (catch $bar $catch0)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i64.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.load $foo
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
@@ -220,6 +236,22 @@
)
)
)
+ (drop
+ (block $catch (result i32)
+ (try_table (catch $foo $catch)
+ (nop)
+ )
+ (i32.const 0)
+ )
+ )
+ (drop
+ (block $catch (result i64)
+ (try_table (catch $bar $catch)
+ (nop)
+ )
+ (i64.const 0)
+ )
+ )
;; Memories
(drop
@@ -310,6 +342,22 @@
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
+;; CHECK-NEXT: (block $catch (result f32)
+;; CHECK-NEXT: (try_table (catch $foo_2 $catch)
+;; CHECK-NEXT: (nop)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (f32.const 0)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (drop
+;; CHECK-NEXT: (block $catch0 (result f64)
+;; CHECK-NEXT: (try_table (catch $other $catch0)
+;; CHECK-NEXT: (nop)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (f64.const 0)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.load $foo_2
;; CHECK-NEXT: (i32.const 3)
;; CHECK-NEXT: )
diff --git a/test/lit/merge/renamings.wat.second b/test/lit/merge/renamings.wat.second
index 25d3d5e81..c17b00cc5 100644
--- a/test/lit/merge/renamings.wat.second
+++ b/test/lit/merge/renamings.wat.second
@@ -70,6 +70,22 @@
)
)
)
+ (drop
+ (block $catch (result f32)
+ (try_table (catch $foo $catch)
+ (nop)
+ )
+ (f32.const 0.0)
+ )
+ )
+ (drop
+ (block $catch (result f64)
+ (try_table (catch $other $catch)
+ (nop)
+ )
+ (f64.const 0.0)
+ )
+ )
;; Memories
(drop
diff --git a/test/lit/passes/coalesce-locals-eh-legacy.wast b/test/lit/passes/coalesce-locals-eh-legacy.wast
index 63b1445dd..9091fdcb9 100644
--- a/test/lit/passes/coalesce-locals-eh-legacy.wast
+++ b/test/lit/passes/coalesce-locals-eh-legacy.wast
@@ -3,8 +3,10 @@
(module
;; CHECK: (tag $e)
+ (tag $e)
;; CHECK: (tag $any (param (ref any)))
+ (tag $any (param (ref any)))
;; CHECK: (func $bar (type $2) (result i32)
;; CHECK-NEXT: (i32.const 1984)
@@ -13,10 +15,6 @@
(i32.const 1984)
)
- (tag $e)
-
- (tag $any (param (ref any)))
-
;; CHECK: (func $bug-cfg-traversal (type $3) (param $0 i32) (result i32)
;; CHECK-NEXT: (try
;; CHECK-NEXT: (do
diff --git a/test/lit/passes/coalesce-locals-eh.wast b/test/lit/passes/coalesce-locals-eh.wast
new file mode 100644
index 000000000..90458f32f
--- /dev/null
+++ b/test/lit/passes/coalesce-locals-eh.wast
@@ -0,0 +1,84 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+;; RUN: wasm-opt %s --coalesce-locals -all -S -o - | filecheck %s
+
+(module
+ ;; CHECK: (tag $e)
+ (tag $e)
+
+ ;; CHECK: (tag $any (param (ref any)))
+ (tag $any (param (ref any)))
+
+ ;; CHECK: (func $bar (type $2) (result i32)
+ ;; CHECK-NEXT: (i32.const 1984)
+ ;; CHECK-NEXT: )
+ (func $bar (result i32)
+ (i32.const 1984)
+ )
+
+ ;; CHECK: (func $bug-cfg-traversal (type $3) (param $0 i32) (result i32)
+ ;; CHECK-NEXT: (block $tryend
+ ;; CHECK-NEXT: (block $catch
+ ;; CHECK-NEXT: (try_table (catch_all $catch)
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (call $bar)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $tryend)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ (func $bug-cfg-traversal (param $0 i32) (result i32)
+ (local $x i32)
+ ;; This is a regression test case for a bug in cfg-traversal for EH.
+ ;; See https://github.com/WebAssembly/binaryen/pull/3594
+ (block $tryend
+ (block $catch
+ (try_table (catch_all $catch)
+ (local.set $x
+ ;; the call may or may not throw, so we may reach the get of $x
+ (call $bar)
+ )
+ )
+ (br $tryend)
+ )
+ (unreachable)
+ )
+ (local.get $x)
+ )
+
+ ;; CHECK: (func $0 (type $0)
+ ;; CHECK-NEXT: (local $0 anyref)
+ ;; CHECK-NEXT: (block $tryend
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result anyref)
+ ;; CHECK-NEXT: (block $catch (result (ref any))
+ ;; CHECK-NEXT: (try_table (catch $any $catch)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $tryend)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $0
+ (local $0 (ref null any))
+ (block $tryend
+ (drop
+ ;; There is a difference between the type of the value here and the type
+ ;; of the local, due to the local being nullable. We should not error on
+ ;; that as we replace the tee with a drop (as it has no gets).
+ (local.tee $0
+ (block $catch (result (ref any))
+ (try_table (catch $any $catch)
+ (nop)
+ )
+ (br $tryend)
+ )
+ )
+ )
+ )
+ )
+)
diff --git a/test/lit/passes/code-folding-eh-legacy.wast b/test/lit/passes/code-folding-eh-legacy.wast
index 05cd8db8b..852ec126a 100644
--- a/test/lit/passes/code-folding-eh-legacy.wast
+++ b/test/lit/passes/code-folding-eh-legacy.wast
@@ -96,6 +96,7 @@
(func $try-call-optimize-terminating-tails-success (result i32)
(try
(do
+ ;; Expressions that cannot throw can be taken out of 'try' scope.
(drop (i32.const 1))
(drop (i32.const 1))
(return (i32.const 0))
@@ -243,6 +244,9 @@
(drop (i32.const 1))
(drop (i32.const 1))
(drop (i32.const 1))
+ ;; return_call executes the call after returning from this function.
+ ;; This try cannot catch exceptions it throws, so we can fold it out of
+ ;; the try.
(return_call $foo-i32)
)
(catch_all
@@ -281,6 +285,7 @@
(block $x
(try
(do
+ ;; Expressions that cannot throw can be taken out of 'try' scope.
(drop (i32.const 1))
(drop (i32.const 1))
(drop (i32.const 1))
diff --git a/test/lit/passes/code-folding-eh.wast b/test/lit/passes/code-folding-eh.wast
new file mode 100644
index 000000000..5a7cd68c7
--- /dev/null
+++ b/test/lit/passes/code-folding-eh.wast
@@ -0,0 +1,296 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+;; RUN: wasm-opt %s --remove-unused-names --code-folding -all -S -o - \
+;; RUN: | filecheck %s
+
+(module
+ ;; CHECK: (tag $e-i32 (param i32))
+ (tag $e-i32 (param i32))
+
+ ;; CHECK: (func $try_table-call-optimize-terminating-tails-success (type $0) (result i32)
+ ;; CHECK-NEXT: (block $folding-inner0
+ ;; CHECK-NEXT: (return
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (block $tryend
+ ;; CHECK-NEXT: (block $catch
+ ;; CHECK-NEXT: (try_table (catch_all $catch)
+ ;; CHECK-NEXT: (br $folding-inner0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $tryend)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $folding-inner0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (return
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try_table-call-optimize-terminating-tails-success (result i32)
+ (block $tryend
+ (block $catch
+ (try_table (catch_all $catch)
+ ;; Expressions that cannot throw can be taken out of 'try' scope.
+ (drop (i32.const 1))
+ (drop (i32.const 1))
+ (return (i32.const 0))
+ )
+ (br $tryend)
+ )
+ (drop (i32.const 1))
+ (drop (i32.const 1))
+ (return (i32.const 0))
+ )
+ (i32.const 0)
+ )
+
+
+ ;; CHECK: (func $foo (type $1)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $foo)
+
+ ;; CHECK: (func $try_table-call-optimize-terminating-tails (type $0) (result i32)
+ ;; CHECK-NEXT: (block $tryend
+ ;; CHECK-NEXT: (block $catch
+ ;; CHECK-NEXT: (try_table (catch_all $catch)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (return
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $tryend)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (return
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ (func $try_table-call-optimize-terminating-tails (result i32)
+ (block $tryend
+ (block $catch
+ (try_table (catch_all $catch)
+ ;; Expressions that can throw should NOT be taken out of 'try' scope.
+ (call $foo)
+ (call $foo)
+ (call $foo)
+ (call $foo)
+ (return (i32.const 0))
+ )
+ (br $tryend)
+ )
+ (call $foo)
+ (call $foo)
+ (call $foo)
+ (call $foo)
+ (return (i32.const 0))
+ )
+ (i32.const 0)
+ )
+
+ ;; CHECK: (func $foo-i32 (type $0) (result i32)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ (func $foo-i32 (result i32)
+ (i32.const 0)
+ )
+
+ ;; CHECK: (func $try_table-call-optimize-terminating-tails-call-return (type $0) (result i32)
+ ;; CHECK-NEXT: (block $tryend
+ ;; CHECK-NEXT: (block $catch
+ ;; CHECK-NEXT: (try_table (catch_all $catch)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (return
+ ;; CHECK-NEXT: (call $foo-i32)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $tryend)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (return
+ ;; CHECK-NEXT: (call $foo-i32)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ (func $try_table-call-optimize-terminating-tails-call-return (result i32)
+ (block $tryend
+ (block $catch
+ (try_table (catch_all $catch)
+ (drop (i32.const 1))
+ (drop (i32.const 1))
+ ;; Cannot be folded out of the try because it might throw.
+ (return (call $foo-i32))
+ )
+ (br $tryend)
+ )
+ (drop (i32.const 1))
+ (drop (i32.const 1))
+ (return (call $foo-i32))
+ )
+ (i32.const 0)
+ )
+
+ ;; CHECK: (func $try_table-call-optimize-terminating-tails-return-call (type $0) (result i32)
+ ;; CHECK-NEXT: (block $folding-inner0
+ ;; CHECK-NEXT: (return
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (block $tryend
+ ;; CHECK-NEXT: (block $catch
+ ;; CHECK-NEXT: (try_table (catch_all $catch)
+ ;; CHECK-NEXT: (br $folding-inner0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $tryend)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $folding-inner0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (return_call $foo-i32)
+ ;; CHECK-NEXT: )
+ (func $try_table-call-optimize-terminating-tails-return-call (result i32)
+ (block $tryend
+ (block $catch
+ (try_table (catch_all $catch)
+ (drop (i32.const 1))
+ (drop (i32.const 1))
+ (drop (i32.const 1))
+ ;; return_call executes the call after returning from this function.
+ ;; This try_table cannot catch exceptions it throws, so we can fold it
+ ;; out of the try_table.
+ (return_call $foo-i32)
+ )
+ (br $tryend)
+ )
+ (drop (i32.const 1))
+ (drop (i32.const 1))
+ (drop (i32.const 1))
+ (return_call $foo-i32)
+ )
+ (i32.const 0)
+ )
+
+ ;; CHECK: (func $try_table-call-optimize-expression-tails-success (type $1)
+ ;; CHECK-NEXT: (block $x
+ ;; CHECK-NEXT: (block $tryend
+ ;; CHECK-NEXT: (block $catch
+ ;; CHECK-NEXT: (try_table (catch_all $catch)
+ ;; CHECK-NEXT: (br $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $tryend)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try_table-call-optimize-expression-tails-success
+ (block $x
+ (block $tryend
+ (block $catch
+ (try_table (catch_all $catch)
+ ;; Expressions that cannot throw can be taken out of 'try_table'
+ ;; scope.
+ (drop (i32.const 1))
+ (drop (i32.const 1))
+ (drop (i32.const 1))
+ (br $x)
+ )
+ (br $tryend)
+ )
+ (drop (i32.const 1))
+ (drop (i32.const 1))
+ (drop (i32.const 1))
+ (br $x)
+ )
+ (unreachable)
+ )
+ )
+
+ ;; CHECK: (func $try_table-call-optimize-expression-tails (type $1)
+ ;; CHECK-NEXT: (block $x
+ ;; CHECK-NEXT: (block $tryend
+ ;; CHECK-NEXT: (block $catch
+ ;; CHECK-NEXT: (try_table (catch_all $catch)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (br $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $tryend)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (br $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try_table-call-optimize-expression-tails
+ (block $x
+ (block $tryend
+ (block $catch
+ (try_table (catch_all $catch)
+ ;; Expressions that can throw should NOT be taken out of 'try' scope.
+ (call $foo)
+ (call $foo)
+ (call $foo)
+ (br $x)
+ )
+ (br $tryend)
+ )
+ (call $foo)
+ (call $foo)
+ (call $foo)
+ (br $x)
+ )
+ (unreachable)
+ )
+ )
+)
diff --git a/test/lit/passes/code-pushing-eh-legacy.wast b/test/lit/passes/code-pushing-eh-legacy.wast
index 8fc0d423d..9511d244a 100644
--- a/test/lit/passes/code-pushing-eh-legacy.wast
+++ b/test/lit/passes/code-pushing-eh-legacy.wast
@@ -7,69 +7,6 @@
;; CHECK: (tag $e (param i32))
(tag $e (param i32))
- ;; CHECK: (func $cannot-push-past-call (type $0)
- ;; CHECK-NEXT: (local $x i32)
- ;; CHECK-NEXT: (block $out
- ;; CHECK-NEXT: (local.set $x
- ;; CHECK-NEXT: (i32.const 1)
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: (call $cannot-push-past-call)
- ;; 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-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 $cannot-push-past-call)
- (drop (i32.const 1))
- (br_if $out (i32.const 2))
- (drop (local.get $x))
- )
- )
-
- ;; CHECK: (func $cannot-push-past-throw (type $0)
- ;; CHECK-NEXT: (local $x i32)
- ;; CHECK-NEXT: (block $out
- ;; CHECK-NEXT: (local.set $x
- ;; CHECK-NEXT: (i32.const 1)
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: (throw $e
- ;; CHECK-NEXT: (i32.const 0)
- ;; 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-throw
- (local $x i32)
- (block $out
- ;; 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))
- (br_if $out (i32.const 2))
- (drop (local.get $x))
- )
- )
-
;; CHECK: (func $can-push-past-try (type $0)
;; CHECK-NEXT: (local $x i32)
;; CHECK-NEXT: (block $out
@@ -323,80 +260,4 @@
(drop (local.get $x))
)
)
-
- ;; CHECK: (func $can-push-past-conditional-throw (type $1) (param $param i32)
- ;; CHECK-NEXT: (local $x i32)
- ;; CHECK-NEXT: (block $block
- ;; CHECK-NEXT: (if
- ;; CHECK-NEXT: (local.get $param)
- ;; CHECK-NEXT: (then
- ;; CHECK-NEXT: (throw $e
- ;; CHECK-NEXT: (i32.const 0)
- ;; CHECK-NEXT: )
- ;; 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)
- (then
- (throw $e (i32.const 0))
- )
- )
- (drop (local.get $x))
- )
- )
-
- ;; CHECK: (func $cannot-push-past-conditional-throw-extra-use (type $1) (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: (then
- ;; CHECK-NEXT: (throw $e
- ;; CHECK-NEXT: (i32.const 0)
- ;; CHECK-NEXT: )
- ;; 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)
- (then
- (throw $e (i32.const 0))
- )
- )
- (drop (local.get $x))
- )
- (drop (local.get $x))
- )
)
diff --git a/test/lit/passes/code-pushing-eh.wast b/test/lit/passes/code-pushing-eh.wast
new file mode 100644
index 000000000..ee2798c46
--- /dev/null
+++ b/test/lit/passes/code-pushing-eh.wast
@@ -0,0 +1,300 @@
+;; 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 $cannot-push-past-call (type $0)
+ ;; CHECK-NEXT: (local $x i32)
+ ;; CHECK-NEXT: (block $out
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $cannot-push-past-call)
+ ;; 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-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 $cannot-push-past-call)
+ (drop (i32.const 1))
+ (br_if $out (i32.const 2))
+ (drop (local.get $x))
+ )
+ )
+
+ ;; CHECK: (func $cannot-push-past-throw (type $0)
+ ;; CHECK-NEXT: (local $x i32)
+ ;; CHECK-NEXT: (block $out
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (throw $e
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; 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-throw
+ (local $x i32)
+ (block $out
+ ;; 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))
+ (br_if $out (i32.const 2))
+ (drop (local.get $x))
+ )
+ )
+
+ ;; CHECK: (func $can-push-past-try_table (type $0)
+ ;; CHECK-NEXT: (local $x i32)
+ ;; CHECK-NEXT: (block $out
+ ;; CHECK-NEXT: (block $tryend
+ ;; CHECK-NEXT: (block $catch
+ ;; CHECK-NEXT: (try_table (catch_all $catch)
+ ;; CHECK-NEXT: (throw $e
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $tryend)
+ ;; 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_table
+ (local $x i32)
+ (block $out
+ ;; This local.set can be pushed down, because the 'throw' below is going
+ ;; to be caught by the inner catch_all.
+ (local.set $x (i32.const 1))
+ (block $tryend
+ (block $catch
+ (try_table (catch_all $catch)
+ (throw $e (i32.const 0))
+ )
+ (br $tryend)
+ )
+ )
+ (drop (i32.const 1))
+ (br_if $out (i32.const 2))
+ (drop (local.get $x))
+ )
+ )
+
+ ;; CHECK: (func $foo (type $0)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $foo)
+
+ ;; CHECK: (func $cannot-push-past-try_table (type $0)
+ ;; CHECK-NEXT: (local $x i32)
+ ;; CHECK-NEXT: (block $out
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block $tryend
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block $catch (result i32)
+ ;; CHECK-NEXT: (try_table (catch $e $catch)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $tryend)
+ ;; CHECK-NEXT: )
+ ;; 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_table
+ (local $x i32)
+ (block $out
+ ;; This local.set cannot be pushed down, because the exception thrown by
+ ;; 'call $foo' below may not be caught by 'catch $e'.
+ (local.set $x (i32.const 1))
+ (block $tryend
+ (drop
+ (block $catch (result i32)
+ (try_table (catch $e $catch)
+ (call $foo)
+ )
+ (br $tryend)
+ )
+ )
+ )
+ (drop (i32.const 1))
+ (br_if $out (i32.const 2))
+ (drop (local.get $x))
+ )
+ )
+
+ ;; CHECK: (func $cannot-push-past-throw_ref-within-catch (type $0)
+ ;; CHECK-NEXT: (local $x i32)
+ ;; CHECK-NEXT: (block $out
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block $tryend
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (block $catch (result exnref)
+ ;; CHECK-NEXT: (try_table (catch_all_ref $catch)
+ ;; CHECK-NEXT: (throw $e
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $tryend)
+ ;; CHECK-NEXT: )
+ ;; 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-throw_ref-within-catch
+ (local $x i32)
+ (block $out
+ ;; This local.set cannot be pushed down, because there is 'throw_ref'
+ ;; within the catch handler.
+ (local.set $x (i32.const 1))
+ (block $tryend
+ (throw_ref
+ (block $catch (result exnref)
+ (try_table (catch_all_ref $catch)
+ (throw $e (i32.const 0))
+ )
+ (br $tryend)
+ )
+ )
+ )
+ (drop (i32.const 1))
+ (br_if $out (i32.const 2))
+ (drop (local.get $x))
+ )
+ )
+
+ ;; CHECK: (func $can-push-past-conditional-throw (type $1) (param $param i32)
+ ;; CHECK-NEXT: (local $x i32)
+ ;; CHECK-NEXT: (block $block
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (local.get $param)
+ ;; CHECK-NEXT: (then
+ ;; CHECK-NEXT: (throw $e
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; 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)
+ (then
+ (throw $e (i32.const 0))
+ )
+ )
+ (drop (local.get $x))
+ )
+ )
+
+ ;; CHECK: (func $cannot-push-past-conditional-throw-extra-use (type $1) (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: (then
+ ;; CHECK-NEXT: (throw $e
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; 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)
+ (then
+ (throw $e (i32.const 0))
+ )
+ )
+ (drop (local.get $x))
+ )
+ (drop (local.get $x))
+ )
+)
diff --git a/test/lit/passes/dce-eh-legacy.wast b/test/lit/passes/dce-eh-legacy.wast
index 120ec4e11..ef6d569d6 100644
--- a/test/lit/passes/dce-eh-legacy.wast
+++ b/test/lit/passes/dce-eh-legacy.wast
@@ -79,32 +79,6 @@
(call $foo) ;; should be dce'd
)
- ;; CHECK: (func $throw (type $0)
- ;; CHECK-NEXT: (block $label$0
- ;; CHECK-NEXT: (block $label$1
- ;; CHECK-NEXT: (throw $e)
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: )
- (func $throw
- ;; All these wrapping expressions before 'throw' will be dce'd
- (drop
- (block $label$0 (result externref)
- (if
- (i32.clz
- (block $label$1 (result i32)
- (throw $e)
- )
- )
- (then
- (nop)
- )
- )
- (ref.null extern)
- )
- )
- )
-
;; CHECK: (func $rethrow (type $0)
;; CHECK-NEXT: (try $l0
;; CHECK-NEXT: (do
diff --git a/test/lit/passes/dce-eh.wast b/test/lit/passes/dce-eh.wast
new file mode 100644
index 000000000..413a278d0
--- /dev/null
+++ b/test/lit/passes/dce-eh.wast
@@ -0,0 +1,145 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+;; RUN: wasm-opt %s --dce -all -S -o - | filecheck %s
+
+;; If either try_table body or any of catch handler is reachable, the whole
+;; try_table construct is reachable.
+(module
+ ;; CHECK: (tag $e)
+ (tag $e)
+
+ ;; CHECK: (tag $e-i32 (param i32))
+ (tag $e-i32 (param i32))
+
+ ;; CHECK: (func $foo (type $0)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $foo)
+
+ ;; CHECK: (func $try_table_unreachable (type $0)
+ ;; CHECK-NEXT: (block $catch
+ ;; CHECK-NEXT: (try_table (catch_all $catch)
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: )
+ (func $try_table_unreachable
+ (block $catch
+ (try_table (catch_all $catch)
+ (unreachable)
+ )
+ )
+ (call $foo) ;; shouldn't be dce'd
+ )
+
+ ;; CHECK: (func $catch_unreachable (type $0)
+ ;; CHECK-NEXT: (block $tryend
+ ;; CHECK-NEXT: (block $catch
+ ;; CHECK-NEXT: (try_table (catch_all $catch)
+ ;; CHECK-NEXT: (br $tryend)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: )
+ (func $catch_unreachable
+ (block $tryend
+ (block $catch
+ (try_table (catch_all $catch)
+ (br $tryend)
+ )
+ )
+ (unreachable)
+ )
+ (call $foo) ;; shouldn't be dce'd
+ )
+
+ ;; CHECK: (func $both_unreachable (type $0)
+ ;; CHECK-NEXT: (block $tryend
+ ;; CHECK-NEXT: (block $catch
+ ;; CHECK-NEXT: (try_table (catch_all $catch)
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $both_unreachable
+ (block $tryend
+ (block $catch
+ (try_table (catch_all $catch)
+ (unreachable)
+ )
+ )
+ (unreachable)
+ )
+ (call $foo) ;; should be dce'd
+ )
+
+ ;; CHECK: (func $throw (type $0)
+ ;; CHECK-NEXT: (block $label$0
+ ;; CHECK-NEXT: (block $label$1
+ ;; CHECK-NEXT: (throw $e)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $throw
+ ;; All these wrapping expressions before 'throw' will be dce'd.
+ (drop
+ (block $label$0 (result externref)
+ (if
+ (i32.clz
+ (block $label$1 (result i32)
+ (throw $e)
+ )
+ )
+ (then
+ (nop)
+ )
+ )
+ (ref.null extern)
+ )
+ )
+ )
+
+ ;; CHECK: (func $throw_ref (type $0)
+ ;; CHECK-NEXT: (local $ex exnref)
+ ;; CHECK-NEXT: (block $tryend
+ ;; CHECK-NEXT: (local.set $ex
+ ;; CHECK-NEXT: (block $catch (result exnref)
+ ;; CHECK-NEXT: (try_table (catch_all_ref $catch)
+ ;; CHECK-NEXT: (br $tryend)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (local.get $ex)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $throw_ref
+ (local $ex exnref)
+ (block $tryend
+ (local.set $ex
+ (block $catch (result exnref)
+ (try_table (catch_all_ref $catch)
+ (br $tryend)
+ )
+ )
+ )
+ (drop
+ ;; This i32.add will be dce'd.
+ (i32.add
+ (i32.const 0)
+ (throw_ref (local.get $ex))
+ )
+ )
+ )
+ )
+)
diff --git a/test/lit/passes/global-effects-eh-legacy.wast b/test/lit/passes/global-effects-eh-legacy.wast
new file mode 100644
index 000000000..7390f996d
--- /dev/null
+++ b/test/lit/passes/global-effects-eh-legacy.wast
@@ -0,0 +1,507 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+
+;; Run without global effects, and run with, and also run with but discard them
+;; first (to check that discard works; that should be the same as without).
+
+;; RUN: foreach %s %t wasm-opt -all --vacuum -S -o - | filecheck %s --check-prefix WITHOUT
+;; RUN: foreach %s %t wasm-opt -all --generate-global-effects --vacuum -S -o - | filecheck %s --check-prefix INCLUDE
+;; RUN: foreach %s %t wasm-opt -all --generate-global-effects --discard-global-effects --vacuum -S -o - | filecheck %s --check-prefix WITHOUT
+
+(module
+
+ ;; WITHOUT: (type $void (func))
+ ;; INCLUDE: (type $void (func))
+ (type $void (func))
+
+ ;; WITHOUT: (type $1 (func (result i32)))
+
+ ;; WITHOUT: (type $2 (func (param i32)))
+
+ ;; WITHOUT: (import "a" "b" (func $import (type $void)))
+ ;; INCLUDE: (type $1 (func (result i32)))
+
+ ;; INCLUDE: (type $2 (func (param i32)))
+
+ ;; INCLUDE: (import "a" "b" (func $import (type $void)))
+ (import "a" "b" (func $import))
+
+ ;; WITHOUT: (table $t 0 funcref)
+ ;; INCLUDE: (table $t 0 funcref)
+ (table $t 0 funcref)
+
+ ;; WITHOUT: (elem declare func $throw)
+
+ ;; WITHOUT: (tag $tag)
+ ;; INCLUDE: (elem declare func $throw)
+
+ ;; INCLUDE: (tag $tag)
+ (tag $tag)
+
+ ;; WITHOUT: (func $main (type $void)
+ ;; WITHOUT-NEXT: (call $nop)
+ ;; WITHOUT-NEXT: (call $unreachable)
+ ;; WITHOUT-NEXT: (call $call-nop)
+ ;; WITHOUT-NEXT: (call $call-unreachable)
+ ;; WITHOUT-NEXT: (drop
+ ;; WITHOUT-NEXT: (call $unimportant-effects)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: (call $throw)
+ ;; WITHOUT-NEXT: (call $throw-and-import)
+ ;; WITHOUT-NEXT: )
+ ;; INCLUDE: (func $main (type $void)
+ ;; INCLUDE-NEXT: (call $unreachable)
+ ;; INCLUDE-NEXT: (call $call-unreachable)
+ ;; INCLUDE-NEXT: (call $throw)
+ ;; INCLUDE-NEXT: (call $throw-and-import)
+ ;; INCLUDE-NEXT: )
+ (func $main
+ ;; Calling a function with no effects can be optimized away in INCLUDE (but
+ ;; not WITHOUT or DISCARD, where the global effect info is not available).
+ (call $nop)
+ ;; Calling a function with effects cannot.
+ (call $unreachable)
+ ;; Calling something that calls something with no effects can be optimized
+ ;; away, since we compute transitive effects
+ (call $call-nop)
+ ;; Calling something that calls something with effects cannot.
+ (call $call-unreachable)
+ ;; Calling something that only has unimportant effects can be optimized
+ ;; (see below for details).
+ (drop
+ (call $unimportant-effects)
+ )
+ ;; A throwing function cannot be removed.
+ (call $throw)
+ ;; A function that throws and calls an import definitely cannot be removed.
+ (call $throw-and-import)
+ )
+
+ ;; WITHOUT: (func $cycle (type $void)
+ ;; WITHOUT-NEXT: (call $cycle)
+ ;; WITHOUT-NEXT: )
+ ;; INCLUDE: (func $cycle (type $void)
+ ;; INCLUDE-NEXT: (call $cycle)
+ ;; INCLUDE-NEXT: )
+ (func $cycle
+ ;; Calling a function with no effects in a cycle cannot be optimized out -
+ ;; this must keep hanging forever.
+ (call $cycle)
+ )
+
+ ;; WITHOUT: (func $cycle-1 (type $void)
+ ;; WITHOUT-NEXT: (call $cycle-2)
+ ;; WITHOUT-NEXT: )
+ ;; INCLUDE: (func $cycle-1 (type $void)
+ ;; INCLUDE-NEXT: (call $cycle-2)
+ ;; INCLUDE-NEXT: )
+ (func $cycle-1
+ ;; $cycle-1 and -2 form a cycle together, in which no call can be removed.
+ (call $cycle-2)
+ )
+
+ ;; WITHOUT: (func $cycle-2 (type $void)
+ ;; WITHOUT-NEXT: (call $cycle-1)
+ ;; WITHOUT-NEXT: )
+ ;; INCLUDE: (func $cycle-2 (type $void)
+ ;; INCLUDE-NEXT: (call $cycle-1)
+ ;; INCLUDE-NEXT: )
+ (func $cycle-2
+ (call $cycle-1)
+ )
+
+ ;; WITHOUT: (func $nop (type $void)
+ ;; WITHOUT-NEXT: (nop)
+ ;; WITHOUT-NEXT: )
+ ;; INCLUDE: (func $nop (type $void)
+ ;; INCLUDE-NEXT: (nop)
+ ;; INCLUDE-NEXT: )
+ (func $nop
+ (nop)
+ )
+
+ ;; WITHOUT: (func $unreachable (type $void)
+ ;; WITHOUT-NEXT: (unreachable)
+ ;; WITHOUT-NEXT: )
+ ;; INCLUDE: (func $unreachable (type $void)
+ ;; INCLUDE-NEXT: (unreachable)
+ ;; INCLUDE-NEXT: )
+ (func $unreachable
+ (unreachable)
+ )
+
+ ;; WITHOUT: (func $call-nop (type $void)
+ ;; WITHOUT-NEXT: (call $nop)
+ ;; WITHOUT-NEXT: )
+ ;; INCLUDE: (func $call-nop (type $void)
+ ;; INCLUDE-NEXT: (nop)
+ ;; INCLUDE-NEXT: )
+ (func $call-nop
+ ;; This call to a nop can be optimized out, as above, in INCLUDE.
+ (call $nop)
+ )
+
+ ;; WITHOUT: (func $call-unreachable (type $void)
+ ;; WITHOUT-NEXT: (call $unreachable)
+ ;; WITHOUT-NEXT: )
+ ;; INCLUDE: (func $call-unreachable (type $void)
+ ;; INCLUDE-NEXT: (call $unreachable)
+ ;; INCLUDE-NEXT: )
+ (func $call-unreachable
+ (call $unreachable)
+ )
+
+ ;; WITHOUT: (func $unimportant-effects (type $1) (result i32)
+ ;; WITHOUT-NEXT: (local $x i32)
+ ;; WITHOUT-NEXT: (local.set $x
+ ;; WITHOUT-NEXT: (i32.const 100)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: (return
+ ;; WITHOUT-NEXT: (local.get $x)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: )
+ ;; INCLUDE: (func $unimportant-effects (type $1) (result i32)
+ ;; INCLUDE-NEXT: (local $x i32)
+ ;; INCLUDE-NEXT: (local.set $x
+ ;; INCLUDE-NEXT: (i32.const 100)
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: (return
+ ;; INCLUDE-NEXT: (local.get $x)
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: )
+ (func $unimportant-effects (result i32)
+ (local $x i32)
+ ;; Operations on locals should not prevent optimization, as when we return
+ ;; from the function they no longer matter.
+ (local.set $x
+ (i32.const 100)
+ )
+ ;; A return is an effect that no longer matters once we exit the function.
+ (return
+ (local.get $x)
+ )
+ )
+
+ ;; WITHOUT: (func $call-throw-and-catch (type $void)
+ ;; WITHOUT-NEXT: (try
+ ;; WITHOUT-NEXT: (do
+ ;; WITHOUT-NEXT: (call $throw)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: (catch_all
+ ;; WITHOUT-NEXT: (nop)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: (try
+ ;; WITHOUT-NEXT: (do
+ ;; WITHOUT-NEXT: (call $throw-and-import)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: (catch_all
+ ;; WITHOUT-NEXT: (nop)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: )
+ ;; INCLUDE: (func $call-throw-and-catch (type $void)
+ ;; INCLUDE-NEXT: (try
+ ;; INCLUDE-NEXT: (do
+ ;; INCLUDE-NEXT: (call $throw-and-import)
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: (catch_all
+ ;; INCLUDE-NEXT: (nop)
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: )
+ (func $call-throw-and-catch
+ (try
+ (do
+ ;; This call cannot be optimized out, as the target throws. However, the
+ ;; entire try-catch can be, since the call's only effect is to throw,
+ ;; and the catch_all catches that.
+ (call $throw)
+ )
+ (catch_all)
+ )
+ (try
+ (do
+ ;; This call both throws and calls an import, and cannot be removed.
+ (call $throw-and-import)
+ )
+ (catch_all)
+ )
+ )
+
+ ;; WITHOUT: (func $return-call-throw-and-catch (type $void)
+ ;; WITHOUT-NEXT: (return_call $throw)
+ ;; WITHOUT-NEXT: )
+ ;; INCLUDE: (func $return-call-throw-and-catch (type $void)
+ ;; INCLUDE-NEXT: (return_call $throw)
+ ;; INCLUDE-NEXT: )
+ (func $return-call-throw-and-catch
+ (try
+ (do
+ ;; This call cannot be optimized out, as the target throws. However, the
+ ;; surrounding try-catch can be removed even without global effects
+ ;; because the throw from the return_call is never observed by this
+ ;; try-catch.
+ (return_call $throw)
+ )
+ (catch_all)
+ )
+ )
+
+ ;; WITHOUT: (func $return-call-indirect-throw-and-catch (type $void)
+ ;; WITHOUT-NEXT: (return_call_indirect $t (type $void)
+ ;; WITHOUT-NEXT: (i32.const 0)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: )
+ ;; INCLUDE: (func $return-call-indirect-throw-and-catch (type $void)
+ ;; INCLUDE-NEXT: (return_call_indirect $t (type $void)
+ ;; INCLUDE-NEXT: (i32.const 0)
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: )
+ (func $return-call-indirect-throw-and-catch
+ (try
+ (do
+ ;; This call cannot be optimized out, as the target may throw. However,
+ ;; the surrounding try-catch can be removed even without global effects
+ ;; because the throw from the return_call is never observed by this
+ ;; try-catch.
+ (return_call_indirect
+ (i32.const 0)
+ )
+ )
+ (catch_all)
+ )
+ )
+
+ ;; WITHOUT: (func $return-call-ref-throw-and-catch (type $void)
+ ;; WITHOUT-NEXT: (return_call_ref $void
+ ;; WITHOUT-NEXT: (ref.func $throw)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: )
+ ;; INCLUDE: (func $return-call-ref-throw-and-catch (type $void)
+ ;; INCLUDE-NEXT: (return_call_ref $void
+ ;; INCLUDE-NEXT: (ref.func $throw)
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: )
+ (func $return-call-ref-throw-and-catch
+ (try
+ (do
+ ;; This call cannot be optimized out, as the target may throw. However,
+ ;; the surrounding try-catch can be removed even without global effects
+ ;; because the throw from the return_call is never observed by this
+ ;; try-catch.
+ (return_call_ref $void
+ (ref.func $throw)
+ )
+ )
+ (catch_all)
+ )
+ )
+
+ ;; WITHOUT: (func $call-return-call-throw-and-catch (type $void)
+ ;; WITHOUT-NEXT: (try
+ ;; WITHOUT-NEXT: (do
+ ;; WITHOUT-NEXT: (call $return-call-throw-and-catch)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: (catch_all
+ ;; WITHOUT-NEXT: (nop)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: (try
+ ;; WITHOUT-NEXT: (do
+ ;; WITHOUT-NEXT: (call $return-call-indirect-throw-and-catch)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: (catch_all
+ ;; WITHOUT-NEXT: (nop)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: (try
+ ;; WITHOUT-NEXT: (do
+ ;; WITHOUT-NEXT: (call $return-call-ref-throw-and-catch)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: (catch_all
+ ;; WITHOUT-NEXT: (nop)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: (call $return-call-throw-and-catch)
+ ;; WITHOUT-NEXT: (call $return-call-indirect-throw-and-catch)
+ ;; WITHOUT-NEXT: (call $return-call-ref-throw-and-catch)
+ ;; WITHOUT-NEXT: )
+ ;; INCLUDE: (func $call-return-call-throw-and-catch (type $void)
+ ;; INCLUDE-NEXT: (try
+ ;; INCLUDE-NEXT: (do
+ ;; INCLUDE-NEXT: (call $return-call-indirect-throw-and-catch)
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: (catch_all
+ ;; INCLUDE-NEXT: (nop)
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: (try
+ ;; INCLUDE-NEXT: (do
+ ;; INCLUDE-NEXT: (call $return-call-ref-throw-and-catch)
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: (catch_all
+ ;; INCLUDE-NEXT: (nop)
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: (call $return-call-throw-and-catch)
+ ;; INCLUDE-NEXT: (call $return-call-indirect-throw-and-catch)
+ ;; INCLUDE-NEXT: (call $return-call-ref-throw-and-catch)
+ ;; INCLUDE-NEXT: )
+ (func $call-return-call-throw-and-catch
+ (try
+ (do
+ ;; Even though the body of the previous function is a try-catch_all, the
+ ;; function still throws because of its return_call, so this cannot be
+ ;; optimized out, but once again the entire try-catch can be.
+ (call $return-call-throw-and-catch)
+ )
+ (catch_all)
+ )
+ (try
+ (do
+ ;; This would be the same, except since it performs an indirect call, we
+ ;; conservatively assume it could have any effect, so we can't optimize.
+ (call $return-call-indirect-throw-and-catch)
+ )
+ (catch_all)
+ )
+ (try
+ (do
+ ;; Same here.
+ (call $return-call-ref-throw-and-catch)
+ )
+ (catch_all)
+ )
+
+ ;; These cannot be optimized out at all.
+ (call $return-call-throw-and-catch)
+ (call $return-call-indirect-throw-and-catch)
+ (call $return-call-ref-throw-and-catch)
+ )
+
+ ;; WITHOUT: (func $call-unreachable-and-catch (type $void)
+ ;; WITHOUT-NEXT: (try
+ ;; WITHOUT-NEXT: (do
+ ;; WITHOUT-NEXT: (call $unreachable)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: (catch_all
+ ;; WITHOUT-NEXT: (nop)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: )
+ ;; INCLUDE: (func $call-unreachable-and-catch (type $void)
+ ;; INCLUDE-NEXT: (call $unreachable)
+ ;; INCLUDE-NEXT: )
+ (func $call-unreachable-and-catch
+ (try
+ (do
+ ;; This call has a non-throw effect. We can optimize away the try-catch
+ ;; (since no exception can be thrown anyhow), but we must leave the
+ ;; call.
+ (call $unreachable)
+ )
+ (catch_all)
+ )
+ )
+
+ ;; WITHOUT: (func $call-throw-or-unreachable-and-catch (type $2) (param $x i32)
+ ;; WITHOUT-NEXT: (try
+ ;; WITHOUT-NEXT: (do
+ ;; WITHOUT-NEXT: (if
+ ;; WITHOUT-NEXT: (local.get $x)
+ ;; WITHOUT-NEXT: (then
+ ;; WITHOUT-NEXT: (call $throw)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: (else
+ ;; WITHOUT-NEXT: (call $unreachable)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: (catch_all
+ ;; WITHOUT-NEXT: (nop)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: )
+ ;; INCLUDE: (func $call-throw-or-unreachable-and-catch (type $2) (param $x i32)
+ ;; INCLUDE-NEXT: (try
+ ;; INCLUDE-NEXT: (do
+ ;; INCLUDE-NEXT: (if
+ ;; INCLUDE-NEXT: (local.get $x)
+ ;; INCLUDE-NEXT: (then
+ ;; INCLUDE-NEXT: (call $throw)
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: (else
+ ;; INCLUDE-NEXT: (call $unreachable)
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: (catch_all
+ ;; INCLUDE-NEXT: (nop)
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: )
+ (func $call-throw-or-unreachable-and-catch (param $x i32)
+ ;; This try-catch-all's body will either call a throw or an unreachable.
+ ;; Since we have both possible effects, we cannot optimize anything here.
+ (try
+ (do
+ (if
+ (local.get $x)
+ (then
+ (call $throw)
+ )
+ (else
+ (call $unreachable)
+ )
+ )
+ )
+ (catch_all)
+ )
+ )
+
+ ;; WITHOUT: (func $throw (type $void)
+ ;; WITHOUT-NEXT: (throw $tag)
+ ;; WITHOUT-NEXT: )
+ ;; INCLUDE: (func $throw (type $void)
+ ;; INCLUDE-NEXT: (throw $tag)
+ ;; INCLUDE-NEXT: )
+ (func $throw
+ (throw $tag)
+ )
+
+ ;; WITHOUT: (func $throw-and-import (type $void)
+ ;; WITHOUT-NEXT: (throw $tag)
+ ;; WITHOUT-NEXT: )
+ ;; INCLUDE: (func $throw-and-import (type $void)
+ ;; INCLUDE-NEXT: (throw $tag)
+ ;; INCLUDE-NEXT: )
+ (func $throw-and-import
+ (if
+ (i32.const 1)
+ (then
+ (throw $tag)
+ )
+ (else
+ (call $import)
+ )
+ )
+ )
+
+ ;; WITHOUT: (func $cycle-with-unknown-call (type $void)
+ ;; WITHOUT-NEXT: (call $cycle-with-unknown-call)
+ ;; WITHOUT-NEXT: (call $import)
+ ;; WITHOUT-NEXT: )
+ ;; INCLUDE: (func $cycle-with-unknown-call (type $void)
+ ;; INCLUDE-NEXT: (call $cycle-with-unknown-call)
+ ;; INCLUDE-NEXT: (call $import)
+ ;; INCLUDE-NEXT: )
+ (func $cycle-with-unknown-call
+ ;; This function can not only call itself recursively, but also calls an
+ ;; import. We should not remove anything here, and not error during the
+ ;; analysis (this guards against a bug where the import would make us toss
+ ;; away the effects object, and the infinite loop makes us set a property on
+ ;; that object, so it must check the object still exists).
+ (call $cycle-with-unknown-call)
+ (call $import)
+ )
+)
diff --git a/test/lit/passes/global-effects.wast b/test/lit/passes/global-effects.wast
index 7390f996d..c3c6d2073 100644
--- a/test/lit/passes/global-effects.wast
+++ b/test/lit/passes/global-effects.wast
@@ -182,49 +182,44 @@
)
;; WITHOUT: (func $call-throw-and-catch (type $void)
- ;; WITHOUT-NEXT: (try
- ;; WITHOUT-NEXT: (do
+ ;; WITHOUT-NEXT: (block $tryend
+ ;; WITHOUT-NEXT: (try_table (catch_all $tryend)
;; WITHOUT-NEXT: (call $throw)
;; WITHOUT-NEXT: )
- ;; WITHOUT-NEXT: (catch_all
- ;; WITHOUT-NEXT: (nop)
- ;; WITHOUT-NEXT: )
;; WITHOUT-NEXT: )
- ;; WITHOUT-NEXT: (try
- ;; WITHOUT-NEXT: (do
+ ;; WITHOUT-NEXT: (block $tryend0
+ ;; WITHOUT-NEXT: (try_table (catch_all $tryend0)
;; WITHOUT-NEXT: (call $throw-and-import)
;; WITHOUT-NEXT: )
- ;; WITHOUT-NEXT: (catch_all
- ;; WITHOUT-NEXT: (nop)
- ;; WITHOUT-NEXT: )
;; WITHOUT-NEXT: )
;; WITHOUT-NEXT: )
;; INCLUDE: (func $call-throw-and-catch (type $void)
- ;; INCLUDE-NEXT: (try
- ;; INCLUDE-NEXT: (do
- ;; INCLUDE-NEXT: (call $throw-and-import)
+ ;; INCLUDE-NEXT: (block $tryend
+ ;; INCLUDE-NEXT: (try_table (catch_all $tryend)
+ ;; INCLUDE-NEXT: (call $throw)
;; INCLUDE-NEXT: )
- ;; INCLUDE-NEXT: (catch_all
- ;; INCLUDE-NEXT: (nop)
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: (block $tryend0
+ ;; INCLUDE-NEXT: (try_table (catch_all $tryend0)
+ ;; INCLUDE-NEXT: (call $throw-and-import)
;; INCLUDE-NEXT: )
;; INCLUDE-NEXT: )
;; INCLUDE-NEXT: )
(func $call-throw-and-catch
- (try
- (do
+ (block $tryend
+ (try_table (catch_all $tryend)
;; This call cannot be optimized out, as the target throws. However, the
- ;; entire try-catch can be, since the call's only effect is to throw,
- ;; and the catch_all catches that.
+ ;; entire try_table could be, since the call's only effect is to throw,
+ ;; and the catch_all catches that. We do this for `try` but not yet for
+ ;; `try_table`.
(call $throw)
)
- (catch_all)
)
- (try
- (do
+ (block $tryend
+ (try_table (catch_all $tryend)
;; This call both throws and calls an import, and cannot be removed.
(call $throw-and-import)
)
- (catch_all)
)
)
@@ -235,15 +230,14 @@
;; INCLUDE-NEXT: (return_call $throw)
;; INCLUDE-NEXT: )
(func $return-call-throw-and-catch
- (try
- (do
+ (block $tryend
+ (try_table (catch_all $tryend)
;; This call cannot be optimized out, as the target throws. However, the
- ;; surrounding try-catch can be removed even without global effects
+ ;; surrounding try_table can be removed even without global effects
;; because the throw from the return_call is never observed by this
- ;; try-catch.
+ ;; try_table.
(return_call $throw)
)
- (catch_all)
)
)
@@ -258,17 +252,16 @@
;; INCLUDE-NEXT: )
;; INCLUDE-NEXT: )
(func $return-call-indirect-throw-and-catch
- (try
- (do
+ (block $tryend
+ (try_table (catch_all $tryend)
;; This call cannot be optimized out, as the target may throw. However,
- ;; the surrounding try-catch can be removed even without global effects
+ ;; the surrounding try_table can be removed even without global effects
;; because the throw from the return_call is never observed by this
;; try-catch.
(return_call_indirect
(i32.const 0)
)
)
- (catch_all)
)
)
@@ -283,94 +276,81 @@
;; INCLUDE-NEXT: )
;; INCLUDE-NEXT: )
(func $return-call-ref-throw-and-catch
- (try
- (do
+ (block $tryend
+ (try_table (catch_all $tryend)
;; This call cannot be optimized out, as the target may throw. However,
- ;; the surrounding try-catch can be removed even without global effects
+ ;; the surrounding try_table can be removed even without global effects
;; because the throw from the return_call is never observed by this
;; try-catch.
(return_call_ref $void
(ref.func $throw)
)
)
- (catch_all)
)
)
;; WITHOUT: (func $call-return-call-throw-and-catch (type $void)
- ;; WITHOUT-NEXT: (try
- ;; WITHOUT-NEXT: (do
+ ;; WITHOUT-NEXT: (block $tryend
+ ;; WITHOUT-NEXT: (try_table (catch_all $tryend)
;; WITHOUT-NEXT: (call $return-call-throw-and-catch)
;; WITHOUT-NEXT: )
- ;; WITHOUT-NEXT: (catch_all
- ;; WITHOUT-NEXT: (nop)
- ;; WITHOUT-NEXT: )
;; WITHOUT-NEXT: )
- ;; WITHOUT-NEXT: (try
- ;; WITHOUT-NEXT: (do
+ ;; WITHOUT-NEXT: (block $tryend0
+ ;; WITHOUT-NEXT: (try_table (catch_all $tryend0)
;; WITHOUT-NEXT: (call $return-call-indirect-throw-and-catch)
;; WITHOUT-NEXT: )
- ;; WITHOUT-NEXT: (catch_all
- ;; WITHOUT-NEXT: (nop)
- ;; WITHOUT-NEXT: )
;; WITHOUT-NEXT: )
- ;; WITHOUT-NEXT: (try
- ;; WITHOUT-NEXT: (do
+ ;; WITHOUT-NEXT: (block $tryend1
+ ;; WITHOUT-NEXT: (try_table (catch_all $tryend1)
;; WITHOUT-NEXT: (call $return-call-ref-throw-and-catch)
;; WITHOUT-NEXT: )
- ;; WITHOUT-NEXT: (catch_all
- ;; WITHOUT-NEXT: (nop)
- ;; WITHOUT-NEXT: )
;; WITHOUT-NEXT: )
;; WITHOUT-NEXT: (call $return-call-throw-and-catch)
;; WITHOUT-NEXT: (call $return-call-indirect-throw-and-catch)
;; WITHOUT-NEXT: (call $return-call-ref-throw-and-catch)
;; WITHOUT-NEXT: )
;; INCLUDE: (func $call-return-call-throw-and-catch (type $void)
- ;; INCLUDE-NEXT: (try
- ;; INCLUDE-NEXT: (do
- ;; INCLUDE-NEXT: (call $return-call-indirect-throw-and-catch)
+ ;; INCLUDE-NEXT: (block $tryend
+ ;; INCLUDE-NEXT: (try_table (catch_all $tryend)
+ ;; INCLUDE-NEXT: (call $return-call-throw-and-catch)
;; INCLUDE-NEXT: )
- ;; INCLUDE-NEXT: (catch_all
- ;; INCLUDE-NEXT: (nop)
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: (block $tryend0
+ ;; INCLUDE-NEXT: (try_table (catch_all $tryend0)
+ ;; INCLUDE-NEXT: (call $return-call-indirect-throw-and-catch)
;; INCLUDE-NEXT: )
;; INCLUDE-NEXT: )
- ;; INCLUDE-NEXT: (try
- ;; INCLUDE-NEXT: (do
+ ;; INCLUDE-NEXT: (block $tryend1
+ ;; INCLUDE-NEXT: (try_table (catch_all $tryend1)
;; INCLUDE-NEXT: (call $return-call-ref-throw-and-catch)
;; INCLUDE-NEXT: )
- ;; INCLUDE-NEXT: (catch_all
- ;; INCLUDE-NEXT: (nop)
- ;; INCLUDE-NEXT: )
;; INCLUDE-NEXT: )
;; INCLUDE-NEXT: (call $return-call-throw-and-catch)
;; INCLUDE-NEXT: (call $return-call-indirect-throw-and-catch)
;; INCLUDE-NEXT: (call $return-call-ref-throw-and-catch)
;; INCLUDE-NEXT: )
(func $call-return-call-throw-and-catch
- (try
- (do
- ;; Even though the body of the previous function is a try-catch_all, the
+ (block $tryend
+ (try_table (catch_all $tryend)
+ ;; Even though the body of the previous function has a catch_all, the
;; function still throws because of its return_call, so this cannot be
- ;; optimized out, but once again the entire try-catch can be.
+ ;; optimized out, but once again the entire try_table could be. Again,
+ ;; this is something we do for `try` for not yet for `try_table`.
(call $return-call-throw-and-catch)
)
- (catch_all)
)
- (try
- (do
+ (block $tryend
+ (try_table (catch_all $tryend)
;; This would be the same, except since it performs an indirect call, we
;; conservatively assume it could have any effect, so we can't optimize.
(call $return-call-indirect-throw-and-catch)
)
- (catch_all)
)
- (try
- (do
+ (block $tryend
+ (try_table (catch_all $tryend)
;; Same here.
(call $return-call-ref-throw-and-catch)
)
- (catch_all)
)
;; These cannot be optimized out at all.
@@ -380,33 +360,29 @@
)
;; WITHOUT: (func $call-unreachable-and-catch (type $void)
- ;; WITHOUT-NEXT: (try
- ;; WITHOUT-NEXT: (do
+ ;; WITHOUT-NEXT: (block $tryend
+ ;; WITHOUT-NEXT: (try_table (catch_all $tryend)
;; WITHOUT-NEXT: (call $unreachable)
;; WITHOUT-NEXT: )
- ;; WITHOUT-NEXT: (catch_all
- ;; WITHOUT-NEXT: (nop)
- ;; WITHOUT-NEXT: )
;; WITHOUT-NEXT: )
;; WITHOUT-NEXT: )
;; INCLUDE: (func $call-unreachable-and-catch (type $void)
;; INCLUDE-NEXT: (call $unreachable)
;; INCLUDE-NEXT: )
(func $call-unreachable-and-catch
- (try
- (do
+ (block $tryend
+ (try_table (catch_all $tryend)
;; This call has a non-throw effect. We can optimize away the try-catch
;; (since no exception can be thrown anyhow), but we must leave the
;; call.
(call $unreachable)
)
- (catch_all)
)
)
;; WITHOUT: (func $call-throw-or-unreachable-and-catch (type $2) (param $x i32)
- ;; WITHOUT-NEXT: (try
- ;; WITHOUT-NEXT: (do
+ ;; WITHOUT-NEXT: (block $tryend
+ ;; WITHOUT-NEXT: (try_table (catch_all $tryend)
;; WITHOUT-NEXT: (if
;; WITHOUT-NEXT: (local.get $x)
;; WITHOUT-NEXT: (then
@@ -417,14 +393,11 @@
;; WITHOUT-NEXT: )
;; WITHOUT-NEXT: )
;; WITHOUT-NEXT: )
- ;; WITHOUT-NEXT: (catch_all
- ;; WITHOUT-NEXT: (nop)
- ;; WITHOUT-NEXT: )
;; WITHOUT-NEXT: )
;; WITHOUT-NEXT: )
;; INCLUDE: (func $call-throw-or-unreachable-and-catch (type $2) (param $x i32)
- ;; INCLUDE-NEXT: (try
- ;; INCLUDE-NEXT: (do
+ ;; INCLUDE-NEXT: (block $tryend
+ ;; INCLUDE-NEXT: (try_table (catch_all $tryend)
;; INCLUDE-NEXT: (if
;; INCLUDE-NEXT: (local.get $x)
;; INCLUDE-NEXT: (then
@@ -435,16 +408,13 @@
;; INCLUDE-NEXT: )
;; INCLUDE-NEXT: )
;; INCLUDE-NEXT: )
- ;; INCLUDE-NEXT: (catch_all
- ;; INCLUDE-NEXT: (nop)
- ;; INCLUDE-NEXT: )
;; INCLUDE-NEXT: )
;; INCLUDE-NEXT: )
(func $call-throw-or-unreachable-and-catch (param $x i32)
- ;; This try-catch-all's body will either call a throw or an unreachable.
+ ;; This try_table's body will either call a throw or an unreachable.
;; Since we have both possible effects, we cannot optimize anything here.
- (try
- (do
+ (block $tryend
+ (try_table (catch_all $tryend)
(if
(local.get $x)
(then
@@ -455,7 +425,6 @@
)
)
)
- (catch_all)
)
)
diff --git a/test/lit/passes/gufa-eh.wast b/test/lit/passes/gufa-eh.wast
new file mode 100644
index 000000000..79265a318
--- /dev/null
+++ b/test/lit/passes/gufa-eh.wast
@@ -0,0 +1,60 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+
+;; RUN: foreach %s %t wasm-opt -all --gufa -S -o - | filecheck %s
+
+(module
+ ;; CHECK: (type $0 (func (param i32)))
+
+ ;; CHECK: (type $1 (func (result i32)))
+
+ ;; CHECK: (type $2 (func))
+
+ ;; CHECK: (tag $e (param i32))
+ (tag $e (param i32))
+
+ ;; CHECK: (func $try_table-target-block-is-not-unreachable (type $1) (result i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block $catch (result i32)
+ ;; CHECK-NEXT: (try_table (catch $e $catch)
+ ;; CHECK-NEXT: (throw $e
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ (func $try_table-target-block-is-not-unreachable (result i32)
+ ;; Ensure that try_table connects caught tags with their branch targets.
+ (block $catch (result i32)
+ (try_table (catch $e $catch)
+ (throw $e (i32.const 0))
+ )
+ )
+ )
+
+ ;; CHECK: (func $try_table-materializes-exnref (type $2)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block $catch (result exnref)
+ ;; CHECK-NEXT: (try_table (catch_all_ref $catch)
+ ;; CHECK-NEXT: (throw $e
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try_table-materializes-exnref
+ ;; Ensure that catch_all_ref materializes a non-null exnref value. If we do
+ ;; not connect a non-null exnref value to the branch target, GUFA will think
+ ;; no value can possibly get out of that block, and will insert an
+ ;; unreachable instruction after the block.
+ (drop
+ (block $catch (result exnref)
+ (try_table (catch_all_ref $catch)
+ (throw $e (i32.const 0))
+ )
+ )
+ )
+ )
+)
diff --git a/test/lit/passes/local-subtyping.wast b/test/lit/passes/local-subtyping.wast
index 582c24f23..2985102e1 100644
--- a/test/lit/passes/local-subtyping.wast
+++ b/test/lit/passes/local-subtyping.wast
@@ -24,6 +24,9 @@
;; CHECK: (import "out" "i64" (func $i64 (type $6) (result i64)))
(import "out" "i64" (func $i64 (result i64)))
+ ;; CHECK: (tag $e-anyref (param anyref))
+ (tag $e-anyref (param anyref))
+
;; Refinalization can find a more specific type, where the declared type was
;; not the optimal LUB.
;; CHECK: (func $refinalize (type $2) (param $x i32)
@@ -80,7 +83,7 @@
;; A simple case where a local has a single assignment that we can use as a
;; more specific type. A similar thing with a parameter, however, is not a
;; thing we can optimize. Also, ignore a local with zero assignments.
- ;; CHECK: (func $simple-local-but-not-param (type $7) (param $x funcref)
+ ;; CHECK: (func $simple-local-but-not-param (type $8) (param $x funcref)
;; CHECK-NEXT: (local $y (ref $1))
;; CHECK-NEXT: (local $unused funcref)
;; CHECK-NEXT: (local.set $x
@@ -101,7 +104,7 @@
)
)
- ;; CHECK: (func $locals-with-multiple-assignments (type $8) (param $struct structref)
+ ;; CHECK: (func $locals-with-multiple-assignments (type $9) (param $struct structref)
;; CHECK-NEXT: (local $x eqref)
;; CHECK-NEXT: (local $y (ref i31))
;; CHECK-NEXT: (local $z structref)
@@ -568,4 +571,49 @@
(local.get $x)
)
)
+
+ ;; CHECK: (func $try_table-catch-result (type $0)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block $catch (result anyref)
+ ;; CHECK-NEXT: (try_table (catch $e-anyref $catch)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try_table-catch-result
+ (drop
+ ;; Must not be refined to (result nullref).
+ (block $catch (result anyref)
+ (try_table (catch $e-anyref $catch)
+ (nop)
+ )
+ (ref.null none)
+ )
+ )
+ )
+
+ ;; CHECK: (func $try_table-ref (type $0)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block $catch (result exnref)
+ ;; CHECK-NEXT: (try_table (catch_all_ref $catch)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.null noexn)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try_table-ref
+ (drop
+ ;; Must not be refined to nullexnref.
+ ;; An exnref comes from the catch_all_ref.
+ (block $catch (result exnref)
+ (try_table (catch_all_ref $catch)
+ (nop)
+ )
+ (ref.null exn)
+ )
+ )
+ )
)
diff --git a/test/lit/passes/simplify-locals-eh-legacy.wast b/test/lit/passes/simplify-locals-eh-legacy.wast
index 7b48707d4..d7fb75776 100644
--- a/test/lit/passes/simplify-locals-eh-legacy.wast
+++ b/test/lit/passes/simplify-locals-eh-legacy.wast
@@ -4,7 +4,7 @@
(module
;; CHECK: (tag $e-i32 (param i32))
(tag $e-i32 (param i32))
- ;; CHECK: (func $foo (type $2) (param $0 i32) (param $1 i32)
+ ;; CHECK: (func $foo (type $3) (param $0 i32) (param $1 i32)
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
(func $foo (param i32 i32))
@@ -83,7 +83,7 @@
)
)
- ;; CHECK: (func $bar (type $3) (result i32)
+ ;; CHECK: (func $bar (type $1) (result i32)
;; CHECK-NEXT: (i32.const 3)
;; CHECK-NEXT: )
(func $bar (result i32) (i32.const 3))
@@ -152,7 +152,7 @@
)
)
- ;; CHECK: (func $return-call-can-be-sinked-into-try (type $3) (result i32)
+ ;; CHECK: (func $return-call-can-be-sinked-into-try (type $1) (result i32)
;; CHECK-NEXT: (local $0 i32)
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: (try (result i32)
@@ -202,103 +202,4 @@
)
)
)
-
- ;; CHECK: (func $equivalent-set-removal-call (type $1) (param $0 i32)
- ;; CHECK-NEXT: (local $1 i32)
- ;; CHECK-NEXT: (nop)
- ;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (local.get $0)
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: (nop)
- ;; CHECK-NEXT: (call $equivalent-set-removal-call
- ;; CHECK-NEXT: (i32.const 2)
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (local.get $0)
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (local.get $0)
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: )
- (func $equivalent-set-removal-call (param $0 i32)
- (local $1 i32)
- (local.set $1 (local.get $0))
- (drop (local.get $0))
- (drop (local.get $1))
- ;; Even with EH enabled we can look past the call and optimize the final 1
- ;; to a 0, since they contain the same (and while the call might branch,
- ;; such a branch does not cause a problem here, as if we branch we just
- ;; don't reach the change later down).
- (call $equivalent-set-removal-call
- (i32.const 2)
- )
- (drop (local.get $0))
- (drop (local.get $1))
- )
-
- ;; CHECK: (func $equivalent-set-removal-if (type $2) (param $p i32) (param $0 i32)
- ;; CHECK-NEXT: (local $1 i32)
- ;; CHECK-NEXT: (nop)
- ;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (local.get $0)
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: (local.set $1
- ;; CHECK-NEXT: (local.get $0)
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: (if
- ;; CHECK-NEXT: (local.get $p)
- ;; CHECK-NEXT: (then
- ;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (local.get $0)
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (local.get $0)
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: (else
- ;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (local.get $0)
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (local.get $1)
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (local.get $0)
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (local.get $1)
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: )
- (func $equivalent-set-removal-if (param $p i32) (param $0 i32)
- (local $1 i32)
- (local.set $1 (local.get $0))
- (drop (local.get $0))
- ;; This local.get of 1 can be of 0.
- (drop (local.get $1))
- (if
- (local.get $p)
- (then
- (block
- ;; We also optimize in this block, which is adjacent to the code before
- ;; us. It is valid to optimize the 1 to a 0 here, as it is dominated by
- ;; the code earlier.
- (drop (local.get $0))
- (drop (local.get $1))
- )
- )
- (else
- (block
- ;; We could also optimize here, but atm just look at code adjacent to
- ;; its dominator. TODO
- (drop (local.get $0))
- (drop (local.get $1))
- )
- )
- )
- ;; As in the else, this could be optimized. TODO
- (drop (local.get $0))
- (drop (local.get $1))
- )
)
diff --git a/test/lit/passes/simplify-locals-eh.wast b/test/lit/passes/simplify-locals-eh.wast
new file mode 100644
index 000000000..f455bd4e5
--- /dev/null
+++ b/test/lit/passes/simplify-locals-eh.wast
@@ -0,0 +1,226 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+;; RUN: wasm-opt %s --simplify-locals -all -S -o - | filecheck %s
+
+(module
+ ;; CHECK: (tag $e-i32 (param i32))
+ (tag $e-i32 (param i32))
+
+ ;; CHECK: (func $bar (type $1) (result i32)
+ ;; CHECK-NEXT: (i32.const 3)
+ ;; CHECK-NEXT: )
+ (func $bar (result i32) (i32.const 3))
+
+ ;; CHECK: (func $call-cannot-be-sinked-into-try_table (type $2)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (call $bar)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block $tryend
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block $catch (result i32)
+ ;; CHECK-NEXT: (try_table (catch $e-i32 $catch)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $tryend)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $call-cannot-be-sinked-into-try_table (local $0 i32)
+ (drop
+ ;; This local.tee should NOT be sinked into 'try_table' below, because it
+ ;; may throw
+ (local.tee $0 (call $bar))
+ )
+ (block $tryend
+ (drop
+ (block $catch (result i32)
+ (try_table (catch $e-i32 $catch)
+ (drop (local.get $0))
+ )
+ (br $tryend)
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $non-call-can-be-sinked-into-try_table (type $2)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (block $tryend
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block $catch (result i32)
+ ;; CHECK-NEXT: (try_table (catch $e-i32 $catch)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 3)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $tryend)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $non-call-can-be-sinked-into-try_table (local $0 i32)
+ (drop
+ ;; This local.tee can be sinked into 'try_table' below, because it cannot
+ ;; throw
+ (local.tee $0 (i32.const 3))
+ )
+ (block $tryend
+ (drop
+ (block $catch (result i32)
+ (try_table (catch $e-i32 $catch)
+ (drop (local.get $0))
+ )
+ (br $tryend)
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $return-call-can-be-sinked-into-try_table (type $1) (result i32)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (block $tryend (result i32)
+ ;; CHECK-NEXT: (try_table (result i32) (catch $e-i32 $tryend)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (if (result i32)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (then
+ ;; CHECK-NEXT: (return_call $return-call-can-be-sinked-into-try_table)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (else
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $return-call-can-be-sinked-into-try_table (result i32)
+ (local $0 i32)
+ (drop
+ ;; This cannot throw either, so it can be sunk. Wrap the return_call in an
+ ;; if so the whole expression does not return unconditionally.
+ (local.tee $0
+ (if (result i32)
+ (i32.const 0)
+ (then
+ (return_call $return-call-can-be-sinked-into-try_table)
+ )
+ (else
+ (i32.const 1)
+ )
+ )
+ )
+ )
+ (block $tryend (result i32)
+ (try_table (result i32) (catch $e-i32 $tryend)
+ (drop (local.get $0))
+ (i32.const 0)
+ )
+ )
+ )
+
+ ;; CHECK: (func $equivalent-set-removal-call (type $0) (param $0 i32)
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (call $equivalent-set-removal-call
+ ;; CHECK-NEXT: (i32.const 2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $equivalent-set-removal-call (param $0 i32)
+ (local $1 i32)
+ (local.set $1 (local.get $0))
+ (drop (local.get $0))
+ (drop (local.get $1))
+ ;; Even with EH enabled we can look past the call and optimize the final 1
+ ;; to a 0, since they contain the same (and while the call might branch,
+ ;; such a branch does not cause a problem here, as if we branch we just
+ ;; don't reach the change later down).
+ (call $equivalent-set-removal-call
+ (i32.const 2)
+ )
+ (drop (local.get $0))
+ (drop (local.get $1))
+ )
+
+ ;; CHECK: (func $equivalent-set-removal-if (type $3) (param $p i32) (param $0 i32)
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $1
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (local.get $p)
+ ;; CHECK-NEXT: (then
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (else
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $equivalent-set-removal-if (param $p i32) (param $0 i32)
+ (local $1 i32)
+ (local.set $1 (local.get $0))
+ (drop (local.get $0))
+ ;; This local.get of 1 can be of 0.
+ (drop (local.get $1))
+ (if
+ (local.get $p)
+ (then
+ (block
+ ;; We also optimize in this block, which is adjacent to the code before
+ ;; us. It is valid to optimize the 1 to a 0 here, as it is dominated by
+ ;; the code earlier.
+ (drop (local.get $0))
+ (drop (local.get $1))
+ )
+ )
+ (else
+ (block
+ ;; We could also optimize here, but atm just look at code adjacent to
+ ;; its dominator. TODO
+ (drop (local.get $0))
+ (drop (local.get $1))
+ )
+ )
+ )
+ ;; As in the else, this could be optimized. TODO
+ (drop (local.get $0))
+ (drop (local.get $1))
+ )
+)
diff --git a/test/lit/passes/vacuum-eh-legacy.wast b/test/lit/passes/vacuum-eh-legacy.wast
index 125475e26..0eb53bb56 100644
--- a/test/lit/passes/vacuum-eh-legacy.wast
+++ b/test/lit/passes/vacuum-eh-legacy.wast
@@ -6,14 +6,13 @@
(type $void (func))
;; CHECK: (table $t 0 funcref)
+ (table $t 0 funcref)
;; CHECK: (tag $e (param i32))
(tag $e (param i32))
;; CHECK: (tag $e2 (param i32))
(tag $e2 (param i32))
- (table $t 0 funcref)
-
;; CHECK: (func $try-test (type $void)
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
diff --git a/test/lit/passes/vacuum-eh.wast b/test/lit/passes/vacuum-eh.wast
new file mode 100644
index 000000000..ec62f3c58
--- /dev/null
+++ b/test/lit/passes/vacuum-eh.wast
@@ -0,0 +1,235 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+;; RUN: wasm-opt %s --vacuum -all -S -o - | filecheck %s
+
+(module
+ ;; CHECK: (type $void (func))
+ (type $void (func))
+
+ ;; CHECK: (table $t 0 funcref)
+ (table $t 0 funcref)
+
+ ;; CHECK: (tag $e (param i32))
+ (tag $e (param i32))
+ ;; CHECK: (tag $e2 (param i32))
+ (tag $e2 (param i32))
+
+ ;; CHECK: (func $try_table-test (type $void)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $try_table-test
+ ;; When try_table body does not throw, the try_table can be replaced with
+ ;; its body
+ (block $tryend
+ (drop
+ (block $catch (result i32)
+ (try_table (catch $e $catch)
+ (drop (i32.const 0))
+ )
+ (br $tryend)
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $inner-try_table-catch_all-test (type $2) (result i32)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (block $inner-catch
+ ;; CHECK-NEXT: (try_table (catch_all $inner-catch)
+ ;; CHECK-NEXT: (throw $e
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (return
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 2)
+ ;; CHECK-NEXT: )
+ (func $inner-try_table-catch_all-test (result i32)
+ (local $0 i32)
+ ;; The exception thrown in the inner try_table is caught by the inner
+ ;; catch_all, so the outer try_table body does not throw and the outer
+ ;; try_table can be removed
+ (block $outer-tryend
+ (drop
+ (block $outer-catch (result i32)
+ (try_table (catch $e $outer-catch)
+ (block $inner-catch
+ (try_table (catch_all $inner-catch)
+ (throw $e (i32.const 0))
+ )
+ (unreachable)
+ )
+ (return (i32.const 1))
+ )
+ (br $outer-tryend)
+ )
+ )
+ )
+ (i32.const 2)
+ )
+
+ ;; CHECK: (func $inner-try_table-catch-test (type $void)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (block $outer-tryend
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block $outer-catch (result i32)
+ ;; CHECK-NEXT: (try_table (catch $e $outer-catch)
+ ;; CHECK-NEXT: (block $inner-tryend
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block $inner-catch (result i32)
+ ;; CHECK-NEXT: (try_table (catch $e $inner-catch)
+ ;; CHECK-NEXT: (throw $e2
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $outer-tryend)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $inner-try_table-catch-test (local $0 i32)
+ ;; The exception thrown in the inner try_table will not be caught by the
+ ;; inner catch, so the outer try_table cannot be removed.
+ (block $outer-tryend
+ (drop
+ (block $outer-catch (result i32)
+ (try_table (catch $e $outer-catch)
+ (block $inner-tryend
+ (drop
+ (block $inner-catch (result i32)
+ (try_table (catch $e $inner-catch)
+ (throw $e2 (i32.const 0))
+ )
+ (br $inner-tryend)
+ )
+ )
+ (local.set $0 (i32.const 1))
+ )
+ )
+ (br $outer-tryend)
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $trivial-catch-all-of-throw (type $void)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (block $catch
+ ;; CHECK-NEXT: (try_table (catch_all $catch)
+ ;; CHECK-NEXT: (throw $e
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block $catch0
+ ;; CHECK-NEXT: (try_table (catch_all $catch0)
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: (then
+ ;; CHECK-NEXT: (throw $e
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (else
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $trivial-catch-all-of-throw (local $0 i32)
+ ;; This try_table's body throws, but the catch_all catches it, so the entire
+ ;; try_table could be optimized out. We do this for `try` but not yet for
+ ;; `try_table`.
+ (block $catch
+ (try_table (catch_all $catch)
+ (throw $e (i32.const 0))
+ )
+ )
+ ;; Here we also have a possible trap, so we can't do it.
+ (block $catch
+ (try_table (catch_all $catch)
+ (if
+ (local.get $0)
+ (then
+ (throw $e (i32.const 0))
+ )
+ (else
+ (unreachable)
+ )
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $throw (type $void)
+ ;; CHECK-NEXT: (throw $e
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $throw
+ ;; Helper for the tail call tests below.
+ (throw $e
+ (i32.const 0)
+ )
+ )
+
+ ;; CHECK: (func $return-call-catch (type $void)
+ ;; CHECK-NEXT: (return_call $throw)
+ ;; CHECK-NEXT: )
+ (func $return-call-catch
+ (block $catch
+ (try_table (catch_all $catch)
+ ;; This returns before it throws, so we can optimize out the surrounding
+ ;; try_table.
+ (return_call $throw)
+ )
+ )
+ )
+
+ ;; CHECK: (func $return-call-indirect-catch (type $void)
+ ;; CHECK-NEXT: (return_call_indirect $t (type $void)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $return-call-indirect-catch
+ (block $catch
+ (try_table (catch_all $catch)
+ ;; This returns before it throws, so we can optimize out the surrounding
+ ;; try_table.
+ (return_call_indirect
+ (i32.const 0)
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $return-call-ref-catch (type $void)
+ ;; CHECK-NEXT: (return_call_ref $void
+ ;; CHECK-NEXT: (ref.func $throw)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $return-call-ref-catch
+ (block $catch
+ (try_table (catch_all $catch)
+ ;; This returns before it throws, so we can optimize out the surrounding
+ ;; try_table.
+ (return_call_ref $void
+ (ref.func $throw)
+ )
+ )
+ )
+ )
+)
diff --git a/test/spec/exception-handling.wast b/test/spec/exception-handling.wast
index 95350b77b..c6da12eef 100644
--- a/test/spec/exception-handling.wast
+++ b/test/spec/exception-handling.wast
@@ -1,3 +1,153 @@
+(module
+ (tag $e-v)
+ (tag $e-i32 (param i32))
+ (tag $e-f32 (param f32))
+ (tag $e-i32-f32 (param i32 f32))
+
+ (func $throw_single_value (export "throw_single_value")
+ (throw $e-i32 (i32.const 5))
+ )
+
+ (func (export "throw_multiple_values")
+ (throw $e-i32-f32 (i32.const 3) (f32.const 3.5))
+ )
+
+ (func (export "try_table_nothrow") (result i32)
+ (block $tryend (result i32)
+ (drop
+ (block $catch (result i32)
+ (br $tryend
+ (try_table (result i32) (catch $e-i32 $catch)
+ (i32.const 3)
+ )
+ )
+ )
+ )
+ (i32.const 0)
+ )
+ )
+
+ (func (export "try_table_throw_catch") (result i32)
+ (block $tryend (result i32)
+ (drop
+ (block $catch (result i32)
+ (br $tryend
+ (try_table (result i32) (catch $e-i32 $catch)
+ (throw $e-i32 (i32.const 5))
+ )
+ )
+ )
+ )
+ (i32.const 3)
+ )
+ )
+
+ (func (export "try_table_throw_nocatch") (result i32)
+ (block $tryend (result i32)
+ (drop
+ (block $catch (result f32)
+ (br $tryend
+ (try_table (result i32) (catch $e-f32 $catch)
+ (throw $e-i32 (i32.const 5))
+ )
+ )
+ )
+ )
+ (i32.const 3)
+ )
+ )
+
+ (func (export "try_table_throw_catchall") (result i32)
+ (block $tryend (result i32)
+ (block $catch-all
+ (drop
+ (block $catch-f32 (result f32)
+ (br $tryend
+ (try_table (result i32) (catch $e-f32 $catch-f32) (catch_all $catch-all)
+ (throw $e-i32 (i32.const 5))
+ )
+ )
+ )
+ )
+ (br $tryend (i32.const 4))
+ )
+ (i32.const 3)
+ )
+ )
+
+ (func (export "try_table_call_catch") (result i32)
+ (block $tryend (result i32)
+ (block $catch (result i32)
+ (br $tryend
+ (try_table (result i32) (catch $e-i32 $catch)
+ (call $throw_single_value)
+ (unreachable)
+ )
+ )
+ )
+ )
+ )
+
+ (func (export "try_table_throw_multivalue_catch") (result i32) (local $x (tuple i32 f32))
+ (block $tryend (result i32)
+ (local.set $x
+ (block $catch (result i32) (result f32)
+ (br $tryend
+ (try_table (result i32) (catch $e-i32-f32 $catch)
+ (throw $e-i32-f32 (i32.const 5) (f32.const 1.5))
+ )
+ )
+ )
+ )
+ (tuple.extract 2 0
+ (local.get $x)
+ )
+ )
+ )
+
+ (func (export "try_table_throw_throw_ref") (local $x (tuple i32 exnref))
+ (block $tryend
+ (local.set $x
+ (block $catch (result i32) (result exnref)
+ (try_table (catch_ref $e-i32 $catch)
+ (throw $e-i32 (i32.const 5))
+ )
+ (br $tryend)
+ )
+ )
+ (throw_ref
+ (tuple.extract 2 1
+ (local.get $x)
+ )
+ )
+ )
+ )
+
+ (func (export "try_table_call_throw_ref")
+ (block $tryend
+ (throw_ref
+ (block $catch (result exnref)
+ (try_table (catch_all_ref $catch)
+ (call $throw_single_value)
+ )
+ (br $tryend)
+ )
+ )
+ )
+ )
+)
+
+(assert_exception (invoke "throw_single_value"))
+(assert_exception (invoke "throw_multiple_values"))
+(assert_return (invoke "try_table_nothrow") (i32.const 3))
+(assert_return (invoke "try_table_throw_catch") (i32.const 3))
+(assert_exception (invoke "try_table_throw_nocatch"))
+(assert_return (invoke "try_table_throw_catchall") (i32.const 3))
+(assert_return (invoke "try_table_call_catch") (i32.const 5))
+(assert_return (invoke "try_table_throw_multivalue_catch") (i32.const 5))
+(assert_exception (invoke "try_table_throw_throw_ref"))
+(assert_exception (invoke "try_table_call_throw_ref"))
+
(assert_invalid
(module
(tag $e-i32 (param i32))
diff --git a/test/spec/return_call_eh-legacy.wast b/test/spec/return_call_eh-legacy.wast
new file mode 100644
index 000000000..8b0b54ee0
--- /dev/null
+++ b/test/spec/return_call_eh-legacy.wast
@@ -0,0 +1,35 @@
+;; Test the combination of 'return_call' with legacy exception handling.
+
+(module
+ (tag $t)
+
+ (func $test (export "test") (result i32)
+ (try (result i32)
+ (do
+ (call $return-call-in-try)
+ )
+ (catch_all
+ ;; Catch the exception thrown from $return-callee.
+ (i32.const 42)
+ )
+ )
+
+ )
+
+ (func $return-call-in-try (result i32)
+ (try (result i32)
+ (do
+ (return_call $return-callee)
+ )
+ (catch_all
+ (unreachable)
+ )
+ )
+ )
+
+ (func $return-callee (result i32)
+ (throw $t)
+ )
+)
+
+(assert_return (invoke "test") (i32.const 42))
diff --git a/test/spec/return_call_eh.wast b/test/spec/return_call_eh.wast
index e85fdf7c6..11950c4b4 100644
--- a/test/spec/return_call_eh.wast
+++ b/test/spec/return_call_eh.wast
@@ -1,34 +1,31 @@
-;; Test the combination of 'return_call' with exception handling
+;; Test the combination of 'return_call' with exception handling.
(module
(tag $t)
(func $test (export "test") (result i32)
- (try (result i32)
- (do
- (call $return-call-in-try)
- )
- (catch_all
- ;; Catch the exception thrown from $return-callee
- (i32.const 42)
+ (block $catch
+ (return
+ (try_table (result i32) (catch_all $catch)
+ (call $return-call-in-try_table)
+ )
)
)
-
+ ;; Catch the exception thrown from $return-callee.
+ (i32.const 42)
)
- (func $return-call-in-try (result i32)
- (try (result i32)
- (do
+ (func $return-call-in-try_table (result i32)
+ (block $catch
+ (try_table (catch_all $catch)
(return_call $return-callee)
)
- (catch_all
- (unreachable)
- )
)
+ (unreachable)
)
(func $return-callee (result i32)
- (throw $t)
+ (throw $t)
)
)