diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py index ebb7213635a..a8be50b8d8f 100755 --- a/scripts/gen-s-parser.py +++ b/scripts/gen-s-parser.py @@ -603,6 +603,8 @@ ("resume", "makeResume()"), ("resume_throw", "makeResumeThrow()"), ("switch", "makeStackSwitch()"), + ("suspend_to", "makeSuspendTo()"), + ("resume_with", "makeResumeWith()"), # GC ("ref.i31", "makeRefI31(Unshared)"), ("ref.i31_shared", "makeRefI31(Shared)"), diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 32b4f44f078..cc055281dff 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -101,6 +101,9 @@ 'stack_switching_resume.wast', 'stack_switching_resume_throw.wast', 'stack_switching_switch.wast', + 'stack_switching_named.wast', + 'stack_switching_suspend_to.wast', + 'stack_switching_resume_with.wast', # TODO: fuzzer support for exact references 'exact-references.wast', ] diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 8eb73b4c259..78b8e7d44dc 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -90,6 +90,7 @@ BinaryenLiteral toBinaryenLiteral(Literal x) { case HeapType::struct_: case HeapType::array: case HeapType::exn: + case HeapType::handler: WASM_UNREACHABLE("invalid type"); case HeapType::string: WASM_UNREACHABLE("TODO: string literals"); @@ -98,6 +99,7 @@ BinaryenLiteral toBinaryenLiteral(Literal x) { case HeapType::nofunc: case HeapType::nocont: case HeapType::noexn: + case HeapType::nohandler: // Null. return ret; } @@ -144,6 +146,7 @@ Literal fromBinaryenLiteral(BinaryenLiteral x) { case HeapType::struct_: case HeapType::array: case HeapType::exn: + case HeapType::handler: WASM_UNREACHABLE("invalid type"); case HeapType::string: WASM_UNREACHABLE("TODO: string literals"); @@ -152,6 +155,7 @@ Literal fromBinaryenLiteral(BinaryenLiteral x) { case HeapType::nofunc: case HeapType::nocont: case HeapType::noexn: + case HeapType::nohandler: assert(type.isNullable()); return Literal::makeNull(heapType); } diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc index d7d1c5197e3..0c22d801eb8 100644 --- a/src/gen-s-parser.inc +++ b/src/gen-s-parser.inc @@ -4815,12 +4815,23 @@ switch (buf[0]) { return Ok{}; } goto parse_error; - case '_': - if (op == "resume_throw"sv) { - CHECK_ERR(makeResumeThrow(ctx, pos, annotations)); - return Ok{}; + case '_': { + switch (buf[7]) { + case 't': + if (op == "resume_throw"sv) { + CHECK_ERR(makeResumeThrow(ctx, pos, annotations)); + return Ok{}; + } + goto parse_error; + case 'w': + if (op == "resume_with"sv) { + CHECK_ERR(makeResumeWith(ctx, pos, annotations)); + return Ok{}; + } + goto parse_error; + default: goto parse_error; } - goto parse_error; + } default: goto parse_error; } } @@ -5177,12 +5188,23 @@ switch (buf[0]) { default: goto parse_error; } } - case 'u': - if (op == "suspend"sv) { - CHECK_ERR(makeSuspend(ctx, pos, annotations)); - return Ok{}; + case 'u': { + switch (buf[7]) { + case '\0': + if (op == "suspend"sv) { + CHECK_ERR(makeSuspend(ctx, pos, annotations)); + return Ok{}; + } + goto parse_error; + case '_': + if (op == "suspend_to"sv) { + CHECK_ERR(makeSuspendTo(ctx, pos, annotations)); + return Ok{}; + } + goto parse_error; + default: goto parse_error; } - goto parse_error; + } case 'w': if (op == "switch"sv) { CHECK_ERR(makeStackSwitch(ctx, pos, annotations)); diff --git a/src/interpreter/interpreter.cpp b/src/interpreter/interpreter.cpp index fea5a67ba0f..70352197b27 100644 --- a/src/interpreter/interpreter.cpp +++ b/src/interpreter/interpreter.cpp @@ -268,6 +268,8 @@ struct ExpressionInterpreter : OverriddenVisitor { Flow visitResume(Resume* curr) { WASM_UNREACHABLE("TODO"); } Flow visitResumeThrow(ResumeThrow* curr) { WASM_UNREACHABLE("TODO"); } Flow visitStackSwitch(StackSwitch* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitResumeWith(ResumeWith* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitSuspendTo(SuspendTo* curr) { WASM_UNREACHABLE("TODO"); } }; } // anonymous namespace diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp index 42b13919726..55101d07bed 100644 --- a/src/ir/ReFinalize.cpp +++ b/src/ir/ReFinalize.cpp @@ -186,6 +186,10 @@ void ReFinalize::visitSuspend(Suspend* curr) { curr->finalize(getModule()); } void ReFinalize::visitResume(Resume* curr) { curr->finalize(); } void ReFinalize::visitResumeThrow(ResumeThrow* curr) { curr->finalize(); } void ReFinalize::visitStackSwitch(StackSwitch* curr) { curr->finalize(); } +void ReFinalize::visitResumeWith(ResumeWith* curr) { curr->finalize(); } +void ReFinalize::visitSuspendTo(SuspendTo* curr) { + curr->finalize(getModule()); +} void ReFinalize::visitExport(Export* curr) { WASM_UNREACHABLE("unimp"); } void ReFinalize::visitGlobal(Global* curr) { WASM_UNREACHABLE("unimp"); } diff --git a/src/ir/branch-utils.h b/src/ir/branch-utils.h index 1771f2e0e88..baadfa5c23d 100644 --- a/src/ir/branch-utils.h +++ b/src/ir/branch-utils.h @@ -96,6 +96,13 @@ void operateOnScopeNameUsesAndSentTypes(Expression* expr, T func) { func(name, r->sentTypes[i]); } } + } else if (auto* r = expr->dynCast()) { + for (Index i = 0; i < r->handlerTags.size(); i++) { + auto dest = r->handlerTags[i]; + if (!dest.isNull() && dest == name) { + func(name, r->sentTypes[i]); + } + } } else { assert(expr->is() || expr->is()); // delegate or rethrow } diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index 68398fa906b..881f054695c 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -1168,6 +1168,35 @@ template struct ChildTyper : OverriddenVisitor { } note(&curr->cont, Type(*ct, Nullable)); } + + void visitSuspendTo(SuspendTo* curr, + std::optional ht = std::nullopt) { + if (!ht.has_value()) { + ht = curr->handler->type.getHeapType(); + } + assert(ht->isHandler()); + auto params = wasm.getTag(curr->tag)->params(); + assert(params.size() == curr->operands.size()); + for (size_t i = 0; i < params.size(); ++i) { + note(&curr->operands[i], params[i]); + } + note(&curr->handler, Type(*ht, Nullable)); + } + + void visitResumeWith(ResumeWith* curr, + std::optional ct = std::nullopt) { + if (!ct.has_value()) { + ct = curr->cont->type.getHeapType(); + } + assert(ct->isContinuation()); + auto params = ct->getContinuation().type.getSignature().params; + assert(params.size() >= 1 && + ((params.size() - 1) == curr->operands.size())); + for (size_t i = 0; i < params.size() - 1; ++i) { + note(&curr->operands[i], params[i]); + } + note(&curr->cont, Type(*ct, Nullable)); + } }; } // namespace wasm diff --git a/src/ir/cost.h b/src/ir/cost.h index 8c15e2d2acc..9e14f00d30c 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -813,6 +813,21 @@ struct CostAnalyzer : public OverriddenVisitor { } return ret; } + CostType visitResumeWith(ResumeWith* curr) { + // Inspired by indirect calls, but twice the cost. + CostType ret = 12 + visit(curr->cont); + for (auto* arg : curr->operands) { + ret += visit(arg); + } + return ret; + } + CostType visitSuspendTo(SuspendTo* curr) { + CostType ret = 12 + visit(curr->handler); + for (auto* arg : curr->operands) { + ret += visit(arg); + } + return ret; + } private: CostType nullCheckCost(Expression* ref) { diff --git a/src/ir/effects.h b/src/ir/effects.h index 23290c34005..cc56ddebf7b 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -1104,6 +1104,29 @@ class EffectAnalyzer { parent.throws_ = true; } } + void visitResumeWith(ResumeWith* curr) { + // This acts as a kitchen sink effect. + parent.calls = true; + + // resume instructions accept nullable continuation references and trap + // on null. + parent.implicitTrap = true; + + if (parent.features.hasExceptionHandling() && parent.tryDepth == 0) { + parent.throws_ = true; + } + } + void visitSuspendTo(SuspendTo* curr) { + // Similar to resume/call: Suspending means that we execute arbitrary + // other code before we may resume here. + parent.calls = true; + if (parent.features.hasExceptionHandling() && parent.tryDepth == 0) { + parent.throws_ = true; + } + + // A suspend may go unhandled and therefore trap. + parent.implicitTrap = true; + } }; public: diff --git a/src/ir/gc-type-utils.h b/src/ir/gc-type-utils.h index 6ac6db3ba06..c7b624e649d 100644 --- a/src/ir/gc-type-utils.h +++ b/src/ir/gc-type-utils.h @@ -156,6 +156,7 @@ inline std::optional getField(HeapType type, Index index = 0) { return type.getArray().element; case HeapTypeKind::Func: case HeapTypeKind::Cont: + case HeapTypeKind::Handler: case HeapTypeKind::Basic: break; } diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index ecf5f513c2b..e9f0f33aaed 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -1293,6 +1293,14 @@ struct InfoCollector // TODO: optimize when possible addRoot(curr); } + void visitResumeWith(ResumeWith* curr) { + // TODO: optimize when possible + addRoot(curr); + } + void visitSuspendTo(SuspendTo* curr) { + // TODO: optimize when possible + addRoot(curr); + } void visitFunction(Function* func) { // Functions with a result can flow a value out from their body. diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index 7d9f30b6778..7bd5329cfaa 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -422,6 +422,10 @@ struct SubtypingDiscoverer : public OverriddenVisitor { void visitStackSwitch(StackSwitch* curr) { WASM_UNREACHABLE("not implemented"); } + void visitResumeWith(ResumeWith* curr) { + WASM_UNREACHABLE("not implemented"); + } + void visitSuspendTo(SuspendTo* curr) { WASM_UNREACHABLE("not implemented"); } }; } // namespace wasm diff --git a/src/ir/subtypes.h b/src/ir/subtypes.h index c69250043b8..ddd214b640b 100644 --- a/src/ir/subtypes.h +++ b/src/ir/subtypes.h @@ -127,6 +127,8 @@ struct SubTypes { break; case HeapTypeKind::Cont: WASM_UNREACHABLE("TODO: cont"); + case HeapTypeKind::Handler: + WASM_UNREACHABLE("TODO: handler"); case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } diff --git a/src/ir/type-updating.cpp b/src/ir/type-updating.cpp index b90d8eb8790..276beb7d5f9 100644 --- a/src/ir/type-updating.cpp +++ b/src/ir/type-updating.cpp @@ -152,6 +152,8 @@ GlobalTypeRewriter::TypeMap GlobalTypeRewriter::rebuildTypes( } case HeapTypeKind::Cont: WASM_UNREACHABLE("TODO: cont"); + case HeapTypeKind::Handler: + WASM_UNREACHABLE("TODO: handler"); case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 3b9cb21c3e0..7fc68ca16b8 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -92,6 +92,7 @@ struct NullTypeParserCtx { using BlockTypeT = Ok; using SignatureT = Ok; using ContinuationT = Ok; + using HandlerT = Ok; using StorageT = Ok; using FieldT = Ok; using FieldsT = Ok; @@ -115,11 +116,13 @@ struct NullTypeParserCtx { HeapTypeT makeExnType(Shareability) { return Ok{}; } HeapTypeT makeStringType(Shareability) { return Ok{}; } HeapTypeT makeContType(Shareability) { return Ok{}; } + HeapTypeT makeHandlerType(Shareability) { return Ok{}; } HeapTypeT makeNoneType(Shareability) { return Ok{}; } HeapTypeT makeNoextType(Shareability) { return Ok{}; } HeapTypeT makeNofuncType(Shareability) { return Ok{}; } HeapTypeT makeNoexnType(Shareability) { return Ok{}; } HeapTypeT makeNocontType(Shareability) { return Ok{}; } + HeapTypeT makeNohandlerType(Shareability) { return Ok{}; } TypeT makeI32() { return Ok{}; } TypeT makeI64() { return Ok{}; } @@ -147,6 +150,7 @@ struct NullTypeParserCtx { SignatureT makeFuncType(ParamsT*, ResultsT*) { return Ok{}; } ContinuationT makeContType(HeapTypeT) { return Ok{}; } + HandlerT makeHandlerType(ResultsT*) { return Ok{}; } StorageT makeI8() { return Ok{}; } StorageT makeI16() { return Ok{}; } @@ -190,6 +194,7 @@ template struct TypeParserCtx { using BlockTypeT = HeapType; using SignatureT = Signature; using ContinuationT = Continuation; + using HandlerT = Handler; using StorageT = Field; using FieldT = Field; using FieldsT = std::pair, std::vector>; @@ -237,6 +242,9 @@ template struct TypeParserCtx { HeapTypeT makeContType(Shareability share) { return HeapTypes::cont.getBasic(share); } + HeapTypeT makeHandlerType(Shareability share) { + return HeapTypes::handler.getBasic(share); + } HeapTypeT makeNoneType(Shareability share) { return HeapTypes::none.getBasic(share); } @@ -252,6 +260,9 @@ template struct TypeParserCtx { HeapTypeT makeNocontType(Shareability share) { return HeapTypes::nocont.getBasic(share); } + HeapTypeT makeNohandlerType(Shareability share) { + return HeapTypes::nohandler.getBasic(share); + } TypeT makeI32() { return Type::i32; } TypeT makeI64() { return Type::i64; } @@ -292,6 +303,11 @@ template struct TypeParserCtx { } ContinuationT makeContType(HeapTypeT ft) { return Continuation(ft); } + HandlerT makeHandlerType(ResultsT* results) { + std::vector empty; + const auto& resultTypes = results ? *results : empty; + return Handler(self().makeTupleType(resultTypes)); + } StorageT makeI8() { return Field(Field::i8, Immutable); } StorageT makeI16() { return Field(Field::i16, Immutable); } @@ -898,6 +914,18 @@ struct NullInstrParserCtx { makeStackSwitch(Index, const std::vector&, HeapTypeT, TagIdxT) { return Ok{}; } + template + Result<> + makeSuspendTo(Index, const std::vector&, HeapTypeT, TagIdxT) { + return Ok{}; + } + template + Result<> makeResumeWith(Index, + const std::vector&, + HeapTypeT, + const TagLabelListT&) { + return Ok{}; + } }; struct NullCtx : NullTypeParserCtx, NullInstrParserCtx { @@ -978,6 +1006,7 @@ struct ParseDeclsCtx : NullTypeParserCtx, NullInstrParserCtx { void addFuncType(SignatureT) {} void addContType(ContinuationT) {} + void addHandlerType(HandlerT) {} void addStructType(StructT) {} void addArrayType(ArrayT) {} void setOpen() {} @@ -1140,6 +1169,7 @@ struct ParseTypeDefsCtx : TypeParserCtx { void addFuncType(SignatureT& type) { builder[index] = type; } void addContType(ContinuationT& type) { builder[index] = type; } + void addHandlerType(HandlerT& type) { builder[index] = type; } void addStructType(StructT& type) { auto& [fieldNames, str] = type; @@ -2742,6 +2772,32 @@ struct ParseDefsCtx : TypeParserCtx { Name tag) { return withLoc(pos, irBuilder.makeStackSwitch(type, tag)); } + + Result<> makeSuspendTo(Index pos, + const std::vector& annotations, + HeapType type, + Name tag) { + return withLoc(pos, irBuilder.makeSuspendTo(type, tag)); + } + + Result<> makeResumeWith(Index pos, + const std::vector& annotations, + HeapType type, + const std::vector& resumetable) { + std::vector tags; + std::vector> labels; + tags.reserve(resumetable.size()); + labels.reserve(resumetable.size()); + for (const OnClauseInfo& info : resumetable) { + tags.push_back(info.tag); + if (info.isOnSwitch) { + labels.push_back(std::nullopt); + } else { + labels.push_back(std::optional(info.label)); + } + } + return withLoc(pos, irBuilder.makeResumeWith(type, tags, labels)); + } }; } // namespace wasm::WATParser diff --git a/src/parser/parsers.h b/src/parser/parsers.h index b54de9979c6..8f39e0008b5 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -322,6 +322,10 @@ template Result<> makeResumeThrow(Ctx&, Index, const std::vector&); template Result<> makeStackSwitch(Ctx&, Index, const std::vector&); +template +Result<> makeResumeWith(Ctx&, Index, const std::vector&); +template +Result<> makeSuspendTo(Ctx&, Index, const std::vector&); template Result<> ignore(Ctx&, Index, const std::vector&) { @@ -415,6 +419,9 @@ Result absheaptype(Ctx& ctx, Shareability share) { if (ctx.in.takeKeyword("cont"sv)) { return ctx.makeContType(share); } + if (ctx.in.takeKeyword("handler"sv)) { + return ctx.makeHandlerType(share); + } if (ctx.in.takeKeyword("none"sv)) { return ctx.makeNoneType(share); } @@ -430,6 +437,9 @@ Result absheaptype(Ctx& ctx, Shareability share) { if (ctx.in.takeKeyword("nocont"sv)) { return ctx.makeNocontType(share); } + if (ctx.in.takeKeyword("nohandler"sv)) { + return ctx.makeNohandlerType(share); + } return ctx.in.err("expected abstract heap type"); } @@ -491,6 +501,9 @@ MaybeResult maybeReftypeAbbrev(Ctx& ctx) { if (ctx.in.takeKeyword("contref"sv)) { return ctx.makeRefType(ctx.makeContType(Unshared), Nullable, Inexact); } + if (ctx.in.takeKeyword("handlerref"sv)) { + return ctx.makeRefType(ctx.makeHandlerType(Unshared), Nullable, Inexact); + } if (ctx.in.takeKeyword("nullref"sv)) { return ctx.makeRefType(ctx.makeNoneType(Unshared), Nullable, Inexact); } @@ -506,6 +519,9 @@ MaybeResult maybeReftypeAbbrev(Ctx& ctx) { if (ctx.in.takeKeyword("nullcontref"sv)) { return ctx.makeRefType(ctx.makeNocontType(Unshared), Nullable, Inexact); } + if (ctx.in.takeKeyword("nullhandlerref"sv)) { + return ctx.makeRefType(ctx.makeNohandlerType(Unshared), Nullable, Inexact); + } return {}; } @@ -694,6 +710,23 @@ MaybeResult conttype(Ctx& ctx) { return ctx.makeContType(*x); } +// handlertype ::= '(' 'handler' t*:vec(param) ')' => handler [t*] +template +MaybeResult handlertype(Ctx& ctx) { + if (!ctx.in.takeSExprStart("handler"sv)) { + return {}; + } + + auto parsedResults = results(ctx); + CHECK_ERR(parsedResults); + + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected end of handler name type"); + } + + return ctx.makeHandlerType(parsedResults.getPtr()); +} + // storagetype ::= valtype | packedtype // packedtype ::= i8 | i16 template Result storagetype(Ctx& ctx) { @@ -2631,6 +2664,33 @@ Result<> makeStackSwitch(Ctx& ctx, return ctx.makeStackSwitch(pos, annotations, *type, *tag); } +// suspend_to ::= 'suspend_to' typeidx tagidx +template +Result<> +makeSuspendTo(Ctx& ctx, Index pos, const std::vector& annotations) { + auto type = typeidx(ctx); + CHECK_ERR(type); + auto tag = tagidx(ctx); + CHECK_ERR(tag); + + return ctx.makeSuspendTo(pos, annotations, *type, *tag); +} + +// resume_with ::= 'resume_throw' typeidx tagidx ('(' 'on' tagidx labelidx | +// 'on' tagidx switch ')')* +template +Result<> makeResumeWith(Ctx& ctx, + Index pos, + const std::vector& annotations) { + auto type = typeidx(ctx); + CHECK_ERR(type); + + auto resumetable = makeResumeTable(ctx); + CHECK_ERR(resumetable); + + return ctx.makeResumeWith(pos, annotations, *type, *resumetable); +} + // ======= // Modules // ======= @@ -2937,6 +2997,11 @@ template Result<> comptype(Ctx& ctx) { ctx.addArrayType(*type); return Ok{}; } + if (auto type = handlertype(ctx)) { + CHECK_ERR(type); + ctx.addHandlerType(*type); + return Ok{}; + } return ctx.in.err("expected type description"); } diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 71878c973b3..37fd7ff51e5 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -427,6 +427,18 @@ struct PrintSExpression : public UnifiedExpressionVisitor { visitExpression(curr); } } + void visitSuspendTo(SuspendTo* curr) { + if (!maybePrintUnreachableOrNullReplacement(curr, curr->handler->type) && + !maybePrintUnreachableOrNullReplacement(curr, curr->type)) { + visitExpression(curr); + } + } + void visitResumeWith(ResumeWith* curr) { + if (!maybePrintUnreachableOrNullReplacement(curr, curr->cont->type) && + !maybePrintUnreachableOrNullReplacement(curr, curr->type)) { + visitExpression(curr); + } + } // Module-level visitors void handleSignature(Function* curr, bool printImplicitNames = false); @@ -2563,7 +2575,8 @@ struct PrintExpressionContents template static void handleResumeTable(std::ostream& o, ResumeType* curr) { static_assert(std::is_base_of::value || - std::is_base_of::value); + std::is_base_of::value || + std::is_base_of::value); for (Index i = 0; i < curr->handlerTags.size(); i++) { o << " ("; printMedium(o, "on "); @@ -2606,6 +2619,21 @@ struct PrintExpressionContents o << ' '; curr->tag.print(o); } + void visitSuspendTo(SuspendTo* curr) { + printMedium(o, "suspend_to "); + printHeapType(curr->handler->type.getHeapType()); + o << ' '; + curr->tag.print(o); + } + void visitResumeWith(ResumeWith* curr) { + assert(curr->cont->type.isContinuation()); + printMedium(o, "resume_with"); + + o << ' '; + printHeapType(curr->cont->type.getHeapType()); + + handleResumeTable(o, curr); + } }; void PrintSExpression::setModule(Module* module) { diff --git a/src/passes/TypeGeneralizing.cpp b/src/passes/TypeGeneralizing.cpp index 4dc0cbb444b..7df76ef06db 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -881,6 +881,8 @@ struct TransferFn : OverriddenVisitor { void visitResume(Resume* curr) { WASM_UNREACHABLE("TODO"); } void visitResumeThrow(ResumeThrow* curr) { WASM_UNREACHABLE("TODO"); } void visitStackSwitch(StackSwitch* curr) { WASM_UNREACHABLE("TODO"); } + void visitResumeWith(ResumeWith* curr) { WASM_UNREACHABLE("TODO"); } + void visitSuspendTo(SuspendTo* curr) { WASM_UNREACHABLE("TODO"); } }; struct TypeGeneralizing : WalkerPass> { diff --git a/src/passes/TypeMerging.cpp b/src/passes/TypeMerging.cpp index e7a25cf372c..6e8916bbad5 100644 --- a/src/passes/TypeMerging.cpp +++ b/src/passes/TypeMerging.cpp @@ -572,6 +572,8 @@ bool shapeEq(HeapType a, HeapType b) { return shapeEq(a.getArray(), b.getArray()); case HeapTypeKind::Cont: WASM_UNREACHABLE("TODO: cont"); + case HeapTypeKind::Handler: + WASM_UNREACHABLE("TODO: handler"); case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } @@ -595,6 +597,8 @@ size_t shapeHash(HeapType a) { return digest; case HeapTypeKind::Cont: WASM_UNREACHABLE("TODO: cont"); + case HeapTypeKind::Handler: + WASM_UNREACHABLE("TODO: handler"); case HeapTypeKind::Basic: break; } diff --git a/src/passes/TypeSSA.cpp b/src/passes/TypeSSA.cpp index 3d68c991396..df83916904c 100644 --- a/src/passes/TypeSSA.cpp +++ b/src/passes/TypeSSA.cpp @@ -251,6 +251,7 @@ struct TypeSSA : public Pass { break; case HeapTypeKind::Func: case HeapTypeKind::Cont: + case HeapTypeKind::Handler: case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } diff --git a/src/passes/Unsubtyping.cpp b/src/passes/Unsubtyping.cpp index 8d76f348a5a..b9b038a0df7 100644 --- a/src/passes/Unsubtyping.cpp +++ b/src/passes/Unsubtyping.cpp @@ -287,6 +287,8 @@ struct Unsubtyping } case HeapTypeKind::Cont: WASM_UNREACHABLE("TODO: cont"); + case HeapTypeKind::Handler: + WASM_UNREACHABLE("TODO: handler"); case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index e529706f786..7e76db1e58f 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -516,6 +516,8 @@ void TranslateToFuzzReader::setupHeapTypes() { break; case HeapTypeKind::Cont: WASM_UNREACHABLE("TODO: cont"); + case HeapTypeKind::Handler: + WASM_UNREACHABLE("TODO: handler"); case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } @@ -3421,6 +3423,9 @@ Expression* TranslateToFuzzReader::makeBasicRef(Type type) { case HeapType::cont: { WASM_UNREACHABLE("not implemented"); } + case HeapType::handler: { + WASM_UNREACHABLE("TODO: handler"); + } case HeapType::any: { // Choose a subtype we can materialize a constant for. We cannot // materialize non-nullable refs to func or i31 in global contexts. @@ -3544,6 +3549,7 @@ Expression* TranslateToFuzzReader::makeBasicRef(Type type) { case HeapType::noext: case HeapType::nofunc: case HeapType::nocont: + case HeapType::nohandler: case HeapType::noexn: { auto null = builder.makeRefNull(heapType.getBasic(share)); if (!type.isNullable()) { @@ -3649,6 +3655,8 @@ Expression* TranslateToFuzzReader::makeCompoundRef(Type type) { } case HeapTypeKind::Cont: WASM_UNREACHABLE("TODO: cont"); + case HeapTypeKind::Handler: + WASM_UNREACHABLE("TODO: handler"); case HeapTypeKind::Basic: break; } @@ -5345,6 +5353,8 @@ HeapType TranslateToFuzzReader::getSubType(HeapType type) { .getBasic(share); case HeapType::cont: return pick(HeapTypes::cont, HeapTypes::nocont).getBasic(share); + case HeapType::handler: + return pick(HeapTypes::handler, HeapTypes::nohandler).getBasic(share); case HeapType::ext: return pick(FeatureOptions() .add(FeatureSet::ReferenceTypes, HeapType::ext) @@ -5392,6 +5402,7 @@ HeapType TranslateToFuzzReader::getSubType(HeapType type) { case HeapType::nofunc: case HeapType::nocont: case HeapType::noexn: + case HeapType::nohandler: break; } } diff --git a/src/tools/fuzzing/heap-types.cpp b/src/tools/fuzzing/heap-types.cpp index dc6e574c7e9..a8c43f1faf8 100644 --- a/src/tools/fuzzing/heap-types.cpp +++ b/src/tools/fuzzing/heap-types.cpp @@ -145,6 +145,8 @@ struct HeapTypeGeneratorImpl { break; case wasm::HeapTypeKind::Cont: WASM_UNREACHABLE("TODO: cont"); + case wasm::HeapTypeKind::Handler: + WASM_UNREACHABLE("TODO: handler"); case wasm::HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } @@ -435,6 +437,7 @@ struct HeapTypeGeneratorImpl { case HeapType::func: return pickSubFunc(share); case HeapType::cont: + case HeapType::handler: WASM_UNREACHABLE("not implemented"); case HeapType::any: return pickSubAny(share); @@ -454,6 +457,7 @@ struct HeapTypeGeneratorImpl { case HeapType::nofunc: case HeapType::nocont: case HeapType::noexn: + case HeapType::nohandler: return type; } WASM_UNREACHABLE("unexpected type"); @@ -498,6 +502,7 @@ struct HeapTypeGeneratorImpl { case HeapType::func: case HeapType::exn: case HeapType::cont: + case HeapType::handler: case HeapType::any: break; case HeapType::eq: @@ -517,6 +522,7 @@ struct HeapTypeGeneratorImpl { case HeapType::nofunc: return pickSubFunc(share); case HeapType::nocont: + case HeapType::nohandler: WASM_UNREACHABLE("not implemented"); case HeapType::noext: candidates.push_back(HeapTypes::ext.getBasic(share)); @@ -942,6 +948,8 @@ std::vector Inhabitator::build() { } case HeapTypeKind::Cont: WASM_UNREACHABLE("TODO: cont"); + case HeapTypeKind::Handler: + WASM_UNREACHABLE("TODO: handler"); case HeapTypeKind::Basic: break; } @@ -1038,6 +1046,7 @@ bool isUninhabitable(HeapType type, return false; case HeapTypeKind::Struct: case HeapTypeKind::Array: + case HeapTypeKind::Handler: break; } if (visited.count(type)) { @@ -1060,6 +1069,11 @@ bool isUninhabitable(HeapType type, return true; } break; + case HeapTypeKind::Handler: + if (isUninhabitable(type.getHandler().results, visited, visiting)) { + return true; + } + break; case HeapTypeKind::Basic: case HeapTypeKind::Func: case HeapTypeKind::Cont: diff --git a/src/tools/wasm-fuzz-types.cpp b/src/tools/wasm-fuzz-types.cpp index 7ba341e09df..a9d51b68f54 100644 --- a/src/tools/wasm-fuzz-types.cpp +++ b/src/tools/wasm-fuzz-types.cpp @@ -309,6 +309,8 @@ void Fuzzer::checkCanonicalization() { continue; case HeapTypeKind::Cont: WASM_UNREACHABLE("TODO: cont"); + case HeapTypeKind::Handler: + WASM_UNREACHABLE("TODO: handler"); case HeapTypeKind::Basic: break; } diff --git a/src/wasm-binary.h b/src/wasm-binary.h index bf5d9708583..cb11ed6f8d0 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -330,6 +330,8 @@ enum EncodedType { exact = -0x1e, // 0x62 contref = -0x18, // 0x68 nullcontref = -0x0b, // 0x75 + handlerref = -0x1e, + nullhandlerref = -0x1a, // 0x66 // exception handling exnref = -0x17, // 0x69 nullexnref = -0xc, // 0x74 @@ -345,26 +347,29 @@ enum EncodedType { SharedDef = 0x65, Shared = -0x1b, // Also 0x65 as an SLEB128 Rec = 0x4e, + Handler = 0x5C, // 0x5C // block_type Empty = -0x40, // 0x40 }; enum EncodedHeapType { - nofunc = -0xd, // 0x73 - noext = -0xe, // 0x72 - none = -0xf, // 0x71 - func = -0x10, // 0x70 - ext = -0x11, // 0x6f - any = -0x12, // 0x6e - eq = -0x13, // 0x6d - exn = -0x17, // 0x69 - noexn = -0xc, // 0x74 - cont = -0x18, // 0x68 - nocont = -0x0b, // 0x75 - i31 = -0x14, // 0x6c - struct_ = -0x15, // 0x6b - array = -0x16, // 0x6a - string = -0x19, // 0x67 + nofunc = -0xd, // 0x73 + noext = -0xe, // 0x72 + none = -0xf, // 0x71 + func = -0x10, // 0x70 + ext = -0x11, // 0x6f + any = -0x12, // 0x6e + eq = -0x13, // 0x6d + exn = -0x17, // 0x69 + noexn = -0xc, // 0x74 + cont = -0x18, // 0x68 + nocont = -0x0b, // 0x75 + handler = -0x1e, + nohandler = -0x1a, // 0x66 + i31 = -0x14, // 0x6c + struct_ = -0x15, // 0x6b + array = -0x16, // 0x6a + string = -0x19, // 0x67 }; namespace CustomSections { @@ -1168,9 +1173,11 @@ enum ASTNodes { Suspend = 0xe2, Resume = 0xe3, ResumeThrow = 0xe4, - Switch = 0xe5, // NOTE(dhil): the internal class is known as - // StackSwitch to avoid conflict with the existing - // 'switch table'. + Switch = 0xe5, // NOTE(dhil): the internal class is known as + // StackSwitch to avoid conflict with the existing + // 'switch table'. + SuspendTo = 0xe7, + ResumeWith = 0xe8, OnLabel = 0x00, // (on $tag $label) OnSwitch = 0x01 // (on $tag switch) }; diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 0e28f3d5a66..df5b7a12027 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -1256,6 +1256,30 @@ class Builder { ret->finalize(); return ret; } + SuspendTo* makeSuspendTo(Name tag, + Expression* handler, + const std::vector& args) { + auto* ret = wasm.allocator.alloc(); + ret->tag = tag; + ret->handler = handler; + ret->operands.set(args); + ret->finalize(&wasm); + return ret; + } + ResumeWith* makeResumeWith(const std::vector& handlerTags, + const std::vector& handlerBlocks, + const std::vector& sentTypes, + ExpressionList&& operands, + Expression* cont) { + auto* ret = wasm.allocator.alloc(); + ret->handlerTags.set(handlerTags); + ret->handlerBlocks.set(handlerBlocks); + ret->sentTypes.set(sentTypes); + ret->operands = std::move(operands); + ret->cont = cont; + ret->finalize(); + return ret; + } // Additional helpers diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def index e40f7a06f02..8f13f0f3da9 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -820,6 +820,19 @@ DELEGATE_FIELD_CHILD_VECTOR(StackSwitch, operands) DELEGATE_FIELD_NAME_KIND(StackSwitch, tag, ModuleItemKind::Tag) DELEGATE_FIELD_CASE_END(StackSwitch) +DELEGATE_FIELD_CASE_START(SuspendTo) +DELEGATE_FIELD_CHILD(SuspendTo, handler) +DELEGATE_FIELD_CHILD_VECTOR(SuspendTo, operands) +DELEGATE_FIELD_NAME_KIND(SuspendTo, tag, ModuleItemKind::Tag) +DELEGATE_FIELD_CASE_END(SuspendTo) + +DELEGATE_FIELD_CASE_START(ResumeWith) +DELEGATE_FIELD_TYPE_VECTOR(ResumeWith, sentTypes) +DELEGATE_FIELD_CHILD(ResumeWith, cont) +DELEGATE_FIELD_CHILD_VECTOR(ResumeWith, operands) +DELEGATE_FIELD_SCOPE_NAME_USE_VECTOR(ResumeWith, handlerBlocks) +DELEGATE_FIELD_NAME_KIND_VECTOR(ResumeWith, handlerTags, ModuleItemKind::Tag) +DELEGATE_FIELD_CASE_END(ResumeWith) DELEGATE_FIELD_MAIN_END diff --git a/src/wasm-delegations.def b/src/wasm-delegations.def index 4e78de1a56d..a1cc088d7fd 100644 --- a/src/wasm-delegations.def +++ b/src/wasm-delegations.def @@ -109,5 +109,7 @@ DELEGATE(Suspend); DELEGATE(Resume); DELEGATE(ResumeThrow); DELEGATE(StackSwitch); +DELEGATE(SuspendTo); +DELEGATE(ResumeWith); #undef DELEGATE diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 2a019513d2a..d4b7861cfa4 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2627,6 +2627,8 @@ class ConstantExpressionRunner : public ExpressionRunner { Flow visitStackSwitch(StackSwitch* curr) { WASM_UNREACHABLE("unimplemented"); } + Flow visitResumeWith(ResumeWith* curr) { WASM_UNREACHABLE("unimplemented"); } + Flow visitSuspendTo(SuspendTo* curr) { WASM_UNREACHABLE("unimplemented"); } void trap(const char* why) override { throw NonconstantException(); } @@ -4342,6 +4344,8 @@ class ModuleRunnerBase : public ExpressionRunner { Flow visitResume(Resume* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitResumeThrow(ResumeThrow* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitStackSwitch(StackSwitch* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitSuspendTo(SuspendTo* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitResumeWith(ResumeWith* curr) { return Flow(NONCONSTANT_FLOW); } void trap(const char* why) override { externalInterface->trap(why); } diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index fb2727bf94c..7e9bbaafca7 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -244,6 +244,10 @@ class IRBuilder : public UnifiedExpressionVisitor> { const std::vector& tags, const std::vector>& labels); Result<> makeStackSwitch(HeapType ct, Name tag); + Result<> makeResumeWith(HeapType ct, + const std::vector& tags, + const std::vector>& labels); + Result<> makeSuspendTo(HeapType handler, Name tag); // Private functions that must be public for technical reasons. Result<> visitExpression(Expression*); diff --git a/src/wasm-type-printing.h b/src/wasm-type-printing.h index 11afde88212..c90979cf24f 100644 --- a/src/wasm-type-printing.h +++ b/src/wasm-type-printing.h @@ -56,6 +56,7 @@ struct DefaultTypeNameGenerator size_t contCount = 0; size_t structCount = 0; size_t arrayCount = 0; + size_t handlerCount = 0; // Cached names for types that have already been seen. std::unordered_map nameCache; diff --git a/src/wasm-type.h b/src/wasm-type.h index f09e1e440fe..1c5ac0b3283 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -56,6 +56,7 @@ struct Continuation; struct Field; struct Struct; struct Array; +struct Handler; using TypeList = std::vector; using Tuple = TypeList; @@ -87,6 +88,7 @@ enum class HeapTypeKind { Struct, Array, Cont, + Handler, }; class HeapType { @@ -119,9 +121,11 @@ class HeapType { nofunc = 13 << UsedBits, nocont = 14 << UsedBits, noexn = 15 << UsedBits, + handler = 16 << UsedBits, + nohandler = 17 << UsedBits, }; static constexpr BasicHeapType _last_basic_type = - BasicHeapType(noexn | SharedMask); + BasicHeapType(nohandler | SharedMask); // BasicHeapType can be implicitly upgraded to HeapType constexpr HeapType(BasicHeapType id) : id(id) {} @@ -138,6 +142,8 @@ class HeapType { HeapType(Signature signature); HeapType(Continuation cont); + HeapType(const Handler& cont); + HeapType(Handler&& cont); // Create a HeapType with the given structure. In equirecursive mode, this may // be the same as a previous HeapType created with the same contents. In @@ -168,6 +174,7 @@ class HeapType { bool isBottom() const; bool isOpen() const; bool isShared() const { return getShared() == Shared; } + bool isHandler() const { return getKind() == HeapTypeKind::Handler; } Shareability getShared() const; @@ -179,6 +186,7 @@ class HeapType { Signature getSignature() const; Continuation getContinuation() const; + Handler getHandler() const; const Struct& getStruct() const; Array getArray() const; @@ -387,6 +395,7 @@ class Type { bool isContinuation() const { return isRef() && getHeapType().isContinuation(); } + bool isHandler() const { return isRef() && getHeapType().isHandler(); } bool isDefaultable() const; // TODO: Allow this only for reference types. @@ -543,6 +552,8 @@ constexpr HeapType noext = HeapType::noext; constexpr HeapType nofunc = HeapType::nofunc; constexpr HeapType nocont = HeapType::nocont; constexpr HeapType noexn = HeapType::noexn; +constexpr HeapType handler = HeapType::handler; +constexpr HeapType nohandler = HeapType::nohandler; } // namespace HeapTypes @@ -594,6 +605,16 @@ struct Continuation { std::string toString() const; }; +struct Handler { + Type results; + Handler(Type results) : results(results) {} + bool operator==(const Handler& other) const { + return results == other.results; + } + bool operator!=(const Handler& other) const { return !(*this == other); } + std::string toString() const; +}; + struct Field { Type type; enum PackedType { @@ -692,6 +713,7 @@ struct TypeBuilder { void setHeapType(size_t i, const Struct& struct_); void setHeapType(size_t i, Struct&& struct_); void setHeapType(size_t i, Array array); + void setHeapType(size_t i, Handler handler); // Sets the heap type at index `i` to be a copy of the given heap type with // its referenced HeapTypes to be replaced according to the provided mapping @@ -749,6 +771,11 @@ struct TypeBuilder { case HeapTypeKind::Cont: setHeapType(i, Continuation(map(type.getContinuation().type))); return; + case HeapTypeKind::Handler: { + Handler h = type.getHandler(); + setHeapType(i, Handler(copyType(h.results))); + return; + } case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } @@ -842,6 +869,10 @@ struct TypeBuilder { builder.setHeapType(index, array); return *this; } + Entry& operator=(Handler handler) { + builder.setHeapType(index, handler); + return *this; + } Entry& subTypeOf(std::optional other) { builder.setSubType(index, other); return *this; @@ -909,12 +940,14 @@ inline bool HeapType::isBottom() const { case array: case exn: case string: + case handler: return false; case none: case noext: case nofunc: case nocont: case noexn: + case nohandler: return true; } } @@ -937,6 +970,10 @@ template<> class hash { public: size_t operator()(const wasm::Continuation&) const; }; +template<> class hash { +public: + size_t operator()(const wasm::Handler&) const; +}; template<> class hash { public: size_t operator()(const wasm::Field&) const; diff --git a/src/wasm.h b/src/wasm.h index 7458a3d14e3..1877ac38eaf 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -751,6 +751,8 @@ class Expression { ResumeThrowId, // Id for the stack switching `switch` StackSwitchId, + ResumeWithId, + SuspendToId, NumExpressionIds }; Id _id; @@ -2063,6 +2065,50 @@ class StackSwitch : public SpecificExpression { void finalize(); }; +class ResumeWith : public SpecificExpression { +public: + ResumeWith(MixedArena& allocator) + : handlerTags(allocator), handlerBlocks(allocator), operands(allocator), + sentTypes(allocator) {} + + // The following two vectors are to be understood together + // pointwise. That is, the ith component of each vector together + // classifies an on-clause `(on $tag $label)` or `(on $tag + // switch)`. The first vector stores reifies the `$tag` bit of the + // aforementioned syntax... + ArenaVector handlerTags; + // ... whilst this vector reifies the `$label` bit of the + // syntax. For `switch` clauses the ith component will be the Empty + // name (i.e. `Name()`). + ArenaVector handlerBlocks; + + ExpressionList operands; + Expression* cont; + + void finalize(); + + // sentTypes[i] contains the type of the values that will be sent to + // the block handlerBlocks[i] if suspending with tag + // handlerTags[i]. Not part of the instruction's syntax, but stored + // here for subsequent use. This information is cached here in + // order not to query the module every time we query the sent types. + ArenaVector sentTypes; +}; + +class SuspendTo : public SpecificExpression { +public: + SuspendTo(MixedArena& allocator) : operands(allocator) {} + + Name tag; + Expression* handler; + ExpressionList operands; + + // We need access to the module to obtain the signature of the tag, + // which determines this node's type. + // If no module is given, then the type must have been set already. + void finalize(Module* wasm = nullptr); +}; + // Globals struct Named { diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp index 05027ee6bd6..668fefaa8c3 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -149,6 +149,7 @@ Literal::Literal(const Literal& other) : type(other.type) { case HeapType::nofunc: case HeapType::noexn: case HeapType::nocont: + case HeapType::nohandler: WASM_UNREACHABLE("null literals should already have been handled"); case HeapType::any: case HeapType::eq: @@ -156,6 +157,7 @@ Literal::Literal(const Literal& other) : type(other.type) { case HeapType::cont: case HeapType::struct_: case HeapType::array: + case HeapType::handler: WASM_UNREACHABLE("invalid type"); case HeapType::string: WASM_UNREACHABLE("TODO: string literals"); @@ -644,6 +646,9 @@ std::ostream& operator<<(std::ostream& o, Literal literal) { case HeapType::nocont: o << "nullcontref"; break; + case HeapType::nohandler: + o << "nullhandlerref"; + break; case HeapType::ext: o << "externref"; break; @@ -656,6 +661,7 @@ std::ostream& operator<<(std::ostream& o, Literal literal) { case HeapType::cont: case HeapType::struct_: case HeapType::array: + case HeapType::handler: WASM_UNREACHABLE("invalid type"); case HeapType::string: { auto data = literal.getGCData(); diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index f7abd4908f3..1ad8bdf8649 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -316,6 +316,15 @@ void WasmBinaryWriter::writeTypes() { o << uint8_t(BinaryConsts::EncodedType::Cont); writeHeapType(type.getContinuation().type); break; + case HeapTypeKind::Handler: { + o << uint8_t(BinaryConsts::EncodedType::Handler); + auto results = type.getHandler().results; + o << U32LEB(results.size()); + for (const auto& type : results) { + writeType(type); + } + break; + } case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } @@ -1594,6 +1603,9 @@ void WasmBinaryWriter::writeType(Type type) { case HeapType::cont: o << S32LEB(BinaryConsts::EncodedType::contref); return; + case HeapType::handler: + o << S32LEB(BinaryConsts::EncodedType::handlerref); + return; case HeapType::eq: o << S32LEB(BinaryConsts::EncodedType::eqref); return; @@ -1627,6 +1639,9 @@ void WasmBinaryWriter::writeType(Type type) { case HeapType::nocont: o << S32LEB(BinaryConsts::EncodedType::nullcontref); return; + case HeapType::nohandler: + o << S32LEB(BinaryConsts::EncodedType::nullhandlerref); + return; } } if (type.isNullable()) { @@ -1692,6 +1707,9 @@ void WasmBinaryWriter::writeHeapType(HeapType type) { case HeapType::cont: ret = BinaryConsts::EncodedHeapType::cont; break; + case HeapType::handler: + ret = BinaryConsts::EncodedHeapType::handler; + break; case HeapType::any: ret = BinaryConsts::EncodedHeapType::any; break; @@ -1728,6 +1746,9 @@ void WasmBinaryWriter::writeHeapType(HeapType type) { case HeapType::nocont: ret = BinaryConsts::EncodedHeapType::nocont; break; + case HeapType::nohandler: + ret = BinaryConsts::EncodedHeapType::nohandler; + break; } o << S64LEB(ret); // TODO: Actually s33 } @@ -2059,6 +2080,9 @@ bool WasmBinaryReader::getBasicType(int32_t code, Type& out) { case BinaryConsts::EncodedType::contref: out = Type(HeapType::cont, Nullable); return true; + case BinaryConsts::EncodedType::handlerref: + out = Type(HeapType::handler, Nullable); + return true; case BinaryConsts::EncodedType::externref: out = Type(HeapType::ext, Nullable); return true; @@ -2098,6 +2122,9 @@ bool WasmBinaryReader::getBasicType(int32_t code, Type& out) { case BinaryConsts::EncodedType::nullcontref: out = Type(HeapType::nocont, Nullable); return true; + case BinaryConsts::EncodedType::nullhandlerref: + out = Type(HeapType::nohandler, Nullable); + return true; default: return false; } @@ -2111,6 +2138,9 @@ bool WasmBinaryReader::getBasicHeapType(int64_t code, HeapType& out) { case BinaryConsts::EncodedHeapType::cont: out = HeapType::cont; return true; + case BinaryConsts::EncodedHeapType::handler: + out = HeapType::handler; + return true; case BinaryConsts::EncodedHeapType::ext: out = HeapType::ext; return true; @@ -2150,6 +2180,9 @@ bool WasmBinaryReader::getBasicHeapType(int64_t code, HeapType& out) { case BinaryConsts::EncodedHeapType::nocont: out = HeapType::nocont; return true; + case BinaryConsts::EncodedHeapType::nohandler: + out = HeapType::nohandler; + return true; default: return false; } @@ -2415,6 +2448,15 @@ void WasmBinaryReader::readTypes() { return Continuation(ht); }; + auto readHandlerDef = [&]() { + std::vector params; + size_t numParams = getU32LEB(); + for (size_t j = 0; j < numParams; j++) { + params.push_back(readType()); + } + return Handler(builder.getTempTupleType(params)); + }; + auto readMutability = [&]() { switch (getU32LEB()) { case 0: @@ -2495,6 +2537,8 @@ void WasmBinaryReader::readTypes() { builder[i] = readStructDef(); } else if (form == BinaryConsts::EncodedType::Array) { builder[i] = Array(readFieldDef()); + } else if (form == BinaryConsts::EncodedType::Handler) { + builder[i] = readHandlerDef(); } else { throwError("Bad type form " + std::to_string(form)); } @@ -3121,6 +3165,31 @@ Result<> WasmBinaryReader::readInst() { auto tag = getTagName(getU32LEB()); return builder.makeStackSwitch(type, tag); } + case BinaryConsts::SuspendTo: { + auto type = getIndexedHeapType(); + return builder.makeSuspendTo(type, getTagName(getU32LEB())); + } + case BinaryConsts::ResumeWith: { + auto type = getIndexedHeapType(); + auto numHandlers = getU32LEB(); + std::vector tags; + std::vector> labels; + tags.reserve(numHandlers); + labels.reserve(numHandlers); + for (Index i = 0; i < numHandlers; ++i) { + uint8_t code = getInt8(); + if (code == BinaryConsts::OnLabel) { + tags.push_back(getTagName(getU32LEB())); + labels.push_back(std::optional{getU32LEB()}); + } else if (code == BinaryConsts::OnSwitch) { + tags.push_back(getTagName(getU32LEB())); + labels.push_back(std::nullopt); + } else { + return Err{"ON opcode expected"}; + } + } + return builder.makeResumeWith(type, tags, labels); + } #define BINARY_INT(code) \ case BinaryConsts::I32##code: \ diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 1ea7b86afdd..c11e4352d9f 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -727,6 +727,20 @@ struct IRBuilder::ChildPopper ConstraintCollector{builder, children}.visitStackSwitch(curr, ct); return popConstrainedChildren(children); } + + Result<> visitSuspendTo(SuspendTo* curr, + std::optional ht = std::nullopt) { + std::vector children; + ConstraintCollector{builder, children}.visitSuspendTo(curr, ht); + return popConstrainedChildren(children); + } + + Result<> visitResumeWith(ResumeWith* curr, + std::optional ct = std::nullopt) { + std::vector children; + ConstraintCollector{builder, children}.visitResumeWith(curr, ct); + return popConstrainedChildren(children); + } }; Result<> IRBuilder::visit(Expression* curr) { @@ -2500,4 +2514,52 @@ Result<> IRBuilder::makeStackSwitch(HeapType ct, Name tag) { return Ok{}; } +Result<> IRBuilder::makeSuspendTo(HeapType handler, Name tag) { + SuspendTo curr(wasm.allocator); + curr.tag = tag; + curr.operands.resize(wasm.getTag(tag)->params().size()); + CHECK_ERR(ChildPopper{*this}.visitSuspendTo(&curr, handler)); + CHECK_ERR(validateTypeAnnotation(handler, curr.handler)); + + std::vector operands(curr.operands.begin(), curr.operands.end()); + push(builder.makeSuspendTo(tag, curr.handler, operands)); + return Ok{}; +} + +Result<> +IRBuilder::makeResumeWith(HeapType ct, + const std::vector& tags, + const std::vector>& labels) { + if (tags.size() != labels.size()) { + return Err{"the sizes of tags and labels must be equal"}; + } + if (!ct.isContinuation()) { + return Err{"expected continuation type"}; + } + + ResumeWith curr(wasm.allocator); + auto nparams = ct.getContinuation().type.getSignature().params.size(); + if (nparams < 1) { + return Err{"arity mismatch: the continuation argument must have, at least, " + "unary arity"}; + } + curr.operands.resize(nparams - 1); + + Result resumetable = makeResumeTable( + labels, + [this](Index i) { return this->getLabelName(i); }, + [this](Index i) { return this->getLabelType(i); }); + CHECK_ERR(resumetable); + CHECK_ERR(ChildPopper{*this}.visitResumeWith(&curr, ct)); + CHECK_ERR(validateTypeAnnotation(ct, curr.cont)); + + push(builder.makeResumeWith(tags, + resumetable->targets, + resumetable->sentTypes, + std::move(curr.operands), + curr.cont)); + + return Ok{}; +} + } // namespace wasm diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 060b01b04ee..a93d434419a 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2728,6 +2728,33 @@ void BinaryInstWriter::visitStackSwitch(StackSwitch* curr) { o << U32LEB(parent.getTagIndex(curr->tag)); } +void BinaryInstWriter::visitResumeWith(ResumeWith* curr) { + assert(curr->cont->type.isContinuation()); + o << int8_t(BinaryConsts::ResumeWith); + parent.writeIndexedHeapType(curr->cont->type.getHeapType()); + + size_t handlerNum = curr->handlerTags.size(); + o << U32LEB(handlerNum); + for (size_t i = 0; i < handlerNum; i++) { + if (curr->handlerBlocks[i].isNull()) { + // on switch + o << int8_t(BinaryConsts::OnSwitch) + << U32LEB(parent.getTagIndex(curr->handlerTags[i])); + } else { + // on label + o << int8_t(BinaryConsts::OnLabel) + << U32LEB(parent.getTagIndex(curr->handlerTags[i])) + << U32LEB(getBreakIndex(curr->handlerBlocks[i])); + } + } +} + +void BinaryInstWriter::visitSuspendTo(SuspendTo* curr) { + o << int8_t(BinaryConsts::SuspendTo); + parent.writeIndexedHeapType(curr->handler->type.getHeapType()); + o << U32LEB(parent.getTagIndex(curr->tag)); +} + void BinaryInstWriter::emitScopeEnd(Expression* curr) { assert(!breakStack.empty()); breakStack.pop_back(); diff --git a/src/wasm/wasm-type-shape.cpp b/src/wasm/wasm-type-shape.cpp index 734c5e4b903..fb6dbe9d284 100644 --- a/src/wasm/wasm-type-shape.cpp +++ b/src/wasm/wasm-type-shape.cpp @@ -86,6 +86,9 @@ template struct RecGroupComparator { case HeapTypeKind::Cont: assert(a.isContinuation() && b.isContinuation()); return compare(a.getContinuation(), b.getContinuation()); + case HeapTypeKind::Handler: + assert(a.isHandler() && b.isHandler()); + return compare(a.getHandler(), b.getHandler()); case HeapTypeKind::Basic: break; } @@ -117,6 +120,10 @@ template struct RecGroupComparator { return compare(a.type, b.type); } + Comparison compare(Handler a, Handler b) { + return compare(a.results, b.results); + } + Comparison compare(Field a, Field b) { if (a.mutable_ != b.mutable_) { return a.mutable_ < b.mutable_ ? LT : GT; @@ -242,6 +249,11 @@ struct RecGroupHasher { wasm::rehash(digest, 2381496927); hash_combine(digest, hash(type.getContinuation())); return digest; + case HeapTypeKind::Handler: + assert(type.isHandler()); + wasm::rehash(digest, 5241904230); + hash_combine(digest, hash(type.getHandler())); + return digest; case HeapTypeKind::Basic: break; } @@ -266,6 +278,8 @@ struct RecGroupHasher { size_t hash(Continuation cont) { return hash(cont.type); } + size_t hash(Handler handler) { return hash(handler.results); } + size_t hash(Field field) { size_t digest = wasm::hash(field.mutable_); wasm::rehash(digest, field.packedType); diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index d35d1537c47..76034238a91 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -62,6 +62,7 @@ struct HeapTypeInfo { Continuation continuation; Struct struct_; Array array; + Handler handler; }; HeapTypeInfo(Signature sig) : kind(HeapTypeKind::Func), signature(sig) {} @@ -72,6 +73,8 @@ struct HeapTypeInfo { HeapTypeInfo(Struct&& struct_) : kind(HeapTypeKind::Struct), struct_(std::move(struct_)) {} HeapTypeInfo(Array array) : kind(HeapTypeKind::Array), array(array) {} + HeapTypeInfo(Handler handler) + : kind(HeapTypeKind::Handler), handler(handler) {} ~HeapTypeInfo(); constexpr bool isSignature() const { return kind == HeapTypeKind::Func; } @@ -79,6 +82,7 @@ struct HeapTypeInfo { constexpr bool isStruct() const { return kind == HeapTypeKind::Struct; } constexpr bool isArray() const { return kind == HeapTypeKind::Array; } constexpr bool isData() const { return isStruct() || isArray(); } + constexpr bool isHandler() const { return kind == HeapTypeKind::Handler; } }; // Helper for coinductively checking whether a pair of Types or HeapTypes are in @@ -92,6 +96,7 @@ struct SubTyper { bool isSubType(const Continuation& a, const Continuation& b); bool isSubType(const Struct& a, const Struct& b); bool isSubType(const Array& a, const Array& b); + bool isSubType(const Handler& a, const Handler& b); }; // Helper for finding the equirecursive least upper bound of two types. @@ -125,6 +130,7 @@ struct TypePrinter { std::ostream& print(const Struct& struct_, const std::unordered_map& fieldNames); std::ostream& print(const Array& array); + std::ostream& print(const Handler& handler); }; struct RecGroupHasher { @@ -150,6 +156,7 @@ struct RecGroupHasher { size_t hash(const Continuation& sig) const; size_t hash(const Struct& struct_) const; size_t hash(const Array& array) const; + size_t hash(const Handler& Handler) const; }; struct RecGroupEquator { @@ -176,6 +183,7 @@ struct RecGroupEquator { bool eq(const Continuation& a, const Continuation& b) const; bool eq(const Struct& a, const Struct& b) const; bool eq(const Array& a, const Array& b) const; + bool eq(const Handler& a, const Handler& b) const; }; // A wrapper around a RecGroup that provides equality and hashing based on the @@ -291,6 +299,9 @@ template struct TypeGraphWalkerBase { case HeapTypeKind::Array: taskList.push_back(Task::scan(&info->array.element.type)); break; + case HeapTypeKind::Handler: + taskList.push_back(Task::scan(&info->handler.results)); + break; case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } @@ -375,6 +386,8 @@ HeapType::BasicHeapType getBasicHeapSupertype(HeapType type) { return HeapTypes::struct_.getBasic(info->share); case HeapTypeKind::Array: return HeapTypes::array.getBasic(info->share); + case HeapTypeKind::Handler: + return HeapTypes::handler.getBasic(info->share); case HeapTypeKind::Basic: break; } @@ -406,6 +419,7 @@ std::optional getBasicHeapTypeLUB(HeapType::BasicHeapType a, case HeapType::func: case HeapType::cont: case HeapType::exn: + case HeapType::handler: return std::nullopt; case HeapType::any: lubUnshared = HeapType::any; @@ -441,6 +455,7 @@ std::optional getBasicHeapTypeLUB(HeapType::BasicHeapType a, case HeapType::nofunc: case HeapType::nocont: case HeapType::noexn: + case HeapType::nohandler: // Bottom types already handled. WASM_UNREACHABLE("unexpected basic type"); } @@ -464,6 +479,9 @@ HeapTypeInfo::~HeapTypeInfo() { case HeapTypeKind::Array: array.~Array(); return; + case HeapTypeKind::Handler: + handler.~Handler(); + return; case HeapTypeKind::Basic: break; } @@ -859,6 +877,16 @@ HeapType::HeapType(Continuation continuation) { globalRecGroupStore.insert(std::make_unique(continuation))); } +HeapType::HeapType(const Handler& handler) { + new (this) HeapType( + globalRecGroupStore.insert(std::make_unique(handler))); +} + +HeapType::HeapType(Handler&& handler) { + new (this) HeapType(globalRecGroupStore.insert( + std::make_unique(std::move(handler)))); +} + HeapType::HeapType(const Struct& struct_) { new (this) HeapType( globalRecGroupStore.insert(std::make_unique(struct_))); @@ -907,6 +935,11 @@ Continuation HeapType::getContinuation() const { return getHeapTypeInfo(*this)->continuation; } +Handler HeapType::getHandler() const { + assert(isHandler()); + return getHeapTypeInfo(*this)->handler; +} + const Struct& HeapType::getStruct() const { assert(isStruct()); return getHeapTypeInfo(*this)->struct_; @@ -950,6 +983,8 @@ std::optional HeapType::getSuperType() const { case exn: case noexn: case string: + case handler: + case nohandler: return {}; case eq: return HeapType(any).getBasic(share); @@ -970,6 +1005,8 @@ std::optional HeapType::getSuperType() const { return HeapType(struct_).getBasic(share); case HeapTypeKind::Array: return HeapType(array).getBasic(share); + case HeapTypeKind::Handler: + return HeapType(handler).getBasic(share); case HeapTypeKind::Basic: break; } @@ -995,6 +1032,7 @@ size_t HeapType::getDepth() const { case HeapType::cont: case HeapType::any: case HeapType::exn: + case HeapType::handler: break; case HeapType::eq: depth++; @@ -1010,12 +1048,14 @@ size_t HeapType::getDepth() const { case HeapType::nocont: case HeapType::noext: case HeapType::noexn: + case HeapType::nohandler: // Bottom types are infinitely deep. depth = size_t(-1l); } break; case HeapTypeKind::Func: case HeapTypeKind::Cont: + case HeapTypeKind::Handler: ++depth; break; case HeapTypeKind::Struct: @@ -1039,6 +1079,8 @@ HeapType::BasicHeapType HeapType::getUnsharedBottom() const { return nofunc; case cont: return nocont; + case handler: + return nohandler; case exn: return noexn; case any: @@ -1057,6 +1099,8 @@ HeapType::BasicHeapType HeapType::getUnsharedBottom() const { return nocont; case noexn: return noexn; + case nohandler: + return nohandler; } } auto* info = getHeapTypeInfo(*this); @@ -1065,6 +1109,8 @@ HeapType::BasicHeapType HeapType::getUnsharedBottom() const { return nofunc; case HeapTypeKind::Cont: return nocont; + case HeapTypeKind::Handler: + return nohandler; case HeapTypeKind::Struct: case HeapTypeKind::Array: return none; @@ -1082,6 +1128,8 @@ HeapType::BasicHeapType HeapType::getUnsharedTop() const { return func; case nocont: return cont; + case nohandler: + return handler; case noext: return ext; case noexn: @@ -1089,6 +1137,7 @@ HeapType::BasicHeapType HeapType::getUnsharedTop() const { case ext: case func: case cont: + case handler: case any: case eq: case i31: @@ -1134,6 +1183,13 @@ std::vector HeapType::getTypeChildren() const { return {getArray().element.type}; case HeapTypeKind::Cont: return {}; + case HeapTypeKind::Handler: { + std::vector children; + for (auto t : getHandler().results) { + children.push_back(t); + } + return children; + } } WASM_UNREACHABLE("unexpected kind"); } @@ -1270,6 +1326,8 @@ FeatureSet HeapType::getFeatures() const { return; case HeapType::cont: case HeapType::nocont: + case HeapType::handler: + case HeapType::nohandler: feats |= FeatureSet::StackSwitching; return; } @@ -1295,7 +1353,7 @@ FeatureSet HeapType::getFeatures() const { if (sig.results.isTuple()) { feats |= FeatureSet::Multivalue; } - } else if (heapType.isContinuation()) { + } else if (heapType.isContinuation() || heapType.isHandler()) { feats |= FeatureSet::StackSwitching; } @@ -1355,6 +1413,9 @@ TypeNames DefaultTypeNameGenerator::getNames(HeapType type) { case HeapTypeKind::Cont: stream << "cont." << contCount++; break; + case HeapTypeKind::Handler: + stream << "handler." << handlerCount++; + break; case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } @@ -1372,6 +1433,7 @@ std::string Type::toString() const { return genericToString(*this); } std::string HeapType::toString() const { return genericToString(*this); } std::string Signature::toString() const { return genericToString(*this); } std::string Continuation::toString() const { return genericToString(*this); } +std::string Handler::toString() const { return genericToString(*this); } std::string Struct::toString() const { return genericToString(*this); } std::string Array::toString() const { return genericToString(*this); } @@ -1396,6 +1458,9 @@ std::ostream& operator<<(std::ostream& os, Signature sig) { std::ostream& operator<<(std::ostream& os, Continuation cont) { return TypePrinter(os).print(cont); } +std::ostream& operator<<(std::ostream& os, Handler handler) { + return TypePrinter(os).print(handler); +} std::ostream& operator<<(std::ostream& os, Field field) { return TypePrinter(os).print(field); } @@ -1487,6 +1552,8 @@ bool SubTyper::isSubType(HeapType a, HeapType b) { return aTop == HeapType::func; case HeapType::cont: return aTop == HeapType::cont; + case HeapType::handler: + return aTop == HeapType::handler; case HeapType::exn: return aTop == HeapType::exn; case HeapType::any: @@ -1507,6 +1574,7 @@ bool SubTyper::isSubType(HeapType a, HeapType b) { case HeapType::nofunc: case HeapType::nocont: case HeapType::noexn: + case HeapType::nohandler: return false; } } @@ -1555,6 +1623,10 @@ bool SubTyper::isSubType(const Continuation& a, const Continuation& b) { return isSubType(a.type, b.type); } +bool SubTyper::isSubType(const Handler& a, const Handler& b) { + return isSubType(a.results, b.results); +} + bool SubTyper::isSubType(const Struct& a, const Struct& b) { // There may be more fields on the left, but not fewer. if (a.fields.size() < b.fields.size()) { @@ -1661,6 +1733,12 @@ std::ostream& TypePrinter::print(Type type) { case HeapType::noexn: os << "nullexnref"; break; + case HeapType::nohandler: + os << "nullhandlerref"; + break; + case HeapType::handler: + os << "handlerref"; + break; } if (type.isExact()) { os << ')'; @@ -1733,6 +1811,12 @@ std::ostream& TypePrinter::print(HeapType type) { case HeapType::noexn: os << "noexn"; break; + case HeapType::nohandler: + os << "nohandler"; + break; + case HeapType::handler: + os << "handler"; + break; } if (type.isShared()) { os << ')'; @@ -1778,6 +1862,9 @@ std::ostream& TypePrinter::print(HeapType type) { case HeapTypeKind::Cont: print(type.getContinuation()); break; + case HeapTypeKind::Handler: + print(type.getHandler()); + break; case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } @@ -1849,6 +1936,15 @@ std::ostream& TypePrinter::print(const Continuation& continuation) { return os << ')'; } +std::ostream& TypePrinter::print(const Handler& handler) { + os << "(handler"; + for (Type t : handler.results) { + os << ' '; + print(t); + } + return os << ')'; +} + std::ostream& TypePrinter::print(const Struct& struct_, const std::unordered_map& fieldNames) { @@ -1946,6 +2042,9 @@ size_t RecGroupHasher::hash(const HeapTypeInfo& info) const { case HeapTypeKind::Array: hash_combine(digest, hash(info.array)); return digest; + case HeapTypeKind::Handler: + hash_combine(digest, hash(info.handler)); + return digest; case HeapTypeKind::Basic: break; } @@ -1981,6 +2080,13 @@ size_t RecGroupHasher::hash(const Continuation& continuation) const { return digest; } +size_t RecGroupHasher::hash(const Handler& handler) const { + size_t magic = 0xe05a2; + size_t digest = hash(handler.results); + rehash(digest, magic); + return digest; +} + size_t RecGroupHasher::hash(const Struct& struct_) const { size_t digest = wasm::hash(struct_.fields.size()); for (const auto& field : struct_.fields) { @@ -2075,6 +2181,8 @@ bool RecGroupEquator::eq(const HeapTypeInfo& a, const HeapTypeInfo& b) const { return eq(a.signature, b.signature); case HeapTypeKind::Cont: return eq(a.continuation, b.continuation); + case HeapTypeKind::Handler: + return eq(a.handler, b.handler); case HeapTypeKind::Struct: return eq(a.struct_, b.struct_); case HeapTypeKind::Array: @@ -2105,6 +2213,10 @@ bool RecGroupEquator::eq(const Continuation& a, const Continuation& b) const { return eq(a.type, b.type); } +bool RecGroupEquator::eq(const Handler& a, const Handler& b) const { + return eq(a.results, b.results); +} + bool RecGroupEquator::eq(const Struct& a, const Struct& b) const { return std::equal(a.fields.begin(), a.fields.end(), @@ -2147,6 +2259,9 @@ struct TypeBuilder::Impl { case HeapTypeKind::Cont: info->continuation = hti.continuation; break; + case HeapTypeKind::Handler: + info->handler = hti.handler; + break; case HeapTypeKind::Struct: info->struct_ = std::move(hti.struct_); break; @@ -2192,6 +2307,11 @@ void TypeBuilder::setHeapType(size_t i, Continuation continuation) { impl->entries[i].set(continuation); } +void TypeBuilder::setHeapType(size_t i, Handler handler) { + assert(i < size() && "index out of bounds"); + impl->entries[i].set(handler); +} + void TypeBuilder::setHeapType(size_t i, const Struct& struct_) { assert(i < size() && "index out of bounds"); impl->entries[i].set(struct_); @@ -2275,6 +2395,8 @@ bool isValidSupertype(const HeapTypeInfo& sub, const HeapTypeInfo& super) { return typer.isSubType(sub.signature, super.signature); case HeapTypeKind::Cont: return typer.isSubType(sub.continuation, super.continuation); + case HeapTypeKind::Handler: + return typer.isSubType(sub.handler, super.handler); case HeapTypeKind::Struct: return typer.isSubType(sub.struct_, super.struct_); case HeapTypeKind::Array: @@ -2327,6 +2449,9 @@ validateType(HeapTypeInfo& info, std::unordered_set& seenTypes) { } break; } + case HeapTypeKind::Handler: + // TODO: Figure out shared handler names. + return TypeBuilder::ErrorReason::InvalidUnsharedField; case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 157226994c1..a14520dc7f2 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -528,6 +528,8 @@ struct FunctionValidator : public WalkerPass> { void visitResume(Resume* curr); void visitResumeThrow(ResumeThrow* curr); void visitStackSwitch(StackSwitch* curr); + void visitSuspendTo(SuspendTo* curr); + void visitResumeWith(ResumeWith* curr); void visitFunction(Function* curr); @@ -3731,6 +3733,37 @@ void FunctionValidator::visitStackSwitch(StackSwitch* curr) { } } +void FunctionValidator::visitSuspendTo(SuspendTo* curr) { + // TODO implement actual type-checking + shouldBeTrue(!getModule() || getModule()->features.hasStackSwitching(), + curr, + "suspend requires stack-switching [--enable-stack-switching]"); + + shouldBeTrue(curr->handler->type.isHandler() || + curr->type == Type::unreachable, + curr, + "suspend_to must be annotated with a handler type"); +} + +void FunctionValidator::visitResumeWith(ResumeWith* curr) { + // TODO implement actual type-checking + shouldBeTrue(!getModule() || getModule()->features.hasStackSwitching(), + curr, + "resume requires stack-switching [--enable-stack-switching]"); + + shouldBeTrue( + curr->sentTypes.size() == curr->handlerBlocks.size(), + curr, + "sentTypes cache in resume instruction has not been initialized"); + + shouldBeTrue( + (curr->cont->type.isContinuation() && + curr->cont->type.getHeapType().getContinuation().type.isSignature()) || + curr->type == Type::unreachable, + curr, + "resume must be annotated with a continuation type"); +} + void FunctionValidator::visitFunction(Function* curr) { FeatureSet features; // Check for things like having a rec group with GC enabled. The type we're diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index d023109ce7c..6f2bc8b12f4 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -1446,6 +1446,36 @@ void StackSwitch::finalize() { this->cont->type.getHeapType().getContinuation().type.getSignature().params; } +void SuspendTo::finalize(Module* wasm) { + if (handler->type == Type::unreachable) { + type = Type::unreachable; + return; + } + + if (!handleUnreachableOperands(this) && wasm) { + auto tag_results = wasm->getTag(this->tag)->results(); + Tuple results{}; + results.assign(tag_results.begin(), tag_results.end()); + results.push_back(handler->type); + type = Type(results); + } +} + +void ResumeWith::finalize() { + if (cont->type == Type::unreachable) { + type = Type::unreachable; + return; + } + if (handleUnreachableOperands(this)) { + return; + } + + assert(this->cont->type.isContinuation()); + const Signature& contSig = + this->cont->type.getHeapType().getContinuation().type.getSignature(); + type = contSig.results; +} + size_t Function::getNumParams() { return getParams().size(); } size_t Function::getNumVars() { return vars.size(); } diff --git a/src/wasm2js.h b/src/wasm2js.h index 9a4416068c5..1b445c34dcb 100644 --- a/src/wasm2js.h +++ b/src/wasm2js.h @@ -2440,6 +2440,14 @@ Ref Wasm2JSBuilder::processExpression(Expression* curr, unimplemented(curr); WASM_UNREACHABLE("unimp"); } + Ref visitSuspendTo(SuspendTo* curr) { + unimplemented(curr); + WASM_UNREACHABLE("unimp"); + } + Ref visitResumeWith(ResumeWith* curr) { + unimplemented(curr); + WASM_UNREACHABLE("unimp"); + } private: Ref makePointer(Expression* ptr, Address offset) { diff --git a/test/gtest/type-domains.cpp b/test/gtest/type-domains.cpp index b59bc2a0dd7..294124a6e8d 100644 --- a/test/gtest/type-domains.cpp +++ b/test/gtest/type-domains.cpp @@ -114,6 +114,19 @@ void printCont(std::ostream& o, ContPlan& plan) { o << ")"; } +void printHandler(std::ostream& o, HandlerPlan& plan) { + o << "(handler "; + if (!plan.empty()) { + o << " (result"; + for (auto& type : plan) { + o << " "; + printType(o, type); + } + o << ")"; + } + o << ")"; +} + void printTypeDef(std::ostream& o, const TypeBuilderPlan& plan, size_t i) { auto def = plan.defs[i]; auto super = plan.supertypes[i]; @@ -137,6 +150,8 @@ void printTypeDef(std::ostream& o, const TypeBuilderPlan& plan, size_t i) { printArray(o, *array); } else if (auto* cont = def.getCont()) { printCont(o, *cont); + } else if (auto* handler = def.getHandler()) { + printHandler(o, *handler); } else { WASM_UNREACHABLE("unexpected kind"); } @@ -184,6 +199,9 @@ std::ostream& operator<<(std::ostream& o, const TypeBuilderPlan& plan) { case ContKind: o << "s"; break; + case HandlerKind: + o << "h"; + break; } if (auto super = plan.supertypes[i]) { o << "(" << *super << ")"; @@ -496,6 +514,10 @@ fuzztest::Domain AvailableStrictSubHeapType(TypeBuilderPlan plan, return matchingOrAbstract( [](auto kind) { return kind == ContKind; }, fuzztest::Just(HeapType(HeapTypes::nocont.getBasic(share)))); + case HeapType::handler: + return matchingOrAbstract( + [](auto kind) { return kind == HandlerKind; }, + fuzztest::Just(HeapType(HeapTypes::nocont.getBasic(share)))); case HeapType::any: return matchingOrAbstract( [](auto kind) { return kind == StructKind || kind == ArrayKind; }, @@ -532,6 +554,7 @@ fuzztest::Domain AvailableStrictSubHeapType(TypeBuilderPlan plan, case HeapType::nofunc: case HeapType::nocont: case HeapType::noexn: + case HeapType::nohandler: // No strict subtypes, so just return super. return fuzztest::Just(super); } @@ -553,6 +576,9 @@ fuzztest::Domain AvailableStrictSubHeapType(TypeBuilderPlan plan, case ContKind: bottom = HeapTypes::nocont.getBasic(share); break; + case HandlerKind: + bottom = HeapTypes::nohandler.getBasic(share); + break; } if (matches.empty()) { return fuzztest::Just(HeapTypePlan{bottom}); @@ -586,6 +612,7 @@ AvailableStrictSuperHeapType(TypeBuilderPlan plan, HeapTypePlan sub) { case HeapType::cont: case HeapType::any: case HeapType::exn: + case HeapType::handler: // No strict supertypes, so just return sub. return fuzztest::Just(sub); case HeapType::eq: @@ -617,6 +644,10 @@ AvailableStrictSuperHeapType(TypeBuilderPlan plan, HeapTypePlan sub) { return matchingOrAbstract( [](auto kind) { return kind == ContKind; }, fuzztest::Just(HeapType(HeapTypes::cont.getBasic(share)))); + case HeapType::nohandler: + return matchingOrAbstract( + [](auto kind) { return kind == HandlerKind; }, + fuzztest::Just(HeapType(HeapTypes::handler.getBasic(share)))); case HeapType::noexn: return fuzztest::Just( HeapTypePlan{HeapType(HeapTypes::exn.getBasic(share))}); @@ -649,6 +680,9 @@ AvailableStrictSuperHeapType(TypeBuilderPlan plan, HeapTypePlan sub) { case ContKind: abstract = {HeapTypes::cont.getBasic(share)}; break; + case HandlerKind: + abstract = {HeapTypes::handler.getBasic(share)}; + break; } assert(!abstract.empty()); if (possibleIndices.empty()) { @@ -884,6 +918,22 @@ fuzztest::Domain SubContDef(TypeBuilderPlan plan, ContPlan super) { } } +fuzztest::Domain HandlerDef(TypeBuilderPlan plan) { + auto results = fuzztest::VectorOf(AvailableType(std::move(plan))) + .WithMaxSize(MaxResultsSize); + return results; +} + +fuzztest::Domain SubHandlerDef(TypeBuilderPlan plan, + HandlerPlan super) { + auto results = MapElements( + [plan = std::move(plan)](TypePlan type) { + return AvailableSubType(std::move(plan), type); + }, + super); + return results; +} + fuzztest::Domain StepTypeDefinition(TypeBuilderPlan plan); template @@ -932,6 +982,12 @@ fuzztest::Domain StepTypeDefinition(TypeBuilderPlan plan) { return fuzztest::FlatMap( AppendTypeDef, fuzztest::Just(std::move(plan)), def); } + case HandlerKind: { + auto def = super ? SubHandlerDef(plan, *plan.defs[*super].getHandler()) + : HandlerDef(plan); + return fuzztest::FlatMap( + AppendTypeDef, fuzztest::Just(std::move(plan)), def); + } } WASM_UNREACHABLE("unexpected kind"); } @@ -1134,6 +1190,15 @@ void TestBuiltTypes(std::pair, TypeBuilderPlan> pair) { } }; + auto checkHandler = [&](HandlerPlan& plan, HeapType type) { + ASSERT_TRUE(type.isHandler()); + auto results = type.getHandler().results; + ASSERT_EQ(plan.size(), results.size()); + for (size_t i = 0; i < plan.size(); ++i) { + checkType(plan[i], results[i]); + } + }; + auto checkDef = [&](TypeDefPlan& plan, HeapType type) { if (auto* f = plan.getFunc()) { checkFunc(*f, type); @@ -1143,6 +1208,8 @@ void TestBuiltTypes(std::pair, TypeBuilderPlan> pair) { checkArray(*a, type); } else if (auto* c = plan.getCont()) { checkCont(*c, type); + } else if (auto* h = plan.getHandler()) { + checkHandler(*h, type); } else { WASM_UNREACHABLE("unexpected variant"); } diff --git a/test/gtest/type-domains.h b/test/gtest/type-domains.h index 17ad7fe9530..5579b9b7cc1 100644 --- a/test/gtest/type-domains.h +++ b/test/gtest/type-domains.h @@ -31,6 +31,7 @@ inline fuzztest::Domain ArbitraryUnsharedAbstractHeapType() { HeapTypes::ext, HeapTypes::func, HeapTypes::cont, + HeapTypes::handler, HeapTypes::any, HeapTypes::eq, HeapTypes::i31, @@ -43,6 +44,7 @@ inline fuzztest::Domain ArbitraryUnsharedAbstractHeapType() { HeapTypes::nofunc, HeapTypes::nocont, HeapTypes::noexn, + HeapTypes::nohandler, }); } @@ -67,7 +69,13 @@ inline fuzztest::Domain ArbitraryNonRefType() { std::vector{Type::i32, Type::i64, Type::f32, Type::f64, Type::v128}); } -enum UnsharedTypeKind { FuncKind, StructKind, ArrayKind, ContKind }; +enum UnsharedTypeKind { + FuncKind, + StructKind, + ArrayKind, + ContKind, + HandlerKind +}; struct TypeKind { UnsharedTypeKind kind; @@ -108,12 +116,15 @@ using ArrayPlan = FieldPlan; // If there is no available func type definition, this will be nullopt and we // will have to use a default fallback. using ContPlan = std::optional; +using HandlerPlan = std::vector; -struct TypeDefPlan : std::variant { +struct TypeDefPlan + : std::variant { FuncPlan* getFunc() { return std::get_if(this); } StructPlan* getStruct() { return std::get_if(this); } ArrayPlan* getArray() { return std::get_if(this); } ContPlan* getCont() { return std::get_if(this); } + HandlerPlan* getHandler() { return std::get_if(this); } }; struct TypeBuilderPlan { diff --git a/test/lit/basic/stack_switching_named.wast b/test/lit/basic/stack_switching_named.wast new file mode 100644 index 00000000000..4a3e8bba63d --- /dev/null +++ b/test/lit/basic/stack_switching_named.wast @@ -0,0 +1,75 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-opt %s -all -o %t.text.wast -g -S +;; RUN: wasm-as %s -all -g -o %t.wasm +;; RUN: wasm-dis %t.wasm -all -o %t.bin.wast +;; RUN: wasm-as %s -all -o %t.nodebug.wasm +;; RUN: wasm-dis %t.nodebug.wasm -all -o %t.bin.nodebug.wast +;; RUN: cat %t.text.wast | filecheck %s --check-prefix=CHECK-TEXT +;; RUN: cat %t.bin.wast | filecheck %s --check-prefix=CHECK-BIN +;; RUN: cat %t.bin.nodebug.wast | filecheck %s --check-prefix=CHECK-BIN-NODEBUG + +(module + ;; CHECK-TEXT: (type $ht-1 (handler i32)) + ;; CHECK-BIN: (type $ht-1 (handler i32)) + (type $ht-1 (handler (result i32))) + ;; CHECK-TEXT: (type $ht-2 (handler)) + ;; CHECK-BIN: (type $ht-2 (handler)) + (type $ht-2 (handler)) + + (func (export "main-1") (param (ref $ht-1)) + (unreachable)) + (func (export "main=2") (param (ref $ht-2)) + (unreachable)) +) +;; CHECK-TEXT: (type $2 (func (param (ref $ht-1)))) + +;; CHECK-TEXT: (type $3 (func (param (ref $ht-2)))) + +;; CHECK-TEXT: (export "main-1" (func $0)) + +;; CHECK-TEXT: (export "main=2" (func $1)) + +;; CHECK-TEXT: (func $0 (type $2) (param $0 (ref $ht-1)) +;; CHECK-TEXT-NEXT: (unreachable) +;; CHECK-TEXT-NEXT: ) + +;; CHECK-TEXT: (func $1 (type $3) (param $0 (ref $ht-2)) +;; CHECK-TEXT-NEXT: (unreachable) +;; CHECK-TEXT-NEXT: ) + +;; CHECK-BIN: (type $2 (func (param (ref $ht-1)))) + +;; CHECK-BIN: (type $3 (func (param (ref $ht-2)))) + +;; CHECK-BIN: (export "main-1" (func $0)) + +;; CHECK-BIN: (export "main=2" (func $1)) + +;; CHECK-BIN: (func $0 (type $2) (param $0 (ref $ht-1)) +;; CHECK-BIN-NEXT: (unreachable) +;; CHECK-BIN-NEXT: ) + +;; CHECK-BIN: (func $1 (type $3) (param $0 (ref $ht-2)) +;; CHECK-BIN-NEXT: (unreachable) +;; CHECK-BIN-NEXT: ) + +;; CHECK-BIN-NODEBUG: (type $0 (handler i32)) + +;; CHECK-BIN-NODEBUG: (type $1 (handler)) + +;; CHECK-BIN-NODEBUG: (type $2 (func (param (ref $0)))) + +;; CHECK-BIN-NODEBUG: (type $3 (func (param (ref $1)))) + +;; CHECK-BIN-NODEBUG: (export "main-1" (func $0)) + +;; CHECK-BIN-NODEBUG: (export "main=2" (func $1)) + +;; CHECK-BIN-NODEBUG: (func $0 (type $2) (param $0 (ref $0)) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $1 (type $3) (param $0 (ref $1)) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/basic/stack_switching_resume_with.wast b/test/lit/basic/stack_switching_resume_with.wast new file mode 100644 index 00000000000..366dbb799c0 --- /dev/null +++ b/test/lit/basic/stack_switching_resume_with.wast @@ -0,0 +1,127 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-opt %s -all -o %t.text.wast -g -S +;; RUN: wasm-as %s -all -g -o %t.wasm +;; RUN: wasm-dis %t.wasm -all -o %t.bin.wast +;; RUN: wasm-as %s -all -o %t.nodebug.wasm +;; RUN: wasm-dis %t.nodebug.wasm -all -o %t.bin.nodebug.wast +;; RUN: cat %t.text.wast | filecheck %s --check-prefix=CHECK-TEXT +;; RUN: cat %t.bin.wast | filecheck %s --check-prefix=CHECK-BIN +;; RUN: cat %t.bin.nodebug.wast | filecheck %s --check-prefix=CHECK-BIN-NODEBUG + +(module + ;; CHECK-TEXT: (type $ht (handler i32)) + ;; CHECK-BIN: (type $ht (handler i32)) + (type $ht (handler (result i32))) + ;; CHECK-TEXT: (type $ft (func (param i32 (ref $ht)) (result i32))) + ;; CHECK-BIN: (type $ft (func (param i32 (ref $ht)) (result i32))) + (type $ft (func (param i32 (ref $ht)) (result i32))) + + ;; CHECK-TEXT: (type $ct (cont $ft)) + ;; CHECK-BIN: (type $ct (cont $ft)) + (type $ct (cont $ft)) + + ;; CHECK-TEXT: (type $3 (func (result i32 (ref $ct)))) + + ;; CHECK-TEXT: (type $4 (func (param i32) (result i32))) + + ;; CHECK-TEXT: (type $5 (func (param (ref $ct)) (result i32))) + + ;; CHECK-TEXT: (tag $t (type $4) (param i32) (result i32)) + ;; CHECK-BIN: (type $3 (func (result i32 (ref $ct)))) + + ;; CHECK-BIN: (type $4 (func (param i32) (result i32))) + + ;; CHECK-BIN: (type $5 (func (param (ref $ct)) (result i32))) + + ;; CHECK-BIN: (tag $t (type $4) (param i32) (result i32)) + (tag $t (param i32) (result i32)) + + ;; CHECK-TEXT: (func $go (type $5) (param $x (ref $ct)) (result i32) + ;; CHECK-TEXT-NEXT: (tuple.extract 2 0 + ;; CHECK-TEXT-NEXT: (block $handler (type $3) (result i32 (ref $ct)) + ;; CHECK-TEXT-NEXT: (return + ;; CHECK-TEXT-NEXT: (resume_with $ct (on $t $handler) + ;; CHECK-TEXT-NEXT: (i32.const 123) + ;; CHECK-TEXT-NEXT: (local.get $x) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $go (type $5) (param $x (ref $ct)) (result i32) + ;; CHECK-BIN-NEXT: (local $scratch (tuple i32 (ref $ct))) + ;; CHECK-BIN-NEXT: (local $scratch_2 i32) + ;; CHECK-BIN-NEXT: (local.set $scratch_2 + ;; CHECK-BIN-NEXT: (tuple.extract 2 0 + ;; CHECK-BIN-NEXT: (local.tee $scratch + ;; CHECK-BIN-NEXT: (block $block (type $3) (result i32 (ref $ct)) + ;; CHECK-BIN-NEXT: (return + ;; CHECK-BIN-NEXT: (resume_with $ct (on $t $block) + ;; CHECK-BIN-NEXT: (i32.const 123) + ;; CHECK-BIN-NEXT: (local.get $x) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (tuple.extract 2 1 + ;; CHECK-BIN-NEXT: (local.get $scratch) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (local.get $scratch_2) + ;; CHECK-BIN-NEXT: ) + (func $go (param $x (ref $ct)) (result i32) + (tuple.extract 2 0 + (block $handler (result i32 (ref $ct)) + (return + (resume_with $ct + (on $t $handler) + (i32.const 123) + (local.get $x) + ) + ) + ) + ) + ) +) +;; CHECK-BIN-NODEBUG: (type $0 (handler i32)) + +;; CHECK-BIN-NODEBUG: (type $1 (func (param i32 (ref $0)) (result i32))) + +;; CHECK-BIN-NODEBUG: (type $2 (cont $1)) + +;; CHECK-BIN-NODEBUG: (type $3 (func (result i32 (ref $2)))) + +;; CHECK-BIN-NODEBUG: (type $4 (func (param i32) (result i32))) + +;; CHECK-BIN-NODEBUG: (type $5 (func (param (ref $2)) (result i32))) + +;; CHECK-BIN-NODEBUG: (tag $tag$0 (type $4) (param i32) (result i32)) + +;; CHECK-BIN-NODEBUG: (func $0 (type $5) (param $0 (ref $2)) (result i32) +;; CHECK-BIN-NODEBUG-NEXT: (local $scratch (tuple i32 (ref $2))) +;; CHECK-BIN-NODEBUG-NEXT: (local $scratch_2 i32) +;; CHECK-BIN-NODEBUG-NEXT: (local.set $scratch_2 +;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 2 0 +;; CHECK-BIN-NODEBUG-NEXT: (local.tee $scratch +;; CHECK-BIN-NODEBUG-NEXT: (block $block (type $3) (result i32 (ref $2)) +;; CHECK-BIN-NODEBUG-NEXT: (return +;; CHECK-BIN-NODEBUG-NEXT: (resume_with $2 (on $tag$0 $block) +;; CHECK-BIN-NODEBUG-NEXT: (i32.const 123) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 2 1 +;; CHECK-BIN-NODEBUG-NEXT: (local.get $scratch) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $scratch_2) +;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/basic/stack_switching_suspend_to.wast b/test/lit/basic/stack_switching_suspend_to.wast new file mode 100644 index 00000000000..6e0ddebac4e --- /dev/null +++ b/test/lit/basic/stack_switching_suspend_to.wast @@ -0,0 +1,129 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-opt %s -all -o %t.text.wast -g -S +;; RUN: wasm-as %s -all -g -o %t.wasm +;; RUN: wasm-dis %t.wasm -all -o %t.bin.wast +;; RUN: wasm-as %s -all -o %t.nodebug.wasm +;; RUN: wasm-dis %t.nodebug.wasm -all -o %t.bin.nodebug.wast +;; RUN: cat %t.text.wast | filecheck %s --check-prefix=CHECK-TEXT +;; RUN: cat %t.bin.wast | filecheck %s --check-prefix=CHECK-BIN +;; RUN: cat %t.bin.nodebug.wast | filecheck %s --check-prefix=CHECK-BIN-NODEBUG + +(module + ;; CHECK-TEXT: (type $ht (handler i32 i32 i64 f32)) + ;; CHECK-BIN: (type $ht (handler i32 i32 i64 f32)) + (type $ht (handler (result i32 i32 i64 f32))) + (type $ft (func (param (ref $ht)))) + (type $ct (cont $ft)) + + ;; CHECK-TEXT: (type $1 (func (param i32) (result i64))) + + ;; CHECK-TEXT: (type $2 (func (param (ref $ht)) (result i64))) + + ;; CHECK-TEXT: (tag $t (type $1) (param i32) (result i64)) + ;; CHECK-BIN: (type $1 (func (param i32) (result i64))) + + ;; CHECK-BIN: (type $2 (func (param (ref $ht)) (result i64))) + + ;; CHECK-BIN: (tag $t (type $1) (param i32) (result i64)) + (tag $t (param i32) (result i64)) + + ;; CHECK-TEXT: (func $f (type $2) (param $h (ref $ht)) (result i64) + ;; CHECK-TEXT-NEXT: (local $scratch (tuple i64 (ref $ht))) + ;; CHECK-TEXT-NEXT: (local $scratch_2 i64) + ;; CHECK-TEXT-NEXT: (local.set $scratch_2 + ;; CHECK-TEXT-NEXT: (tuple.extract 2 0 + ;; CHECK-TEXT-NEXT: (local.tee $scratch + ;; CHECK-TEXT-NEXT: (suspend_to $ht $t + ;; CHECK-TEXT-NEXT: (i32.const 123) + ;; CHECK-TEXT-NEXT: (local.get $h) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (tuple.extract 2 1 + ;; CHECK-TEXT-NEXT: (local.get $scratch) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (local.get $scratch_2) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $f (type $2) (param $h (ref $ht)) (result i64) + ;; CHECK-BIN-NEXT: (local $scratch i64) + ;; CHECK-BIN-NEXT: (local $scratch_2 i64) + ;; CHECK-BIN-NEXT: (local $3 (ref $ht)) + ;; CHECK-BIN-NEXT: (local $scratch_4 (tuple i64 (ref $ht))) + ;; CHECK-BIN-NEXT: (local $scratch_5 i64) + ;; CHECK-BIN-NEXT: (local.set $scratch_2 + ;; CHECK-BIN-NEXT: (local.tee $scratch + ;; CHECK-BIN-NEXT: (block (result i64) + ;; CHECK-BIN-NEXT: (local.set $scratch_5 + ;; CHECK-BIN-NEXT: (tuple.extract 2 0 + ;; CHECK-BIN-NEXT: (local.tee $scratch_4 + ;; CHECK-BIN-NEXT: (suspend_to $ht $t + ;; CHECK-BIN-NEXT: (i32.const 123) + ;; CHECK-BIN-NEXT: (local.get $h) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (local.set $3 + ;; CHECK-BIN-NEXT: (tuple.extract 2 1 + ;; CHECK-BIN-NEXT: (local.get $scratch_4) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (local.get $scratch_5) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (local.get $3) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (local.get $scratch_2) + ;; CHECK-BIN-NEXT: ) + (func $f (param $h (ref $ht)) (result i64) + (suspend_to $ht $t (i32.const 123) (local.get $h)) + (drop) + ) +) +;; CHECK-BIN-NODEBUG: (type $0 (handler i32 i32 i64 f32)) + +;; CHECK-BIN-NODEBUG: (type $1 (func (param i32) (result i64))) + +;; CHECK-BIN-NODEBUG: (type $2 (func (param (ref $0)) (result i64))) + +;; CHECK-BIN-NODEBUG: (tag $tag$0 (type $1) (param i32) (result i64)) + +;; CHECK-BIN-NODEBUG: (func $0 (type $2) (param $0 (ref $0)) (result i64) +;; CHECK-BIN-NODEBUG-NEXT: (local $1 i64) +;; CHECK-BIN-NODEBUG-NEXT: (local $2 i64) +;; CHECK-BIN-NODEBUG-NEXT: (local $3 (ref $0)) +;; CHECK-BIN-NODEBUG-NEXT: (local $scratch (tuple i64 (ref $0))) +;; CHECK-BIN-NODEBUG-NEXT: (local $scratch_5 i64) +;; CHECK-BIN-NODEBUG-NEXT: (local.set $2 +;; CHECK-BIN-NODEBUG-NEXT: (local.tee $1 +;; CHECK-BIN-NODEBUG-NEXT: (block (result i64) +;; CHECK-BIN-NODEBUG-NEXT: (local.set $scratch_5 +;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 2 0 +;; CHECK-BIN-NODEBUG-NEXT: (local.tee $scratch +;; CHECK-BIN-NODEBUG-NEXT: (suspend_to $0 $tag$0 +;; CHECK-BIN-NODEBUG-NEXT: (i32.const 123) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (local.set $3 +;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 2 1 +;; CHECK-BIN-NODEBUG-NEXT: (local.get $scratch) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $scratch_5) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (local.get $3) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $2) +;; CHECK-BIN-NODEBUG-NEXT: )