summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xscripts/gen-s-parser.py10
-rw-r--r--src/gen-s-parser.inc6
-rw-r--r--src/parser/contexts.h37
-rw-r--r--src/parser/parsers.h192
-rw-r--r--src/wasm-ir-builder.h80
-rw-r--r--src/wasm/wasm-ir-builder.cpp172
-rw-r--r--test/lit/wat-kitchen-sink.wast456
7 files changed, 902 insertions, 51 deletions
diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py
index 08c5fe31f..bd29f4f10 100755
--- a/scripts/gen-s-parser.py
+++ b/scripts/gen-s-parser.py
@@ -709,15 +709,13 @@ class Node:
def instruction_parser(new_parser=False):
"""Build a trie out of all the instructions, then emit it as C++ code."""
global instructions
- if new_parser:
- # Filter out instructions that the new parser does not need.
- instructions = [(inst, code) for (inst, code) in instructions
- if inst not in ('block', 'loop', 'if', 'then', 'else')]
trie = Node()
inst_length = 0
for inst, expr in instructions:
- if new_parser and inst in {"then", "else"}:
- # These are not real instructions! skip them.
+ if new_parser and inst in {"block", "loop", "if", "try", "then",
+ "else"}:
+ # These are either control flow handled manually or not real
+ # instructions. Skip them.
continue
inst_length = max(inst_length, len(inst))
trie.insert(inst, expr)
diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc
index 309f190ce..22c7a0c90 100644
--- a/src/gen-s-parser.inc
+++ b/src/gen-s-parser.inc
@@ -8649,12 +8649,6 @@ switch (buf[0]) {
return Ok{};
}
goto parse_error;
- case 'r':
- if (op == "try"sv) {
- CHECK_ERR(makeTry(ctx, pos));
- return Ok{};
- }
- goto parse_error;
case 'u': {
switch (buf[6]) {
case 'e':
diff --git a/src/parser/contexts.h b/src/parser/contexts.h
index ed4d1283c..adb7694f6 100644
--- a/src/parser/contexts.h
+++ b/src/parser/contexts.h
@@ -308,8 +308,8 @@ struct NullInstrParserCtx {
MemoryIdxT getMemoryFromName(Name) { return Ok{}; }
DataIdxT getDataFromIdx(uint32_t) { return Ok{}; }
DataIdxT getDataFromName(Name) { return Ok{}; }
- LabelIdxT getLabelFromIdx(uint32_t) { return Ok{}; }
- LabelIdxT getLabelFromName(Name) { return Ok{}; }
+ LabelIdxT getLabelFromIdx(uint32_t, bool) { return Ok{}; }
+ LabelIdxT getLabelFromName(Name, bool) { return Ok{}; }
TagIdxT getTagFromIdx(uint32_t) { return Ok{}; }
TagIdxT getTagFromName(Name) { return Ok{}; }
@@ -328,6 +328,13 @@ struct NullInstrParserCtx {
Result<> makeLoop(Index, std::optional<Name>, BlockTypeT) {
return Ok{};
}
+ template<typename BlockTypeT>
+ Result<> makeTry(Index, std::optional<Name>, BlockTypeT) {
+ return Ok{};
+ }
+ Result<> visitCatch(Index, TagIdxT) { return Ok{}; }
+ Result<> visitCatchAll(Index) { return Ok{}; }
+ Result<> visitDelegate(Index, LabelIdxT) { return Ok{}; }
Result<> visitEnd() { return Ok{}; }
Result<> makeUnreachable(Index) { return Ok{}; }
@@ -1020,10 +1027,10 @@ struct ParseDefsCtx : TypeParserCtx<ParseDefsCtx> {
return name;
}
- Result<Index> getLabelFromIdx(uint32_t idx) { return idx; }
+ Result<Index> getLabelFromIdx(uint32_t idx, bool) { return idx; }
- Result<Index> getLabelFromName(Name name) {
- return irBuilder.getLabelIndex(name);
+ Result<Index> getLabelFromName(Name name, bool inDelegate) {
+ return irBuilder.getLabelIndex(name, inDelegate);
}
Result<Name> getTagFromIdx(uint32_t idx) {
@@ -1109,6 +1116,26 @@ struct ParseDefsCtx : TypeParserCtx<ParseDefsCtx> {
irBuilder.makeLoop(label ? *label : Name{}, type.getSignature().results));
}
+ Result<> makeTry(Index pos, std::optional<Name> label, HeapType type) {
+ // TODO: validate labels?
+ // TODO: Move error on input types to here?
+ return withLoc(
+ pos,
+ irBuilder.makeTry(label ? *label : Name{}, type.getSignature().results));
+ }
+
+ Result<> visitCatch(Index pos, Name tag) {
+ return withLoc(pos, irBuilder.visitCatch(tag));
+ }
+
+ Result<> visitCatchAll(Index pos) {
+ return withLoc(pos, irBuilder.visitCatchAll());
+ }
+
+ Result<> visitDelegate(Index pos, Index label) {
+ return withLoc(pos, irBuilder.visitDelegate(label));
+ }
+
Result<> visitEnd() { return withLoc(irBuilder.visitEnd()); }
Result<> makeUnreachable(Index pos) {
diff --git a/src/parser/parsers.h b/src/parser/parsers.h
index 9201314dc..f16cca2b5 100644
--- a/src/parser/parsers.h
+++ b/src/parser/parsers.h
@@ -56,6 +56,7 @@ template<typename Ctx> Result<typename Ctx::BlockTypeT> blocktype(Ctx&);
template<typename Ctx> MaybeResult<> block(Ctx&, bool);
template<typename Ctx> MaybeResult<> ifelse(Ctx&, bool);
template<typename Ctx> MaybeResult<> loop(Ctx&, bool);
+template<typename Ctx> MaybeResult<> trycatch(Ctx&, bool);
template<typename Ctx> Result<> makeUnreachable(Ctx&, Index);
template<typename Ctx> Result<> makeNop(Ctx&, Index);
template<typename Ctx> Result<> makeBinary(Ctx&, Index, BinaryOp op);
@@ -113,9 +114,6 @@ template<typename Ctx> Result<> makeTableSize(Ctx&, Index);
template<typename Ctx> Result<> makeTableGrow(Ctx&, Index);
template<typename Ctx> Result<> makeTableFill(Ctx&, Index);
template<typename Ctx> Result<> makeTableCopy(Ctx&, Index);
-template<typename Ctx> Result<> makeTry(Ctx&, Index);
-template<typename Ctx>
-Result<> makeTryOrCatchBody(Ctx&, Index, Type type, bool isTry);
template<typename Ctx> Result<> makeThrow(Ctx&, Index);
template<typename Ctx> Result<> makeRethrow(Ctx&, Index);
template<typename Ctx> Result<> makeTupleMake(Ctx&, Index);
@@ -173,7 +171,8 @@ template<typename Ctx> Result<typename Ctx::MemoryIdxT> memidx(Ctx&);
template<typename Ctx> MaybeResult<typename Ctx::MemoryIdxT> maybeMemuse(Ctx&);
template<typename Ctx> Result<typename Ctx::GlobalIdxT> globalidx(Ctx&);
template<typename Ctx> Result<typename Ctx::LocalIdxT> localidx(Ctx&);
-template<typename Ctx> Result<typename Ctx::LabelIdxT> labelidx(Ctx&);
+template<typename Ctx>
+Result<typename Ctx::LabelIdxT> labelidx(Ctx&, bool inDelegate = false);
template<typename Ctx> Result<typename Ctx::TagIdxT> tagidx(Ctx&);
template<typename Ctx> Result<typename Ctx::TypeUseT> typeuse(Ctx&);
MaybeResult<ImportNames> inlineImport(ParseInput&);
@@ -573,6 +572,9 @@ template<typename Ctx> MaybeResult<> foldedBlockinstr(Ctx& ctx) {
if (auto i = loop(ctx, true)) {
return i;
}
+ if (auto i = trycatch(ctx, true)) {
+ return i;
+ }
// TODO: Other block instructions
return {};
}
@@ -587,6 +589,9 @@ template<typename Ctx> MaybeResult<> unfoldedBlockinstr(Ctx& ctx) {
if (auto i = loop(ctx, false)) {
return i;
}
+ if (auto i = trycatch(ctx, false)) {
+ return i;
+ }
// TODO: Other block instructions
return {};
}
@@ -619,7 +624,9 @@ template<typename Ctx> MaybeResult<> instr(Ctx& ctx) {
// Check for valid strings that are not instructions.
if (auto tok = ctx.in.peek()) {
if (auto keyword = tok->getKeyword()) {
- if (keyword == "end"sv || keyword == "then"sv || keyword == "else"sv) {
+ if (keyword == "end"sv || keyword == "then"sv || keyword == "else"sv ||
+ keyword == "catch"sv || keyword == "catch_all"sv ||
+ keyword == "delegate"sv) {
return {};
}
}
@@ -860,7 +867,7 @@ template<typename Ctx> MaybeResult<> ifelse(Ctx& ctx, bool folded) {
return ctx.visitEnd();
}
-// loop ::= 'loop' label blocktype instr* end id?
+// loop ::= 'loop' label blocktype instr* 'end' id?
// | '(' 'loop' label blocktype instr* ')'
template<typename Ctx> MaybeResult<> loop(Ctx& ctx, bool folded) {
auto pos = ctx.in.getPos();
@@ -895,6 +902,163 @@ template<typename Ctx> MaybeResult<> loop(Ctx& ctx, bool folded) {
return ctx.visitEnd();
}
+// trycatch ::= 'try' label blocktype instr* ('catch' id? tagidx instr*)*
+// ('catch_all' id? instr*)? 'end' id?
+// | '(' 'try' label blocktype '(' 'do' instr* ')'
+// ('(' 'catch' tagidx instr* ')')*
+// ('(' 'catch_all' instr* ')')? ')'
+// | 'try' label blocktype instr* 'deledate' label
+// | '(' 'try' label blocktype '(' 'do' instr* ')'
+// '(' 'delegate' label ')' ')'
+template<typename Ctx> MaybeResult<> trycatch(Ctx& ctx, bool folded) {
+ auto pos = ctx.in.getPos();
+
+ if ((folded && !ctx.in.takeSExprStart("try"sv)) ||
+ (!folded && !ctx.in.takeKeyword("try"sv))) {
+ return {};
+ }
+
+ auto label = ctx.in.takeID();
+
+ auto type = blocktype(ctx);
+ CHECK_ERR(type);
+
+ CHECK_ERR(ctx.makeTry(pos, label, *type));
+
+ if (folded) {
+ if (!ctx.in.takeSExprStart("do"sv)) {
+ return ctx.in.err("expected 'do' in try");
+ }
+ }
+
+ CHECK_ERR(instrs(ctx));
+
+ if (folded) {
+ if (!ctx.in.takeRParen()) {
+ return ctx.in.err("expected ')' at end of do");
+ }
+ }
+
+ if ((folded && ctx.in.takeSExprStart("delegate")) ||
+ (!folded && ctx.in.takeKeyword("delegate"))) {
+ auto delegatePos = ctx.in.getPos();
+
+ auto label = labelidx(ctx, true);
+ CHECK_ERR(label);
+
+ if (folded) {
+ if (!ctx.in.takeRParen()) {
+ return ctx.in.err("expected ')' at end of delegate");
+ }
+ if (!ctx.in.takeRParen()) {
+ return ctx.in.err("expected ')' at end of try");
+ }
+ }
+
+ CHECK_ERR(ctx.visitDelegate(delegatePos, *label));
+ return Ok{};
+ }
+
+ while (true) {
+ auto catchPos = ctx.in.getPos();
+
+ if ((folded && !ctx.in.takeSExprStart("catch"sv)) ||
+ (!folded && !ctx.in.takeKeyword("catch"sv))) {
+ break;
+ }
+
+ // It can be ambiguous whether the name after `catch` is intended to be the
+ // optional ID or the tag identifier. For example:
+ //
+ // (tag $t)
+ // (func $ambiguous
+ // try $t
+ // catch $t
+ // end
+ // )
+ //
+ // When parsing the `catch`, the parser first tries to parse an optional ID
+ // that must match the label of the `try`, and it succeeds because it sees
+ // `$t` after the catch. However, when it then tries to parse the mandatory
+ // tag index, it fails because the next token is `end`. The problem is that
+ // the `$t` after the `catch` was the tag name and there was no optional ID
+ // after all. The parser sets `parseID = false` and resets to just after the
+ // `catch`, and now it skips parsing the optional ID so it correctly parses
+ // the `$t` as a tag name.
+ bool parseID = !folded;
+ auto afterCatchPos = ctx.in.getPos();
+ while (true) {
+ if (!folded && parseID) {
+ auto id = ctx.in.takeID();
+ if (id && id != label) {
+ // Instead of returning an error, retry without the ID.
+ parseID = false;
+ ctx.in.lexer.setIndex(afterCatchPos);
+ continue;
+ }
+ }
+
+ auto tag = tagidx(ctx);
+ if (parseID && tag.getErr()) {
+ // Instead of returning an error, retry without the ID.
+ parseID = false;
+ ctx.in.lexer.setIndex(afterCatchPos);
+ continue;
+ }
+ CHECK_ERR(tag);
+
+ CHECK_ERR(ctx.visitCatch(catchPos, *tag));
+
+ CHECK_ERR(instrs(ctx));
+
+ if (folded) {
+ if (!ctx.in.takeRParen()) {
+ return ctx.in.err("expected ')' at end of catch");
+ }
+ }
+ break;
+ }
+ }
+
+ if ((folded && ctx.in.takeSExprStart("catch_all"sv)) ||
+ (!folded && ctx.in.takeKeyword("catch_all"sv))) {
+ auto catchPos = ctx.in.getPos();
+
+ if (!folded) {
+ auto id = ctx.in.takeID();
+ if (id && id != label) {
+ return ctx.in.err("catch_all label does not match try label");
+ }
+ }
+
+ CHECK_ERR(ctx.visitCatchAll(catchPos));
+
+ CHECK_ERR(instrs(ctx));
+
+ if (folded) {
+ if (!ctx.in.takeRParen()) {
+ return ctx.in.err("expected ')' at end of catch_all");
+ }
+ }
+ }
+
+ if (folded) {
+ if (!ctx.in.takeRParen()) {
+ return ctx.in.err("expected ')' at end of try");
+ }
+ } else {
+ if (!ctx.in.takeKeyword("end"sv)) {
+ return ctx.in.err("expected 'end' at end of try");
+ }
+
+ auto id = ctx.in.takeID();
+ if (id && id != label) {
+ return ctx.in.err("end label does not match try label");
+ }
+ }
+ return ctx.visitEnd();
+}
+
template<typename Ctx> Result<> makeUnreachable(Ctx& ctx, Index pos) {
return ctx.makeUnreachable(pos);
}
@@ -1266,15 +1430,6 @@ template<typename Ctx> Result<> makeTableCopy(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
-template<typename Ctx> Result<> makeTry(Ctx& ctx, Index pos) {
- return ctx.in.err("unimplemented instruction");
-}
-
-template<typename Ctx>
-Result<> makeTryOrCatchBody(Ctx& ctx, Index pos, Type type, bool isTry) {
- return ctx.in.err("unimplemented instruction");
-}
-
template<typename Ctx> Result<> makeThrow(Ctx& ctx, Index pos) {
auto tag = tagidx(ctx);
CHECK_ERR(tag);
@@ -1621,12 +1776,13 @@ template<typename Ctx> Result<typename Ctx::LocalIdxT> localidx(Ctx& ctx) {
// labelidx ::= x:u32 => x
// | v:id => x (if labels[x] = v)
-template<typename Ctx> Result<typename Ctx::LabelIdxT> labelidx(Ctx& ctx) {
+template<typename Ctx>
+Result<typename Ctx::LabelIdxT> labelidx(Ctx& ctx, bool inDelegate) {
if (auto x = ctx.in.takeU32()) {
- return ctx.getLabelFromIdx(*x);
+ return ctx.getLabelFromIdx(*x, inDelegate);
}
if (auto id = ctx.in.takeID()) {
- return ctx.getLabelFromName(*id);
+ return ctx.getLabelFromName(*id, inDelegate);
}
return ctx.in.err("expected label index or identifier");
}
diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h
index e7613b373..ee6721738 100644
--- a/src/wasm-ir-builder.h
+++ b/src/wasm-ir-builder.h
@@ -64,13 +64,21 @@ public:
[[nodiscard]] Result<> visitIfStart(If* iff, Name label = {});
[[nodiscard]] Result<> visitElse();
[[nodiscard]] Result<> visitLoopStart(Loop* iff);
+ [[nodiscard]] Result<> visitTryStart(Try* tryy, Name label = {});
+ [[nodiscard]] Result<> visitCatch(Name tag);
+ [[nodiscard]] Result<> visitCatchAll();
+ [[nodiscard]] Result<> visitDelegate(Index label);
[[nodiscard]] Result<> visitEnd();
// Binaryen IR uses names to refer to branch targets, but in general there may
// be branches to constructs that do not yet have names, so in IRBuilder we
// use indices to refer to branch targets instead, just as the binary format
// does. This function converts a branch target name to the correct index.
- [[nodiscard]] Result<Index> getLabelIndex(Name label);
+ //
+ // Labels in delegates need special handling because the indexing needs to be
+ // relative to the try's enclosing scope rather than the try itself.
+ [[nodiscard]] Result<Index> getLabelIndex(Name label,
+ bool inDelegate = false);
// Instead of calling visit, call makeXYZ to have the IRBuilder allocate the
// nodes. This is generally safer than calling `visit` because the function
@@ -145,7 +153,7 @@ public:
// [[nodiscard]] Result<> makeTableGrow();
// [[nodiscard]] Result<> makeTableFill();
// [[nodiscard]] Result<> makeTableCopy();
- // [[nodiscard]] Result<> makeTry();
+ [[nodiscard]] Result<> makeTry(Name label, Type type);
[[nodiscard]] Result<> makeThrow(Name tag);
// [[nodiscard]] Result<> makeRethrow();
// [[nodiscard]] Result<> makeTupleMake();
@@ -232,8 +240,27 @@ private:
struct LoopScope {
Loop* loop;
};
- using Scope = std::
- variant<NoScope, FuncScope, BlockScope, IfScope, ElseScope, LoopScope>;
+ struct TryScope {
+ Try* tryy;
+ Name originalLabel;
+ };
+ struct CatchScope {
+ Try* tryy;
+ Name originalLabel;
+ };
+ struct CatchAllScope {
+ Try* tryy;
+ Name originalLabel;
+ };
+ using Scope = std::variant<NoScope,
+ FuncScope,
+ BlockScope,
+ IfScope,
+ ElseScope,
+ LoopScope,
+ TryScope,
+ CatchScope,
+ CatchAllScope>;
// The control flow structure we are building expressions for.
Scope scope;
@@ -264,6 +291,15 @@ private:
return ScopeCtx(ElseScope{iff, originalLabel}, label);
}
static ScopeCtx makeLoop(Loop* loop) { return ScopeCtx(LoopScope{loop}); }
+ static ScopeCtx makeTry(Try* tryy, Name originalLabel = {}) {
+ return ScopeCtx(TryScope{tryy, originalLabel});
+ }
+ static ScopeCtx makeCatch(Try* tryy, Name originalLabel, Name label) {
+ return ScopeCtx(CatchScope{tryy, originalLabel}, label);
+ }
+ static ScopeCtx makeCatchAll(Try* tryy, Name originalLabel, Name label) {
+ return ScopeCtx(CatchAllScope{tryy, originalLabel}, label);
+ }
bool isNone() { return std::get_if<NoScope>(&scope); }
Function* getFunction() {
@@ -296,6 +332,24 @@ private:
}
return nullptr;
}
+ Try* getTry() {
+ if (auto* tryScope = std::get_if<TryScope>(&scope)) {
+ return tryScope->tryy;
+ }
+ return nullptr;
+ }
+ Try* getCatch() {
+ if (auto* catchScope = std::get_if<CatchScope>(&scope)) {
+ return catchScope->tryy;
+ }
+ return nullptr;
+ }
+ Try* getCatchAll() {
+ if (auto* catchAllScope = std::get_if<CatchAllScope>(&scope)) {
+ return catchAllScope->tryy;
+ }
+ return nullptr;
+ }
Type getResultType() {
if (auto* func = getFunction()) {
return func->type.getSignature().results;
@@ -312,6 +366,15 @@ private:
if (auto* loop = getLoop()) {
return loop->type;
}
+ if (auto* tryy = getTry()) {
+ return tryy->type;
+ }
+ if (auto* tryy = getCatch()) {
+ return tryy->type;
+ }
+ if (auto* tryy = getCatchAll()) {
+ return tryy->type;
+ }
WASM_UNREACHABLE("unexpected scope kind");
}
Name getOriginalLabel() {
@@ -330,6 +393,15 @@ private:
if (auto* loop = getLoop()) {
return loop->name;
}
+ if (auto* tryScope = std::get_if<TryScope>(&scope)) {
+ return tryScope->originalLabel;
+ }
+ if (auto* catchScope = std::get_if<CatchScope>(&scope)) {
+ return catchScope->originalLabel;
+ }
+ if (auto* catchAllScope = std::get_if<CatchAllScope>(&scope)) {
+ return catchAllScope->originalLabel;
+ }
WASM_UNREACHABLE("unexpected scope kind");
}
};
diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp
index 9bca1967b..ed81a5c18 100644
--- a/src/wasm/wasm-ir-builder.cpp
+++ b/src/wasm/wasm-ir-builder.cpp
@@ -207,18 +207,33 @@ void IRBuilder::dump() {
for (auto& scope : scopeStack) {
std::cerr << " scope ";
- if (std::get_if<ScopeCtx::NoScope>(&scope.scope)) {
+ if (scope.isNone()) {
std::cerr << "none";
- } else if (auto* f = std::get_if<ScopeCtx::FuncScope>(&scope.scope)) {
- std::cerr << "func " << f->func->name;
- } else if (std::get_if<ScopeCtx::BlockScope>(&scope.scope)) {
+ } else if (auto* f = scope.getFunction()) {
+ std::cerr << "func " << f->name;
+ } else if (scope.getBlock()) {
std::cerr << "block";
- } else if (std::get_if<ScopeCtx::IfScope>(&scope.scope)) {
+ } else if (scope.getIf()) {
std::cerr << "if";
- } else if (std::get_if<ScopeCtx::ElseScope>(&scope.scope)) {
+ } else if (scope.getElse()) {
std::cerr << "else";
- } else if (std::get_if<ScopeCtx::LoopScope>(&scope.scope)) {
+ } else if (scope.getLoop()) {
std::cerr << "loop";
+ } else if (auto* tryy = scope.getTry()) {
+ std::cerr << "try";
+ if (tryy->name) {
+ std::cerr << " " << tryy->name;
+ }
+ } else if (auto* tryy = scope.getCatch()) {
+ std::cerr << "catch";
+ if (tryy->name) {
+ std::cerr << " " << tryy->name;
+ }
+ } else if (auto* tryy = scope.getCatchAll()) {
+ std::cerr << "catch_all";
+ if (tryy->name) {
+ std::cerr << " " << tryy->name;
+ }
} else {
WASM_UNREACHABLE("unexpected scope");
}
@@ -483,6 +498,14 @@ Result<> IRBuilder::visitLoopStart(Loop* loop) {
return Ok{};
}
+Result<> IRBuilder::visitTryStart(Try* tryy, Name label) {
+ // The delegate label will be regenerated if we need it. See `visitDelegate`
+ // for details.
+ tryy->name = Name();
+ pushScope(ScopeCtx::makeTry(tryy, label));
+ return Ok{};
+}
+
Result<Expression*> IRBuilder::finishScope(Block* block) {
if (scopeStack.empty() || scopeStack.back().isNone()) {
return Err{"unexpected end of scope"};
@@ -586,6 +609,97 @@ Result<> IRBuilder::visitElse() {
return Ok{};
}
+Result<> IRBuilder::visitCatch(Name tag) {
+ auto& scope = getScope();
+ bool wasTry = true;
+ auto* tryy = scope.getTry();
+ if (!tryy) {
+ wasTry = false;
+ tryy = scope.getCatch();
+ }
+ if (!tryy) {
+ return Err{"unexpected catch"};
+ }
+ auto originalLabel = scope.getOriginalLabel();
+ auto label = scope.label;
+ auto expr = finishScope();
+ CHECK_ERR(expr);
+ if (wasTry) {
+ tryy->body = *expr;
+ } else {
+ tryy->catchBodies.push_back(*expr);
+ }
+ tryy->catchTags.push_back(tag);
+ pushScope(ScopeCtx::makeCatch(tryy, originalLabel, label));
+ // Push a pop for the exception payload.
+ auto params = wasm.getTag(tag)->sig.params;
+ if (params != Type::none) {
+ push(builder.makePop(params));
+ }
+ return Ok{};
+}
+
+Result<> IRBuilder::visitCatchAll() {
+ auto& scope = getScope();
+ bool wasTry = true;
+ auto* tryy = scope.getTry();
+ if (!tryy) {
+ wasTry = false;
+ tryy = scope.getCatch();
+ }
+ if (!tryy) {
+ return Err{"unexpected catch"};
+ }
+ auto originalLabel = scope.getOriginalLabel();
+ auto label = scope.label;
+ auto expr = finishScope();
+ CHECK_ERR(expr);
+ if (wasTry) {
+ tryy->body = *expr;
+ } else {
+ tryy->catchBodies.push_back(*expr);
+ }
+ pushScope(ScopeCtx::makeCatchAll(tryy, originalLabel, label));
+ return Ok{};
+}
+
+Result<> IRBuilder::visitDelegate(Index label) {
+ auto& scope = getScope();
+ auto* tryy = scope.getTry();
+ if (!tryy) {
+ return Err{"unexpected delegate"};
+ }
+ // In Binaryen IR, delegates can only target try or function scopes directly.
+ // Search upward to find the nearest enclosing try or function scope. Since
+ // the given label is relative the parent scope of the try, start by adjusting
+ // it to be relative to the try scope.
+ ++label;
+ for (size_t size = scopeStack.size(); label < size; ++label) {
+ auto& delegateScope = scopeStack[size - label - 1];
+ if (auto* delegateTry = delegateScope.getTry()) {
+ // Only delegates can reference the try name in Binaryen IR, so trys might
+ // need two labels: one for delegates and one for all other control flow.
+ // These labels must be different to satisfy the Binaryen validator. To
+ // keep this complexity contained within the handling of trys and
+ // delegates, pretend there is just the single normal label and add a
+ // prefix to it to generate the delegate label.
+ auto delegateName =
+ Name(std::string("__delegate__") + getLabelName(label)->toString());
+ delegateTry->name = delegateName;
+ tryy->delegateTarget = delegateName;
+ break;
+ } else if (delegateScope.getFunction()) {
+ tryy->delegateTarget = DELEGATE_CALLER_TARGET;
+ break;
+ }
+ }
+ if (label == scopeStack.size()) {
+ return Err{"unexpected delegate"};
+ }
+ // Delegate ends the try.
+ return visitEnd();
+}
+
Result<> IRBuilder::visitEnd() {
auto scope = getScope();
if (scope.isNone()) {
@@ -634,18 +748,50 @@ Result<> IRBuilder::visitEnd() {
iff->ifFalse = *expr;
iff->finalize(iff->type);
push(maybeWrapForLabel(iff));
+ } else if (auto* tryy = scope.getTry()) {
+ tryy->body = *expr;
+ tryy->finalize(tryy->type);
+ push(maybeWrapForLabel(tryy));
+ } else if (Try * tryy;
+ (tryy = scope.getCatch()) || (tryy = scope.getCatchAll())) {
+ tryy->catchBodies.push_back(*expr);
+ tryy->finalize(tryy->type);
+ push(maybeWrapForLabel(tryy));
} else {
WASM_UNREACHABLE("unexpected scope kind");
}
return Ok{};
}
-Result<Index> IRBuilder::getLabelIndex(Name label) {
+Result<Index> IRBuilder::getLabelIndex(Name label, bool inDelegate) {
auto it = labelDepths.find(label);
if (it == labelDepths.end() || it->second.empty()) {
- return Err{"unexpected label '"s + label.toString()};
+ return Err{"unexpected label '"s + label.toString() + "'"};
}
- return scopeStack.size() - it->second.back();
+ auto index = scopeStack.size() - it->second.back();
+ if (inDelegate) {
+ if (index == 0) {
+ // The real label we're referencing, if it exists, has been shadowed by
+ // the `try`. Get the previous label with this name instead. For example:
+ //
+ // block $l
+ // try $l
+ // delegate $l
+ // end
+ //
+ // The `delegate $l` should target the block, not the try, even though a
+ // normal branch to $l in the try's scope would target the try.
+ if (it->second.size() <= 1) {
+ return Err{"unexpected self-referencing label '"s + label.toString() +
+ "'"};
+ }
+ index = scopeStack.size() - it->second[it->second.size() - 2];
+ assert(index != 0);
+ }
+ // Adjust the index to be relative to the try.
+ --index;
+ }
+ return index;
}
Result<Name> IRBuilder::getLabelName(Index label) {
@@ -1014,7 +1160,11 @@ Result<> IRBuilder::makeRefEq() {
// Result<> IRBuilder::makeTableGrow() {}
-// Result<> IRBuilder::makeTry() {}
+Result<> IRBuilder::makeTry(Name label, Type type) {
+ auto* tryy = wasm.allocator.alloc<Try>();
+ tryy->type = type;
+ return visitTryStart(tryy, label);
+}
Result<> IRBuilder::makeThrow(Name tag) {
Throw curr(wasm.allocator);
diff --git a/test/lit/wat-kitchen-sink.wast b/test/lit/wat-kitchen-sink.wast
index c61f502ca..f23d494af 100644
--- a/test/lit/wat-kitchen-sink.wast
+++ b/test/lit/wat-kitchen-sink.wast
@@ -1515,6 +1515,460 @@
)
)
+ ;; CHECK: (func $try (type $void)
+ ;; CHECK-NEXT: (try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try
+ try
+ nop
+ end
+ )
+
+ ;; CHECK: (func $try-catch (type $void)
+ ;; CHECK-NEXT: (try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch $empty
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-catch
+ try
+ catch $empty
+ end
+ )
+
+ ;; CHECK: (func $try-catch-params (type $5) (result i32 i64)
+ ;; CHECK-NEXT: (try (type $5) (result i32 i64)
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (tuple.make
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (i64.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch $tag-pair
+ ;; CHECK-NEXT: (pop i32 i64)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-catch-params (result i32 i64)
+ try (result i32 i64)
+ i32.const 0
+ i64.const 1
+ catch $tag-pair
+ end
+ )
+
+
+ ;; CHECK: (func $try-catch_all (type $void)
+ ;; CHECK-NEXT: (try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch_all
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-catch_all
+ try
+ catch_all
+ end
+ )
+
+ ;; CHECK: (func $try-catch-catch_all (type $void)
+ ;; CHECK-NEXT: (try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch $empty
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch $timport$0
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch_all
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-catch-catch_all
+ try
+ catch $empty
+ catch 1
+ catch_all
+ end
+ )
+
+ ;; CHECK: (func $try-delegate (type $void)
+ ;; CHECK-NEXT: (try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (delegate 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-delegate
+ try
+ delegate 0
+ )
+
+ ;; CHECK: (func $try-delegate-nested-func-direct (type $void)
+ ;; CHECK-NEXT: (block $l
+ ;; CHECK-NEXT: (try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (delegate 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-delegate-nested-func-direct
+ block $l
+ try
+ delegate 1
+ end
+ )
+
+ ;; CHECK: (func $try-delegate-nested-func-indirect-index (type $void)
+ ;; CHECK-NEXT: (block $l
+ ;; CHECK-NEXT: (try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (delegate 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-delegate-nested-func-indirect-index
+ block $l
+ try
+ delegate 0
+ end
+ )
+
+ ;; CHECK: (func $try-delegate-nested-func-indirect-name (type $void)
+ ;; CHECK-NEXT: (block $l
+ ;; CHECK-NEXT: (try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (delegate 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-delegate-nested-func-indirect-name
+ block $l
+ try
+ delegate $l
+ end
+ )
+
+ ;; CHECK: (func $try-delegate-nested-try-direct-index (type $void)
+ ;; CHECK-NEXT: (block $label
+ ;; CHECK-NEXT: (try $__delegate__label
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (delegate $__delegate__label)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-delegate-nested-try-direct-index
+ try
+ block
+ try
+ delegate 1
+ end
+ end
+ )
+
+ ;; CHECK: (func $try-delegate-nested-try-direct-name (type $void)
+ ;; CHECK-NEXT: (block $l
+ ;; CHECK-NEXT: (try $__delegate__l
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (delegate $__delegate__l)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-delegate-nested-try-direct-name
+ try $l
+ block
+ try
+ delegate $l
+ end
+ end
+ )
+
+ ;; CHECK: (func $try-delegate-nested-try-indirect-index (type $void)
+ ;; CHECK-NEXT: (block $label
+ ;; CHECK-NEXT: (try $__delegate__label
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (delegate $__delegate__label)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-delegate-nested-try-indirect-index
+ try
+ block
+ try
+ delegate 0
+ end
+ end
+ )
+
+ ;; CHECK: (func $try-delegate-nested-try-indirect-name (type $void)
+ ;; CHECK-NEXT: (block $label
+ ;; CHECK-NEXT: (try $__delegate__label
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (block $l
+ ;; CHECK-NEXT: (try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (delegate $__delegate__label)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-delegate-nested-try-indirect-name
+ try
+ block $l
+ try
+ delegate $l
+ end
+ end
+ )
+
+ ;; CHECK: (func $try-delegate-nested-try-shadowing (type $void)
+ ;; CHECK-NEXT: (block $l
+ ;; CHECK-NEXT: (try $__delegate__l
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (block $l_0
+ ;; CHECK-NEXT: (block $l_1
+ ;; CHECK-NEXT: (try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (delegate $__delegate__l)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-delegate-nested-try-shadowing
+ try $l
+ block $l
+ try $l
+ delegate $l
+ end
+ end $l
+ )
+
+ ;; CHECK: (func $try-delegate-nested-catch-shadowing (type $void)
+ ;; CHECK-NEXT: (block $l
+ ;; CHECK-NEXT: (try $__delegate__l
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (block $l_0
+ ;; CHECK-NEXT: (try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch $empty
+ ;; CHECK-NEXT: (block $l_1
+ ;; CHECK-NEXT: (try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (delegate $__delegate__l)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-delegate-nested-catch-shadowing
+ try $l
+ try $l
+ catch $empty
+ try $l
+ ;; goes to the outermost try, not the middle one
+ delegate $l
+ end
+ end
+ )
+
+ ;; CHECK: (func $try-delegate-nested-catch_all-shadowing (type $void)
+ ;; CHECK-NEXT: (block $l
+ ;; CHECK-NEXT: (try $__delegate__l
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (block $l_0
+ ;; CHECK-NEXT: (try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch_all
+ ;; CHECK-NEXT: (block $l_1
+ ;; CHECK-NEXT: (try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (delegate $__delegate__l)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-delegate-nested-catch_all-shadowing
+ try $l
+ try $l
+ catch_all
+ try $l
+ ;; goes to the outermost try, not the middle one
+ delegate $l
+ end
+ end
+ )
+
+ ;; CHECK: (func $try-br-index (type $void)
+ ;; CHECK-NEXT: (block $label
+ ;; CHECK-NEXT: (try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (br $label)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch $empty
+ ;; CHECK-NEXT: (br $label)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch_all
+ ;; CHECK-NEXT: (br $label)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-br-index
+ try
+ br 0
+ catch $empty
+ br 0
+ catch_all
+ br 0
+ end
+ )
+
+ ;; CHECK: (func $try-br-name (type $void)
+ ;; CHECK-NEXT: (block $l
+ ;; CHECK-NEXT: (try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (br $l)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch $empty
+ ;; CHECK-NEXT: (br $l)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch_all
+ ;; CHECK-NEXT: (br $l)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-br-name
+ try $l
+ br $l
+ catch $l $empty
+ br $l
+ catch_all $l
+ br $l
+ end $l
+ )
+
+ ;; CHECK: (func $try-folded (type $void)
+ ;; CHECK-NEXT: (try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch $empty
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch $timport$0
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch_all
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-folded
+ (try
+ (do
+ nop
+ (nop)
+ )
+ (catch $empty
+ (nop)
+ nop
+ )
+ (catch 1
+ nop
+ (nop)
+ )
+ (catch_all
+ (nop)
+ nop
+ )
+ )
+ )
+
+ ;; CHECK: (func $try-delegate-folded (type $void)
+ ;; CHECK-NEXT: (try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (delegate 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-delegate-folded
+ (try
+ (do)
+ (delegate 0)
+ )
+ )
+
;; CHECK: (func $label-siblings (type $void)
;; CHECK-NEXT: (block $l
;; CHECK-NEXT: (br $l)
@@ -2413,7 +2867,7 @@
(func $ref-func
ref.func $ref-func
drop
- ref.func 109
+ ref.func 129
drop
)