From abba33d5c27e4bb4eb574606eebe9b768918423c Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 13 Dec 2024 10:38:34 -0800 Subject: [PATCH 1/3] Support control flow inputs in IRBuilder Since multivalue was standardized, WebAssembly has supported not only multiple results but also an arbitrary number of inputs on control flow structures, but until now Binaryen did not support control flow input. Binaryen IR still has no way to represent control flow input, so lower it away using scratch locals in IRBuilder. Since both the text and binary parsers use IRBuilder, this gives us full support for parsing control flow inputs. The lowering scheme is mostly simple. A local.set writing the control flow inputs to a scratch local is inserted immediately before the control flow structure begins and a local.get retrieving those inputs is inserted inside the control flow structure before the rest of its body. The only complications come from ifs, in which the inputs must be retrieved at the beginning of both arms, and from loops, where branches to the beginning of the loop must be transformed so their values are written to the scratch local along the way. Resolves #6407. --- src/parser/contexts.h | 45 +- src/wasm-binary.h | 4 +- src/wasm-ir-builder.h | 95 ++- src/wasm/wasm-binary.cpp | 40 +- src/wasm/wasm-ir-builder.cpp | 129 ++-- test/lit/binary/bad-multivalue-block.test | 16 - .../lit/binary/bad-multivalue-block.test.wasm | Bin 34 -> 0 bytes test/lit/binary/bad-multivalue-if.test | 22 - test/lit/binary/bad-multivalue-if.test.wasm | Bin 38 -> 0 bytes test/lit/control-flow-input.wast | 623 ++++++++++++++++++ test/lit/control-flow-input.wast.wasm | Bin 0 -> 793 bytes test/lit/parse-bad-block-params.wast | 12 - 12 files changed, 824 insertions(+), 162 deletions(-) delete mode 100644 test/lit/binary/bad-multivalue-block.test delete mode 100644 test/lit/binary/bad-multivalue-block.test.wasm delete mode 100644 test/lit/binary/bad-multivalue-if.test delete mode 100644 test/lit/binary/bad-multivalue-if.test.wasm create mode 100644 test/lit/control-flow-input.wast create mode 100644 test/lit/control-flow-input.wast.wasm delete mode 100644 test/lit/parse-bad-block-params.wast diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 807b6c0030e..3e0bc7c4094 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -1447,11 +1447,7 @@ struct ParseDefsCtx : TypeParserCtx { Result getBlockTypeFromTypeUse(Index pos, HeapType type) { assert(type.isSignature()); - if (type.getSignature().params != Type::none) { - return in.err(pos, "block parameters not yet supported"); - } - // TODO: Once we support block parameters, return an error here if any of - // them are named. + // TODO: Error if block parameters are named return type; } @@ -1822,9 +1818,11 @@ struct ParseDefsCtx : TypeParserCtx { HeapType type) { // TODO: validate labels? // TODO: Move error on input types to here? - return withLoc(pos, - irBuilder.makeBlock(label ? *label : Name{}, - type.getSignature().results)); + if (!type.isSignature()) { + return in.err(pos, "expected function type"); + } + return withLoc( + pos, irBuilder.makeBlock(label ? *label : Name{}, type.getSignature())); } Result<> makeIf(Index pos, @@ -1832,10 +1830,11 @@ struct ParseDefsCtx : TypeParserCtx { std::optional label, HeapType type) { // TODO: validate labels? - // TODO: Move error on input types to here? + if (!type.isSignature()) { + return in.err(pos, "expected function type"); + } return withLoc( - pos, - irBuilder.makeIf(label ? *label : Name{}, type.getSignature().results)); + pos, irBuilder.makeIf(label ? *label : Name{}, type.getSignature())); } Result<> visitElse() { return withLoc(irBuilder.visitElse()); } @@ -1845,10 +1844,11 @@ struct ParseDefsCtx : TypeParserCtx { std::optional label, HeapType type) { // TODO: validate labels? - // TODO: Move error on input types to here? + if (!type.isSignature()) { + return in.err(pos, "expected function type"); + } return withLoc( - pos, - irBuilder.makeLoop(label ? *label : Name{}, type.getSignature().results)); + pos, irBuilder.makeLoop(label ? *label : Name{}, type.getSignature())); } Result<> makeTry(Index pos, @@ -1856,10 +1856,11 @@ struct ParseDefsCtx : TypeParserCtx { std::optional label, HeapType type) { // TODO: validate labels? - // TODO: Move error on input types to here? + if (!type.isSignature()) { + return in.err(pos, "expected function type"); + } return withLoc( - pos, - irBuilder.makeTry(label ? *label : Name{}, type.getSignature().results)); + pos, irBuilder.makeTry(label ? *label : Name{}, type.getSignature())); } Result<> makeTryTable(Index pos, @@ -1875,12 +1876,10 @@ struct ParseDefsCtx : TypeParserCtx { labels.push_back(info.label); isRefs.push_back(info.isRef); } - return withLoc(pos, - irBuilder.makeTryTable(label ? *label : Name{}, - type.getSignature().results, - tags, - labels, - isRefs)); + return withLoc( + pos, + irBuilder.makeTryTable( + label ? *label : Name{}, type.getSignature(), tags, labels, isRefs)); } Result<> visitCatch(Index pos, Name tag) { diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 163a62b1e41..61f4faf69e2 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1467,10 +1467,12 @@ class WasmBinaryReader { bool getBasicType(int32_t code, Type& out); bool getBasicHeapType(int64_t code, HeapType& out); + // Get the signature of control flow structure. + Signature getBlockType(); // Read a value and get a type for it. Type getType(); // Get a type given the initial S32LEB has already been read, and is provided. - Type getType(int initial); + Type getType(int code); HeapType getHeapType(); HeapType getIndexedHeapType(); diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index 84ac2697930..250d5d17c18 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -80,15 +80,18 @@ class IRBuilder : public UnifiedExpressionVisitor> { // the corresponding `makeXYZ` function below instead of `visitXYZStart`, but // either way must call `visitEnd` and friends at the appropriate times. Result<> visitFunctionStart(Function* func); - Result<> visitBlockStart(Block* block); - Result<> visitIfStart(If* iff, Name label = {}); + Result<> visitBlockStart(Block* block, Type inputType = Type::none); + Result<> visitIfStart(If* iff, Name label = {}, Type inputType = Type::none); Result<> visitElse(); - Result<> visitLoopStart(Loop* iff); - Result<> visitTryStart(Try* tryy, Name label = {}); + Result<> visitLoopStart(Loop* iff, Type inputType = Type::none); + Result<> + visitTryStart(Try* tryy, Name label = {}, Type inputType = Type::none); Result<> visitCatch(Name tag); Result<> visitCatchAll(); Result<> visitDelegate(Index label); - Result<> visitTryTableStart(TryTable* trytable, Name label = {}); + Result<> visitTryTableStart(TryTable* trytable, + Name label = {}, + Type inputType = Type::none); Result<> visitEnd(); // Used to visit break nodes when traversing a single block without its @@ -113,9 +116,9 @@ class IRBuilder : public UnifiedExpressionVisitor> { // nodes. This is generally safer than calling `visit` because the function // signatures ensure that there are no missing fields. Result<> makeNop(); - Result<> makeBlock(Name label, Type type); - Result<> makeIf(Name label, Type type); - Result<> makeLoop(Name label, Type type); + Result<> makeBlock(Name label, Signature sig); + Result<> makeIf(Name label, Signature sig); + Result<> makeLoop(Name label, Signature sig); Result<> makeBreak(Index label, bool isConditional); Result<> makeSwitch(const std::vector& labels, Index defaultLabel); // Unlike Builder::makeCall, this assumes the function already exists. @@ -180,9 +183,9 @@ class IRBuilder : public UnifiedExpressionVisitor> { Result<> makeTableFill(Name table); Result<> makeTableCopy(Name destTable, Name srcTable); Result<> makeTableInit(Name elem, Name table); - Result<> makeTry(Name label, Type type); + Result<> makeTry(Name label, Signature sig); Result<> makeTryTable(Name label, - Type type, + Signature sig, const std::vector& tags, const std::vector& labels, const std::vector& isRefs); @@ -323,13 +326,21 @@ class IRBuilder : public UnifiedExpressionVisitor> { // The branch label name for this scope. Always fresh, never shadowed. Name label; + // For Try/Catch/CatchAll scopes, we need to separately track a label used // for branches, since the normal label is only used for delegates. Name branchLabel; bool labelUsed = false; + // If the control flow scope has an input type, we need to lower it using a + // scratch local because we cannot represent control flow input in the IR. + Type inputType; + Index inputLocal = -1; + + // The stack of instructions being built in this scope. std::vector exprStack; + // Whether we have seen an unreachable instruction and are in // stack-polymorphic unreachable mode. bool unreachable = false; @@ -338,29 +349,39 @@ class IRBuilder : public UnifiedExpressionVisitor> { size_t startPos = 0; ScopeCtx() : scope(NoScope{}) {} - ScopeCtx(Scope scope) : scope(scope) {} - ScopeCtx(Scope scope, Name label, bool labelUsed) - : scope(scope), label(label), labelUsed(labelUsed) {} + ScopeCtx(Scope scope, Type inputType) + : scope(scope), inputType(inputType) {} + ScopeCtx( + Scope scope, Name label, bool labelUsed, Type inputType, Index inputLocal) + : scope(scope), label(label), labelUsed(labelUsed), inputType(inputType), + inputLocal(inputLocal) {} ScopeCtx(Scope scope, Name label, bool labelUsed, Name branchLabel) : scope(scope), label(label), branchLabel(branchLabel), labelUsed(labelUsed) {} static ScopeCtx makeFunc(Function* func) { - return ScopeCtx(FuncScope{func}); + return ScopeCtx(FuncScope{func}, Type::none); } - static ScopeCtx makeBlock(Block* block) { - return ScopeCtx(BlockScope{block}); + static ScopeCtx makeBlock(Block* block, Type inputType) { + return ScopeCtx(BlockScope{block}, inputType); } - static ScopeCtx makeIf(If* iff, Name originalLabel = {}) { - return ScopeCtx(IfScope{iff, originalLabel}); + static ScopeCtx makeIf(If* iff, Name originalLabel, Type inputType) { + return ScopeCtx(IfScope{iff, originalLabel}, inputType); } - static ScopeCtx - makeElse(If* iff, Name originalLabel, Name label, bool labelUsed) { - return ScopeCtx(ElseScope{iff, originalLabel}, label, labelUsed); + static ScopeCtx makeElse(If* iff, + Name originalLabel, + Name label, + bool labelUsed, + Type inputType, + Index inputLocal) { + return ScopeCtx( + ElseScope{iff, originalLabel}, label, labelUsed, inputType, inputLocal); } - static ScopeCtx makeLoop(Loop* loop) { return ScopeCtx(LoopScope{loop}); } - static ScopeCtx makeTry(Try* tryy, Name originalLabel = {}) { - return ScopeCtx(TryScope{tryy, originalLabel}); + static ScopeCtx makeLoop(Loop* loop, Type inputType) { + return ScopeCtx(LoopScope{loop}, inputType); + } + static ScopeCtx makeTry(Try* tryy, Name originalLabel, Type inputType) { + return ScopeCtx(TryScope{tryy, originalLabel}, inputType); } static ScopeCtx makeCatch(Try* tryy, Name originalLabel, @@ -378,8 +399,9 @@ class IRBuilder : public UnifiedExpressionVisitor> { return ScopeCtx( CatchAllScope{tryy, originalLabel}, label, labelUsed, branchLabel); } - static ScopeCtx makeTryTable(TryTable* trytable, Name originalLabel = {}) { - return ScopeCtx(TryTableScope{trytable, originalLabel}); + static ScopeCtx + makeTryTable(TryTable* trytable, Name originalLabel, Type inputType) { + return ScopeCtx(TryTableScope{trytable, originalLabel}, inputType); } bool isNone() { return std::get_if(&scope); } @@ -518,6 +540,7 @@ class IRBuilder : public UnifiedExpressionVisitor> { } WASM_UNREACHABLE("unexpected scope kind"); } + bool isDelimiter() { return getElse() || getCatch() || getCatchAll(); } }; // The stack of block contexts currently being parsed. @@ -541,7 +564,7 @@ class IRBuilder : public UnifiedExpressionVisitor> { Index blockHint = 0; Index labelHint = 0; - void pushScope(ScopeCtx scope) { + Result<> pushScope(ScopeCtx&& scope) { if (auto label = scope.getOriginalLabel()) { // Assign a fresh label to the scope, if necessary. if (!scope.label) { @@ -554,7 +577,21 @@ class IRBuilder : public UnifiedExpressionVisitor> { scope.startPos = lastBinaryPos; lastBinaryPos = *binaryPos; } - scopeStack.push_back(scope); + bool hasInput = scope.inputType != Type::none; + Index inputLocal = scope.inputLocal; + if (hasInput && !scope.isDelimiter()) { + if (inputLocal == Index(-1)) { + auto scratch = addScratchLocal(scope.inputType); + CHECK_ERR(scratch); + inputLocal = scope.inputLocal = *scratch; + } + CHECK_ERR(makeLocalSet(inputLocal)); + } + scopeStack.emplace_back(std::move(scope)); + if (hasInput) { + CHECK_ERR(makeLocalGet(inputLocal)); + } + return Ok{}; } ScopeCtx& getScope() { @@ -610,6 +647,8 @@ class IRBuilder : public UnifiedExpressionVisitor> { Result getLabelType(Index label); Result getLabelType(Name labelName); + void fixLoopWithInput(Loop* loop, Type inputType, Index scratch); + void dump(); }; diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 0c931f51864..791dc53d777 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -2121,30 +2121,30 @@ bool WasmBinaryReader::getBasicHeapType(int64_t code, HeapType& out) { } } -Type WasmBinaryReader::getType(int initial) { - // Single value types are negative; signature indices are non-negative - if (initial >= 0) { - // TODO: Handle block input types properly. - auto sig = getSignatureByTypeIndex(initial); - if (sig.params != Type::none) { - throwError("control flow inputs are not supported yet"); - } - return sig.results; +Signature WasmBinaryReader::getBlockType() { + // Single value types are negative; signature indices are non-negative. + auto code = getS32LEB(); + if (code >= 0) { + return getSignatureByTypeIndex(code); + } + if (code == BinaryConsts::EncodedType::Empty) { + return Signature(); } + return Signature(Type::none, getType(code)); +} + +Type WasmBinaryReader::getType(int code) { Type type; - if (getBasicType(initial, type)) { + if (getBasicType(code, type)) { return type; } - switch (initial) { - // None only used for block signatures. TODO: Separate out? - case BinaryConsts::EncodedType::Empty: - return Type::none; + switch (code) { case BinaryConsts::EncodedType::nullable: return Type(getHeapType(), Nullable); case BinaryConsts::EncodedType::nonnullable: return Type(getHeapType(), NonNullable); default: - throwError("invalid wasm type: " + std::to_string(initial)); + throwError("invalid wasm type: " + std::to_string(code)); } WASM_UNREACHABLE("unexpected type"); } @@ -2885,11 +2885,11 @@ Result<> WasmBinaryReader::readInst() { uint8_t code = getInt8(); switch (code) { case BinaryConsts::Block: - return builder.makeBlock(Name(), getType()); + return builder.makeBlock(Name(), getBlockType()); case BinaryConsts::If: - return builder.makeIf(Name(), getType()); + return builder.makeIf(Name(), getBlockType()); case BinaryConsts::Loop: - return builder.makeLoop(Name(), getType()); + return builder.makeLoop(Name(), getBlockType()); case BinaryConsts::Br: return builder.makeBreak(getU32LEB(), false); case BinaryConsts::BrIf: @@ -2974,9 +2974,9 @@ Result<> WasmBinaryReader::readInst() { case BinaryConsts::TableSet: return builder.makeTableSet(getTableName(getU32LEB())); case BinaryConsts::Try: - return builder.makeTry(Name(), getType()); + return builder.makeTry(Name(), getBlockType()); case BinaryConsts::TryTable: { - auto type = getType(); + auto type = getBlockType(); std::vector tags; std::vector labels; std::vector isRefs; diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 96212ccd7fc..6cd62e43991 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -679,9 +679,8 @@ Result<> IRBuilder::visitExpression(Expression* curr) { Result IRBuilder::getLabelType(Index label) { auto scope = getScope(label); CHECK_ERR(scope); - // Loops would receive their input type rather than their output type, if we - // supported that. - return (*scope)->getLoop() ? Type::none : (*scope)->getResultType(); + // Loops receive their input type rather than their output type. + return (*scope)->getLoop() ? (*scope)->inputType : (*scope)->getResultType(); } Result IRBuilder::getLabelType(Name labelName) { @@ -722,35 +721,31 @@ Result<> IRBuilder::visitFunctionStart(Function* func) { return Ok{}; } -Result<> IRBuilder::visitBlockStart(Block* curr) { +Result<> IRBuilder::visitBlockStart(Block* curr, Type inputType) { applyDebugLoc(curr); - pushScope(ScopeCtx::makeBlock(curr)); - return Ok{}; + return pushScope(ScopeCtx::makeBlock(curr, inputType)); } -Result<> IRBuilder::visitIfStart(If* iff, Name label) { +Result<> IRBuilder::visitIfStart(If* iff, Name label, Type inputType) { applyDebugLoc(iff); CHECK_ERR(visitIf(iff)); - pushScope(ScopeCtx::makeIf(iff, label)); - return Ok{}; + return pushScope(ScopeCtx::makeIf(iff, label, inputType)); } -Result<> IRBuilder::visitLoopStart(Loop* loop) { +Result<> IRBuilder::visitLoopStart(Loop* loop, Type inputType) { applyDebugLoc(loop); - pushScope(ScopeCtx::makeLoop(loop)); - return Ok{}; + return pushScope(ScopeCtx::makeLoop(loop, inputType)); } -Result<> IRBuilder::visitTryStart(Try* tryy, Name label) { +Result<> IRBuilder::visitTryStart(Try* tryy, Name label, Type inputType) { applyDebugLoc(tryy); - pushScope(ScopeCtx::makeTry(tryy, label)); - return Ok{}; + return pushScope(ScopeCtx::makeTry(tryy, label, inputType)); } -Result<> IRBuilder::visitTryTableStart(TryTable* trytable, Name label) { +Result<> +IRBuilder::visitTryTableStart(TryTable* trytable, Name label, Type inputType) { applyDebugLoc(trytable); - pushScope(ScopeCtx::makeTryTable(trytable, label)); - return Ok{}; + return pushScope(ScopeCtx::makeTryTable(trytable, label, inputType)); } Result IRBuilder::finishScope(Block* block) { @@ -849,6 +844,8 @@ Result<> IRBuilder::visitElse() { auto originalLabel = scope.getOriginalLabel(); auto label = scope.label; auto labelUsed = scope.labelUsed; + auto inputType = scope.inputType; + auto inputLocal = scope.inputLocal; auto expr = finishScope(); CHECK_ERR(expr); iff->ifTrue = *expr; @@ -858,8 +855,8 @@ Result<> IRBuilder::visitElse() { lastBinaryPos - codeSectionOffset; } - pushScope(ScopeCtx::makeElse(iff, originalLabel, label, labelUsed)); - return Ok{}; + return pushScope(ScopeCtx::makeElse( + iff, originalLabel, label, labelUsed, inputType, inputLocal)); } Result<> IRBuilder::visitCatch(Name tag) { @@ -891,8 +888,8 @@ Result<> IRBuilder::visitCatch(Name tag) { delimiterLocs[delimiterLocs.size()] = lastBinaryPos - codeSectionOffset; } - pushScope( - ScopeCtx::makeCatch(tryy, originalLabel, label, labelUsed, branchLabel)); + CHECK_ERR(pushScope( + ScopeCtx::makeCatch(tryy, originalLabel, label, labelUsed, branchLabel))); // Push a pop for the exception payload if necessary. auto params = wasm.getTag(tag)->sig.params; if (params != Type::none) { @@ -933,9 +930,8 @@ Result<> IRBuilder::visitCatchAll() { delimiterLocs[delimiterLocs.size()] = lastBinaryPos - codeSectionOffset; } - pushScope( + return pushScope( ScopeCtx::makeCatchAll(tryy, originalLabel, label, labelUsed, branchLabel)); - return Ok{}; } Result<> IRBuilder::visitDelegate(Index label) { @@ -1035,11 +1031,24 @@ Result<> IRBuilder::visitEnd() { } else if (auto* loop = scope.getLoop()) { loop->body = *expr; loop->name = scope.label; + if (scope.inputType != Type::none && scope.labelUsed) { + // Branches to this loop carry values, but Binaryen IR does not support + // that. Fix this by trampolining the branches through new code that sets + // the branch value to the appropriate scratch local. + fixLoopWithInput(loop, scope.inputType, scope.inputLocal); + } loop->finalize(loop->type); push(loop); } else if (auto* iff = scope.getIf()) { iff->ifTrue = *expr; - iff->ifFalse = nullptr; + if (scope.inputType != Type::none) { + // Normally an if without an else must have type none, but if there is an + // input parameter, the empty else arm must propagate its value. + // Synthesize an else arm that loads the value from the scratch local. + iff->ifFalse = builder.makeLocalGet(scope.inputLocal, scope.inputType); + } else { + iff->ifFalse = nullptr; + } iff->finalize(iff->type); push(maybeWrapForLabel(iff)); } else if (auto* iff = scope.getElse()) { @@ -1067,6 +1076,46 @@ Result<> IRBuilder::visitEnd() { return Ok{}; } +// Branches to this loop need to be trampolined through code that sets the value +// carried by the branch to the appropriate scratch local before branching to +// the loop. Transform this: +// +// (loop $l (param t1) (result t2) ...) +// +// to this: +// +// (loop $l0 (result t2) +// (block $l1 (result t2) +// (local.set $scratch ;; set the branch values to the scratch local +// (block $l (result t1) +// (br $l1 ;; exit the loop with the fallthrough value, if any. +// ... ;; contains branches to $l +// ) +// ) +// ) +// (br $l0) ;; continue the loop +// ) +// ) +void IRBuilder::fixLoopWithInput(Loop* loop, Type inputType, Index scratch) { + auto l = loop->name; + auto l0 = makeFresh(l, 0); + auto l1 = makeFresh(l, 1); + + Block* inner = + loop->type == Type::none + ? builder.blockifyWithName( + loop->body, l, builder.makeBreak(l1), inputType) + : builder.makeBlock(l, {builder.makeBreak(l1, loop->body)}, inputType); + + Block* outer = builder.makeBlock( + l1, + {builder.makeLocalSet(scratch, inner), builder.makeBreak(l0)}, + loop->type); + + loop->body = outer; + loop->name = l0; +} + Result IRBuilder::getLabelIndex(Name label, bool inDelegate) { auto it = labelDepths.find(label); if (it == labelDepths.end() || it->second.empty()) { @@ -1128,24 +1177,24 @@ Result<> IRBuilder::makeNop() { return Ok{}; } -Result<> IRBuilder::makeBlock(Name label, Type type) { +Result<> IRBuilder::makeBlock(Name label, Signature sig) { auto* block = wasm.allocator.alloc(); block->name = label; - block->type = type; - return visitBlockStart(block); + block->type = sig.results; + return visitBlockStart(block, sig.params); } -Result<> IRBuilder::makeIf(Name label, Type type) { +Result<> IRBuilder::makeIf(Name label, Signature sig) { auto* iff = wasm.allocator.alloc(); - iff->type = type; - return visitIfStart(iff, label); + iff->type = sig.results; + return visitIfStart(iff, label, sig.params); } -Result<> IRBuilder::makeLoop(Name label, Type type) { +Result<> IRBuilder::makeLoop(Name label, Signature sig) { auto* loop = wasm.allocator.alloc(); loop->name = label; - loop->type = type; - return visitLoopStart(loop); + loop->type = sig.results; + return visitLoopStart(loop, sig.params); } Result<> IRBuilder::makeBreak(Index label, bool isConditional) { @@ -1584,19 +1633,19 @@ Result<> IRBuilder::makeTableInit(Name elem, Name table) { return Ok{}; } -Result<> IRBuilder::makeTry(Name label, Type type) { +Result<> IRBuilder::makeTry(Name label, Signature sig) { auto* tryy = wasm.allocator.alloc(); - tryy->type = type; - return visitTryStart(tryy, label); + tryy->type = sig.results; + return visitTryStart(tryy, label, sig.params); } Result<> IRBuilder::makeTryTable(Name label, - Type type, + Signature sig, const std::vector& tags, const std::vector& labels, const std::vector& isRefs) { auto* trytable = wasm.allocator.alloc(); - trytable->type = type; + trytable->type = sig.results; trytable->catchTags.set(tags); trytable->catchRefs.set(isRefs); trytable->catchDests.reserve(labels.size()); @@ -1605,7 +1654,7 @@ Result<> IRBuilder::makeTryTable(Name label, CHECK_ERR(name); trytable->catchDests.push_back(*name); } - return visitTryTableStart(trytable, label); + return visitTryTableStart(trytable, label, sig.params); } Result<> IRBuilder::makeThrow(Name tag) { diff --git a/test/lit/binary/bad-multivalue-block.test b/test/lit/binary/bad-multivalue-block.test deleted file mode 100644 index 8b100fe89ed..00000000000 --- a/test/lit/binary/bad-multivalue-block.test +++ /dev/null @@ -1,16 +0,0 @@ -;; Test that we error properly on a block with a bad multivalue (inputs). - -;; File contents: -;; -;; (module -;; (func $test -;; i32.const 0 -;; (block (param i32) -;; drop -;; ) -;; ) -;; ) - -;; RUN: not wasm-opt -all %s.wasm 2>&1 | filecheck %s - -;; CHECK: control flow inputs are not supported yet diff --git a/test/lit/binary/bad-multivalue-block.test.wasm b/test/lit/binary/bad-multivalue-block.test.wasm deleted file mode 100644 index e44b9033f2001cdaa291bb87f198d97958e2ac00..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34 pcmZQbEY4+QU|?Y6U`k+MNMNjIU}j=u;NoKBU~pt$VwB?M1^`B110?_e diff --git a/test/lit/binary/bad-multivalue-if.test b/test/lit/binary/bad-multivalue-if.test deleted file mode 100644 index 8fe20601285..00000000000 --- a/test/lit/binary/bad-multivalue-if.test +++ /dev/null @@ -1,22 +0,0 @@ -;; Test that we error properly on an if with a bad multivalue (inputs). - -;; File contents: -;; -;; (module -;; (func $test -;; i32.const 0 -;; i32.const 1 -;; (if (param i32) -;; (then -;; drop -;; ) -;; (else -;; drop -;; ) -;; ) -;; ) -;; ) - -;; RUN: not wasm-opt -all %s.wasm 2>&1 | filecheck %s - -;; CHECK: control flow inputs are not supported yet diff --git a/test/lit/binary/bad-multivalue-if.test.wasm b/test/lit/binary/bad-multivalue-if.test.wasm deleted file mode 100644 index baddfec4ed77c13dfb6d31fde432c4891f6a7dfa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 38 tcmZQbEY4+QU|?Y6U`k+MNMNjIU}j=u;NoNCVQ^${WMpBKVwK|N1^`u(1CRg! diff --git a/test/lit/control-flow-input.wast b/test/lit/control-flow-input.wast new file mode 100644 index 00000000000..7baae35f0ef --- /dev/null +++ b/test/lit/control-flow-input.wast @@ -0,0 +1,623 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; Check that control flow input is correctly parsed using scratch locals. The +;; binary input file is generated from this file using WABT's wat2wasm +;; --enable-all --debug-names and should be regenerated when new tests are added +;; here. + +;; RUN: wasm-opt -all %s -S -o - | filecheck %s +;; RUN: wasm-opt -all %s.wasm -S -o - | filecheck %s + +(module + (type $id (func (param i32) (result i32))) + + ;; CHECK: (tag $e (param i32)) + (tag $e (param i32)) + + ;; CHECK: (func $block (type $0) (result i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $block (result i32) + i32.const 0 + block (param i32) (result i32) + end + ) + + ;; CHECK: (func $block-multivalue (type $1) (result i32 i64) + ;; CHECK-NEXT: (local $scratch (tuple i32 i64)) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (type $1) (result i32 i64) + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $block-multivalue (result i32 i64) + i32.const 0 + i64.const 1 + block (param i32 i64) (result i32 i64) + end + ) + + ;; CHECK: (func $block-drop (type $2) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $block-drop + i32.const 0 + block (param i32) + drop + end + ) + + ;; CHECK: (func $block-multivalue-drop (type $2) + ;; CHECK-NEXT: (local $scratch (tuple i32 i64)) + ;; CHECK-NEXT: (local $scratch_1 (tuple i32 i64)) + ;; CHECK-NEXT: (local $scratch_2 i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_2 + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.tee $scratch_1 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $scratch_1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $block-multivalue-drop + i32.const 0 + i64.const 1 + block (param i32 i64) + drop + drop + end + ) + + ;; CHECK: (func $block-passthrough-nop (type $0) (result i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local $scratch_1 i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_1 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (local.get $scratch_1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $block-passthrough-nop (result i32) + i32.const 0 + block (param i32) (result i32) + nop + end + ) + + ;; CHECK: (func $block-passthrough-type (type $0) (result i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $block-passthrough-type (result i32) + i32.const 0 + block (type $id) + end + ) + + ;; CHECK: (func $loop (type $0) (result i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (loop (result i32) + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $loop (result i32) + i32.const 0 + loop (param i32) (result i32) + end + ) + + ;; CHECK: (func $loop-branch (type $2) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (loop $label0 + ;; CHECK-NEXT: (block $label1 + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (block $label (result i32) + ;; CHECK-NEXT: (br $label + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $label1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $label0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $loop-branch + i32.const 0 + loop (param i32) + br 0 + end + ) + + ;; CHECK: (func $loop-branch-cond (type $0) (result i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (loop $label0 (result i32) + ;; CHECK-NEXT: (block $label1 (result i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (block $label (result i32) + ;; CHECK-NEXT: (br $label1 + ;; CHECK-NEXT: (br_if $label + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $label0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $loop-branch-cond (result i32) + i32.const 0 + loop (param i32) (result i32) + i32.const 1 + br_if 0 + end + ) + + ;; CHECK: (func $loop-branch-cond-drop (type $2) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (loop $label0 + ;; CHECK-NEXT: (block $label1 + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (block $label (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br_if $label + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $label1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $label0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $loop-branch-cond-drop + i32.const 0 + loop (param i32) + i32.const 1 + br_if 0 + drop + end + ) + + ;; CHECK: (func $loop-branch-cond-new-val (type $4) (result i64) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (loop $label0 (result i64) + ;; CHECK-NEXT: (block $label1 (result i64) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (block $label (result i32) + ;; CHECK-NEXT: (br $label1 + ;; CHECK-NEXT: (block (result i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br_if $label + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $label0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $loop-branch-cond-new-val (result i64) + i32.const 0 + loop (param i32) (result i64) + i32.const 1 + br_if 0 + drop + i64.const 2 + end + ) + + ;; CHECK: (func $nested-loops-multivalue (type $1) (result i32 i64) + ;; CHECK-NEXT: (local $scratch (tuple i32 i64)) + ;; CHECK-NEXT: (local $scratch_1 (tuple i32 i64)) + ;; CHECK-NEXT: (block $label2 (type $1) (result i32 i64) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (loop $label10 + ;; CHECK-NEXT: (block $label11 + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (block $label1 (type $1) (result i32 i64) + ;; CHECK-NEXT: (local.set $scratch_1 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (loop $label0 + ;; CHECK-NEXT: (block $label3 + ;; CHECK-NEXT: (local.set $scratch_1 + ;; CHECK-NEXT: (block $label (type $1) (result i32 i64) + ;; CHECK-NEXT: (br_table $label $label1 $label2 + ;; CHECK-NEXT: (local.get $scratch_1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $label3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $label0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $label11) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $label10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $nested-loops-multivalue (result i32 i64) + i32.const 0 + i64.const 1 + loop (param i32 i64) + loop (param i32 i64) + i32.const 2 + br_table 0 1 2 + end + end + unreachable + ) + + ;; CHECK: (func $if (type $0) (result i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if (result i32) + i32.const 0 + i32.const 1 + if (param i32) (result i32) + end + ) + + ;; CHECK: (func $if-new-val (type $0) (result i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-new-val (result i32) + i32.const 0 + i32.const 1 + if (param i32) (result i32) + drop + i32.const 2 + end + ) + + ;; CHECK: (func $if-multivalue (type $1) (result i32 i64) + ;; CHECK-NEXT: (local $scratch (tuple i32 i64)) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if (type $1) (result i32 i64) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-multivalue (result i32 i64) + i32.const 0 + i64.const 1 + i32.const 2 + if (param i32 i64) (result i32 i64) + end + ) + + ;; CHECK: (func $if-else (type $0) (result i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-else (result i32) + i32.const 0 + i32.const 1 + if (param i32) (result i32) + else + end + ) + + ;; CHECK: (func $if-else-drop (type $2) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-else-drop + i32.const 0 + i32.const 1 + if (param i32) + drop + else + drop + end + ) + + ;; CHECK: (func $if-else-multivalue (type $5) (result f32) + ;; CHECK-NEXT: (local $scratch (tuple i32 i64)) + ;; CHECK-NEXT: (local $scratch_1 (tuple i32 i64)) + ;; CHECK-NEXT: (local $scratch_2 i32) + ;; CHECK-NEXT: (local $scratch_3 (tuple i32 i64)) + ;; CHECK-NEXT: (local $scratch_4 i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if (result f32) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_2 + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.tee $scratch_1 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $scratch_1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_4 + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.tee $scratch_3 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f32.const 4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-else-multivalue (result f32) + i32.const 0 + i64.const 1 + i32.const 2 + if (param i32 i64) (result f32) + drop + drop + f32.const 3 + else + drop + drop + f32.const 4 + end + ) + + ;; CHECK: (func $try (type $0) (result i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (try (result i32) + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $try (result i32) + i32.const 0 + try (param i32) (result i32) + end + ) + + ;; CHECK: (func $try-catch (type $0) (result i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (try (result i32) + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $e + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $try-catch (result i32) + i32.const 0 + try (param i32) (result i32) + catch $e + end + ) + + ;; CHECK: (func $try-catch-all (type $0) (result i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (try (result i32) + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch_all + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $try-catch-all (result i32) + i32.const 0 + try (param i32) (result i32) + catch_all + i32.const 1 + end + ) + + ;; CHECK: (func $try-catch-delegate (type $0) (result i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (try (result i32) + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (delegate 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $try-catch-delegate (result i32) + i32.const 0 + try (param i32) (result i32) + delegate 0 + ) + + ;; CHECK: (func $try-table (type $0) (result i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (try_table (result i32) + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $try-table (result i32) + i32.const 0 + try_table (param i32) (result i32) + end + ) +) diff --git a/test/lit/control-flow-input.wast.wasm b/test/lit/control-flow-input.wast.wasm new file mode 100644 index 0000000000000000000000000000000000000000..664d1be843a4e81a76bd4dc15f445160e49a08c4 GIT binary patch literal 793 zcmZWnOP1O&5G=JU8!Ve)7=H04C-BQEdrU5Z!J2^=WAm^{-YjOw0dkR?D;G&iW&)Wk z++9^&EpfGmweBQ8VeCW*t>64M%S)dW5hH7t$VALIvRGKm zy#q^njx0&*G1H}xzVtI15Cltb*Jr}Jv|5y8Tw1FqMOy{BWSfsf!Keo3#&_eT7t8PvM0H($ljrF4HP+ugUk zJQ)f*r8w3HIeA}At?XUoL*6v)_E?{uwrN!#)UB(1;LYfIAFvj}aa)#spJ%iru?Hd;D_EAL7TOLI3~& literal 0 HcmV?d00001 diff --git a/test/lit/parse-bad-block-params.wast b/test/lit/parse-bad-block-params.wast deleted file mode 100644 index 67e05989ce6..00000000000 --- a/test/lit/parse-bad-block-params.wast +++ /dev/null @@ -1,12 +0,0 @@ -;; RUN: not wasm-opt %s -S -o - 2>&1 | filecheck %s - -;; CHECK: 8:11: error: block parameters not yet supported - -(module - (func - (i32.const 0) - (block (param i32) - (drop) - ) - ) -) From 27053720a798bd39fca84246531022945b24331b Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 13 Dec 2024 10:57:48 -0800 Subject: [PATCH 2/3] changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 324c29526f3..52e453ee48f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Current Trunk - BinaryenSelect no longer takes a type parameter. - AutoDrop APIs have been removed. + - Binaryen now supports parsing control flow structures with parameter types. v120 ---- From b197db5392ab22c44eb6448d538aaaa7936324b9 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 13 Dec 2024 11:49:13 -0800 Subject: [PATCH 3/3] [NFC] Move HeapType::isBottom() to header This makes Precompute about 5% faster on a WasmGC binary. Inspired by #6931. --- src/wasm-type.h | 27 +++++++++++++++++++++++++++ src/wasm/wasm-type.cpp | 25 ------------------------- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/src/wasm-type.h b/src/wasm-type.h index b48a1071328..1e7c0a0ba15 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -870,6 +870,33 @@ std::ostream& operator<<(std::ostream&, Struct); std::ostream& operator<<(std::ostream&, Array); std::ostream& operator<<(std::ostream&, TypeBuilder::ErrorReason); +// Inline some nontrivial methods here for performance reasons. + +inline bool HeapType::isBottom() const { + if (isBasic()) { + switch (getBasic(Unshared)) { + case ext: + case func: + case cont: + case any: + case eq: + case i31: + case struct_: + case array: + case exn: + case string: + return false; + case none: + case noext: + case nofunc: + case nocont: + case noexn: + return true; + } + } + return false; +} + } // namespace wasm namespace std { diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index a6ed6877097..4f341588b6a 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -866,31 +866,6 @@ HeapTypeKind HeapType::getKind() const { return getHeapTypeInfo(*this)->kind; } -bool HeapType::isBottom() const { - if (isBasic()) { - switch (getBasic(Unshared)) { - case ext: - case func: - case cont: - case any: - case eq: - case i31: - case struct_: - case array: - case exn: - case string: - return false; - case none: - case noext: - case nofunc: - case nocont: - case noexn: - return true; - } - } - return false; -} - bool HeapType::isOpen() const { if (isBasic()) { return false;