From 609c1fc8b9fac70ef0f47c5bc97423f80fc42a1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hillerstr=C3=B6m?= Date: Wed, 26 Feb 2025 14:44:54 +0000 Subject: [PATCH 1/9] Named handlers support --- scripts/gen-s-parser.py | 2 + src/binaryen-c.cpp | 4 + src/gen-s-parser.inc | 42 ++++-- src/interpreter/interpreter.cpp | 2 + src/ir/ReFinalize.cpp | 4 + src/ir/branch-utils.h | 7 + src/ir/child-typer.h | 29 ++++ src/ir/cost.h | 15 +++ src/ir/effects.h | 23 ++++ src/ir/gc-type-utils.h | 1 + src/ir/possible-contents.cpp | 8 ++ src/ir/subtype-exprs.h | 4 + src/ir/subtypes.h | 2 + src/ir/type-updating.cpp | 2 + src/parser/contexts.h | 52 +++++++ src/parser/parsers.h | 65 +++++++++ src/passes/Print.cpp | 30 ++++- src/passes/TypeGeneralizing.cpp | 2 + src/passes/TypeMerging.cpp | 4 + src/passes/TypeSSA.cpp | 1 + src/passes/Unsubtyping.cpp | 2 + src/tools/fuzzing/fuzzing.cpp | 11 ++ src/tools/fuzzing/heap-types.cpp | 14 ++ src/tools/wasm-fuzz-types.cpp | 2 + src/wasm-binary.h | 43 +++--- src/wasm-builder.h | 24 ++++ src/wasm-delegations-fields.def | 13 ++ src/wasm-delegations.def | 2 + src/wasm-interpreter.h | 4 + src/wasm-ir-builder.h | 4 + src/wasm-type-printing.h | 1 + src/wasm-type.h | 37 +++++ src/wasm.h | 46 +++++++ src/wasm/literal.cpp | 6 + src/wasm/wasm-binary.cpp | 69 ++++++++++ src/wasm/wasm-ir-builder.cpp | 62 +++++++++ src/wasm/wasm-stack.cpp | 27 ++++ src/wasm/wasm-type-shape.cpp | 14 ++ src/wasm/wasm-type.cpp | 127 +++++++++++++++++- src/wasm/wasm-validator.cpp | 33 +++++ src/wasm/wasm.cpp | 27 ++++ src/wasm2js.h | 8 ++ test/lit/basic/stack_switching_named.wast | 44 ++++++ .../basic/stack_switching_resume_with.wast | 127 ++++++++++++++++++ .../lit/basic/stack_switching_suspend_to.wast | 60 +++++++++ 45 files changed, 1076 insertions(+), 30 deletions(-) create mode 100644 test/lit/basic/stack_switching_named.wast create mode 100644 test/lit/basic/stack_switching_resume_with.wast create mode 100644 test/lit/basic/stack_switching_suspend_to.wast 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/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 55601b174c5..444fce46f85 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{}; } @@ -145,6 +148,7 @@ struct NullTypeParserCtx { SignatureT makeFuncType(ParamsT*, ResultsT*) { return Ok{}; } ContinuationT makeContType(HeapTypeT) { return Ok{}; } + HandlerT makeHandlerType(TypeT) { return Ok{}; } StorageT makeI8() { return Ok{}; } StorageT makeI16() { return Ok{}; } @@ -188,6 +192,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>; @@ -235,6 +240,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); } @@ -250,6 +258,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; } @@ -287,6 +298,7 @@ template struct TypeParserCtx { } ContinuationT makeContType(HeapTypeT ft) { return Continuation(ft); } + HandlerT makeHandlerType(TypeT type) { return Handler(type); } StorageT makeI8() { return Field(Field::i8, Immutable); } StorageT makeI16() { return Field(Field::i16, Immutable); } @@ -893,6 +905,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 { @@ -973,6 +997,7 @@ struct ParseDeclsCtx : NullTypeParserCtx, NullInstrParserCtx { void addFuncType(SignatureT) {} void addContType(ContinuationT) {} + void addHandlerType(HandlerT) {} void addStructType(StructT) {} void addArrayType(ArrayT) {} void setOpen() {} @@ -1132,6 +1157,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; @@ -2734,6 +2760,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 4ba88fd8bf2..c1eb75f2871 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -320,6 +320,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&) { @@ -413,6 +417,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); } @@ -428,6 +435,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"); } @@ -488,6 +498,9 @@ template MaybeResult maybeReftype(Ctx& ctx) { if (ctx.in.takeKeyword("contref"sv)) { return ctx.makeRefType(ctx.makeContType(Unshared), Nullable); } + if (ctx.in.takeKeyword("handlerref"sv)) { + return ctx.makeRefType(ctx.makeHandlerType(Unshared), Nullable); + } if (ctx.in.takeKeyword("nullref"sv)) { return ctx.makeRefType(ctx.makeNoneType(Unshared), Nullable); } @@ -503,6 +516,9 @@ template MaybeResult maybeReftype(Ctx& ctx) { if (ctx.in.takeKeyword("nullcontref"sv)) { return ctx.makeRefType(ctx.makeNocontType(Unshared), Nullable); } + if (ctx.in.takeKeyword("nullhandlerref"sv)) { + return ctx.makeRefType(ctx.makeNohandlerType(Unshared), Nullable); + } if (!ctx.in.takeSExprStart("ref"sv)) { return {}; @@ -675,6 +691,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 elems = ctx.makeTupleElemList(); + while (!ctx.in.takeRParen()) { + auto type = valtype(ctx); + CHECK_ERR(type); + ctx.appendTupleElem(elems, *type); + } + + return ctx.makeHandlerType(ctx.makeTupleType(elems)); +} + // storagetype ::= valtype | packedtype // packedtype ::= i8 | i16 template Result storagetype(Ctx& ctx) { @@ -2612,6 +2645,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 // ======= @@ -2918,6 +2978,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..86c40811f56 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().value_types, 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 9765afd0cbe..85421e6ac76 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -329,6 +329,8 @@ enum EncodedType { nullable = -0x1d, // 0x63 contref = -0x18, // 0x68 nullcontref = -0x0b, // 0x75 + handlerref = -0x1e, + nullhandlerref = -0x1a, // 0x66 // exception handling exnref = -0x17, // 0x69 nullexnref = -0xc, // 0x74 @@ -344,26 +346,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 { @@ -1166,9 +1171,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..e1b7a8010ee 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,6 +121,8 @@ 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); @@ -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 value_types; + Handler(Type value_types) : value_types(value_types) {} + bool operator==(const Handler& other) const { + return value_types == other.value_types; + } + 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.value_types))); + 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 f9d9225bbc7..7768286add2 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 value_types = type.getHandler().value_types; + o << U32LEB(value_types.size()); + for (const auto& type : value_types) { + writeType(type); + } + break; + } case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } @@ -1580,6 +1589,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; @@ -1613,6 +1625,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()) { @@ -1678,6 +1693,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; @@ -1714,6 +1732,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 } @@ -2045,6 +2066,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; @@ -2084,6 +2108,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; } @@ -2097,6 +2124,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; @@ -2136,6 +2166,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; } @@ -2379,6 +2412,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: @@ -2459,6 +2501,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)); } @@ -3085,6 +3129,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 1835949b63e..01663a66511 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) { @@ -2497,4 +2511,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..b2973ac8999 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.value_types, b.value_types); + } + 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.value_types); } + 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..a2e65923134 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.value_types)); + 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().value_types) { + 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.value_types, b.value_types); +} + 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.value_types) { + 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.value_types); + 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.value_types, b.value_types); +} + 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 3f67906f105..f4eadae5604 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -516,6 +516,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); @@ -3719,6 +3721,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 c8a82311c30..aecd54d0ae2 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -1445,6 +1445,33 @@ 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 = wasm->getTag(this->tag); + type = tag->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/lit/basic/stack_switching_named.wast b/test/lit/basic/stack_switching_named.wast new file mode 100644 index 00000000000..b2630e92c54 --- /dev/null +++ b/test/lit/basic/stack_switching_named.wast @@ -0,0 +1,44 @@ +;; 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 i32)) + + (func (export "main") (param (ref $ht)) + (unreachable)) +) +;; CHECK-TEXT: (type $1 (func (param (ref $ht)))) + +;; CHECK-TEXT: (export "main" (func $0)) + +;; CHECK-TEXT: (func $0 (type $1) (param $0 (ref $ht)) +;; CHECK-TEXT-NEXT: (unreachable) +;; CHECK-TEXT-NEXT: ) + +;; CHECK-BIN: (type $1 (func (param (ref $ht)))) + +;; CHECK-BIN: (export "main" (func $0)) + +;; CHECK-BIN: (func $0 (type $1) (param $0 (ref $ht)) +;; CHECK-BIN-NEXT: (unreachable) +;; CHECK-BIN-NEXT: ) + +;; CHECK-BIN-NODEBUG: (type $0 (handler i32)) + +;; CHECK-BIN-NODEBUG: (type $1 (func (param (ref $0)))) + +;; CHECK-BIN-NODEBUG: (export "main" (func $0)) + +;; CHECK-BIN-NODEBUG: (func $0 (type $1) (param $0 (ref $0)) +;; 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..d84ddc17b88 --- /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 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..1545695505a --- /dev/null +++ b/test/lit/basic/stack_switching_suspend_to.wast @@ -0,0 +1,60 @@ +;; 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 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: (suspend_to $ht $t + ;; CHECK-TEXT-NEXT: (i32.const 123) + ;; CHECK-TEXT-NEXT: (local.get $h) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $f (type $2) (param $h (ref $ht)) (result i64) + ;; 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: ) + (func $f (param $h (ref $ht)) (result i64) + (suspend_to $ht $t (i32.const 123) (local.get $h)) + ) +) +;; 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: (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: ) From 81ee39bb3a241433bd1cab4920fd180c68691f0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hillerstr=C3=B6m?= Date: Wed, 26 Feb 2025 14:56:41 +0000 Subject: [PATCH 2/9] Make the fuzzer ignore all stack_switching_.wast lit tests --- scripts/test/fuzzing.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 270a9cf6b80..a9907690ce9 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -100,7 +100,10 @@ 'stack_switching_suspend.wast', 'stack_switching_resume.wast', 'stack_switching_resume_throw.wast', - 'stack_switching_switch.wast' + 'stack_switching_switch.wast', + 'stack_switching_named.wast', + 'stack_switching_suspend_to.wast', + 'stack_switching_resume_with.wast', ] From 4bbed5e28b09fa0bb58871af149704d4e6bee1f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hillerstr=C3=B6m?= Date: Wed, 26 Feb 2025 16:07:26 +0000 Subject: [PATCH 3/9] Fuzzing stubs for defined types. --- test/gtest/type-domains.cpp | 21 +++++++++++++++++++++ test/gtest/type-domains.h | 15 +++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/test/gtest/type-domains.cpp b/test/gtest/type-domains.cpp index b59bc2a0dd7..5f903803548 100644 --- a/test/gtest/type-domains.cpp +++ b/test/gtest/type-domains.cpp @@ -496,6 +496,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 +536,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); } @@ -586,6 +591,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 +623,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))}); @@ -1134,6 +1144,15 @@ void TestBuiltTypes(std::pair, TypeBuilderPlan> pair) { } }; + auto checkHandler = [&](HandlerPlan& plan, HeapType type) { + ASSERT_TRUE(type.isHandler()); + auto value_types = type.getHandler().value_types; + ASSERT_EQ(plan.size(), value_types.size()); + for (size_t i = 0; i < plan.size(); ++i) { + checkType(plan[i], value_types[i]); + } + }; + auto checkDef = [&](TypeDefPlan& plan, HeapType type) { if (auto* f = plan.getFunc()) { checkFunc(*f, type); @@ -1143,6 +1162,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()) { + checkCont(*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 { From f9f25e2e61058f92a10b093bb32400ccec20437e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hillerstr=C3=B6m?= Date: Fri, 28 Feb 2025 09:45:38 +0000 Subject: [PATCH 4/9] Fix WAT parsing of handler types. Rename value_types field to results. Fix fuzzer stubs. --- src/parser/contexts.h | 8 ++- src/parser/parsers.h | 12 ++-- src/tools/fuzzing/heap-types.cpp | 2 +- src/wasm-type.h | 8 +-- src/wasm/wasm-binary.cpp | 6 +- src/wasm/wasm-type-shape.cpp | 4 +- src/wasm/wasm-type.cpp | 12 ++-- test/gtest/type-domains.cpp | 39 +++++++++++-- test/lit/basic/stack_switching_named.wast | 57 ++++++++++++++----- .../basic/stack_switching_resume_with.wast | 2 +- .../lit/basic/stack_switching_suspend_to.wast | 2 +- 11 files changed, 109 insertions(+), 43 deletions(-) diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 444fce46f85..a9d07c7a21c 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -148,7 +148,7 @@ struct NullTypeParserCtx { SignatureT makeFuncType(ParamsT*, ResultsT*) { return Ok{}; } ContinuationT makeContType(HeapTypeT) { return Ok{}; } - HandlerT makeHandlerType(TypeT) { return Ok{}; } + HandlerT makeHandlerType(ResultsT*) { return Ok{}; } StorageT makeI8() { return Ok{}; } StorageT makeI16() { return Ok{}; } @@ -298,7 +298,11 @@ template struct TypeParserCtx { } ContinuationT makeContType(HeapTypeT ft) { return Continuation(ft); } - HandlerT makeHandlerType(TypeT type) { return Handler(type); } + 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); } diff --git a/src/parser/parsers.h b/src/parser/parsers.h index c1eb75f2871..7c07e33e895 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -698,14 +698,14 @@ MaybeResult handlertype(Ctx& ctx) { return {}; } - auto elems = ctx.makeTupleElemList(); - while (!ctx.in.takeRParen()) { - auto type = valtype(ctx); - CHECK_ERR(type); - ctx.appendTupleElem(elems, *type); + auto parsedResults = results(ctx); + CHECK_ERR(parsedResults); + + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected end of handler name type"); } - return ctx.makeHandlerType(ctx.makeTupleType(elems)); + return ctx.makeHandlerType(parsedResults.getPtr()); } // storagetype ::= valtype | packedtype diff --git a/src/tools/fuzzing/heap-types.cpp b/src/tools/fuzzing/heap-types.cpp index 86c40811f56..a8c43f1faf8 100644 --- a/src/tools/fuzzing/heap-types.cpp +++ b/src/tools/fuzzing/heap-types.cpp @@ -1070,7 +1070,7 @@ bool isUninhabitable(HeapType type, } break; case HeapTypeKind::Handler: - if (isUninhabitable(type.getHandler().value_types, visited, visiting)) { + if (isUninhabitable(type.getHandler().results, visited, visiting)) { return true; } break; diff --git a/src/wasm-type.h b/src/wasm-type.h index e1b7a8010ee..e71ad5f9d8c 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -606,10 +606,10 @@ struct Continuation { }; struct Handler { - Type value_types; - Handler(Type value_types) : value_types(value_types) {} + Type results; + Handler(Type results) : results(results) {} bool operator==(const Handler& other) const { - return value_types == other.value_types; + return results == other.results; } bool operator!=(const Handler& other) const { return !(*this == other); } std::string toString() const; @@ -773,7 +773,7 @@ struct TypeBuilder { return; case HeapTypeKind::Handler: { Handler h = type.getHandler(); - setHeapType(i, Handler(copyType(h.value_types))); + setHeapType(i, Handler(copyType(h.results))); return; } case HeapTypeKind::Basic: diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 7768286add2..406608488d6 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -318,9 +318,9 @@ void WasmBinaryWriter::writeTypes() { break; case HeapTypeKind::Handler: { o << uint8_t(BinaryConsts::EncodedType::Handler); - auto value_types = type.getHandler().value_types; - o << U32LEB(value_types.size()); - for (const auto& type : value_types) { + auto results = type.getHandler().results; + o << U32LEB(results.size()); + for (const auto& type : results) { writeType(type); } break; diff --git a/src/wasm/wasm-type-shape.cpp b/src/wasm/wasm-type-shape.cpp index b2973ac8999..fb6dbe9d284 100644 --- a/src/wasm/wasm-type-shape.cpp +++ b/src/wasm/wasm-type-shape.cpp @@ -121,7 +121,7 @@ template struct RecGroupComparator { } Comparison compare(Handler a, Handler b) { - return compare(a.value_types, b.value_types); + return compare(a.results, b.results); } Comparison compare(Field a, Field b) { @@ -278,7 +278,7 @@ struct RecGroupHasher { size_t hash(Continuation cont) { return hash(cont.type); } - size_t hash(Handler handler) { return hash(handler.value_types); } + size_t hash(Handler handler) { return hash(handler.results); } size_t hash(Field field) { size_t digest = wasm::hash(field.mutable_); diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index a2e65923134..94430d878ba 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -300,7 +300,7 @@ template struct TypeGraphWalkerBase { taskList.push_back(Task::scan(&info->array.element.type)); break; case HeapTypeKind::Handler: - taskList.push_back(Task::scan(&info->handler.value_types)); + taskList.push_back(Task::scan(&info->handler.results)); break; case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); @@ -1185,7 +1185,7 @@ std::vector HeapType::getTypeChildren() const { return {}; case HeapTypeKind::Handler: { std::vector children; - for (auto t : getHandler().value_types) { + for (auto t : getHandler().results) { children.push_back(t); } return children; @@ -1624,7 +1624,7 @@ bool SubTyper::isSubType(const Continuation& a, const Continuation& b) { } bool SubTyper::isSubType(const Handler& a, const Handler& b) { - return isSubType(a.value_types, b.value_types); + return isSubType(a.results, b.results); } bool SubTyper::isSubType(const Struct& a, const Struct& b) { @@ -1938,7 +1938,7 @@ std::ostream& TypePrinter::print(const Continuation& continuation) { std::ostream& TypePrinter::print(const Handler& handler) { os << "(handler"; - for (Type t : handler.value_types) { + for (Type t : handler.results) { os << ' '; print(t); } @@ -2082,7 +2082,7 @@ size_t RecGroupHasher::hash(const Continuation& continuation) const { size_t RecGroupHasher::hash(const Handler& handler) const { size_t magic = 0xe05a2; - size_t digest = hash(handler.value_types); + size_t digest = hash(handler.results); rehash(digest, magic); return digest; } @@ -2214,7 +2214,7 @@ bool RecGroupEquator::eq(const Continuation& a, const Continuation& b) const { } bool RecGroupEquator::eq(const Handler& a, const Handler& b) const { - return eq(a.value_types, b.value_types); + return eq(a.results, b.results); } bool RecGroupEquator::eq(const Struct& a, const Struct& b) const { diff --git a/test/gtest/type-domains.cpp b/test/gtest/type-domains.cpp index 5f903803548..4b42b140a67 100644 --- a/test/gtest/type-domains.cpp +++ b/test/gtest/type-domains.cpp @@ -184,6 +184,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 << ")"; @@ -558,6 +561,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}); @@ -659,6 +665,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()) { @@ -894,6 +903,22 @@ fuzztest::Domain SubContDef(TypeBuilderPlan plan, ContPlan super) { } } +fuzztest::Domain HandlerDef(TypeBuilderPlan plan) { + auto results = fuzztest::VectorOf(AvailableType(std::move(plan))) + .WithMaxSize(MaxResultsSize); + return fuzztest::Just(results); +} + +fuzztest::Domain SubHandlerDef(TypeBuilderPlan plan, + HandlerPlan super) { + auto results = MapElements( + [plan = std::move(plan)](TypePlan type) { + return AvailableSubType(std::move(plan), type); + }, + super.second); + return fuzztest::Just(results); +} + fuzztest::Domain StepTypeDefinition(TypeBuilderPlan plan); template @@ -942,6 +967,12 @@ fuzztest::Domain StepTypeDefinition(TypeBuilderPlan plan) { return fuzztest::FlatMap( AppendTypeDef, fuzztest::Just(std::move(plan)), def); } + case ContKind: { + 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"); } @@ -1146,10 +1177,10 @@ void TestBuiltTypes(std::pair, TypeBuilderPlan> pair) { auto checkHandler = [&](HandlerPlan& plan, HeapType type) { ASSERT_TRUE(type.isHandler()); - auto value_types = type.getHandler().value_types; - ASSERT_EQ(plan.size(), value_types.size()); + auto results = type.getHandler().results; + ASSERT_EQ(plan.size(), results.size()); for (size_t i = 0; i < plan.size(); ++i) { - checkType(plan[i], value_types[i]); + checkType(plan[i], results[i]); } }; @@ -1163,7 +1194,7 @@ void TestBuiltTypes(std::pair, TypeBuilderPlan> pair) { } else if (auto* c = plan.getCont()) { checkCont(*c, type); } else if (auto* h = plan.getHandler()) { - checkCont(*h, type); + checkHandler(*h, type); } else { WASM_UNREACHABLE("unexpected variant"); } diff --git a/test/lit/basic/stack_switching_named.wast b/test/lit/basic/stack_switching_named.wast index b2630e92c54..4a3e8bba63d 100644 --- a/test/lit/basic/stack_switching_named.wast +++ b/test/lit/basic/stack_switching_named.wast @@ -10,35 +10,66 @@ ;; 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 i32)) + ;; 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") (param (ref $ht)) + (func (export "main-1") (param (ref $ht-1)) + (unreachable)) + (func (export "main=2") (param (ref $ht-2)) (unreachable)) ) -;; CHECK-TEXT: (type $1 (func (param (ref $ht)))) +;; 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" (func $0)) +;; CHECK-TEXT: (export "main=2" (func $1)) -;; CHECK-TEXT: (func $0 (type $1) (param $0 (ref $ht)) +;; CHECK-TEXT: (func $0 (type $2) (param $0 (ref $ht-1)) ;; CHECK-TEXT-NEXT: (unreachable) ;; CHECK-TEXT-NEXT: ) -;; CHECK-BIN: (type $1 (func (param (ref $ht)))) +;; CHECK-TEXT: (func $1 (type $3) (param $0 (ref $ht-2)) +;; CHECK-TEXT-NEXT: (unreachable) +;; CHECK-TEXT-NEXT: ) -;; CHECK-BIN: (export "main" (func $0)) +;; CHECK-BIN: (type $2 (func (param (ref $ht-1)))) -;; CHECK-BIN: (func $0 (type $1) (param $0 (ref $ht)) +;; 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 (func (param (ref $0)))) +;; 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" (func $0)) +;; 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 $0 (type $1) (param $0 (ref $0)) +;; 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 index d84ddc17b88..366dbb799c0 100644 --- a/test/lit/basic/stack_switching_resume_with.wast +++ b/test/lit/basic/stack_switching_resume_with.wast @@ -12,7 +12,7 @@ (module ;; CHECK-TEXT: (type $ht (handler i32)) ;; CHECK-BIN: (type $ht (handler i32)) - (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))) diff --git a/test/lit/basic/stack_switching_suspend_to.wast b/test/lit/basic/stack_switching_suspend_to.wast index 1545695505a..abe88c3c8e9 100644 --- a/test/lit/basic/stack_switching_suspend_to.wast +++ b/test/lit/basic/stack_switching_suspend_to.wast @@ -12,7 +12,7 @@ (module ;; CHECK-TEXT: (type $ht (handler i32 i32 i64 f32)) ;; CHECK-BIN: (type $ht (handler i32 i32 i64 f32)) - (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)) From c8139c663b56a419bcdf47942144b671f8ef8cfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hillerstr=C3=B6m?= Date: Fri, 28 Feb 2025 09:52:49 +0000 Subject: [PATCH 5/9] Format --- src/wasm/wasm-type.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index 94430d878ba..76034238a91 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -1734,8 +1734,8 @@ std::ostream& TypePrinter::print(Type type) { os << "nullexnref"; break; case HeapType::nohandler: - os << "nullhandlerref"; - break; + os << "nullhandlerref"; + break; case HeapType::handler: os << "handlerref"; break; From ab9ceca7de928de1c7daa860aca02a4b22024bf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hillerstr=C3=B6m?= Date: Fri, 28 Feb 2025 10:42:25 +0000 Subject: [PATCH 6/9] Fuzz fix --- test/gtest/type-domains.cpp | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/test/gtest/type-domains.cpp b/test/gtest/type-domains.cpp index 4b42b140a67..a834bc8e3eb 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"); } @@ -906,7 +921,7 @@ fuzztest::Domain SubContDef(TypeBuilderPlan plan, ContPlan super) { fuzztest::Domain HandlerDef(TypeBuilderPlan plan) { auto results = fuzztest::VectorOf(AvailableType(std::move(plan))) .WithMaxSize(MaxResultsSize); - return fuzztest::Just(results); + return results; } fuzztest::Domain SubHandlerDef(TypeBuilderPlan plan, @@ -915,8 +930,8 @@ fuzztest::Domain SubHandlerDef(TypeBuilderPlan plan, [plan = std::move(plan)](TypePlan type) { return AvailableSubType(std::move(plan), type); }, - super.second); - return fuzztest::Just(results); + super); + return results; } fuzztest::Domain StepTypeDefinition(TypeBuilderPlan plan); From acc738b0e2348ebcd7a722f79c82de382a8b5f1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hillerstr=C3=B6m?= Date: Fri, 28 Feb 2025 11:12:37 +0000 Subject: [PATCH 7/9] HandlerKind --- test/gtest/type-domains.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/gtest/type-domains.cpp b/test/gtest/type-domains.cpp index a834bc8e3eb..294124a6e8d 100644 --- a/test/gtest/type-domains.cpp +++ b/test/gtest/type-domains.cpp @@ -982,7 +982,7 @@ fuzztest::Domain StepTypeDefinition(TypeBuilderPlan plan) { return fuzztest::FlatMap( AppendTypeDef, fuzztest::Just(std::move(plan)), def); } - case ContKind: { + case HandlerKind: { auto def = super ? SubHandlerDef(plan, *plan.defs[*super].getHandler()) : HandlerDef(plan); return fuzztest::FlatMap( From 9eec0951e48a184f35dd35f6afe0fe87b9a12a7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hillerstr=C3=B6m?= Date: Tue, 4 Mar 2025 12:27:09 +0000 Subject: [PATCH 8/9] Fix bug in isBasic check. --- src/parser/parsers.h | 8 ++++---- src/wasm-type.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/parser/parsers.h b/src/parser/parsers.h index ef36397dab8..8f39e0008b5 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -502,7 +502,7 @@ MaybeResult maybeReftypeAbbrev(Ctx& ctx) { return ctx.makeRefType(ctx.makeContType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("handlerref"sv)) { - return ctx.makeRefType(ctx.makeHandlerType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeHandlerType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("nullref"sv)) { return ctx.makeRefType(ctx.makeNoneType(Unshared), Nullable, Inexact); @@ -519,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 {}; } @@ -537,9 +540,6 @@ template MaybeResult maybeReftype(Ctx& ctx) { } return ctx.makeRefType(ctx.getHeapTypeFromRefType(*rt), Nullable, Exact); } - if (ctx.in.takeKeyword("nullhandlerref"sv)) { - return ctx.makeRefType(ctx.makeNohandlerType(Unshared), Nullable); - } if (ctx.in.takeSExprStart("ref"sv)) { auto nullability = ctx.in.takeKeyword("null"sv) ? Nullable : NonNullable; diff --git a/src/wasm-type.h b/src/wasm-type.h index e71ad5f9d8c..1c5ac0b3283 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -125,7 +125,7 @@ class HeapType { 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) {} From 973d3188e345739d5b503a56b266605d41842bde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hillerstr=C3=B6m?= Date: Wed, 5 Mar 2025 08:38:19 +0000 Subject: [PATCH 9/9] Fix bug in type of suspend_to --- src/wasm/wasm.cpp | 7 +- .../lit/basic/stack_switching_suspend_to.wast | 87 +++++++++++++++++-- 2 files changed, 83 insertions(+), 11 deletions(-) diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 0a765ee052e..6f2bc8b12f4 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -1453,8 +1453,11 @@ void SuspendTo::finalize(Module* wasm) { } if (!handleUnreachableOperands(this) && wasm) { - auto tag = wasm->getTag(this->tag); - type = tag->results(); + 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); } } diff --git a/test/lit/basic/stack_switching_suspend_to.wast b/test/lit/basic/stack_switching_suspend_to.wast index abe88c3c8e9..6e0ddebac4e 100644 --- a/test/lit/basic/stack_switching_suspend_to.wast +++ b/test/lit/basic/stack_switching_suspend_to.wast @@ -29,19 +29,61 @@ (tag $t (param i32) (result i64)) ;; CHECK-TEXT: (func $f (type $2) (param $h (ref $ht)) (result i64) - ;; CHECK-TEXT-NEXT: (suspend_to $ht $t - ;; CHECK-TEXT-NEXT: (i32.const 123) - ;; CHECK-TEXT-NEXT: (local.get $h) + ;; 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: (suspend_to $ht $t - ;; CHECK-BIN-NEXT: (i32.const 123) - ;; CHECK-BIN-NEXT: (local.get $h) + ;; 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)) @@ -53,8 +95,35 @@ ;; 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: (suspend_to $0 $tag$0 -;; CHECK-BIN-NODEBUG-NEXT: (i32.const 123) -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; 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: )