diff options
author | Thomas Lively <tlively@google.com> | 2023-11-30 02:52:01 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-11-29 17:52:01 -0800 |
commit | 71b9cc0b50b119988b7ad3a5f5d2feec4d6c4a95 (patch) | |
tree | 70a560571537f51d78b75f7804259b8881d368bc /src | |
parent | 24742589f5471a5e72755d8fe1da9e49923a35ff (diff) | |
download | binaryen-71b9cc0b50b119988b7ad3a5f5d2feec4d6c4a95.tar.gz binaryen-71b9cc0b50b119988b7ad3a5f5d2feec4d6c4a95.tar.bz2 binaryen-71b9cc0b50b119988b7ad3a5f5d2feec4d6c4a95.zip |
[Parser] Parse try/catch/catch_all/delegate (#6128)
Parse the legacy v3 syntax for try/catch/catch_all/delegate in both its folded
and unfolded forms.
The first sources of significant complexity is the optional IDs after `catch`
and `catch_all` in the unfolded form, which can be confused for tag indices and
require backtracking to parse correctly.
The second source of complexity is the handling of delegate labels, which are
relative to the try's parent scope despite being parsed after the try's scope
has already started. Handling this correctly requires punching a whole big
enough to drive a truck through through both the parser and IRBuilder
abstractions.
Diffstat (limited to 'src')
-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 |
5 files changed, 443 insertions, 44 deletions
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); |