From 71b9cc0b50b119988b7ad3a5f5d2feec4d6c4a95 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 30 Nov 2023 02:52:01 +0100 Subject: [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. --- src/gen-s-parser.inc | 6 -- src/parser/contexts.h | 37 +++++++-- src/parser/parsers.h | 192 +++++++++++++++++++++++++++++++++++++++---- src/wasm-ir-builder.h | 80 +++++++++++++++++- src/wasm/wasm-ir-builder.cpp | 172 +++++++++++++++++++++++++++++++++++--- 5 files changed, 443 insertions(+), 44 deletions(-) (limited to 'src') 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, BlockTypeT) { return Ok{}; } + template + Result<> makeTry(Index, std::optional, 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 { return name; } - Result getLabelFromIdx(uint32_t idx) { return idx; } + Result getLabelFromIdx(uint32_t idx, bool) { return idx; } - Result getLabelFromName(Name name) { - return irBuilder.getLabelIndex(name); + Result getLabelFromName(Name name, bool inDelegate) { + return irBuilder.getLabelIndex(name, inDelegate); } Result getTagFromIdx(uint32_t idx) { @@ -1109,6 +1116,26 @@ struct ParseDefsCtx : TypeParserCtx { irBuilder.makeLoop(label ? *label : Name{}, type.getSignature().results)); } + Result<> makeTry(Index pos, std::optional 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 Result blocktype(Ctx&); template MaybeResult<> block(Ctx&, bool); template MaybeResult<> ifelse(Ctx&, bool); template MaybeResult<> loop(Ctx&, bool); +template MaybeResult<> trycatch(Ctx&, bool); template Result<> makeUnreachable(Ctx&, Index); template Result<> makeNop(Ctx&, Index); template Result<> makeBinary(Ctx&, Index, BinaryOp op); @@ -113,9 +114,6 @@ template Result<> makeTableSize(Ctx&, Index); template Result<> makeTableGrow(Ctx&, Index); template Result<> makeTableFill(Ctx&, Index); template Result<> makeTableCopy(Ctx&, Index); -template Result<> makeTry(Ctx&, Index); -template -Result<> makeTryOrCatchBody(Ctx&, Index, Type type, bool isTry); template Result<> makeThrow(Ctx&, Index); template Result<> makeRethrow(Ctx&, Index); template Result<> makeTupleMake(Ctx&, Index); @@ -173,7 +171,8 @@ template Result memidx(Ctx&); template MaybeResult maybeMemuse(Ctx&); template Result globalidx(Ctx&); template Result localidx(Ctx&); -template Result labelidx(Ctx&); +template +Result labelidx(Ctx&, bool inDelegate = false); template Result tagidx(Ctx&); template Result typeuse(Ctx&); MaybeResult inlineImport(ParseInput&); @@ -573,6 +572,9 @@ template 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 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 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 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 MaybeResult<> loop(Ctx& ctx, bool folded) { auto pos = ctx.in.getPos(); @@ -895,6 +902,163 @@ template 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 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 Result<> makeUnreachable(Ctx& ctx, Index pos) { return ctx.makeUnreachable(pos); } @@ -1266,15 +1430,6 @@ template Result<> makeTableCopy(Ctx& ctx, Index pos) { return ctx.in.err("unimplemented instruction"); } -template Result<> makeTry(Ctx& ctx, Index pos) { - return ctx.in.err("unimplemented instruction"); -} - -template -Result<> makeTryOrCatchBody(Ctx& ctx, Index pos, Type type, bool isTry) { - return ctx.in.err("unimplemented instruction"); -} - template Result<> makeThrow(Ctx& ctx, Index pos) { auto tag = tagidx(ctx); CHECK_ERR(tag); @@ -1621,12 +1776,13 @@ template Result localidx(Ctx& ctx) { // labelidx ::= x:u32 => x // | v:id => x (if labels[x] = v) -template Result labelidx(Ctx& ctx) { +template +Result 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 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 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; + struct TryScope { + Try* tryy; + Name originalLabel; + }; + struct CatchScope { + Try* tryy; + Name originalLabel; + }; + struct CatchAllScope { + Try* tryy; + Name originalLabel; + }; + using Scope = std::variant; // 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(&scope); } Function* getFunction() { @@ -296,6 +332,24 @@ private: } return nullptr; } + Try* getTry() { + if (auto* tryScope = std::get_if(&scope)) { + return tryScope->tryy; + } + return nullptr; + } + Try* getCatch() { + if (auto* catchScope = std::get_if(&scope)) { + return catchScope->tryy; + } + return nullptr; + } + Try* getCatchAll() { + if (auto* catchAllScope = std::get_if(&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(&scope)) { + return tryScope->originalLabel; + } + if (auto* catchScope = std::get_if(&scope)) { + return catchScope->originalLabel; + } + if (auto* catchAllScope = std::get_if(&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(&scope.scope)) { + if (scope.isNone()) { std::cerr << "none"; - } else if (auto* f = std::get_if(&scope.scope)) { - std::cerr << "func " << f->func->name; - } else if (std::get_if(&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(&scope.scope)) { + } else if (scope.getIf()) { std::cerr << "if"; - } else if (std::get_if(&scope.scope)) { + } else if (scope.getElse()) { std::cerr << "else"; - } else if (std::get_if(&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 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 IRBuilder::getLabelIndex(Name label) { +Result 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 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(); + tryy->type = type; + return visitTryStart(tryy, label); +} Result<> IRBuilder::makeThrow(Name tag) { Throw curr(wasm.allocator); -- cgit v1.2.3