summaryrefslogtreecommitdiff
path: root/src/wasm
diff options
context:
space:
mode:
authorHeejin Ahn <aheejin@gmail.com>2021-01-15 18:48:00 +0900
committerGitHub <noreply@github.com>2021-01-15 18:48:00 +0900
commitbeccdf70258cd99ea25f10af13103e14dc243ffa (patch)
tree1081d7d350fbab7f901b917f2f082c8d351c3157 /src/wasm
parentf18c18e01d03d6d293fe3d701408855bbcea58bd (diff)
downloadbinaryen-beccdf70258cd99ea25f10af13103e14dc243ffa.tar.gz
binaryen-beccdf70258cd99ea25f10af13103e14dc243ffa.tar.bz2
binaryen-beccdf70258cd99ea25f10af13103e14dc243ffa.zip
Basic EH instrucion support for the new spec (#3487)
This updates `try`-`catch`-`catch_all` and `rethrow` instructions to match the new spec. `delegate` is not included. Now `Try` contains not a single `catchBody` expression but a vector of catch bodies and events. This updates most existing routines, optimizations, and tests modulo the interpreter and the CFG traversal. Because the interpreter has not been updated yet, the EH spec test is temporarily disabled in check.py. Also, because the CFG traversal for EH is not yet updated, several EH tests in `rse_all-features.wast`, which uses CFG traversal, are temporarily commented out. Also added a few more tests in existing EH test functions in test/passes. In the previous spec, `catch` was catching all exceptions so it was assumed that anything `try` body throws is caught by its `catch`, but now we can assume the same only if there is a `catch_all`. Newly added tests test cases when there is a `catch_all` and cases there are only `catch`es separately.
Diffstat (limited to 'src/wasm')
-rw-r--r--src/wasm/wasm-binary.cpp60
-rw-r--r--src/wasm/wasm-s-parser.cpp51
-rw-r--r--src/wasm/wasm-stack.cpp32
-rw-r--r--src/wasm/wasm-validator.cpp34
-rw-r--r--src/wasm/wasm.cpp12
5 files changed, 136 insertions, 53 deletions
diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp
index beaed1b11..9b4794997 100644
--- a/src/wasm/wasm-binary.cpp
+++ b/src/wasm/wasm-binary.cpp
@@ -2128,7 +2128,7 @@ void WasmBinaryBuilder::processExpressions() {
}
auto peek = input[pos];
if (peek == BinaryConsts::End || peek == BinaryConsts::Else ||
- peek == BinaryConsts::Catch) {
+ peek == BinaryConsts::Catch || peek == BinaryConsts::CatchAll) {
BYN_TRACE("== processExpressions finished with unreachable"
<< std::endl);
lastSeparator = BinaryConsts::ASTNodes(peek);
@@ -5494,7 +5494,8 @@ void WasmBinaryBuilder::visitTryOrTryInBlock(Expression*& out) {
// blocks instead.
curr->type = getType();
curr->body = getBlockOrSingleton(curr->type);
- if (lastSeparator != BinaryConsts::Catch) {
+ if (lastSeparator != BinaryConsts::Catch &&
+ lastSeparator != BinaryConsts::CatchAll) {
throwError("No catch instruction within a try scope");
}
@@ -5544,25 +5545,48 @@ void WasmBinaryBuilder::visitTryOrTryInBlock(Expression*& out) {
// )
// )
// )
+
+ Builder builder(wasm);
Name catchLabel = getNextLabel();
breakStack.push_back({catchLabel, curr->type});
- auto start = expressionStack.size();
- Builder builder(wasm);
- pushExpression(builder.makePop(Type::exnref));
+ auto readCatchBody = [&](Type eventType) {
+ auto start = expressionStack.size();
+ if (eventType != Type::none) {
+ pushExpression(builder.makePop(eventType));
+ }
+ processExpressions();
+ size_t end = expressionStack.size();
+ if (start > end) {
+ throwError("block cannot pop from outside");
+ }
+ if (end - start == 1) {
+ curr->catchBodies.push_back(popExpression());
+ } else {
+ auto* block = allocator.alloc<Block>();
+ pushBlockElements(block, curr->type, start);
+ block->finalize(curr->type);
+ curr->catchBodies.push_back(block);
+ }
+ };
- processExpressions();
- size_t end = expressionStack.size();
- if (start > end) {
- throwError("block cannot pop from outside");
- }
- if (end - start == 1) {
- curr->catchBody = popExpression();
- } else {
- auto* block = allocator.alloc<Block>();
- pushBlockElements(block, curr->type, start);
- block->finalize(curr->type);
- curr->catchBody = block;
+ while (lastSeparator == BinaryConsts::Catch ||
+ lastSeparator == BinaryConsts::CatchAll) {
+ if (lastSeparator == BinaryConsts::Catch) {
+ auto index = getU32LEB();
+ if (index >= wasm.events.size()) {
+ throwError("bad event index");
+ }
+ auto* event = wasm.events[index].get();
+ curr->catchEvents.push_back(event->name);
+ readCatchBody(event->sig.params);
+
+ } else { // catch_all
+ if (curr->hasCatchAll()) {
+ throwError("there should be at most one 'catch_all' clause per try");
+ }
+ readCatchBody(Type::none);
+ }
}
curr->finalize(curr->type);
@@ -5595,7 +5619,7 @@ void WasmBinaryBuilder::visitThrow(Throw* curr) {
void WasmBinaryBuilder::visitRethrow(Rethrow* curr) {
BYN_TRACE("zz node: Rethrow\n");
- curr->exnref = popNonVoidExpression();
+ curr->depth = getU32LEB();
curr->finalize();
}
diff --git a/src/wasm/wasm-s-parser.cpp b/src/wasm/wasm-s-parser.cpp
index d76a3c49a..84576fde7 100644
--- a/src/wasm/wasm-s-parser.cpp
+++ b/src/wasm/wasm-s-parser.cpp
@@ -1947,13 +1947,20 @@ Expression* SExpressionWasmBuilder::makeRefEq(Element& s) {
// try-catch-end is written in the folded wast format as
// (try
-// ...
+// (do
+// ...
+// )
// (catch
// ...
// )
+// ...
+// (catch_all
+// ...
+// )
// )
-// The parenthesis wrapping 'catch' is just a syntax and does not affect nested
-// depths of instructions within.
+// 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.
Expression* SExpressionWasmBuilder::makeTry(Element& s) {
auto ret = allocator.alloc<Try>();
Index i = 1;
@@ -1966,15 +1973,38 @@ Expression* SExpressionWasmBuilder::makeTry(Element& s) {
}
auto label = nameMapper.pushLabelName(sName);
Type type = parseOptionalResultType(s, i); // signature
+
if (!elementStartsWith(*s[i], "do")) {
throw ParseException(
"try body should start with 'do'", s[i]->line, s[i]->col);
}
- ret->body = makeTryOrCatchBody(*s[i++], type, true);
- if (!elementStartsWith(*s[i], "catch")) {
- throw ParseException("catch clause does not exist", s[i]->line, s[i]->col);
+ ret->body = makeMaybeBlock(*s[i++], 1, type);
+
+ while (i < s.size() && elementStartsWith(*s[i], "catch")) {
+ Element& inner = *s[i++];
+ if (inner.size() < 3) {
+ throw ParseException("invalid catch block", inner.line, inner.col);
+ }
+ Name event = getEventName(*inner[1]);
+ if (!wasm.getEventOrNull(event)) {
+ throw ParseException("bad event name", inner[1]->line, inner[1]->col);
+ }
+ ret->catchEvents.push_back(getEventName(*inner[1]));
+ ret->catchBodies.push_back(makeMaybeBlock(inner, 2, type));
+ }
+
+ if (i < s.size() && elementStartsWith(*s[i], "catch_all")) {
+ ret->catchBodies.push_back(makeMaybeBlock(*s[i++], 1, type));
+ }
+
+ 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);
}
- ret->catchBody = makeTryOrCatchBody(*s[i++], type, false);
+
ret->finalize(type);
nameMapper.popLabelName(label);
// create a break target if we must
@@ -1993,10 +2023,11 @@ SExpressionWasmBuilder::makeTryOrCatchBody(Element& s, Type type, bool isTry) {
if (isTry && !elementStartsWith(s, "do")) {
throw ParseException("invalid try do clause", s.line, s.col);
}
- if (!isTry && !elementStartsWith(s, "catch")) {
+ if (!isTry && !elementStartsWith(s, "catch") &&
+ !elementStartsWith(s, "catch_all")) {
throw ParseException("invalid catch clause", s.line, s.col);
}
- if (s.size() == 1) { // (do) or (catch) without instructions
+ if (s.size() == 1) { // (do) / (catch) / (catch_all) without instructions
return makeNop();
}
auto ret = allocator.alloc<Block>();
@@ -2027,7 +2058,7 @@ Expression* SExpressionWasmBuilder::makeThrow(Element& s) {
Expression* SExpressionWasmBuilder::makeRethrow(Element& s) {
auto ret = allocator.alloc<Rethrow>();
- ret->exnref = parseExpression(*s[1]);
+ ret->depth = atoi(s[1]->str().c_str());
ret->finalize();
return ret;
}
diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp
index 049573fc5..c96ceabb6 100644
--- a/src/wasm/wasm-stack.cpp
+++ b/src/wasm/wasm-stack.cpp
@@ -1859,14 +1859,24 @@ void BinaryInstWriter::visitTry(Try* curr) {
emitResultType(curr->type);
}
-void BinaryInstWriter::emitCatch(Try* curr) {
+void BinaryInstWriter::emitCatch(Try* curr, Index i) {
assert(!breakStack.empty());
breakStack.pop_back();
breakStack.emplace_back(IMPOSSIBLE_CONTINUE);
+ // TODO Fix handling of BinaryLocations for the new EH spec
if (func && !sourceMap) {
parent.writeExtraDebugLocation(curr, func, BinaryLocations::Catch);
}
- o << int8_t(BinaryConsts::Catch);
+ o << int8_t(BinaryConsts::Catch)
+ << U32LEB(parent.getEventIndex(curr->catchEvents[i]));
+}
+
+void BinaryInstWriter::emitCatchAll(Try* curr) {
+ assert(!breakStack.empty());
+ breakStack.pop_back();
+ breakStack.emplace_back(IMPOSSIBLE_CONTINUE);
+ // TODO Fix handling of BinaryLocations for the new EH spec
+ o << int8_t(BinaryConsts::CatchAll);
}
void BinaryInstWriter::visitThrow(Throw* curr) {
@@ -1874,7 +1884,7 @@ void BinaryInstWriter::visitThrow(Throw* curr) {
}
void BinaryInstWriter::visitRethrow(Rethrow* curr) {
- o << int8_t(BinaryConsts::Rethrow);
+ o << int8_t(BinaryConsts::Rethrow) << U32LEB(curr->depth);
}
void BinaryInstWriter::visitBrOnExn(BrOnExn* curr) {
@@ -2200,23 +2210,29 @@ StackInst* StackIRGenerator::makeStackInst(StackInst::Op op,
void StackIRToBinaryWriter::write() {
writer.mapLocalsAndEmitHeader();
+ // Stack to track indices of catches within a try
+ SmallVector<Index, 4> catchIndexStack;
for (auto* inst : *func->stackIR) {
if (!inst) {
continue; // a nullptr is just something we can skip
}
switch (inst->op) {
+ case StackInst::TryBegin:
+ catchIndexStack.push_back(0);
+ // fallthrough
case StackInst::Basic:
case StackInst::BlockBegin:
case StackInst::IfBegin:
- case StackInst::LoopBegin:
- case StackInst::TryBegin: {
+ case StackInst::LoopBegin: {
writer.visit(inst->origin);
break;
}
+ case StackInst::TryEnd:
+ catchIndexStack.pop_back();
+ // fallthrough
case StackInst::BlockEnd:
case StackInst::IfEnd:
- case StackInst::LoopEnd:
- case StackInst::TryEnd: {
+ case StackInst::LoopEnd: {
writer.emitScopeEnd(inst->origin);
break;
}
@@ -2225,7 +2241,7 @@ void StackIRToBinaryWriter::write() {
break;
}
case StackInst::Catch: {
- writer.emitCatch(inst->origin->cast<Try>());
+ writer.emitCatch(inst->origin->cast<Try>(), catchIndexStack.back()++);
break;
}
default:
diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp
index b0fc4d9c8..064a151c3 100644
--- a/src/wasm/wasm-validator.cpp
+++ b/src/wasm/wasm-validator.cpp
@@ -2026,21 +2026,28 @@ void FunctionValidator::visitTry(Try* curr) {
curr->type,
curr->body,
"try's type does not match try body's type");
- shouldBeSubTypeOrFirstIsUnreachable(
- curr->catchBody->type,
- curr->type,
- curr->catchBody,
- "try's type does not match catch's body type");
+ for (auto catchBody : curr->catchBodies) {
+ shouldBeSubTypeOrFirstIsUnreachable(
+ catchBody->type,
+ curr->type,
+ catchBody,
+ "try's type does not match catch's body type");
+ }
} else {
shouldBeEqual(curr->body->type,
Type(Type::unreachable),
curr,
"unreachable try-catch must have unreachable try body");
- shouldBeEqual(curr->catchBody->type,
- Type(Type::unreachable),
- curr,
- "unreachable try-catch must have unreachable catch body");
+ for (auto catchBody : curr->catchBodies) {
+ shouldBeEqual(catchBody->type,
+ Type(Type::unreachable),
+ curr,
+ "unreachable try-catch must have unreachable catch body");
+ }
}
+ shouldBeTrue(curr->catchBodies.size() - curr->catchEvents.size() <= 1,
+ curr,
+ "the number of catch blocks and events do not match");
}
void FunctionValidator::visitThrow(Throw* curr) {
@@ -2084,11 +2091,10 @@ void FunctionValidator::visitRethrow(Rethrow* curr) {
Type(Type::unreachable),
curr,
"rethrow's type must be unreachable");
- shouldBeSubTypeOrFirstIsUnreachable(
- curr->exnref->type,
- Type::exnref,
- curr->exnref,
- "rethrow's argument must be exnref type or its subtype");
+ // TODO Allow non-zero depths and Validate the depth field. The current LLVM
+ // toolchain only generates depth 0 for C++ support.
+ shouldBeEqual(
+ curr->depth, (Index)0, curr, "rethrow only support depth 0 at the moment");
}
void FunctionValidator::visitBrOnExn(BrOnExn* curr) {
diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp
index fb523e254..55b03c228 100644
--- a/src/wasm/wasm.cpp
+++ b/src/wasm/wasm.cpp
@@ -949,13 +949,19 @@ void RefEq::finalize() {
}
void Try::finalize() {
- type = Type::getLeastUpperBound(body->type, catchBody->type);
+ type = body->type;
+ for (auto catchBody : catchBodies) {
+ type = Type::getLeastUpperBound(type, catchBody->type);
+ }
}
void Try::finalize(Type type_) {
type = type_;
- if (type == Type::none && body->type == Type::unreachable &&
- catchBody->type == Type::unreachable) {
+ bool allUnreachable = body->type == Type::unreachable;
+ for (auto catchBody : catchBodies) {
+ allUnreachable &= catchBody->type == Type::unreachable;
+ }
+ if (type == Type::none && allUnreachable) {
type = Type::unreachable;
}
}