diff options
-rwxr-xr-x | scripts/gen-s-parser.py | 10 | ||||
-rw-r--r-- | src/gen-s-parser.inc | 6 | ||||
-rw-r--r-- | src/parser/contexts.h | 37 | ||||
-rw-r--r-- | src/parser/parsers.h | 192 | ||||
-rw-r--r-- | src/wasm-ir-builder.h | 80 | ||||
-rw-r--r-- | src/wasm/wasm-ir-builder.cpp | 172 | ||||
-rw-r--r-- | test/lit/wat-kitchen-sink.wast | 456 |
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 ) |