summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ir/ExpressionAnalyzer.cpp20
-rw-r--r--src/ir/branch-utils.h48
-rw-r--r--src/ir/properties.h10
-rw-r--r--src/passes/Poppify.cpp8
-rw-r--r--src/passes/Print.cpp77
-rw-r--r--src/passes/RemoveUnusedNames.cpp2
-rw-r--r--src/passes/StackIR.cpp4
-rw-r--r--src/shared-constants.h1
-rw-r--r--src/wasm-binary.h4
-rw-r--r--src/wasm-delegations-fields.h2
-rw-r--r--src/wasm-s-parser.h3
-rw-r--r--src/wasm-stack.h15
-rw-r--r--src/wasm-type.h1
-rw-r--r--src/wasm.h4
-rw-r--r--src/wasm/wasm-binary.cpp173
-rw-r--r--src/wasm/wasm-s-parser.cpp56
-rw-r--r--src/wasm/wasm-stack.cpp16
-rw-r--r--src/wasm/wasm-validator.cpp68
-rw-r--r--src/wasm/wasm.cpp1
-rw-r--r--test/break-within-catch.wasm.fromBinary2
-rw-r--r--test/exception-handling.wast87
-rw-r--r--test/exception-handling.wast.from-wast122
-rw-r--r--test/exception-handling.wast.fromBinary126
-rw-r--r--test/exception-handling.wast.fromBinary.noDebugInfo126
-rw-r--r--test/lit/passes/optimize-instructions-exceptions.wast2
-rw-r--r--test/lit/passes/poppify.wast38
-rw-r--r--test/passes/code-pushing_all-features.txt6
-rw-r--r--test/passes/dce_all-features.txt8
-rw-r--r--test/passes/dwarf_with_exceptions.bin.txt12
-rw-r--r--test/passes/generate-stack-ir_optimize-stack-ir_print-stack-ir_all-features.txt40
-rw-r--r--test/passes/generate-stack-ir_optimize-stack-ir_print-stack-ir_all-features.wast18
-rw-r--r--test/passes/instrument-locals_all-features_disable-typed-function-references.txt2
-rw-r--r--test/passes/remove-unused-module-elements_all-features.txt2
-rw-r--r--test/passes/rse_all-features.txt22
-rw-r--r--test/passes/simplify-locals_all-features.txt10
-rw-r--r--test/passes/vacuum_all-features.txt6
-rw-r--r--test/reference-types.wast.from-wast8
-rw-r--r--test/reference-types.wast.fromBinary8
-rw-r--r--test/reference-types.wast.fromBinary.noDebugInfo8
-rw-r--r--test/spec/exception-handling.wast38
-rw-r--r--test/try-delegate.wasmbin0 -> 59 bytes
-rw-r--r--test/try-delegate.wasm.fromBinary40
42 files changed, 1061 insertions, 183 deletions
diff --git a/src/ir/ExpressionAnalyzer.cpp b/src/ir/ExpressionAnalyzer.cpp
index 04ef52026..0b0adae9e 100644
--- a/src/ir/ExpressionAnalyzer.cpp
+++ b/src/ir/ExpressionAnalyzer.cpp
@@ -17,6 +17,7 @@
#include "ir/iteration.h"
#include "ir/load-utils.h"
#include "ir/utils.h"
+#include "shared-constants.h"
#include "support/hash.h"
#include "support/small_vector.h"
#include "wasm-traversal.h"
@@ -261,6 +262,9 @@ size_t ExpressionAnalyzer::hash(Expression* curr) {
Hasher(Expression* curr) {
stack.push_back(curr);
+ // DELEGATE_CALLER_TARGET is a fake target used to denote delegating to
+ // the caller. Add it here to prevent the unknown name error.
+ noteScopeName(DELEGATE_CALLER_TARGET);
while (stack.size() > 0) {
curr = stack.back();
@@ -327,13 +331,15 @@ size_t ExpressionAnalyzer::hash(Expression* curr) {
}
}
void visitScopeName(Name curr) {
- // Names are relative, we give the same hash for
- // (block $x (br $x))
- // (block $y (br $y))
- static_assert(sizeof(Index) == sizeof(int32_t),
- "wasm64 will need changes here");
- assert(internalNames.find(curr) != internalNames.end());
- rehash(digest, internalNames[curr]);
+ if (curr.is()) { // try's delegate target can be null
+ // Names are relative, we give the same hash for
+ // (block $x (br $x))
+ // (block $y (br $y))
+ static_assert(sizeof(Index) == sizeof(int32_t),
+ "wasm64 will need changes here");
+ assert(internalNames.find(curr) != internalNames.end());
+ rehash(digest, internalNames[curr]);
+ }
}
void visitNonScopeName(Name curr) { rehash(digest, uint64_t(curr.str)); }
void visitType(Type curr) { rehash(digest, curr.getID()); }
diff --git a/src/ir/branch-utils.h b/src/ir/branch-utils.h
index a9561ef07..8a4f02a58 100644
--- a/src/ir/branch-utils.h
+++ b/src/ir/branch-utils.h
@@ -40,9 +40,9 @@ inline bool isBranchReachable(Expression* expr) {
return true;
}
-// Perform a generic operation on uses of scope names (branch targets) in an
-// expression. The provided function receives a Name& which it can modify if it
-// needs to.
+// Perform a generic operation on uses of scope names (branch + delegate
+// targets) in an expression. The provided function receives a Name& which it
+// can modify if it needs to.
template<typename T> void operateOnScopeNameUses(Expression* expr, T func) {
#define DELEGATE_ID expr->_id
@@ -83,7 +83,7 @@ void operateOnScopeNameUsesAndSentTypes(Expression* expr, T func) {
} else if (auto* br = expr->dynCast<BrOn>()) {
func(name, br->getCastType());
} else {
- WASM_UNREACHABLE("bad br type");
+ assert(expr->is<Try>()); // delegate
}
});
}
@@ -135,6 +135,46 @@ inline bool replacePossibleTarget(Expression* branch, Name from, Name to) {
return worked;
}
+// Replace all delegate targets within the given AST.
+inline void replaceDelegateTargets(Expression* ast, Name from, Name to) {
+ struct Replacer
+ : public PostWalker<Replacer, UnifiedExpressionVisitor<Replacer>> {
+ Name from, to;
+ Replacer(Name from, Name to) : from(from), to(to) {}
+ void visitExpression(Expression* curr) {
+ if (curr->is<Try>()) {
+ operateOnScopeNameUses(curr, [&](Name& name) {
+ if (name == from) {
+ name = to;
+ }
+ });
+ }
+ }
+ };
+ Replacer replacer(from, to);
+ replacer.walk(ast);
+}
+
+// Replace all branch targets within the given AST.
+inline void replaceBranchTargets(Expression* ast, Name from, Name to) {
+ struct Replacer
+ : public PostWalker<Replacer, UnifiedExpressionVisitor<Replacer>> {
+ Name from, to;
+ Replacer(Name from, Name to) : from(from), to(to) {}
+ void visitExpression(Expression* curr) {
+ if (Properties::isBranch(curr)) {
+ operateOnScopeNameUses(curr, [&](Name& name) {
+ if (name == from) {
+ name = to;
+ }
+ });
+ }
+ }
+ };
+ Replacer replacer(from, to);
+ replacer.walk(ast);
+}
+
// Returns the set of targets to which we branch that are
// outside of an expression.
inline NameSet getExitingBranches(Expression* ast) {
diff --git a/src/ir/properties.h b/src/ir/properties.h
index b60c74d98..8ae7befba 100644
--- a/src/ir/properties.h
+++ b/src/ir/properties.h
@@ -69,13 +69,15 @@ inline bool isControlFlowStructure(Expression* curr) {
curr->is<Try>();
}
-// Check if an expression is a control flow construct with a name,
-// which implies it may have breaks to it.
+// Check if an expression is a control flow construct with a name, which implies
+// it may have breaks or delegates to it.
inline bool isNamedControlFlow(Expression* curr) {
if (auto* block = curr->dynCast<Block>()) {
return block->name.is();
} else if (auto* loop = curr->dynCast<Loop>()) {
return loop->name.is();
+ } else if (auto* try_ = curr->dynCast<Try>()) {
+ return try_->name.is();
}
return false;
}
@@ -104,6 +106,10 @@ inline bool isConstantExpression(const Expression* curr) {
return false;
}
+inline bool isBranch(const Expression* curr) {
+ return curr->is<Break>() || curr->is<Switch>() || curr->is<BrOn>();
+}
+
inline Literal getLiteral(const Expression* curr) {
if (auto* c = curr->dynCast<Const>()) {
return c->value;
diff --git a/src/passes/Poppify.cpp b/src/passes/Poppify.cpp
index b0d07e60c..2ab222697 100644
--- a/src/passes/Poppify.cpp
+++ b/src/passes/Poppify.cpp
@@ -128,6 +128,7 @@ struct Poppifier : BinaryenIRWriter<Poppifier> {
void emitIfElse(If* curr);
void emitCatch(Try* curr, Index i);
void emitCatchAll(Try* curr);
+ void emitDelegate(Try* curr);
void emitScopeEnd(Expression* curr);
void emitFunctionEnd();
void emitUnreachable();
@@ -272,6 +273,13 @@ void Poppifier::emitCatchAll(Try* curr) {
scopeStack.emplace_back(Scope::Catch);
}
+void Poppifier::emitDelegate(Try* curr) {
+ auto& scope = scopeStack.back();
+ assert(scope.kind == Scope::Try);
+ patchScope(curr->body);
+ scopeStack.back().instrs.push_back(curr);
+}
+
void Poppifier::emitScopeEnd(Expression* curr) {
switch (scopeStack.back().kind) {
case Scope::Block:
diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp
index e49735af0..9b59de444 100644
--- a/src/passes/Print.cpp
+++ b/src/passes/Print.cpp
@@ -1764,6 +1764,10 @@ struct PrintExpressionContents
void visitRefEq(RefEq* curr) { printMedium(o, "ref.eq"); }
void visitTry(Try* curr) {
printMedium(o, "try");
+ if (curr->name.is()) {
+ o << ' ';
+ printName(curr->name, o);
+ }
if (curr->type.isConcrete()) {
o << ' ' << ResultType(curr->type);
}
@@ -1955,6 +1959,9 @@ struct PrintSExpression : public OverriddenVisitor<PrintSExpression> {
Function::DebugLocation lastPrintedLocation;
bool debugInfo;
+ // Used to print delegate's depth argument when it throws to the caller
+ int controlFlowDepth = 0;
+
PrintSExpression(std::ostream& o) : o(o) {
setMinify(false);
if (!full) {
@@ -2100,6 +2107,9 @@ struct PrintSExpression : public OverriddenVisitor<PrintSExpression> {
break; // that's all we can recurse, start to unwind
}
}
+
+ int startControlFlowDepth = controlFlowDepth;
+ controlFlowDepth += stack.size();
auto* top = stack.back();
while (stack.size() > 0) {
curr = stack.back();
@@ -2129,8 +2139,10 @@ struct PrintSExpression : public OverriddenVisitor<PrintSExpression> {
o << ' ' << curr->name;
}
}
+ controlFlowDepth = startControlFlowDepth;
}
void visitIf(If* curr) {
+ controlFlowDepth++;
o << '(';
printExpressionContents(curr);
incIndent();
@@ -2147,8 +2159,10 @@ struct PrintSExpression : public OverriddenVisitor<PrintSExpression> {
if (full) {
o << " ;; end if";
}
+ controlFlowDepth--;
}
void visitLoop(Loop* curr) {
+ controlFlowDepth++;
o << '(';
printExpressionContents(curr);
incIndent();
@@ -2160,6 +2174,7 @@ struct PrintSExpression : public OverriddenVisitor<PrintSExpression> {
o << ' ' << curr->name;
}
}
+ controlFlowDepth--;
}
void visitBreak(Break* curr) {
o << '(';
@@ -2490,13 +2505,28 @@ struct PrintSExpression : public OverriddenVisitor<PrintSExpression> {
// (do
// ...
// )
- // (catch
- // ...
+ // (catch $e
+ // ...
+ // )
+ // ...
+ // (catch_all
+ // ...
// )
// )
- // The parenthesis wrapping 'catch' is just a syntax and does not affect
- // nested depths of instructions within.
+ // The parenthesis wrapping do/catch/catch_all is just a syntax and does not
+ // affect nested depths of instructions within.
+ //
+ // try-delegate is written in the forded format as
+ // (try
+ // (do
+ // ...
+ // )
+ // (delegate $label)
+ // )
+ // When the 'delegate' delegates to the caller, we write the argument as an
+ // immediate.
void visitTry(Try* curr) {
+ controlFlowDepth++;
o << '(';
printExpressionContents(curr);
incIndent();
@@ -2521,12 +2551,26 @@ struct PrintSExpression : public OverriddenVisitor<PrintSExpression> {
if (curr->hasCatchAll()) {
doIndent(o, indent);
printDebugDelimiterLocation(curr, curr->catchEvents.size());
- o << "(catch_all";
+ o << '(';
+ printMedium(o, "catch_all");
incIndent();
maybePrintImplicitBlock(curr->catchBodies.back(), true);
decIndent();
o << "\n";
}
+ controlFlowDepth--;
+
+ if (curr->isDelegate()) {
+ doIndent(o, indent);
+ o << '(';
+ printMedium(o, "delegate ");
+ if (curr->delegateTarget == DELEGATE_CALLER_TARGET) {
+ o << controlFlowDepth;
+ } else {
+ printName(curr->delegateTarget, o);
+ }
+ o << ")\n";
+ }
decIndent();
if (full) {
o << " ;; end try";
@@ -2913,6 +2957,7 @@ struct PrintSExpression : public OverriddenVisitor<PrintSExpression> {
} else {
printFullLine(curr->body);
}
+ assert(controlFlowDepth == 0);
} else {
// Print the stack IR.
printStackIR(curr->stackIR.get(), o, curr);
@@ -3324,6 +3369,11 @@ printStackInst(StackInst* inst, std::ostream& o, Function* func) {
printMedium(o, "catch_all");
break;
}
+ case StackInst::Delegate: {
+ printMedium(o, "delegate ");
+ printName(inst->origin->cast<Try>()->delegateTarget, o);
+ break;
+ }
default:
WASM_UNREACHABLE("unexpeted op");
}
@@ -3339,6 +3389,7 @@ printStackIR(StackIR* ir, std::ostream& o, Function* func) {
}
};
+ int controlFlowDepth = 0;
// Stack to track indices of catches within a try
SmallVector<Index, 4> catchIndexStack;
for (Index i = 0; i < (*ir).size(); i++) {
@@ -3364,6 +3415,7 @@ printStackIR(StackIR* ir, std::ostream& o, Function* func) {
case StackInst::BlockBegin:
case StackInst::IfBegin:
case StackInst::LoopBegin: {
+ controlFlowDepth++;
doIndent();
PrintExpressionContents(func, o).visit(inst->origin);
indent++;
@@ -3375,6 +3427,7 @@ printStackIR(StackIR* ir, std::ostream& o, Function* func) {
case StackInst::BlockEnd:
case StackInst::IfEnd:
case StackInst::LoopEnd: {
+ controlFlowDepth--;
indent--;
doIndent();
printMedium(o, "end");
@@ -3403,11 +3456,25 @@ printStackIR(StackIR* ir, std::ostream& o, Function* func) {
indent++;
break;
}
+ case StackInst::Delegate: {
+ controlFlowDepth--;
+ indent--;
+ doIndent();
+ printMedium(o, "delegate ");
+ Try* curr = inst->origin->cast<Try>();
+ if (curr->delegateTarget == DELEGATE_CALLER_TARGET) {
+ o << controlFlowDepth;
+ } else {
+ printName(curr->delegateTarget, o);
+ }
+ break;
+ }
default:
WASM_UNREACHABLE("unexpeted op");
}
std::cout << '\n';
}
+ assert(controlFlowDepth == 0);
return o;
}
diff --git a/src/passes/RemoveUnusedNames.cpp b/src/passes/RemoveUnusedNames.cpp
index 64640aefe..53da00733 100644
--- a/src/passes/RemoveUnusedNames.cpp
+++ b/src/passes/RemoveUnusedNames.cpp
@@ -83,6 +83,8 @@ struct RemoveUnusedNames
}
}
+ void visitTry(Try* curr) { handleBreakTarget(curr->name); }
+
void visitFunction(Function* curr) { assert(branchesSeen.empty()); }
};
diff --git a/src/passes/StackIR.cpp b/src/passes/StackIR.cpp
index 0732a6499..6d163d897 100644
--- a/src/passes/StackIR.cpp
+++ b/src/passes/StackIR.cpp
@@ -258,6 +258,7 @@ private:
case StackInst::LoopEnd:
case StackInst::Catch:
case StackInst::CatchAll:
+ case StackInst::Delegate:
case StackInst::TryEnd: {
return true;
}
@@ -284,7 +285,8 @@ private:
case StackInst::BlockEnd:
case StackInst::IfEnd:
case StackInst::LoopEnd:
- case StackInst::TryEnd: {
+ case StackInst::TryEnd:
+ case StackInst::Delegate: {
return true;
}
default: { return false; }
diff --git a/src/shared-constants.h b/src/shared-constants.h
index 569fd792d..5520f15ac 100644
--- a/src/shared-constants.h
+++ b/src/shared-constants.h
@@ -60,6 +60,7 @@ extern Name CASE;
extern Name BR;
extern Name FUNCREF;
extern Name FAKE_RETURN;
+extern Name DELEGATE_CALLER_TARGET;
extern Name MUT;
extern Name SPECTEST;
extern Name PRINT;
diff --git a/src/wasm-binary.h b/src/wasm-binary.h
index a4d41530e..c2a771a7d 100644
--- a/src/wasm-binary.h
+++ b/src/wasm-binary.h
@@ -1027,6 +1027,7 @@ enum ASTNodes {
Try = 0x06,
Catch = 0x07,
CatchAll = 0x05,
+ Delegate = 0x18,
Throw = 0x08,
Rethrow = 0x09,
@@ -1417,6 +1418,8 @@ public:
// the names that breaks target. this lets us know if a block has breaks to it
// or not.
std::unordered_set<Name> breakTargetNames;
+ // the names that delegates target.
+ std::unordered_set<Name> delegateTargetNames;
std::vector<Expression*> expressionStack;
@@ -1521,6 +1524,7 @@ public:
Expression* getBlockOrSingleton(Type type);
BreakTarget getBreakTarget(int32_t offset);
+ Name getDelegateTargetName(int32_t offset);
void readMemoryAccess(Address& alignment, Address& offset);
diff --git a/src/wasm-delegations-fields.h b/src/wasm-delegations-fields.h
index 033da6314..9677aa4de 100644
--- a/src/wasm-delegations-fields.h
+++ b/src/wasm-delegations-fields.h
@@ -515,8 +515,10 @@ switch (DELEGATE_ID) {
}
case Expression::Id::TryId: {
DELEGATE_START(Try);
+ DELEGATE_FIELD_SCOPE_NAME_USE(Try, delegateTarget);
DELEGATE_FIELD_CHILD_VECTOR(Try, catchBodies);
DELEGATE_FIELD_NAME_VECTOR(Try, catchEvents);
+ DELEGATE_FIELD_SCOPE_NAME_DEF(Try, name);
DELEGATE_FIELD_CHILD(Try, body);
DELEGATE_END(Try);
break;
diff --git a/src/wasm-s-parser.h b/src/wasm-s-parser.h
index 8a92c0935..92bc55d0a 100644
--- a/src/wasm-s-parser.h
+++ b/src/wasm-s-parser.h
@@ -242,7 +242,8 @@ private:
i++;
}
}
- Name getLabel(Element& s);
+ enum class LabelType { Break, Delegate };
+ Name getLabel(Element& s, LabelType labelType = LabelType::Break);
Expression* makeBreak(Element& s);
Expression* makeBreakTable(Element& s);
Expression* makeReturn(Element& s);
diff --git a/src/wasm-stack.h b/src/wasm-stack.h
index bd44ed72e..7f94573fa 100644
--- a/src/wasm-stack.h
+++ b/src/wasm-stack.h
@@ -71,6 +71,7 @@ public:
TryBegin, // the beginning of a try
Catch, // the catch within a try
CatchAll, // the catch_all within a try
+ Delegate, // the delegate within a try
TryEnd // the ending of a try
} op;
@@ -109,6 +110,7 @@ public:
void emitIfElse(If* curr);
void emitCatch(Try* curr, Index i);
void emitCatchAll(Try* curr);
+ void emitDelegate(Try* curr);
// emit an end at the end of a block/loop/if/try
void emitScopeEnd(Expression* curr);
// emit an end at the end of a function
@@ -169,6 +171,9 @@ private:
void emitCatchAll(Try* curr) {
static_cast<SubType*>(this)->emitCatchAll(curr);
}
+ void emitDelegate(Try* curr) {
+ static_cast<SubType*>(this)->emitDelegate(curr);
+ }
void emitScopeEnd(Expression* curr) {
static_cast<SubType*>(this)->emitScopeEnd(curr);
}
@@ -343,7 +348,11 @@ template<typename SubType> void BinaryenIRWriter<SubType>::visitTry(Try* curr) {
emitCatchAll(curr);
visitPossibleBlockContents(curr->catchBodies.back());
}
- emitScopeEnd(curr);
+ if (curr->isDelegate()) {
+ emitDelegate(curr);
+ } else {
+ emitScopeEnd(curr);
+ }
if (curr->type == Type::unreachable) {
emitUnreachable();
}
@@ -375,6 +384,7 @@ public:
void emitIfElse(If* curr) { writer.emitIfElse(curr); }
void emitCatch(Try* curr, Index i) { writer.emitCatch(curr, i); }
void emitCatchAll(Try* curr) { writer.emitCatchAll(curr); }
+ void emitDelegate(Try* curr) { writer.emitDelegate(curr); }
void emitScopeEnd(Expression* curr) { writer.emitScopeEnd(curr); }
void emitFunctionEnd() {
if (func->epilogLocation.size()) {
@@ -414,6 +424,9 @@ public:
void emitCatchAll(Try* curr) {
stackIR.push_back(makeStackInst(StackInst::CatchAll, curr));
}
+ void emitDelegate(Try* curr) {
+ stackIR.push_back(makeStackInst(StackInst::Delegate, curr));
+ }
void emitFunctionEnd() {}
void emitUnreachable() {
stackIR.push_back(makeStackInst(Builder(module).makeUnreachable()));
diff --git a/src/wasm-type.h b/src/wasm-type.h
index fe1b832bc..4188c438d 100644
--- a/src/wasm-type.h
+++ b/src/wasm-type.h
@@ -137,7 +137,6 @@ public:
bool isRef() const;
bool isFunction() const;
bool isData() const;
- bool isException() const;
bool isNullable() const;
bool isRtt() const;
bool isStruct() const;
diff --git a/src/wasm.h b/src/wasm.h
index 7fd284756..876a9bfa7 100644
--- a/src/wasm.h
+++ b/src/wasm.h
@@ -1306,13 +1306,17 @@ class Try : public SpecificExpression<Expression::TryId> {
public:
Try(MixedArena& allocator) : catchEvents(allocator), catchBodies(allocator) {}
+ Name name; // label that can only be targeted by 'delegate's
Expression* body;
ArenaVector<Name> catchEvents;
ExpressionList catchBodies;
+ Name delegateTarget; // target try's label
bool hasCatchAll() const {
return catchBodies.size() - catchEvents.size() == 1;
}
+ bool isCatch() const { return !catchBodies.empty(); }
+ bool isDelegate() const { return delegateTarget.is(); }
void finalize();
void finalize(Type type_);
};
diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp
index f3ec376d1..878293caa 100644
--- a/src/wasm/wasm-binary.cpp
+++ b/src/wasm/wasm-binary.cpp
@@ -1918,7 +1918,9 @@ void WasmBinaryBuilder::readFunctions() {
debugLocation.clear();
willBeIgnored = false;
// process body
- assert(breakTargetNames.size() == 0);
+ assert(breakStack.empty());
+ assert(breakTargetNames.empty());
+ assert(delegateTargetNames.empty());
assert(breakStack.empty());
assert(expressionStack.empty());
assert(controlFlowStack.empty());
@@ -1926,8 +1928,9 @@ void WasmBinaryBuilder::readFunctions() {
assert(depth == 0);
func->body = getBlockOrSingleton(func->sig.results);
assert(depth == 0);
- assert(breakStack.size() == 0);
- assert(breakTargetNames.size() == 0);
+ assert(breakStack.empty());
+ assert(breakTargetNames.empty());
+ assert(delegateTargetNames.empty());
if (!expressionStack.empty()) {
throwError("stack not empty on function exit");
}
@@ -2210,7 +2213,8 @@ void WasmBinaryBuilder::processExpressions() {
}
auto peek = input[pos];
if (peek == BinaryConsts::End || peek == BinaryConsts::Else ||
- peek == BinaryConsts::Catch || peek == BinaryConsts::CatchAll) {
+ peek == BinaryConsts::Catch || peek == BinaryConsts::CatchAll ||
+ peek == BinaryConsts::Delegate) {
BYN_TRACE("== processExpressions finished with unreachable"
<< std::endl);
lastSeparator = BinaryConsts::ASTNodes(peek);
@@ -2987,6 +2991,14 @@ BinaryConsts::ASTNodes WasmBinaryBuilder::readExpression(Expression*& curr) {
}
break;
}
+ case BinaryConsts::Delegate: {
+ curr = nullptr;
+ if (DWARF && currFunction) {
+ assert(!controlFlowStack.empty());
+ controlFlowStack.pop_back();
+ }
+ break;
+ }
case BinaryConsts::RefNull:
visitRefNull((curr = allocator.alloc<RefNull>())->cast<RefNull>());
break;
@@ -3376,7 +3388,8 @@ Expression* WasmBinaryBuilder::getBlockOrSingleton(Type type) {
block->name = label;
block->finalize(type);
// maybe we don't need a block here?
- if (breakTargetNames.find(block->name) == breakTargetNames.end()) {
+ if (breakTargetNames.find(block->name) == breakTargetNames.end() &&
+ delegateTargetNames.find(block->name) == delegateTargetNames.end()) {
block->name = Name();
if (block->list.size() == 1) {
return block->list[0];
@@ -3452,6 +3465,28 @@ WasmBinaryBuilder::getBreakTarget(int32_t offset) {
return ret;
}
+Name WasmBinaryBuilder::getDelegateTargetName(int32_t offset) {
+ BYN_TRACE("getDelegateTarget " << offset << std::endl);
+ // We always start parsing a function by creating a block label and pushing it
+ // in breakStack in getBlockOrSingleton, so if a 'delegate''s target is that
+ // block, it does not mean it targets that block; it throws to the caller.
+ if (breakStack.size() - 1 == size_t(offset)) {
+ return DELEGATE_CALLER_TARGET;
+ }
+ size_t index = breakStack.size() - 1 - offset;
+ if (index > breakStack.size()) {
+ throwError("bad delegate index (high)");
+ }
+ BYN_TRACE("delegate target " << breakStack[index].name << std::endl);
+ auto& ret = breakStack[index];
+ // if the delegate is in literally unreachable code, then we will not emit it
+ // anyhow, so do not note that the target has delegate to it
+ if (!willBeIgnored) {
+ delegateTargetNames.insert(ret.name);
+ }
+ return ret.name;
+}
+
void WasmBinaryBuilder::visitBreak(Break* curr, uint8_t code) {
BYN_TRACE("zz node: Break, code " << int32_t(code) << std::endl);
BreakTarget target = getBreakTarget(getU32LEB());
@@ -5746,58 +5781,13 @@ void WasmBinaryBuilder::visitTryOrTryInBlock(Expression*& out) {
curr->type = getType();
curr->body = getBlockOrSingleton(curr->type);
if (lastSeparator != BinaryConsts::Catch &&
- lastSeparator != BinaryConsts::CatchAll) {
+ lastSeparator != BinaryConsts::CatchAll &&
+ lastSeparator != BinaryConsts::Delegate) {
throwError("No catch instruction within a try scope");
}
- // For simplicity, we create an inner block within the catch body too, but the
- // one within the 'catch' *must* be omitted when we write out the binary back
- // later, because the 'catch' instruction pushes a value onto the stack and
- // the inner block does not support block input parameters without multivalue
- // support.
- // try
- // ...
- // catch ;; Pushes a value onto the stack
- // block ;; Inner block. Should be deleted when writing binary!
- // use the pushed value
- // end
- // end
- //
- // But when input binary code is like
- // try
- // ...
- // catch
- // br 0
- // end
- //
- // 'br 0' accidentally happens to target the inner block, creating code like
- // this in Binaryen IR, making the inner block not deletable, resulting in a
- // validation error:
- // (try
- // ...
- // (catch
- // (block $label0 ;; Cannot be deleted, because there's a branch to this
- // ...
- // (br $label0)
- // )
- // )
- // )
- //
- // When this happens, we fix this by creating a block that wraps the whole
- // try-catch, and making the branches target that block instead, like this:
- // (block $label ;; New enclosing block, new target for the branch
- // (try
- // ...
- // (catch
- // (block ;; Now this can be deleted when writing binary
- // ...
- // (br $label0)
- // )
- // )
- // )
- // )
-
Builder builder(wasm);
+ // A nameless label shared by all catch body blocks
Name catchLabel = getNextLabel();
breakStack.push_back({catchLabel, curr->type});
@@ -5839,8 +5829,84 @@ void WasmBinaryBuilder::visitTryOrTryInBlock(Expression*& out) {
readCatchBody(Type::none);
}
}
+ breakStack.pop_back();
+
+ if (lastSeparator == BinaryConsts::Delegate) {
+ curr->delegateTarget = getDelegateTargetName(getU32LEB());
+ }
+
+ // For simplicity, we make try's labels only can be targeted by delegates, and
+ // delegates can only target try's labels. (If they target blocks or loops, it
+ // is a validation failure.) Because we create an inner block within each try
+ // and catch body, if any delegate targets those inner blocks, we should make
+ // them target the try's label instead.
+ curr->name = getNextLabel();
+ if (auto* block = curr->body->dynCast<Block>()) {
+ if (block->name.is()) {
+ if (delegateTargetNames.find(block->name) != delegateTargetNames.end()) {
+ BranchUtils::replaceDelegateTargets(block, block->name, curr->name);
+ delegateTargetNames.erase(block->name);
+ }
+ // maybe we don't need a block here?
+ if (block->list.size() == 1) {
+ curr->body = block->list[0];
+ }
+ }
+ }
+ if (delegateTargetNames.find(catchLabel) != delegateTargetNames.end()) {
+ for (auto* catchBody : curr->catchBodies) {
+ BranchUtils::replaceDelegateTargets(catchBody, catchLabel, curr->name);
+ }
+ delegateTargetNames.erase(catchLabel);
+ }
curr->finalize(curr->type);
+ // For simplicity, we create an inner block within the catch body too, but the
+ // one within the 'catch' *must* be omitted when we write out the binary back
+ // later, because the 'catch' instruction pushes a value onto the stack and
+ // the inner block does not support block input parameters without multivalue
+ // support.
+ // try
+ // ...
+ // catch $e ;; Pushes value(s) onto the stack
+ // block ;; Inner block. Should be deleted when writing binary!
+ // use the pushed value
+ // end
+ // end
+ //
+ // But when input binary code is like
+ // try
+ // ...
+ // catch $e
+ // br 0
+ // end
+ //
+ // 'br 0' accidentally happens to target the inner block, creating code like
+ // this in Binaryen IR, making the inner block not deletable, resulting in a
+ // validation error:
+ // (try
+ // ...
+ // (catch $e
+ // (block $label0 ;; Cannot be deleted, because there's a branch to this
+ // ...
+ // (br $label0)
+ // )
+ // )
+ // )
+ //
+ // When this happens, we fix this by creating a block that wraps the whole
+ // try-catch, and making the branches target that block instead, like this:
+ // (block $label ;; New enclosing block, new target for the branch
+ // (try
+ // ...
+ // (catch $e
+ // (block ;; Now this can be deleted when writing binary
+ // ...
+ // (br $label0)
+ // )
+ // )
+ // )
+ // )
if (breakTargetNames.find(catchLabel) == breakTargetNames.end()) {
out = curr;
} else {
@@ -5848,7 +5914,6 @@ void WasmBinaryBuilder::visitTryOrTryInBlock(Expression*& out) {
auto* block = builder.makeBlock(catchLabel, curr);
out = block;
}
- breakStack.pop_back();
breakTargetNames.erase(catchLabel);
}
diff --git a/src/wasm/wasm-s-parser.cpp b/src/wasm/wasm-s-parser.cpp
index bcce3993e..d2b24fbed 100644
--- a/src/wasm/wasm-s-parser.cpp
+++ b/src/wasm/wasm-s-parser.cpp
@@ -1859,7 +1859,7 @@ Expression* SExpressionWasmBuilder::makeCallIndirect(Element& s,
return ret;
}
-Name SExpressionWasmBuilder::getLabel(Element& s) {
+Name SExpressionWasmBuilder::getLabel(Element& s, LabelType labelType) {
if (s.dollared()) {
return nameMapper.sourceToUnique(s.str());
} else {
@@ -1876,10 +1876,14 @@ Name SExpressionWasmBuilder::getLabel(Element& s) {
throw ParseException("invalid label", s.line, s.col);
}
if (offset == nameMapper.labelStack.size()) {
- // a break to the function's scope. this means we need an automatic block,
- // with a name
- brokeToAutoBlock = true;
- return FAKE_RETURN;
+ if (labelType == LabelType::Break) {
+ // a break to the function's scope. this means we need an automatic
+ // block, with a name
+ brokeToAutoBlock = true;
+ return FAKE_RETURN;
+ }
+ // This is a delegate that delegates to the caller
+ return DELEGATE_CALLER_TARGET;
}
return nameMapper.labelStack[nameMapper.labelStack.size() - 1 - offset];
}
@@ -1975,12 +1979,13 @@ Expression* SExpressionWasmBuilder::makeRefEq(Element& s) {
return ret;
}
-// try-catch-end is written in the folded wast format as
+// try can be either in the form of try-catch or try-delegate.
+// try-catch is written in the folded wast format as
// (try
// (do
// ...
// )
-// (catch
+// (catch $e
// ...
// )
// ...
@@ -1991,6 +1996,14 @@ Expression* SExpressionWasmBuilder::makeRefEq(Element& s) {
// Any number of catch blocks can exist, including none. Zero or one catch_all
// block can exist, and if it does, it should be at the end. There should be at
// least one catch or catch_all body per try.
+//
+// try-delegate is written in the folded format as
+// (try
+// (do
+// ...
+// )
+// (delegate $label)
+// )
Expression* SExpressionWasmBuilder::makeTry(Element& s) {
auto ret = allocator.alloc<Try>();
Index i = 1;
@@ -2001,7 +2014,7 @@ Expression* SExpressionWasmBuilder::makeTry(Element& s) {
} else {
sName = "try";
}
- auto label = nameMapper.pushLabelName(sName);
+ ret->name = nameMapper.pushLabelName(sName);
Type type = parseOptionalResultType(s, i); // signature
if (!elementStartsWith(*s[i], "do")) {
@@ -2027,21 +2040,38 @@ Expression* SExpressionWasmBuilder::makeTry(Element& s) {
ret->catchBodies.push_back(makeMaybeBlock(*s[i++], 1, type));
}
+ // 'delegate' cannot target its own try. So we pop the name here.
+ nameMapper.popLabelName(ret->name);
+
+ if (i < s.size() && elementStartsWith(*s[i], "delegate")) {
+ Element& inner = *s[i++];
+ if (inner.size() != 2) {
+ throw ParseException("invalid delegate", inner.line, inner.col);
+ }
+ ret->delegateTarget = getLabel(*inner[1], LabelType::Delegate);
+ }
+
if (i != s.size()) {
throw ParseException(
"there should be at most one catch_all block at the end", s.line, s.col);
}
- if (ret->catchBodies.empty()) {
- throw ParseException("no catch bodies", s.line, s.col);
+ if (ret->catchBodies.empty() && !ret->isDelegate()) {
+ throw ParseException("no catch bodies or delegate", s.line, s.col);
}
ret->finalize(type);
- nameMapper.popLabelName(label);
+
// create a break target if we must
- if (BranchUtils::BranchSeeker::has(ret, label)) {
+ if (BranchUtils::BranchSeeker::has(ret, ret->name)) {
auto* block = allocator.alloc<Block>();
- block->name = label;
+ // We create a different name for the wrapping block, because try's name can
+ // be used by internal delegates
+ block->name = nameMapper.pushLabelName(sName);
+ // For simplicity, try's name canonly be targeted by delegates. Make the
+ // branches target the new wrapping block instead.
+ BranchUtils::replaceBranchTargets(ret, ret->name, block->name);
block->list.push_back(ret);
+ nameMapper.popLabelName(block->name);
block->finalize(type);
return block;
}
diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp
index 00b1e5368..cb9d94093 100644
--- a/src/wasm/wasm-stack.cpp
+++ b/src/wasm/wasm-stack.cpp
@@ -1904,7 +1904,7 @@ void BinaryInstWriter::visitRefEq(RefEq* curr) {
}
void BinaryInstWriter::visitTry(Try* curr) {
- breakStack.emplace_back(IMPOSSIBLE_CONTINUE);
+ breakStack.push_back(curr->name);
o << int8_t(BinaryConsts::Try);
emitResultType(curr->type);
}
@@ -1924,6 +1924,13 @@ void BinaryInstWriter::emitCatchAll(Try* curr) {
o << int8_t(BinaryConsts::CatchAll);
}
+void BinaryInstWriter::emitDelegate(Try* curr) {
+ assert(!breakStack.empty());
+ breakStack.pop_back();
+ o << int8_t(BinaryConsts::Delegate)
+ << U32LEB(getBreakIndex(curr->delegateTarget));
+}
+
void BinaryInstWriter::visitThrow(Throw* curr) {
o << int8_t(BinaryConsts::Throw) << U32LEB(parent.getEventIndex(curr->event));
}
@@ -2216,6 +2223,9 @@ void BinaryInstWriter::emitMemoryAccess(size_t alignment,
}
int32_t BinaryInstWriter::getBreakIndex(Name name) { // -1 if not found
+ if (name == DELEGATE_CALLER_TARGET) {
+ return breakStack.size();
+ }
for (int i = breakStack.size() - 1; i >= 0; i--) {
if (breakStack[i] == name) {
return breakStack.size() - 1 - i;
@@ -2321,6 +2331,10 @@ void StackIRToBinaryWriter::write() {
writer.emitCatchAll(inst->origin->cast<Try>());
break;
}
+ case StackInst::Delegate: {
+ writer.emitDelegate(inst->origin->cast<Try>());
+ break;
+ }
default:
WASM_UNREACHABLE("unexpected op");
}
diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp
index 276b9602f..beb994390 100644
--- a/src/wasm/wasm-validator.cpp
+++ b/src/wasm/wasm-validator.cpp
@@ -236,6 +236,7 @@ struct FunctionValidator : public WalkerPass<PostWalker<FunctionValidator>> {
};
std::unordered_map<Name, BreakInfo> breakInfos;
+ std::unordered_set<Name> delegateTargetNames;
std::set<Type> returnTypes; // types used in returns
@@ -276,11 +277,52 @@ public:
void visitLoop(Loop* curr);
void visitIf(If* curr);
+ static void visitPreTry(FunctionValidator* self, Expression** currp) {
+ auto* curr = (*currp)->cast<Try>();
+ if (curr->name.is()) {
+ self->delegateTargetNames.insert(curr->name);
+ }
+ }
+
+ // We remove try's label before proceeding to verify catch bodies because the
+ // following is a validation failure:
+ // (try $l0
+ // (do ... )
+ // (catch $e
+ // (try
+ // (do ...)
+ // (delegate $l0) ;; validation failure
+ // )
+ // )
+ // )
+ // Unlike branches, if delegate's target 'catch' is located above the
+ // delegate, it is a validation failure.
+ static void visitPreCatch(FunctionValidator* self, Expression** currp) {
+ auto* curr = (*currp)->cast<Try>();
+ if (curr->name.is()) {
+ self->delegateTargetNames.erase(curr->name);
+ }
+ }
+
// override scan to add a pre and a post check task to all nodes
static void scan(FunctionValidator* self, Expression** currp) {
+ auto* curr = *currp;
+ // Treat 'Try' specially because we need to run visitPreCatch between the
+ // try body and catch bodies
+ if (curr->is<Try>()) {
+ self->pushTask(doVisitTry, currp);
+ auto& list = curr->cast<Try>()->catchBodies;
+ for (int i = int(list.size()) - 1; i >= 0; i--) {
+ self->pushTask(scan, &list[i]);
+ }
+ self->pushTask(visitPreCatch, currp);
+ self->pushTask(scan, &curr->cast<Try>()->body);
+ self->pushTask(visitPreTry, currp);
+ return;
+ }
+
PostWalker<FunctionValidator>::scan(self, currp);
- auto* curr = *currp;
if (curr->is<Block>()) {
self->pushTask(visitPreBlock, currp);
}
@@ -334,6 +376,7 @@ public:
void visitRefIs(RefIs* curr);
void visitRefFunc(RefFunc* curr);
void visitRefEq(RefEq* curr);
+ void noteDelegate(Name name, Expression* curr);
void visitTry(Try* curr);
void visitThrow(Throw* curr);
void visitRethrow(Rethrow* curr);
@@ -767,6 +810,7 @@ void FunctionValidator::noteBreak(Name name, Type valueType, Expression* curr) {
}
}
}
+
void FunctionValidator::visitBreak(Break* curr) {
noteBreak(curr->name, curr->value, curr);
if (curr->value) {
@@ -2029,6 +2073,14 @@ void FunctionValidator::visitRefEq(RefEq* curr) {
"ref.eq's right argument should be a subtype of eqref");
}
+void FunctionValidator::noteDelegate(Name name, Expression* curr) {
+ if (name != DELEGATE_CALLER_TARGET) {
+ shouldBeTrue(delegateTargetNames.find(name) != delegateTargetNames.end(),
+ curr,
+ "all delegate targets must be valid");
+ }
+}
+
void FunctionValidator::visitTry(Try* curr) {
shouldBeTrue(getModule()->features.hasExceptionHandling(),
curr,
@@ -2061,6 +2113,17 @@ void FunctionValidator::visitTry(Try* curr) {
shouldBeTrue(curr->catchBodies.size() - curr->catchEvents.size() <= 1,
curr,
"the number of catch blocks and events do not match");
+
+ shouldBeFalse(curr->isCatch() && curr->isDelegate(),
+ curr,
+ "try cannot have both catch and delegate at the same time");
+ shouldBeTrue(curr->isCatch() || curr->isDelegate(),
+ curr,
+ "try should have either catches or a delegate");
+
+ if (curr->isDelegate()) {
+ noteDelegate(curr->delegateTarget, curr);
+ }
}
void FunctionValidator::visitThrow(Throw* curr) {
@@ -2470,6 +2533,9 @@ void FunctionValidator::visitFunction(Function* curr) {
shouldBeTrue(
breakInfos.empty(), curr->body, "all named break targets must exist");
+ shouldBeTrue(delegateTargetNames.empty(),
+ curr->body,
+ "all named delegate targets must exist");
returnTypes.clear();
labelNames.clear();
// validate optional local names
diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp
index 7bf9b8604..92e03f579 100644
--- a/src/wasm/wasm.cpp
+++ b/src/wasm/wasm.cpp
@@ -88,6 +88,7 @@ Name CASE("case");
Name BR("br");
Name FUNCREF("funcref");
Name FAKE_RETURN("fake_return_waka123");
+Name DELEGATE_CALLER_TARGET("delegate_caller_target_waka123");
Name MUT("mut");
Name SPECTEST("spectest");
Name PRINT("print");
diff --git a/test/break-within-catch.wasm.fromBinary b/test/break-within-catch.wasm.fromBinary
index d7e2dc57b..fd16d9fcc 100644
--- a/test/break-within-catch.wasm.fromBinary
+++ b/test/break-within-catch.wasm.fromBinary
@@ -4,7 +4,7 @@
(event $event$0 (attr 0) (param i32))
(func $0
(block $label$2
- (try
+ (try $label$3
(do
(nop)
)
diff --git a/test/exception-handling.wast b/test/exception-handling.wast
index b96c4d25f..399fef14c 100644
--- a/test/exception-handling.wast
+++ b/test/exception-handling.wast
@@ -102,6 +102,35 @@
)
)
+ ;; nested try-catch
+ (try
+ (do
+ (try
+ (do
+ (throw $e-i32 (i32.const 0))
+ )
+ (catch $e-i32
+ (drop (pop i32))
+ )
+ (catch_all)
+ )
+ )
+ (catch $e-i32
+ (drop (pop i32))
+ )
+ (catch_all
+ (try
+ (do
+ (throw $e-i32 (i32.const 0))
+ )
+ (catch $e-i32
+ (drop (pop i32))
+ )
+ (catch_all)
+ )
+ )
+ )
+
;; rethrow
(try
(do
@@ -113,4 +142,62 @@
)
)
)
+
+ (func $delegate-test
+ ;; Inner delegates target an outer catch
+ (try $l0
+ (do
+ (try
+ (do
+ (call $foo)
+ )
+ (delegate $l0) ;; by label
+ )
+ (try
+ (do
+ (call $foo)
+ )
+ (delegate 0) ;; by depth
+ )
+ )
+ (catch_all)
+ )
+
+ ;; When there are both a branch and a delegate that target the same try
+ ;; label. Because binaryen only allows blocks and loops to be targetted by
+ ;; branches, we wrap the try with a block and make branches that block
+ ;; instead, resulting in the br and delegate target different labels in the
+ ;; output.
+ (try $l0
+ (do
+ (try
+ (do
+ (br_if $l0 (i32.const 1))
+ )
+ (delegate $l0) ;; by label
+ )
+ (try
+ (do
+ (br_if $l0 (i32.const 1))
+ )
+ (delegate 0) ;; by depth
+ )
+ )
+ (catch_all)
+ )
+
+ ;; The inner delegate targets the outer delegate, which in turn targets the
+ ;; caller.
+ (try $l0
+ (do
+ (try
+ (do
+ (call $foo)
+ )
+ (delegate $l0)
+ )
+ )
+ (delegate 0)
+ )
+ )
)
diff --git a/test/exception-handling.wast.from-wast b/test/exception-handling.wast.from-wast
index 224df569b..5a0a1249d 100644
--- a/test/exception-handling.wast.from-wast
+++ b/test/exception-handling.wast.from-wast
@@ -14,7 +14,7 @@
)
(func $eh_test
(local $x (i32 i64))
- (try
+ (try $try
(do
(throw $e-i32
(i32.const 0)
@@ -26,7 +26,7 @@
)
)
)
- (try
+ (try $try0
(do
(throw $e-i32-i64
(i32.const 0)
@@ -44,20 +44,20 @@
)
)
)
- (block $l1
- (try
+ (block $l11
+ (try $l1
(do
- (br $l1)
+ (br $l11)
)
(catch $e-i32
(drop
(pop i32)
)
- (br $l1)
+ (br $l11)
)
)
)
- (try
+ (try $try2
(do
(nop)
)
@@ -67,7 +67,7 @@
)
)
)
- (try
+ (try $try3
(do
(call $foo)
(call $bar)
@@ -80,7 +80,7 @@
(call $bar)
)
)
- (try
+ (try $try4
(do
(throw $e-i32
(i32.const 0)
@@ -97,7 +97,7 @@
)
)
)
- (try
+ (try $try5
(do
(throw $e-i32
(i32.const 0)
@@ -107,7 +107,7 @@
(nop)
)
)
- (try
+ (try $try6
(do
(throw $e-i32
(i32.const 0)
@@ -128,7 +128,48 @@
(call $bar)
)
)
- (try
+ (try $try7
+ (do
+ (try $try8
+ (do
+ (throw $e-i32
+ (i32.const 0)
+ )
+ )
+ (catch $e-i32
+ (drop
+ (pop i32)
+ )
+ )
+ (catch_all
+ (nop)
+ )
+ )
+ )
+ (catch $e-i32
+ (drop
+ (pop i32)
+ )
+ )
+ (catch_all
+ (try $try9
+ (do
+ (throw $e-i32
+ (i32.const 0)
+ )
+ )
+ (catch $e-i32
+ (drop
+ (pop i32)
+ )
+ )
+ (catch_all
+ (nop)
+ )
+ )
+ )
+ )
+ (try $try10
(do
(throw $e-i32
(i32.const 0)
@@ -142,4 +183,61 @@
)
)
)
+ (func $delegate-test
+ (try $l0
+ (do
+ (try $try
+ (do
+ (call $foo)
+ )
+ (delegate $l0)
+ )
+ (try $try11
+ (do
+ (call $foo)
+ )
+ (delegate $l0)
+ )
+ )
+ (catch_all
+ (nop)
+ )
+ )
+ (block $l015
+ (try $l012
+ (do
+ (try $try13
+ (do
+ (br_if $l015
+ (i32.const 1)
+ )
+ )
+ (delegate $l012)
+ )
+ (try $try14
+ (do
+ (br_if $l015
+ (i32.const 1)
+ )
+ )
+ (delegate $l012)
+ )
+ )
+ (catch_all
+ (nop)
+ )
+ )
+ )
+ (try $l016
+ (do
+ (try $try17
+ (do
+ (call $foo)
+ )
+ (delegate $l016)
+ )
+ )
+ (delegate 0)
+ )
+ )
)
diff --git a/test/exception-handling.wast.fromBinary b/test/exception-handling.wast.fromBinary
index d305aaaa9..d6dd331b1 100644
--- a/test/exception-handling.wast.fromBinary
+++ b/test/exception-handling.wast.fromBinary
@@ -18,7 +18,7 @@
(local $2 (i32 i64))
(local $3 i32)
(local $4 i32)
- (try
+ (try $label$3
(do
(throw $event$0
(i32.const 0)
@@ -30,7 +30,7 @@
)
)
)
- (try
+ (try $label$6
(do
(throw $event$2
(i32.const 0)
@@ -69,20 +69,20 @@
)
)
)
- (block $label$5
- (try
+ (block $label$7
+ (try $label$10
(do
- (br $label$5)
+ (br $label$7)
)
(catch $event$0
(drop
(pop i32)
)
- (br $label$5)
+ (br $label$7)
)
)
)
- (try
+ (try $label$13
(do
(nop)
)
@@ -92,7 +92,7 @@
)
)
)
- (try
+ (try $label$16
(do
(call $foo)
(call $bar)
@@ -105,7 +105,7 @@
(call $bar)
)
)
- (try
+ (try $label$19
(do
(throw $event$0
(i32.const 0)
@@ -122,7 +122,7 @@
)
)
)
- (try
+ (try $label$22
(do
(throw $event$0
(i32.const 0)
@@ -132,7 +132,7 @@
(nop)
)
)
- (try
+ (try $label$25
(do
(throw $event$0
(i32.const 0)
@@ -153,7 +153,48 @@
(call $bar)
)
)
- (try
+ (try $label$34
+ (do
+ (try $label$29
+ (do
+ (throw $event$0
+ (i32.const 0)
+ )
+ )
+ (catch $event$0
+ (drop
+ (pop i32)
+ )
+ )
+ (catch_all
+ (nop)
+ )
+ )
+ )
+ (catch $event$0
+ (drop
+ (pop i32)
+ )
+ )
+ (catch_all
+ (try $label$33
+ (do
+ (throw $event$0
+ (i32.const 0)
+ )
+ )
+ (catch $event$0
+ (drop
+ (pop i32)
+ )
+ )
+ (catch_all
+ (nop)
+ )
+ )
+ )
+ )
+ (try $label$37
(do
(throw $event$0
(i32.const 0)
@@ -167,5 +208,66 @@
)
)
)
+ (func $delegate-test
+ (try $label$9
+ (do
+ (block $label$1
+ (try $label$4
+ (do
+ (call $foo)
+ )
+ (delegate $label$9)
+ )
+ (try $label$7
+ (do
+ (call $foo)
+ )
+ (delegate $label$9)
+ )
+ )
+ )
+ (catch_all
+ (nop)
+ )
+ )
+ (block $label$10
+ (try $label$19
+ (do
+ (block $label$11
+ (try $label$14
+ (do
+ (br_if $label$10
+ (i32.const 1)
+ )
+ )
+ (delegate $label$19)
+ )
+ (try $label$17
+ (do
+ (br_if $label$10
+ (i32.const 1)
+ )
+ )
+ (delegate $label$19)
+ )
+ )
+ )
+ (catch_all
+ (nop)
+ )
+ )
+ )
+ (try $label$25
+ (do
+ (try $label$23
+ (do
+ (call $foo)
+ )
+ (delegate $label$25)
+ )
+ )
+ (delegate 0)
+ )
+ )
)
diff --git a/test/exception-handling.wast.fromBinary.noDebugInfo b/test/exception-handling.wast.fromBinary.noDebugInfo
index d6dc4268e..cf7372b6e 100644
--- a/test/exception-handling.wast.fromBinary.noDebugInfo
+++ b/test/exception-handling.wast.fromBinary.noDebugInfo
@@ -18,7 +18,7 @@
(local $2 (i32 i64))
(local $3 i32)
(local $4 i32)
- (try
+ (try $label$3
(do
(throw $event$0
(i32.const 0)
@@ -30,7 +30,7 @@
)
)
)
- (try
+ (try $label$6
(do
(throw $event$2
(i32.const 0)
@@ -69,20 +69,20 @@
)
)
)
- (block $label$5
- (try
+ (block $label$7
+ (try $label$10
(do
- (br $label$5)
+ (br $label$7)
)
(catch $event$0
(drop
(pop i32)
)
- (br $label$5)
+ (br $label$7)
)
)
)
- (try
+ (try $label$13
(do
(nop)
)
@@ -92,7 +92,7 @@
)
)
)
- (try
+ (try $label$16
(do
(call $0)
(call $1)
@@ -105,7 +105,7 @@
(call $1)
)
)
- (try
+ (try $label$19
(do
(throw $event$0
(i32.const 0)
@@ -122,7 +122,7 @@
)
)
)
- (try
+ (try $label$22
(do
(throw $event$0
(i32.const 0)
@@ -132,7 +132,7 @@
(nop)
)
)
- (try
+ (try $label$25
(do
(throw $event$0
(i32.const 0)
@@ -153,7 +153,48 @@
(call $1)
)
)
- (try
+ (try $label$34
+ (do
+ (try $label$29
+ (do
+ (throw $event$0
+ (i32.const 0)
+ )
+ )
+ (catch $event$0
+ (drop
+ (pop i32)
+ )
+ )
+ (catch_all
+ (nop)
+ )
+ )
+ )
+ (catch $event$0
+ (drop
+ (pop i32)
+ )
+ )
+ (catch_all
+ (try $label$33
+ (do
+ (throw $event$0
+ (i32.const 0)
+ )
+ )
+ (catch $event$0
+ (drop
+ (pop i32)
+ )
+ )
+ (catch_all
+ (nop)
+ )
+ )
+ )
+ )
+ (try $label$37
(do
(throw $event$0
(i32.const 0)
@@ -167,5 +208,66 @@
)
)
)
+ (func $3
+ (try $label$9
+ (do
+ (block $label$1
+ (try $label$4
+ (do
+ (call $0)
+ )
+ (delegate $label$9)
+ )
+ (try $label$7
+ (do
+ (call $0)
+ )
+ (delegate $label$9)
+ )
+ )
+ )
+ (catch_all
+ (nop)
+ )
+ )
+ (block $label$10
+ (try $label$19
+ (do
+ (block $label$11
+ (try $label$14
+ (do
+ (br_if $label$10
+ (i32.const 1)
+ )
+ )
+ (delegate $label$19)
+ )
+ (try $label$17
+ (do
+ (br_if $label$10
+ (i32.const 1)
+ )
+ )
+ (delegate $label$19)
+ )
+ )
+ )
+ (catch_all
+ (nop)
+ )
+ )
+ )
+ (try $label$25
+ (do
+ (try $label$23
+ (do
+ (call $0)
+ )
+ (delegate $label$25)
+ )
+ )
+ (delegate 0)
+ )
+ )
)
diff --git a/test/lit/passes/optimize-instructions-exceptions.wast b/test/lit/passes/optimize-instructions-exceptions.wast
index e2b60e0f1..9d22224fb 100644
--- a/test/lit/passes/optimize-instructions-exceptions.wast
+++ b/test/lit/passes/optimize-instructions-exceptions.wast
@@ -5,7 +5,7 @@
(module
;; CHECK: (func $test
;; CHECK-NEXT: (if
- ;; CHECK-NEXT: (try (result i32)
+ ;; CHECK-NEXT: (try $try (result i32)
;; CHECK-NEXT: (do
;; CHECK-NEXT: (i32.const 123)
;; CHECK-NEXT: )
diff --git a/test/lit/passes/poppify.wast b/test/lit/passes/poppify.wast
index 1b9ba1fb3..bc88dd878 100644
--- a/test/lit/passes/poppify.wast
+++ b/test/lit/passes/poppify.wast
@@ -178,7 +178,7 @@
)
;; CHECK: (func $try-catch (result i32)
- ;; CHECK-NEXT: (try (result i32)
+ ;; CHECK-NEXT: (try $try (result i32)
;; CHECK-NEXT: (do
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: (throw $e
@@ -208,6 +208,42 @@
)
)
+ ;; CHECK: (func $try-delegate (result i32)
+ ;; CHECK-NEXT: (try $l0 (result i32)
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (try $try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (throw $e
+ ;; CHECK-NEXT: (pop i32)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (delegate $l0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch $e
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-delegate (result i32)
+ (try $l0 i32
+ (do
+ (try
+ (do
+ (throw $e
+ (i32.const 0)
+ )
+ )
+ (delegate $l0)
+ )
+ )
+ (catch $e
+ (pop i32)
+ )
+ )
+ )
+
;; CHECK: (func $tuple (result i32 i64)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: (i64.const 1)
diff --git a/test/passes/code-pushing_all-features.txt b/test/passes/code-pushing_all-features.txt
index 4b3d93208..5226a7b3c 100644
--- a/test/passes/code-pushing_all-features.txt
+++ b/test/passes/code-pushing_all-features.txt
@@ -43,7 +43,7 @@
(func $can-push-past-try
(local $x i32)
(block $out
- (try
+ (try $try
(do
(throw $e
(i32.const 0)
@@ -78,7 +78,7 @@
(local.set $x
(i32.const 1)
)
- (try
+ (try $try
(do
(call $foo)
)
@@ -105,7 +105,7 @@
(local.set $x
(i32.const 1)
)
- (try
+ (try $try
(do
(throw $e
(i32.const 0)
diff --git a/test/passes/dce_all-features.txt b/test/passes/dce_all-features.txt
index 447077b6c..575d04065 100644
--- a/test/passes/dce_all-features.txt
+++ b/test/passes/dce_all-features.txt
@@ -547,7 +547,7 @@
(nop)
)
(func $try_unreachable
- (try
+ (try $try
(do
(unreachable)
)
@@ -558,7 +558,7 @@
(call $foo)
)
(func $catch_unreachable
- (try
+ (try $try
(do
(nop)
)
@@ -569,7 +569,7 @@
(call $foo)
)
(func $both_unreachable
- (try
+ (try $try
(do
(unreachable)
)
@@ -616,7 +616,7 @@
)
)
(func $unnecessary-concrete-try (result i32)
- (try
+ (try $try
(do
(unreachable)
)
diff --git a/test/passes/dwarf_with_exceptions.bin.txt b/test/passes/dwarf_with_exceptions.bin.txt
index 324e1d44d..b11fa6437 100644
--- a/test/passes/dwarf_with_exceptions.bin.txt
+++ b/test/passes/dwarf_with_exceptions.bin.txt
@@ -22,7 +22,7 @@
(global.get $global$0)
)
;; code offset: 0x10
- (try
+ (try $label$9
(do
;; code offset: 0x12
(call $foo)
@@ -47,7 +47,7 @@
)
)
;; code offset: 0x31
- (try
+ (try $label$8
(do
;; code offset: 0x33
(call $foo)
@@ -60,7 +60,7 @@
;; code offset: 0x41
(catch_all
;; code offset: 0x42
- (try
+ (try $label$7
(do
;; code offset: 0x44
(call $__cxa_end_catch)
@@ -443,7 +443,7 @@ file_names[ 1]:
(global.get $global$0)
)
;; code offset: 0xe
- (try
+ (try $label$9
(do
;; code offset: 0x10
(call $foo)
@@ -468,7 +468,7 @@ file_names[ 1]:
)
)
;; code offset: 0x1f
- (try
+ (try $label$8
(do
;; code offset: 0x21
(call $foo)
@@ -481,7 +481,7 @@ file_names[ 1]:
;; code offset: 0x27
(catch_all
;; code offset: 0x28
- (try
+ (try $label$7
(do
;; code offset: 0x2a
(call $__cxa_end_catch)
diff --git a/test/passes/generate-stack-ir_optimize-stack-ir_print-stack-ir_all-features.txt b/test/passes/generate-stack-ir_optimize-stack-ir_print-stack-ir_all-features.txt
index ffb2da5be..f8e7778c5 100644
--- a/test/passes/generate-stack-ir_optimize-stack-ir_print-stack-ir_all-features.txt
+++ b/test/passes/generate-stack-ir_optimize-stack-ir_print-stack-ir_all-features.txt
@@ -3,17 +3,27 @@
(type $i32_=>_none (func (param i32)))
(event $e0 (attr 0) (param i32))
(func $eh
- try
+ try $try
i32.const 0
throw $e0
catch $e0
drop
- rethrow 0
catch_all
rethrow 0
end
- unreachable
+ try $l0
+ try $try0
+ i32.const 0
+ throw $e0
+ delegate $l0
+ unreachable
+ catch_all
+ nop
+ end
+ try $l01
+ nop
+ delegate 0
)
)
(module
@@ -21,7 +31,7 @@
(type $i32_=>_none (func (param i32)))
(event $e0 (attr 0) (param i32))
(func $eh (; has Stack IR ;)
- (try
+ (try $try
(do
(throw $e0
(i32.const 0)
@@ -31,11 +41,31 @@
(drop
(pop i32)
)
- (rethrow 0)
)
(catch_all
(rethrow 0)
)
)
+ (try $l0
+ (do
+ (try $try0
+ (do
+ (throw $e0
+ (i32.const 0)
+ )
+ )
+ (delegate $l0)
+ )
+ )
+ (catch_all
+ (nop)
+ )
+ )
+ (try $l01
+ (do
+ (nop)
+ )
+ (delegate 0)
+ )
)
)
diff --git a/test/passes/generate-stack-ir_optimize-stack-ir_print-stack-ir_all-features.wast b/test/passes/generate-stack-ir_optimize-stack-ir_print-stack-ir_all-features.wast
index 047d9c126..f82f7e19a 100644
--- a/test/passes/generate-stack-ir_optimize-stack-ir_print-stack-ir_all-features.wast
+++ b/test/passes/generate-stack-ir_optimize-stack-ir_print-stack-ir_all-features.wast
@@ -8,11 +8,27 @@
)
(catch $e0
(drop (pop i32))
- (rethrow 0)
)
(catch_all
(rethrow 0)
)
)
+
+ (try $l0
+ (do
+ (try
+ (do
+ (throw $e0 (i32.const 0))
+ )
+ (delegate $l0)
+ )
+ )
+ (catch_all)
+ )
+
+ (try $l0
+ (do)
+ (delegate 0) ;; delegate to caller
+ )
)
)
diff --git a/test/passes/instrument-locals_all-features_disable-typed-function-references.txt b/test/passes/instrument-locals_all-features_disable-typed-function-references.txt
index edd927851..481d7a72b 100644
--- a/test/passes/instrument-locals_all-features_disable-typed-function-references.txt
+++ b/test/passes/instrument-locals_all-features_disable-typed-function-references.txt
@@ -203,7 +203,7 @@
)
)
)
- (try
+ (try $try
(do
(nop)
)
diff --git a/test/passes/remove-unused-module-elements_all-features.txt b/test/passes/remove-unused-module-elements_all-features.txt
index 22751f9f5..fdd768e8b 100644
--- a/test/passes/remove-unused-module-elements_all-features.txt
+++ b/test/passes/remove-unused-module-elements_all-features.txt
@@ -299,7 +299,7 @@
(export "e-export" (event $e-export))
(start $start)
(func $start
- (try
+ (try $try
(do
(throw $e-throw
(i32.const 0)
diff --git a/test/passes/rse_all-features.txt b/test/passes/rse_all-features.txt
index 9a428266e..eaef14455 100644
--- a/test/passes/rse_all-features.txt
+++ b/test/passes/rse_all-features.txt
@@ -477,7 +477,7 @@
)
(func $try1
(local $x i32)
- (try
+ (try $try
(do
(nop)
)
@@ -493,7 +493,7 @@
)
(func $try2
(local $x i32)
- (try
+ (try $try
(do
(throw $e
(i32.const 0)
@@ -512,7 +512,7 @@
)
(func $try3
(local $x i32)
- (try
+ (try $try
(do
(throw $e
(i32.const 0)
@@ -533,7 +533,7 @@
)
(func $try4
(local $x i32)
- (try
+ (try $try
(do
(call $foo)
(local.set $x
@@ -550,7 +550,7 @@
)
(func $try5
(local $x i32)
- (try
+ (try $try
(do
(local.set $x
(i32.const 1)
@@ -567,9 +567,9 @@
)
(func $nested-try1
(local $x i32)
- (try
+ (try $try
(do
- (try
+ (try $try6
(do
(throw $e
(i32.const 0)
@@ -592,9 +592,9 @@
)
(func $nested-try2
(local $x i32)
- (try
+ (try $try
(do
- (try
+ (try $try7
(do
(throw $e
(i32.const 0)
@@ -618,9 +618,9 @@
)
(func $nested-try3
(local $x i32)
- (try
+ (try $try
(do
- (try
+ (try $try8
(do
(throw $e
(i32.const 0)
diff --git a/test/passes/simplify-locals_all-features.txt b/test/passes/simplify-locals_all-features.txt
index 34c13d442..21361af65 100644
--- a/test/passes/simplify-locals_all-features.txt
+++ b/test/passes/simplify-locals_all-features.txt
@@ -1905,7 +1905,7 @@
)
(func $pop-cannot-be-sinked
(local $0 i32)
- (try
+ (try $try
(do
(nop)
)
@@ -1922,7 +1922,7 @@
)
(func $pop-within-catch-can-be-sinked
(local $0 i32)
- (try
+ (try $try
(do
(nop)
)
@@ -1930,7 +1930,7 @@
(nop)
(call $foo
(i32.const 3)
- (try (result i32)
+ (try $try0 (result i32)
(do
(i32.const 0)
)
@@ -1950,7 +1950,7 @@
(local.set $0
(call $bar)
)
- (try
+ (try $try
(do
(drop
(local.get $0)
@@ -1966,7 +1966,7 @@
(func $non-call-can-be-sinked-into-try
(local $0 i32)
(nop)
- (try
+ (try $try
(do
(drop
(i32.const 3)
diff --git a/test/passes/vacuum_all-features.txt b/test/passes/vacuum_all-features.txt
index 82fb7e506..9acd26665 100644
--- a/test/passes/vacuum_all-features.txt
+++ b/test/passes/vacuum_all-features.txt
@@ -444,7 +444,7 @@
)
(func $inner-try-catch_all-test
(local $0 i32)
- (try
+ (try $try0
(do
(throw $e
(i32.const 0)
@@ -459,9 +459,9 @@
)
(func $inner-try-catch-test
(local $0 i32)
- (try
+ (try $try
(do
- (try
+ (try $try1
(do
(throw $e2
(i32.const 0)
diff --git a/test/reference-types.wast.from-wast b/test/reference-types.wast.from-wast
index 29aab9bea..9a89da4db 100644
--- a/test/reference-types.wast.from-wast
+++ b/test/reference-types.wast.from-wast
@@ -507,7 +507,7 @@
)
)
(drop
- (try (result externref)
+ (try $try (result externref)
(do
(local.get $local_externref)
)
@@ -520,7 +520,7 @@
)
)
(drop
- (try (result funcref)
+ (try $try35 (result funcref)
(do
(ref.func $foo)
)
@@ -533,7 +533,7 @@
)
)
(drop
- (try (result anyref)
+ (try $try36 (result anyref)
(do
(local.get $local_externref)
)
@@ -546,7 +546,7 @@
)
)
(drop
- (try (result anyref)
+ (try $try37 (result anyref)
(do
(ref.func $foo)
)
diff --git a/test/reference-types.wast.fromBinary b/test/reference-types.wast.fromBinary
index 5e51309ec..02be5171e 100644
--- a/test/reference-types.wast.fromBinary
+++ b/test/reference-types.wast.fromBinary
@@ -507,7 +507,7 @@
)
)
(drop
- (try (result externref)
+ (try $label$47 (result externref)
(do
(local.get $local_funcref)
)
@@ -520,7 +520,7 @@
)
)
(drop
- (try (result funcref)
+ (try $label$50 (result funcref)
(do
(ref.func $foo)
)
@@ -533,7 +533,7 @@
)
)
(drop
- (try (result anyref)
+ (try $label$53 (result anyref)
(do
(local.get $local_funcref)
)
@@ -546,7 +546,7 @@
)
)
(drop
- (try (result anyref)
+ (try $label$56 (result anyref)
(do
(ref.func $foo)
)
diff --git a/test/reference-types.wast.fromBinary.noDebugInfo b/test/reference-types.wast.fromBinary.noDebugInfo
index 23c2bb03b..9613c300b 100644
--- a/test/reference-types.wast.fromBinary.noDebugInfo
+++ b/test/reference-types.wast.fromBinary.noDebugInfo
@@ -507,7 +507,7 @@
)
)
(drop
- (try (result externref)
+ (try $label$47 (result externref)
(do
(local.get $1)
)
@@ -520,7 +520,7 @@
)
)
(drop
- (try (result funcref)
+ (try $label$50 (result funcref)
(do
(ref.func $3)
)
@@ -533,7 +533,7 @@
)
)
(drop
- (try (result anyref)
+ (try $label$53 (result anyref)
(do
(local.get $1)
)
@@ -546,7 +546,7 @@
)
)
(drop
- (try (result anyref)
+ (try $label$56 (result anyref)
(do
(ref.func $3)
)
diff --git a/test/spec/exception-handling.wast b/test/spec/exception-handling.wast
index ebca3a009..807ee0a52 100644
--- a/test/spec/exception-handling.wast
+++ b/test/spec/exception-handling.wast
@@ -241,7 +241,7 @@
)
)
)
- "try's type does not match try body's type"
+ "try's type does not match try body's type"
)
(assert_invalid
@@ -263,3 +263,39 @@
)
"event's param numbers must match"
)
+
+(assert_invalid
+ (module
+ (func $f0
+ (block $l0
+ (try
+ (do
+ (try
+ (do)
+ (delegate $l0) ;; target is a block
+ )
+ )
+ (catch_all)
+ )
+ )
+ )
+ )
+ "all delegate targets must be valid"
+)
+
+(assert_invalid
+ (module
+ (func $f0
+ (try $l0
+ (do)
+ (catch_all
+ (try
+ (do)
+ (delegate $l0) ;; the target catch is above the delegate
+ )
+ )
+ )
+ )
+ )
+ "all delegate targets must be valid"
+)
diff --git a/test/try-delegate.wasm b/test/try-delegate.wasm
new file mode 100644
index 000000000..dd39a4f3a
--- /dev/null
+++ b/test/try-delegate.wasm
Binary files differ
diff --git a/test/try-delegate.wasm.fromBinary b/test/try-delegate.wasm.fromBinary
new file mode 100644
index 000000000..fabbfebd0
--- /dev/null
+++ b/test/try-delegate.wasm.fromBinary
@@ -0,0 +1,40 @@
+(module
+ (type $none_=>_none (func))
+ (event $event$0 (attr 0) (param))
+ (func $0
+ (try $label$6
+ (do
+ (try $label$4
+ (do
+ )
+ (delegate $label$6)
+ )
+ )
+ (catch $event$0
+ )
+ )
+ )
+ (func $1
+ (try $label$9
+ (do
+ (try $label$7
+ (do
+ )
+ (catch $event$0
+ (drop
+ (i32.const 0)
+ )
+ (try $label$6
+ (do
+ )
+ (delegate $label$9)
+ )
+ )
+ )
+ )
+ (catch $event$0
+ )
+ )
+ )
+)
+