diff options
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); |