From 1be4ea5b8b36435a5222f3f331c2516e499a3f4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hillerstr=C3=B6m?= Date: Tue, 29 Oct 2024 17:04:23 +0100 Subject: [PATCH] Stack switching proposal support This patch implements text and binary encoding/decoding support for the stack switching proposal. It does so by adapting the previous typed-continunations implementation. Particular changes: * Support for new `resume` encoding. * Added support for `resume_throw` and `switch`. * Feature flag `typed-continuations` has been renamed to `stack-switching`. A small unfortunate implementation detail is that the internal name `Switch` was already taken by the `br_table` instruction, so I opted to give the `switch` instruction the internal name `StackSwitch`. A minor detail is that I have reordered the declarations/definitions of the stack switching instructions such that they appear in ascending order according to their opcode value (this is the same order that the stack-switching explainer document present them in). --- scripts/gen-s-parser.py | 6 +- src/gen-s-parser.inc | 27 +++- src/ir/ReFinalize.cpp | 4 +- src/ir/branch-utils.h | 9 ++ src/ir/child-typer.h | 25 ++- src/ir/cost.h | 26 ++- src/ir/effects.h | 36 ++++- src/ir/possible-contents.cpp | 12 +- src/ir/subtype-exprs.h | 10 +- src/parser/contexts.h | 112 ++++++++++--- src/parser/parsers.h | 86 ++++++++-- src/passes/Print.cpp | 88 ++++++++-- src/passes/TypeGeneralizing.cpp | 6 +- src/tools/tool-options.h | 2 +- src/wasm-binary.h | 15 +- src/wasm-builder.h | 48 +++++- src/wasm-delegations-fields.def | 36 ++++- src/wasm-delegations.def | 6 +- src/wasm-features.h | 12 +- src/wasm-interpreter.h | 16 +- src/wasm-ir-builder.h | 13 +- src/wasm.h | 79 +++++++-- src/wasm/wasm-binary.cpp | 153 ++++++++++++++---- src/wasm/wasm-ir-builder.cpp | 100 +++++++++--- src/wasm/wasm-stack.cpp | 47 +++++- src/wasm/wasm-type.cpp | 4 +- src/wasm/wasm-validator.cpp | 74 ++++++--- src/wasm/wasm.cpp | 77 +++++++-- src/wasm2js.h | 12 +- ...ontinuations.wast => stack_switching.wast} | 0 ...ind.wast => stack_switching_contbind.wast} | 0 ...tnew.wast => stack_switching_contnew.wast} | 0 ...esume.wast => stack_switching_resume.wast} | 0 .../basic/stack_switching_resume_throw.wast | 132 +++++++++++++++ ...pend.wast => stack_switching_suspend.wast} | 0 test/lit/help/wasm-as.test | 4 +- test/lit/help/wasm-ctor-eval.test | 4 +- test/lit/help/wasm-dis.test | 4 +- test/lit/help/wasm-emscripten-finalize.test | 4 +- test/lit/help/wasm-merge.test | 4 +- test/lit/help/wasm-metadce.test | 4 +- test/lit/help/wasm-opt.test | 4 +- test/lit/help/wasm-reduce.test | 4 +- test/lit/help/wasm-split.test | 4 +- test/lit/help/wasm2js.test | 4 +- ..._roundtrip_print-features_all-features.txt | 2 +- test/unit/test_features.py | 16 +- 47 files changed, 1086 insertions(+), 245 deletions(-) rename test/lit/basic/{typed_continuations.wast => stack_switching.wast} (100%) rename test/lit/basic/{typed_continuations_contbind.wast => stack_switching_contbind.wast} (100%) rename test/lit/basic/{typed_continuations_contnew.wast => stack_switching_contnew.wast} (100%) rename test/lit/basic/{typed_continuations_resume.wast => stack_switching_resume.wast} (100%) create mode 100644 test/lit/basic/stack_switching_resume_throw.wast rename test/lit/basic/{typed_continuations_suspend.wast => stack_switching_suspend.wast} (100%) diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py index d15c07e8eca..86e807d5474 100755 --- a/scripts/gen-s-parser.py +++ b/scripts/gen-s-parser.py @@ -596,11 +596,13 @@ # Typed function references instructions ("call_ref", "makeCallRef(/*isReturn=*/false)"), ("return_call_ref", "makeCallRef(/*isReturn=*/true)"), - # Typed continuations instructions + # Stack switching instructions ("cont.new", "makeContNew()"), ("cont.bind", "makeContBind()"), - ("resume", "makeResume()"), ("suspend", "makeSuspend()"), + ("resume", "makeResume()"), + ("resume_throw", "makeResumeThrow()"), + ("switch", "makeStackSwitch()"), # GC ("ref.i31", "makeRefI31(Unshared)"), ("ref.i31_shared", "makeRefI31(Shared)"), diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc index 75fda4f7a6a..7e2f030eeb2 100644 --- a/src/gen-s-parser.inc +++ b/src/gen-s-parser.inc @@ -4807,12 +4807,23 @@ switch (buf[0]) { default: goto parse_error; } } - case 's': - if (op == "resume"sv) { - CHECK_ERR(makeResume(ctx, pos, annotations)); - return Ok{}; + case 's': { + switch (buf[6]) { + case '\0': + if (op == "resume"sv) { + CHECK_ERR(makeResume(ctx, pos, annotations)); + return Ok{}; + } + goto parse_error; + case '_': + if (op == "resume_throw"sv) { + CHECK_ERR(makeResumeThrow(ctx, pos, annotations)); + return Ok{}; + } + goto parse_error; + default: goto parse_error; } - goto parse_error; + } case 't': { switch (buf[3]) { case 'h': @@ -5076,6 +5087,12 @@ switch (buf[0]) { return Ok{}; } goto parse_error; + case 'w': + if (op == "switch"sv) { + CHECK_ERR(makeStackSwitch(ctx, pos, annotations)); + return Ok{}; + } + goto parse_error; default: goto parse_error; } } diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp index c32d6efbf4c..60329295e09 100644 --- a/src/ir/ReFinalize.cpp +++ b/src/ir/ReFinalize.cpp @@ -180,8 +180,10 @@ void ReFinalize::visitStringWTF16Get(StringWTF16Get* curr) { curr->finalize(); } void ReFinalize::visitStringSliceWTF(StringSliceWTF* curr) { curr->finalize(); } void ReFinalize::visitContNew(ContNew* curr) { curr->finalize(); } void ReFinalize::visitContBind(ContBind* curr) { curr->finalize(); } -void ReFinalize::visitResume(Resume* curr) { curr->finalize(); } 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::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 369365e728c..a5032579692 100644 --- a/src/ir/branch-utils.h +++ b/src/ir/branch-utils.h @@ -89,6 +89,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 == name) { + func(name, r->sentTypes[i]); + } + } } else { assert(expr->is() || expr->is()); // delegate or rethrow } @@ -118,6 +125,8 @@ void operateOnScopeNameUsesAndSentValues(Expression* expr, T func) { // The values are supplied by suspend instructions executed while running // the continuation, so we are unable to know what they will be here. func(name, nullptr); + } else if (expr->is()) { + func(name, nullptr); } else { assert(expr->is() || expr->is()); // delegate or rethrow } diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index 499a7e4ddfc..70388b2bc3c 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -1050,6 +1050,10 @@ template struct ChildTyper : OverriddenVisitor { note(&curr->end, Type::i32); } + void visitContNew(ContNew* curr) { + note(&curr->func, Type(curr->contType.getContinuation().type, Nullable)); + } + void visitContBind(ContBind* curr) { auto paramsBefore = curr->contTypeBefore.getContinuation().type.getSignature().params; @@ -1064,8 +1068,12 @@ template struct ChildTyper : OverriddenVisitor { note(&curr->cont, Type(curr->contTypeBefore, Nullable)); } - void visitContNew(ContNew* curr) { - note(&curr->func, Type(curr->contType.getContinuation().type, Nullable)); + void visitSuspend(Suspend* curr) { + auto params = wasm.getTag(curr->tag)->sig.params; + assert(params.size() == curr->operands.size()); + for (size_t i = 0; i < params.size(); ++i) { + note(&curr->operands[i], params[i]); + } } void visitResume(Resume* curr) { @@ -1077,12 +1085,23 @@ template struct ChildTyper : OverriddenVisitor { note(&curr->cont, Type(curr->contType, Nullable)); } - void visitSuspend(Suspend* curr) { + void visitResumeThrow(ResumeThrow* curr) { auto params = wasm.getTag(curr->tag)->sig.params; assert(params.size() == curr->operands.size()); for (size_t i = 0; i < params.size(); ++i) { note(&curr->operands[i], params[i]); } + note(&curr->cont, Type(curr->contType, Nullable)); + } + + void visitStackSwitch(StackSwitch* curr) { + auto params = curr->contType.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(curr->contType, Nullable)); } }; diff --git a/src/ir/cost.h b/src/ir/cost.h index fcee6c18edb..3bdf45a2c20 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -756,6 +756,10 @@ struct CostAnalyzer : public OverriddenVisitor { return 8 + visit(curr->ref) + visit(curr->start) + visit(curr->end); } + CostType visitContNew(ContNew* curr) { + // Some arbitrary "high" value, reflecting that this may allocate a stack + return 14 + visit(curr->func); + } CostType visitContBind(ContBind* curr) { // Inspired by struct.new: The only cost of cont.bind is that it may need to // allocate a buffer to hold the arguments. @@ -766,9 +770,12 @@ struct CostAnalyzer : public OverriddenVisitor { } return ret; } - CostType visitContNew(ContNew* curr) { - // Some arbitrary "high" value, reflecting that this may allocate a stack - return 14 + visit(curr->func); + CostType visitSuspend(Suspend* curr) { + CostType ret = 12; + for (auto* arg : curr->operands) { + ret += visit(arg); + } + return ret; } CostType visitResume(Resume* curr) { // Inspired by indirect calls, but twice the cost. @@ -778,8 +785,17 @@ struct CostAnalyzer : public OverriddenVisitor { } return ret; } - CostType visitSuspend(Suspend* curr) { - CostType ret = 12; + CostType visitResumeThrow(ResumeThrow* 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 visitStackSwitch(StackSwitch* curr) { + // Inspired by indirect calls, but twice the cost. + CostType ret = 12 + visit(curr->cont); for (auto* arg : curr->operands) { ret += visit(arg); } diff --git a/src/ir/effects.h b/src/ir/effects.h index 716624d6455..1f527dcf4df 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -1008,13 +1008,21 @@ class EffectAnalyzer { // traps when ref is null. parent.implicitTrap = true; } + void visitContNew(ContNew* curr) { + // traps when curr->func is null ref. + parent.implicitTrap = true; + } void visitContBind(ContBind* curr) { // traps when curr->cont is null ref. parent.implicitTrap = true; } - void visitContNew(ContNew* curr) { - // traps when curr->func is null ref. - parent.implicitTrap = true; + void visitSuspend(Suspend* 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; + } } void visitResume(Resume* curr) { // This acts as a kitchen sink effect. @@ -1028,10 +1036,26 @@ class EffectAnalyzer { parent.throws_ = true; } } - void visitSuspend(Suspend* curr) { - // Similar to resume/call: Suspending means that we execute arbitrary - // other code before we may resume here. + void visitResumeThrow(ResumeThrow* curr) { + // This acts as a kitchen sink effect. parent.calls = true; + + // resume_throw instructions accept nullable continuation + // references and trap on null. + parent.implicitTrap = true; + + if (parent.features.hasExceptionHandling() && parent.tryDepth == 0) { + parent.throws_ = true; + } + } + void visitStackSwitch(StackSwitch* curr) { + // This acts as a kitchen sink effect. + parent.calls = true; + + // switch instructions accept nullable continuation references + // and trap on null. + parent.implicitTrap = true; + if (parent.features.hasExceptionHandling() && parent.tryDepth == 0) { parent.throws_ = true; } diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 17e40f1d805..c92c20e9c8e 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -1225,11 +1225,15 @@ struct InfoCollector void visitReturn(Return* curr) { addResult(curr->value); } + void visitContNew(ContNew* curr) { + // TODO: optimize when possible + addRoot(curr); + } void visitContBind(ContBind* curr) { // TODO: optimize when possible addRoot(curr); } - void visitContNew(ContNew* curr) { + void visitSuspend(Suspend* curr) { // TODO: optimize when possible addRoot(curr); } @@ -1237,7 +1241,11 @@ struct InfoCollector // TODO: optimize when possible addRoot(curr); } - void visitSuspend(Suspend* curr) { + void visitResumeThrow(ResumeThrow* curr) { + // TODO: optimize when possible + addRoot(curr); + } + void visitStackSwitch(StackSwitch* curr) { // TODO: optimize when possible addRoot(curr); } diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index 1895c856ae1..9d13dd5a1a3 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -397,10 +397,16 @@ struct SubtypingDiscoverer : public OverriddenVisitor { void visitStringWTF16Get(StringWTF16Get* curr) {} void visitStringSliceWTF(StringSliceWTF* curr) {} - void visitContBind(ContBind* curr) { WASM_UNREACHABLE("not implemented"); } void visitContNew(ContNew* curr) { WASM_UNREACHABLE("not implemented"); } - void visitResume(Resume* curr) { WASM_UNREACHABLE("not implemented"); } + void visitContBind(ContBind* curr) { WASM_UNREACHABLE("not implemented"); } void visitSuspend(Suspend* curr) { WASM_UNREACHABLE("not implemented"); } + void visitResume(Resume* curr) { WASM_UNREACHABLE("not implemented"); } + void visitResumeThrow(ResumeThrow* curr) { + WASM_UNREACHABLE("not implemented"); + } + void visitStackSwitch(StackSwitch* curr) { + WASM_UNREACHABLE("not implemented"); + } }; } // namespace wasm diff --git a/src/parser/contexts.h b/src/parser/contexts.h index b0cd1458bb6..e7804908421 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -349,6 +349,8 @@ struct NullInstrParserCtx { using ExprT = Ok; using CatchT = Ok; using CatchListT = Ok; + using OnClauseT = Ok; + using OnClauseListT = Ok; using TagLabelListT = Ok; using FieldIdxT = Ok; @@ -442,8 +444,10 @@ struct NullInstrParserCtx { return Ok{}; } - TagLabelListT makeTagLabelList() { return Ok{}; } - void appendTagLabel(TagLabelListT&, TagIdxT, LabelIdxT) {} + OnClauseListT makeOnClauseList() { return Ok{}; } + void appendOnClause(OnClauseListT&, OnClauseT) {} + OnClauseT makeOnLabel(TagIdxT, LabelIdxT) { return Ok{}; } + OnClauseT makeOnSwitch(TagIdxT) { return Ok{}; } void setSrcLoc(const std::vector&) {} @@ -840,12 +844,15 @@ struct NullInstrParserCtx { return Ok{}; } template + Result<> makeContNew(Index, const std::vector&, HeapTypeT) { + return Ok{}; + } + template Result<> makeContBind(Index, const std::vector&, HeapTypeT, HeapTypeT) { return Ok{}; } - template - Result<> makeContNew(Index, const std::vector&, HeapTypeT) { + Result<> makeSuspend(Index, const std::vector&, TagIdxT) { return Ok{}; } template @@ -855,7 +862,17 @@ struct NullInstrParserCtx { const TagLabelListT&) { return Ok{}; } - Result<> makeSuspend(Index, const std::vector&, TagIdxT) { + template + Result<> makeResumeThrow(Index, + const std::vector&, + HeapTypeT, + TagIdxT, + const TagLabelListT&) { + return Ok{}; + } + template + Result<> + makeStackSwitch(Index, const std::vector&, HeapTypeT, TagIdxT) { return Ok{}; } }; @@ -1486,9 +1503,20 @@ struct ParseDefsCtx : TypeParserCtx { CatchInfo makeCatchAll(Index label) { return {{}, label, false}; } CatchInfo makeCatchAllRef(Index label) { return {{}, label, true}; } - TagLabelListT makeTagLabelList() { return {}; } - void appendTagLabel(TagLabelListT& tagLabels, Name tag, Index label) { - tagLabels.push_back({tag, label}); + struct OnClauseInfo { + Name tag; + Index label; // unset when isOnSwitch = true. + bool isOnSwitch; + }; + + OnClauseInfo makeOnLabel(Name tag, Index label) { + return {tag, label, false}; + } + OnClauseInfo makeOnSwitch(Name tag) { return {tag, {}, true}; } + + std::vector makeOnClauseList() { return {}; } + void appendOnClause(std::vector& list, OnClauseInfo info) { + list.push_back(info); } Result getHeapTypeFromIdx(Index idx) { @@ -2593,6 +2621,12 @@ struct ParseDefsCtx : TypeParserCtx { return withLoc(pos, irBuilder.makeStringSliceWTF()); } + Result<> makeContNew(Index pos, + const std::vector& annotations, + HeapType type) { + return withLoc(pos, irBuilder.makeContNew(type)); + } + Result<> makeContBind(Index pos, const std::vector& annotations, HeapType contTypeBefore, @@ -2600,30 +2634,64 @@ struct ParseDefsCtx : TypeParserCtx { return withLoc(pos, irBuilder.makeContBind(contTypeBefore, contTypeAfter)); } - Result<> makeContNew(Index pos, - const std::vector& annotations, - HeapType type) { - return withLoc(pos, irBuilder.makeContNew(type)); + Result<> + makeSuspend(Index pos, const std::vector& annotations, Name tag) { + return withLoc(pos, irBuilder.makeSuspend(tag)); } Result<> makeResume(Index pos, const std::vector& annotations, HeapType type, - const TagLabelListT& tagLabels) { + const std::vector& resumetable) { std::vector tags; std::vector labels; - tags.reserve(tagLabels.size()); - labels.reserve(tagLabels.size()); - for (auto& [tag, label] : tagLabels) { - tags.push_back(tag); - labels.push_back(label); + std::vector onTags; + tags.reserve(resumetable.size()); + labels.reserve(resumetable.size()); + onTags.reserve(resumetable.size()); + for (const OnClauseInfo& info : resumetable) { + tags.push_back(info.tag); + if (info.isOnSwitch) { + labels.push_back(Index()); + onTags.push_back(true); + } else { + labels.push_back(info.label); + onTags.push_back(false); + } } - return withLoc(pos, irBuilder.makeResume(type, tags, labels)); + return withLoc(pos, irBuilder.makeResume(type, tags, labels, onTags)); } - Result<> - makeSuspend(Index pos, const std::vector& annotations, Name tag) { - return withLoc(pos, irBuilder.makeSuspend(tag)); + Result<> makeResumeThrow(Index pos, + const std::vector& annotations, + HeapType type, + Name tag, + const std::vector& resumetable) { + std::vector tags; + std::vector labels; + std::vector onTags; + tags.reserve(resumetable.size()); + labels.reserve(resumetable.size()); + onTags.reserve(resumetable.size()); + for (auto& info : resumetable) { + tags.push_back(info.tag); + if (info.isOnSwitch) { + labels.push_back(Index()); + onTags.push_back(true); + } else { + labels.push_back(info.label); + onTags.push_back(false); + } + } + return withLoc(pos, + irBuilder.makeResumeThrow(type, tag, tags, labels, onTags)); + } + + Result<> makeStackSwitch(Index pos, + const std::vector& annotations, + HeapType type, + Name tag) { + return withLoc(pos, irBuilder.makeStackSwitch(type, tag)); } }; diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 02c2e9e4760..4230b2b4bcd 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -297,13 +297,17 @@ Result<> makeStringWTF16Get(Ctx&, Index, const std::vector&); template Result<> makeStringSliceWTF(Ctx&, Index, const std::vector&); template +Result<> makeContNew(Ctx*, Index, const std::vector&); +template Result<> makeContBind(Ctx&, Index, const std::vector&); template -Result<> makeContNew(Ctx*, Index, const std::vector&); +Result<> makeSuspend(Ctx&, Index, const std::vector&); template Result<> makeResume(Ctx&, Index, const std::vector&); template -Result<> makeSuspend(Ctx&, Index, const std::vector&); +Result<> makeResumeThrow(Ctx&, Index, const std::vector&); +template +Result<> makeStackSwitch(Ctx&, Index, const std::vector&); template Result<> ignore(Ctx&, Index, const std::vector&) { @@ -2417,6 +2421,15 @@ Result<> makeStringSliceWTF(Ctx& ctx, return ctx.makeStringSliceWTF(pos, annotations); } +template +Result<> +makeContNew(Ctx& ctx, Index pos, const std::vector& annotations) { + auto type = typeidx(ctx); + CHECK_ERR(type); + + return ctx.makeContNew(pos, annotations, *type); +} + // contbind ::= 'cont.bind' typeidx typeidx template Result<> @@ -2432,42 +2445,85 @@ makeContBind(Ctx& ctx, Index pos, const std::vector& annotations) { template Result<> -makeContNew(Ctx& ctx, Index pos, const std::vector& annotations) { - auto type = typeidx(ctx); - CHECK_ERR(type); +makeSuspend(Ctx& ctx, Index pos, const std::vector& annotations) { + auto tag = tagidx(ctx); + CHECK_ERR(tag); - return ctx.makeContNew(pos, annotations, *type); + return ctx.makeSuspend(pos, annotations, *tag); } -// resume ::= 'resume' typeidx ('(' 'on' tagidx labelidx ')')* +// resume ::= 'resume' typeidx ('(' 'on' tagidx labelidx | 'on' tagidx switch +// ')')* template Result<> makeResume(Ctx& ctx, Index pos, const std::vector& annotations) { auto type = typeidx(ctx); CHECK_ERR(type); - auto tagLabels = ctx.makeTagLabelList(); + auto resumetable = ctx.makeOnClauseList(); while (ctx.in.takeSExprStart("on"sv)) { auto tag = tagidx(ctx); CHECK_ERR(tag); - auto label = labelidx(ctx); - CHECK_ERR(label); - ctx.appendTagLabel(tagLabels, *tag, *label); + auto keyword = ctx.in.peekKeyword(); + if (keyword == "switch") { + ctx.in.takeKeyword(); + ctx.appendOnClause(resumetable, ctx.makeOnSwitch(*tag)); + } else { + auto label = labelidx(ctx); + CHECK_ERR(label); + ctx.appendOnClause(resumetable, ctx.makeOnLabel(*tag, *label)); + } if (!ctx.in.takeRParen()) { return ctx.in.err("expected ')' at end of handler clause"); } } - return ctx.makeResume(pos, annotations, *type, tagLabels); + return ctx.makeResume(pos, annotations, *type, resumetable); } +// resume_throw ::= 'resume_throw' typeidx tagidx ('(' 'on' tagidx labelidx | +// 'on' tagidx switch ')')* template -Result<> -makeSuspend(Ctx& ctx, Index pos, const std::vector& annotations) { +Result<> makeResumeThrow(Ctx& ctx, + Index pos, + const std::vector& annotations) { + auto type = typeidx(ctx); + CHECK_ERR(type); + auto exnTag = tagidx(ctx); + CHECK_ERR(exnTag); + + auto resumetable = ctx.makeOnClauseList(); + while (ctx.in.takeSExprStart("on"sv)) { + auto tag = tagidx(ctx); + CHECK_ERR(tag); + auto keyword = ctx.in.peekKeyword(); + if (keyword == "switch") { + ctx.in.takeKeyword(); + ctx.appendOnClause(resumetable, ctx.makeOnSwitch(*tag)); + } else { + auto label = labelidx(ctx); + CHECK_ERR(label); + ctx.appendOnClause(resumetable, ctx.makeOnLabel(*tag, *label)); + } + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected ')' at end of handler clause"); + } + } + + return ctx.makeResumeThrow(pos, annotations, *type, *exnTag, resumetable); +} + +// switch ::= 'switch' typeidx tagidx +template +Result<> makeStackSwitch(Ctx& ctx, + Index pos, + const std::vector& annotations) { + auto type = typeidx(ctx); + CHECK_ERR(type); auto tag = tagidx(ctx); CHECK_ERR(tag); - return ctx.makeSuspend(pos, annotations, *tag); + return ctx.makeStackSwitch(pos, annotations, *type, *tag); } // ======= diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 106beac39be..ef4c3413c1b 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -302,6 +302,9 @@ struct PrintSExpression : public UnifiedExpressionVisitor { void visitTry(Try* curr); void visitTryTable(TryTable* curr); void visitResume(Resume* curr); + void visitResumeThrow(ResumeThrow* curr); + void visitStackSwitch(StackSwitch* curr); + bool maybePrintUnreachableReplacement(Expression* curr, Type type); bool maybePrintUnreachableOrNullReplacement(Expression* curr, Type type); void visitCallRef(CallRef* curr) { @@ -2427,17 +2430,20 @@ struct PrintExpressionContents void visitStringSliceWTF(StringSliceWTF* curr) { printMedium(o, "stringview_wtf16.slice"); } + void visitContNew(ContNew* curr) { + printMedium(o, "cont.new "); + printHeapType(curr->contType); + } void visitContBind(ContBind* curr) { printMedium(o, "cont.bind "); printHeapType(curr->contTypeBefore); o << ' '; printHeapType(curr->contTypeAfter); } - void visitContNew(ContNew* curr) { - printMedium(o, "cont.new "); - printHeapType(curr->contType); + void visitSuspend(Suspend* curr) { + printMedium(o, "suspend "); + curr->tag.print(o); } - void visitResume(Resume* curr) { printMedium(o, "resume"); @@ -2449,13 +2455,41 @@ struct PrintExpressionContents printMedium(o, "on "); curr->handlerTags[i].print(o); o << ' '; - curr->handlerBlocks[i].print(o); + if (curr->onTags[i]) { + o << "switch"; + } else { + curr->handlerBlocks[i].print(o); + } o << ')'; } } + void visitResumeThrow(ResumeThrow* curr) { + printMedium(o, "resume_throw"); - void visitSuspend(Suspend* curr) { - printMedium(o, "suspend "); + o << ' '; + printHeapType(curr->contType); + o << ' '; + curr->tag.print(o); + + for (Index i = 0; i < curr->handlerTags.size(); i++) { + o << " ("; + printMedium(o, "on "); + curr->handlerTags[i].print(o); + o << ' '; + if (curr->onTags[i]) { + o << "switch"; + } else { + curr->handlerBlocks[i].print(o); + } + o << ')'; + } + } + void visitStackSwitch(StackSwitch* curr) { + printMedium(o, "switch"); + + o << ' '; + printHeapType(curr->contType); + o << ' '; curr->tag.print(o); } }; @@ -2749,7 +2783,7 @@ void PrintSExpression::visitLoop(Loop* curr) { // The parenthesis wrapping do/catch/catch_all is just a syntax and does not // affect nested depths of instructions within. // -// try-delegate is written in the forded format as +// try-delegate is written in the folded format as // (try // (do // ... @@ -2824,7 +2858,7 @@ void PrintSExpression::visitTryTable(TryTable* curr) { } void PrintSExpression::visitResume(Resume* curr) { - controlFlowDepth++; + // controlFlowDepth++; o << '('; printExpressionContents(curr); @@ -2836,7 +2870,41 @@ void PrintSExpression::visitResume(Resume* curr) { printFullLine(curr->cont); - controlFlowDepth--; + // controlFlowDepth--; + decIndent(); +} + +void PrintSExpression::visitResumeThrow(ResumeThrow* curr) { + // controlFlowDepth++; + o << '('; + printExpressionContents(curr); + + incIndent(); + + for (Index i = 0; i < curr->operands.size(); i++) { + printFullLine(curr->operands[i]); + } + + printFullLine(curr->cont); + + // controlFlowDepth--; + decIndent(); +} + +void PrintSExpression::visitStackSwitch(StackSwitch* curr) { + // controlFlowDepth++; + o << '('; + printExpressionContents(curr); + + incIndent(); + + for (Index i = 0; i < curr->operands.size(); i++) { + printFullLine(curr->operands[i]); + } + + printFullLine(curr->cont); + + // controlFlowDepth--; decIndent(); } diff --git a/src/passes/TypeGeneralizing.cpp b/src/passes/TypeGeneralizing.cpp index c0359b897c3..1d88424ff38 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -871,10 +871,12 @@ struct TransferFn : OverriddenVisitor { void visitStringWTF16Get(StringWTF16Get* curr) { WASM_UNREACHABLE("TODO"); } void visitStringSliceWTF(StringSliceWTF* curr) { WASM_UNREACHABLE("TODO"); } - void visitContBind(ContBind* curr) { WASM_UNREACHABLE("TODO"); } void visitContNew(ContNew* curr) { WASM_UNREACHABLE("TODO"); } - void visitResume(Resume* curr) { WASM_UNREACHABLE("TODO"); } + void visitContBind(ContBind* curr) { WASM_UNREACHABLE("TODO"); } void visitSuspend(Suspend* curr) { WASM_UNREACHABLE("TODO"); } + void visitResume(Resume* curr) { WASM_UNREACHABLE("TODO"); } + void visitResumeThrow(ResumeThrow* curr) { WASM_UNREACHABLE("TODO"); } + void visitStackSwitch(StackSwitch* curr) { WASM_UNREACHABLE("TODO"); } }; struct TypeGeneralizing : WalkerPass> { diff --git a/src/tools/tool-options.h b/src/tools/tool-options.h index f900d76ba48..a62707e0be5 100644 --- a/src/tools/tool-options.h +++ b/src/tools/tool-options.h @@ -94,7 +94,7 @@ struct ToolOptions : public Options { .addFeature(FeatureSet::ExtendedConst, "extended const expressions") .addFeature(FeatureSet::Strings, "strings") .addFeature(FeatureSet::MultiMemory, "multimemory") - .addFeature(FeatureSet::TypedContinuations, "typed continuations") + .addFeature(FeatureSet::StackSwitching, "stack switching") .addFeature(FeatureSet::SharedEverything, "shared-everything threads") .addFeature(FeatureSet::FP16, "float 16 operations") .add("--enable-typed-function-references", diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 3c59ed3aacb..745ec10d925 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -391,7 +391,7 @@ extern const char* RelaxedSIMDFeature; extern const char* ExtendedConstFeature; extern const char* StringsFeature; extern const char* MultiMemoryFeature; -extern const char* TypedContinuationsFeature; +extern const char* StackSwitchingFeature; extern const char* SharedEverythingFeature; extern const char* FP16Feature; @@ -1140,12 +1140,17 @@ enum ASTNodes { StringNewLossyUTF8Array = 0xb4, StringEncodeLossyUTF8Array = 0xb6, - // typed continuation opcodes + // stack switching opcodes ContNew = 0xe0, ContBind = 0xe1, 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'. + OnLabel = 0x00, // (on $tag $label) + OnSwitch = 0x01 // (on $tag switch) }; enum MemoryAccess { @@ -1788,8 +1793,10 @@ class WasmBinaryReader { void visitRefAs(RefAs* curr, uint8_t code); void visitContNew(ContNew* curr); void visitContBind(ContBind* curr); - void visitResume(Resume* curr); void visitSuspend(Suspend* curr); + void visitResume(Resume* curr); + void visitResumeThrow(ResumeThrow* curr); + void visitStackSwitch(StackSwitch* curr); [[noreturn]] void throwError(std::string text); diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 8f8895781bd..481da2de8fd 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -1168,6 +1168,13 @@ class Builder { ret->finalize(); return ret; } + ContNew* makeContNew(HeapType contType, Expression* func) { + auto* ret = wasm.allocator.alloc(); + ret->contType = contType; + ret->func = func; + ret->finalize(); + return ret; + } ContBind* makeContBind(HeapType contTypeBefore, HeapType contTypeAfter, const std::vector& operands, @@ -1180,31 +1187,56 @@ class Builder { ret->finalize(); return ret; } - ContNew* makeContNew(HeapType contType, Expression* func) { - auto* ret = wasm.allocator.alloc(); - ret->contType = contType; - ret->func = func; - ret->finalize(); + Suspend* makeSuspend(Name tag, const std::vector& args) { + auto* ret = wasm.allocator.alloc(); + ret->tag = tag; + ret->operands.set(args); + ret->finalize(&wasm); return ret; } Resume* makeResume(HeapType contType, const std::vector& handlerTags, const std::vector& handlerBlocks, + const std::vector& onTags, const std::vector& operands, Expression* cont) { auto* ret = wasm.allocator.alloc(); ret->contType = contType; ret->handlerTags.set(handlerTags); ret->handlerBlocks.set(handlerBlocks); + ret->onTags.set(onTags); ret->operands.set(operands); ret->cont = cont; ret->finalize(&wasm); return ret; } - Suspend* makeSuspend(Name tag, const std::vector& args) { - auto* ret = wasm.allocator.alloc(); + ResumeThrow* makeResumeThrow(HeapType contType, + Name tag, + const std::vector& handlerTags, + const std::vector& handlerBlocks, + const std::vector& onTags, + const std::vector& operands, + Expression* cont) { + auto* ret = wasm.allocator.alloc(); + ret->contType = contType; ret->tag = tag; - ret->operands.set(args); + ret->handlerTags.set(handlerTags); + ret->handlerBlocks.set(handlerBlocks); + ret->onTags.set(onTags); + ret->operands.set(operands); + ret->cont = cont; + ret->finalize(&wasm); + return ret; + } + StackSwitch* makeStackSwitch(HeapType contType, + Name tag, + const std::vector& operands, + Expression* cont) { + auto* ret = wasm.allocator.alloc(); + ret->contType = contType; + ret->tag = tag; + ret->operands.set(operands); + ret->cont = cont; ret->finalize(&wasm); return ret; } diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def index 3be04022094..a0c4b5bab39 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -765,6 +765,11 @@ DELEGATE_FIELD_CHILD(StringSliceWTF, start) DELEGATE_FIELD_CHILD(StringSliceWTF, ref) DELEGATE_FIELD_CASE_END(StringSliceWTF) +DELEGATE_FIELD_CASE_START(ContNew) +DELEGATE_FIELD_CHILD(ContNew, func) +DELEGATE_FIELD_HEAPTYPE(ContNew, contType) +DELEGATE_FIELD_CASE_END(ContNew) + DELEGATE_FIELD_CASE_START(ContBind) DELEGATE_FIELD_CHILD(ContBind, cont) DELEGATE_FIELD_CHILD_VECTOR(ContBind, operands) @@ -772,13 +777,14 @@ DELEGATE_FIELD_HEAPTYPE(ContBind, contTypeAfter) DELEGATE_FIELD_HEAPTYPE(ContBind, contTypeBefore) DELEGATE_FIELD_CASE_END(ContBind) -DELEGATE_FIELD_CASE_START(ContNew) -DELEGATE_FIELD_CHILD(ContNew, func) -DELEGATE_FIELD_HEAPTYPE(ContNew, contType) -DELEGATE_FIELD_CASE_END(ContNew) +DELEGATE_FIELD_CASE_START(Suspend) +DELEGATE_FIELD_CHILD_VECTOR(Suspend, operands) +DELEGATE_FIELD_NAME_KIND(Suspend, tag, ModuleItemKind::Tag) +DELEGATE_FIELD_CASE_END(Suspend) DELEGATE_FIELD_CASE_START(Resume) DELEGATE_FIELD_TYPE_VECTOR(Resume, sentTypes) +DELEGATE_FIELD_INT_VECTOR(Resume, onTags) DELEGATE_FIELD_CHILD(Resume, cont) DELEGATE_FIELD_CHILD_VECTOR(Resume, operands) DELEGATE_FIELD_SCOPE_NAME_USE_VECTOR(Resume, handlerBlocks) @@ -786,10 +792,24 @@ DELEGATE_FIELD_NAME_KIND_VECTOR(Resume, handlerTags, ModuleItemKind::Tag) DELEGATE_FIELD_HEAPTYPE(Resume, contType) DELEGATE_FIELD_CASE_END(Resume) -DELEGATE_FIELD_CASE_START(Suspend) -DELEGATE_FIELD_CHILD_VECTOR(Suspend, operands) -DELEGATE_FIELD_NAME_KIND(Suspend, tag, ModuleItemKind::Tag) -DELEGATE_FIELD_CASE_END(Suspend) +DELEGATE_FIELD_CASE_START(ResumeThrow) +DELEGATE_FIELD_TYPE_VECTOR(ResumeThrow, sentTypes) +DELEGATE_FIELD_INT_VECTOR(ResumeThrow, onTags) +DELEGATE_FIELD_CHILD(ResumeThrow, cont) +DELEGATE_FIELD_CHILD_VECTOR(ResumeThrow, operands) +DELEGATE_FIELD_SCOPE_NAME_USE_VECTOR(ResumeThrow, handlerBlocks) +DELEGATE_FIELD_NAME_KIND_VECTOR(ResumeThrow, handlerTags, ModuleItemKind::Tag) +DELEGATE_FIELD_HEAPTYPE(ResumeThrow, contType) +DELEGATE_FIELD_NAME_KIND(ResumeThrow, tag, ModuleItemKind::Tag) +DELEGATE_FIELD_CASE_END(ResumeThrow) + +DELEGATE_FIELD_CASE_START(StackSwitch) +DELEGATE_FIELD_CHILD(Switch, cont) +DELEGATE_FIELD_CHILD_VECTOR(StackSwitch, operands) +DELEGATE_FIELD_HEAPTYPE(StackSwitch, contType) +DELEGATE_FIELD_NAME_KIND(StackSwitch, tag, ModuleItemKind::Tag) +DELEGATE_FIELD_CASE_END(StackSwitch) + DELEGATE_FIELD_MAIN_END diff --git a/src/wasm-delegations.def b/src/wasm-delegations.def index f4552a98b20..a2d97a8e65b 100644 --- a/src/wasm-delegations.def +++ b/src/wasm-delegations.def @@ -101,9 +101,11 @@ DELEGATE(StringConcat); DELEGATE(StringEq); DELEGATE(StringWTF16Get); DELEGATE(StringSliceWTF); -DELEGATE(ContBind); DELEGATE(ContNew); -DELEGATE(Resume); +DELEGATE(ContBind); DELEGATE(Suspend); +DELEGATE(Resume); +DELEGATE(ResumeThrow); +DELEGATE(StackSwitch); #undef DELEGATE diff --git a/src/wasm-features.h b/src/wasm-features.h index 92b07b5477f..0298d245d7a 100644 --- a/src/wasm-features.h +++ b/src/wasm-features.h @@ -44,7 +44,7 @@ struct FeatureSet { ExtendedConst = 1 << 13, Strings = 1 << 14, MultiMemory = 1 << 15, - TypedContinuations = 1 << 16, + StackSwitching = 1 << 16, SharedEverything = 1 << 17, FP16 = 1 << 18, MVP = None, @@ -88,8 +88,8 @@ struct FeatureSet { return "strings"; case MultiMemory: return "multimemory"; - case TypedContinuations: - return "typed-continuations"; + case StackSwitching: + return "stack-switching"; case SharedEverything: return "shared-everything"; case FP16: @@ -138,9 +138,7 @@ struct FeatureSet { bool hasExtendedConst() const { return (features & ExtendedConst) != 0; } bool hasStrings() const { return (features & Strings) != 0; } bool hasMultiMemory() const { return (features & MultiMemory) != 0; } - bool hasTypedContinuations() const { - return (features & TypedContinuations) != 0; - } + bool hasStackSwitching() const { return (features & StackSwitching) != 0; } bool hasSharedEverything() const { return (features & SharedEverything) != 0; } @@ -166,7 +164,7 @@ struct FeatureSet { void setExtendedConst(bool v = true) { set(ExtendedConst, v); } void setStrings(bool v = true) { set(Strings, v); } void setMultiMemory(bool v = true) { set(MultiMemory, v); } - void setTypedContinuations(bool v = true) { set(TypedContinuations, v); } + void setStackSwitching(bool v = true) { set(StackSwitching, v); } void setSharedEverything(bool v = true) { set(SharedEverything, v); } void setFP16(bool v = true) { set(FP16, v); } void setMVP() { features = MVP; } diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index c5019b2f3bc..6ca1ebe1729 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2551,10 +2551,16 @@ class ConstantExpressionRunner : public ExpressionRunner { } return ExpressionRunner::visitRefAs(curr); } - Flow visitContBind(ContBind* curr) { WASM_UNREACHABLE("unimplemented"); } Flow visitContNew(ContNew* curr) { WASM_UNREACHABLE("unimplemented"); } - Flow visitResume(Resume* curr) { WASM_UNREACHABLE("unimplemented"); } + Flow visitContBind(ContBind* curr) { WASM_UNREACHABLE("unimplemented"); } Flow visitSuspend(Suspend* curr) { WASM_UNREACHABLE("unimplemented"); } + Flow visitResume(Resume* curr) { WASM_UNREACHABLE("unimplemented"); } + Flow visitResumeThrow(ResumeThrow* curr) { + WASM_UNREACHABLE("unimplemented"); + } + Flow visitStackSwitch(StackSwitch* curr) { + WASM_UNREACHABLE("unimplemented"); + } void trap(const char* why) override { throw NonconstantException(); } @@ -4244,10 +4250,12 @@ class ModuleRunnerBase : public ExpressionRunner { multiValues.pop_back(); return ret; } - Flow visitContBind(ContBind* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitContNew(ContNew* curr) { return Flow(NONCONSTANT_FLOW); } - Flow visitResume(Resume* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitContBind(ContBind* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitSuspend(Suspend* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitResume(Resume* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitResumeThrow(ResumeThrow* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitStackSwitch(StackSwitch* 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 d7a1dde87b9..f0bae5ddcc7 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -217,13 +217,20 @@ class IRBuilder : public UnifiedExpressionVisitor> { [[nodiscard]] Result<> makeStringWTF16Get(); [[nodiscard]] Result<> makeStringIterNext(); [[nodiscard]] Result<> makeStringSliceWTF(); + [[nodiscard]] Result<> makeContNew(HeapType ct); [[nodiscard]] Result<> makeContBind(HeapType contTypeBefore, HeapType contTypeAfter); - [[nodiscard]] Result<> makeContNew(HeapType ct); + [[nodiscard]] Result<> makeSuspend(Name tag); [[nodiscard]] Result<> makeResume(HeapType ct, const std::vector& tags, - const std::vector& labels); - [[nodiscard]] Result<> makeSuspend(Name tag); + const std::vector& labels, + const std::vector& onTags); + [[nodiscard]] Result<> makeResumeThrow(HeapType ct, + Name tag, + const std::vector& tags, + const std::vector& labels, + const std::vector& onTags); + [[nodiscard]] Result<> makeStackSwitch(HeapType ct, Name tag); // Private functions that must be public for technical reasons. [[nodiscard]] Result<> visitExpression(Expression*); diff --git a/src/wasm.h b/src/wasm.h index 85473b1f957..422dcfb7eac 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -736,10 +736,12 @@ class Expression { StringEqId, StringWTF16GetId, StringSliceWTFId, - ContBindId, ContNewId, - ResumeId, + ContBindId, SuspendId, + ResumeId, + ResumeThrowId, + StackSwitchId, // Id for the stack switching `switch` NumExpressionIds }; Id _id; @@ -1923,6 +1925,17 @@ class StringSliceWTF : public SpecificExpression { void finalize(); }; +class ContNew : public SpecificExpression { +public: + ContNew() = default; + ContNew(MixedArena& allocator) {} + + HeapType contType; + Expression* func; + + void finalize(); +}; + class ContBind : public SpecificExpression { public: ContBind(MixedArena& allocator) : operands(allocator) {} @@ -1935,26 +1948,31 @@ class ContBind : public SpecificExpression { void finalize(); }; -class ContNew : public SpecificExpression { +class Suspend : public SpecificExpression { public: - ContNew() = default; - ContNew(MixedArena& allocator) {} + Suspend(MixedArena& allocator) : operands(allocator) {} - HeapType contType; - Expression* func; + Name tag; + ExpressionList operands; - void finalize(); + // 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); }; class Resume : public SpecificExpression { public: Resume(MixedArena& allocator) - : handlerTags(allocator), handlerBlocks(allocator), operands(allocator), - sentTypes(allocator) {} + : handlerTags(allocator), handlerBlocks(allocator), onTags(allocator), + operands(allocator), sentTypes(allocator) {} HeapType contType; ArenaVector handlerTags; + // Empty name (i.e. `Name()`) for switch tags. ArenaVector handlerBlocks; + // False if (on $tag $label) and true when (on $tag switch). + ArenaVector onTags; ExpressionList operands; Expression* cont; @@ -1972,16 +1990,47 @@ class Resume : public SpecificExpression { ArenaVector sentTypes; }; -class Suspend : public SpecificExpression { +class ResumeThrow : public SpecificExpression { public: - Suspend(MixedArena& allocator) : operands(allocator) {} + ResumeThrow(MixedArena& allocator) + : handlerTags(allocator), handlerBlocks(allocator), onTags(allocator), + operands(allocator), sentTypes(allocator) {} + HeapType contType; Name tag; + ArenaVector handlerTags; + // Empty name (i.e. `Name()`) for switch tags. + ArenaVector handlerBlocks; + // False if (on $tag $label) and true when (on $tag switch). + ArenaVector onTags; + ExpressionList operands; + Expression* cont; - // 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. + // When 'Module*' parameter is given, we populate the 'sentTypes' array, so + // that the types can be accessed in other analyses without accessing the + // module. + void finalize(Module* wasm = nullptr); + + // 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 StackSwitch : public SpecificExpression { +public: + StackSwitch(MixedArena& allocator) : operands(allocator) {} + + HeapType contType; + Name tag; + + ExpressionList operands; + Expression* cont; + + // We need access to the module to obtain the signature of the tag. void finalize(Module* wasm = nullptr); }; diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index bc7ec8ac898..a56c26d4444 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1347,8 +1347,8 @@ void WasmBinaryWriter::writeFeaturesSection() { return BinaryConsts::CustomSections::StringsFeature; case FeatureSet::MultiMemory: return BinaryConsts::CustomSections::MultiMemoryFeature; - case FeatureSet::TypedContinuations: - return BinaryConsts::CustomSections::TypedContinuationsFeature; + case FeatureSet::StackSwitching: + return BinaryConsts::CustomSections::StackSwitchingFeature; case FeatureSet::SharedEverything: return BinaryConsts::CustomSections::SharedEverythingFeature; case FeatureSet::FP16: @@ -3864,9 +3864,8 @@ void WasmBinaryReader::readFeatures(size_t payloadLen) { feature = FeatureSet::Strings; } else if (name == BinaryConsts::CustomSections::MultiMemoryFeature) { feature = FeatureSet::MultiMemory; - } else if (name == - BinaryConsts::CustomSections::TypedContinuationsFeature) { - feature = FeatureSet::TypedContinuations; + } else if (name == BinaryConsts::CustomSections::StackSwitchingFeature) { + feature = FeatureSet::StackSwitching; } else if (name == BinaryConsts::CustomSections::SharedEverythingFeature) { feature = FeatureSet::SharedEverything; } else if (name == BinaryConsts::CustomSections::FP16Feature) { @@ -4133,24 +4132,34 @@ BinaryConsts::ASTNodes WasmBinaryReader::readExpression(Expression*& curr) { visitCallRef(call); break; } - case BinaryConsts::ContBind: { - visitContBind((curr = allocator.alloc())->cast()); - break; - } case BinaryConsts::ContNew: { auto contNew = allocator.alloc(); curr = contNew; visitContNew(contNew); break; } - case BinaryConsts::Resume: { - visitResume((curr = allocator.alloc())->cast()); + case BinaryConsts::ContBind: { + visitContBind((curr = allocator.alloc())->cast()); break; } case BinaryConsts::Suspend: { visitSuspend((curr = allocator.alloc())->cast()); break; } + case BinaryConsts::Resume: { + visitResume((curr = allocator.alloc())->cast()); + break; + } + case BinaryConsts::ResumeThrow: { + visitResumeThrow( + (curr = allocator.alloc())->cast()); + break; + } + case BinaryConsts::Switch: { + visitStackSwitch( + (curr = allocator.alloc())->cast()); + break; + } case BinaryConsts::AtomicPrefix: { code = static_cast(getU32LEB()); if (maybeVisitLoad(curr, code, BinaryConsts::AtomicPrefix)) { @@ -7870,6 +7879,19 @@ void WasmBinaryReader::visitRefAs(RefAs* curr, uint8_t code) { curr->finalize(); } +void WasmBinaryReader::visitContNew(ContNew* curr) { + + auto contTypeIndex = getU32LEB(); + curr->contType = getTypeByIndex(contTypeIndex); + if (!curr->contType.isContinuation()) { + throwError("non-continuation type in cont.new instruction " + + curr->contType.toString()); + } + + curr->func = popNonVoidExpression(); + curr->finalize(); +} + void WasmBinaryReader::visitContBind(ContBind* curr) { auto contTypeBeforeIndex = getU32LEB(); @@ -7906,17 +7928,23 @@ void WasmBinaryReader::visitContBind(ContBind* curr) { curr->finalize(); } -void WasmBinaryReader::visitContNew(ContNew* curr) { +void WasmBinaryReader::visitSuspend(Suspend* curr) { - auto contTypeIndex = getU32LEB(); - curr->contType = getTypeByIndex(contTypeIndex); - if (!curr->contType.isContinuation()) { - throwError("non-continuation type in cont.new instruction " + - curr->contType.toString()); + auto tagIndex = getU32LEB(); + if (tagIndex >= wasm.tags.size()) { + throwError("bad tag index"); } + auto* tag = wasm.tags[tagIndex].get(); + curr->tag = tag->name; + tagRefs[tagIndex].push_back(&curr->tag); - curr->func = popNonVoidExpression(); - curr->finalize(); + auto numArgs = tag->sig.params.size(); + curr->operands.resize(numArgs); + for (size_t i = 0; i < numArgs; i++) { + curr->operands[numArgs - i - 1] = popNonVoidExpression(); + } + + curr->finalize(&wasm); } void WasmBinaryReader::visitResume(Resume* curr) { @@ -7935,16 +7963,25 @@ void WasmBinaryReader::visitResume(Resume* curr) { // valid until processNames ran. curr->handlerTags.resize(numHandlers); curr->handlerBlocks.resize(numHandlers); + curr->onTags.resize(numHandlers); for (size_t i = 0; i < numHandlers; i++) { + uint8_t code = getInt8(); auto tagIndex = getU32LEB(); auto tag = getTagName(tagIndex); - - auto handlerIndex = getU32LEB(); - auto handler = getBreakTarget(handlerIndex).name; + Name handler; + if (code == BinaryConsts::OnLabel) { // expect (on $tag $label) + auto handlerIndex = getU32LEB(); + handler = getBreakTarget(handlerIndex).name; + } else if (code == BinaryConsts::OnSwitch) { // expect (on $tag switch) + handler = Name(); + } else { // error + throwError("ON opcode expected"); + } curr->handlerTags[i] = tag; curr->handlerBlocks[i] = handler; + curr->onTags[i] = static_cast(code); // 0x00 is false, 0x01 is true // We don't know the final name yet tagRefs[tagIndex].push_back(&curr->handlerTags[i]); @@ -7962,17 +7999,79 @@ void WasmBinaryReader::visitResume(Resume* curr) { curr->finalize(&wasm); } -void WasmBinaryReader::visitSuspend(Suspend* curr) { +void WasmBinaryReader::visitResumeThrow(ResumeThrow* curr) { + auto contTypeIndex = getU32LEB(); + curr->contType = getTypeByIndex(contTypeIndex); + if (!curr->contType.isContinuation()) { + throwError("non-continuation type in resume_throw instruction " + + curr->contType.toString()); + } + auto exnTagIndex = getU32LEB(); + auto* exnTag = wasm.tags[exnTagIndex].get(); + curr->tag = exnTag->name; + tagRefs[exnTagIndex].push_back(&curr->tag); - auto tagIndex = getU32LEB(); - if (tagIndex >= wasm.tags.size()) { - throwError("bad tag index"); + auto numHandlers = getU32LEB(); + + // We *must* bring the handlerTags vector to an appropriate size to ensure + // that we do not invalidate the pointers we add to tagRefs. They need to stay + // valid until processNames ran. + curr->handlerTags.resize(numHandlers); + curr->handlerBlocks.resize(numHandlers); + curr->onTags.resize(numHandlers); + + for (size_t i = 0; i < numHandlers; i++) { + uint8_t code = getInt8(); + auto tagIndex = getU32LEB(); + auto tag = getTagName(tagIndex); + Name handler; + if (code == BinaryConsts::OnLabel) { // expect (on $tag $label) + auto handlerIndex = getU32LEB(); + handler = getBreakTarget(handlerIndex).name; + } else if (code == BinaryConsts::OnSwitch) { // expect (on $tag switch) + handler = Name(); + } else { // error + throwError("ON opcode expected"); + } + + curr->handlerTags[i] = tag; + curr->handlerBlocks[i] = handler; + curr->onTags[i] = static_cast(code); // 0x00 is false, 0x01 is true + + // We don't know the final name yet + tagRefs[tagIndex].push_back(&curr->handlerTags[i]); + } + + curr->cont = popNonVoidExpression(); + + auto numArgs = exnTag->sig.params.size(); + curr->operands.resize(numArgs); + for (size_t i = 0; i < numArgs; i++) { + curr->operands[numArgs - i - 1] = popNonVoidExpression(); + } + + curr->finalize(&wasm); +} + +void WasmBinaryReader::visitStackSwitch(StackSwitch* curr) { + auto contTypeIndex = getU32LEB(); + curr->contType = getTypeByIndex(contTypeIndex); + if (!curr->contType.isContinuation()) { + throwError("non-continuation type in switch instruction " + + curr->contType.toString()); } + auto tagIndex = getU32LEB(); auto* tag = wasm.tags[tagIndex].get(); curr->tag = tag->name; tagRefs[tagIndex].push_back(&curr->tag); - auto numArgs = tag->sig.params.size(); + auto numArgs = + curr->contType.getContinuation().type.getSignature().params.size(); + if (numArgs < 1) { + throwError("switch requires a higher order continuation argument"); + } + numArgs = numArgs - 1; + curr->cont = popNonVoidExpression(); curr->operands.resize(numArgs); for (size_t i = 0; i < numArgs; i++) { curr->operands[numArgs - i - 1] = popNonVoidExpression(); diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index b238a926c42..72c8846fd1e 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -1897,6 +1897,18 @@ Result<> IRBuilder::makeStringSliceWTF() { return Ok{}; } +Result<> IRBuilder::makeContNew(HeapType ct) { + if (!ct.isContinuation()) { + return Err{"expected continuation type"}; + } + ContNew curr; + curr.contType = ct; + CHECK_ERR(visitContNew(&curr)); + + push(builder.makeContNew(ct, curr.func)); + return Ok{}; +} + Result<> IRBuilder::makeContBind(HeapType contTypeBefore, HeapType contTypeAfter) { if (!contTypeBefore.isContinuation() || !contTypeAfter.isContinuation()) { @@ -1924,49 +1936,101 @@ Result<> IRBuilder::makeContBind(HeapType contTypeBefore, return Ok{}; } -Result<> IRBuilder::makeContNew(HeapType ct) { - if (!ct.isContinuation()) { - return Err{"expected continuation type"}; - } - ContNew curr; - curr.contType = ct; - CHECK_ERR(visitContNew(&curr)); +Result<> IRBuilder::makeSuspend(Name tag) { + Suspend curr(wasm.allocator); + curr.tag = tag; + curr.operands.resize(wasm.getTag(tag)->sig.params.size()); + CHECK_ERR(visitSuspend(&curr)); - push(builder.makeContNew(ct, curr.func)); + std::vector operands(curr.operands.begin(), curr.operands.end()); + push(builder.makeSuspend(tag, operands)); return Ok{}; } Result<> IRBuilder::makeResume(HeapType ct, const std::vector& tags, - const std::vector& labels) { + const std::vector& labels, + const std::vector& onTags) { if (!ct.isContinuation()) { return Err{"expected continuation type"}; } Resume curr(wasm.allocator); curr.contType = ct; curr.operands.resize(ct.getContinuation().type.getSignature().params.size()); + curr.handlerTags.set(tags); + curr.onTags.set(onTags); CHECK_ERR(visitResume(&curr)); std::vector labelNames; labelNames.reserve(labels.size()); - for (auto label : labels) { - auto name = getLabelName(label); - CHECK_ERR(name); - labelNames.push_back(*name); + for (size_t i = 0; i < labels.size(); i++) { + if (curr.onTags[i]) { + labelNames.push_back(Name()); + } else { + auto name = getLabelName(labels[i]); + CHECK_ERR(name); + labelNames.push_back(*name); + } } + curr.handlerBlocks.set(labelNames); std::vector operands(curr.operands.begin(), curr.operands.end()); - push(builder.makeResume(ct, tags, labelNames, operands, curr.cont)); + push(builder.makeResume(ct, tags, labelNames, onTags, operands, curr.cont)); return Ok{}; } -Result<> IRBuilder::makeSuspend(Name tag) { - Suspend curr(wasm.allocator); +Result<> IRBuilder::makeResumeThrow(HeapType ct, + Name tag, + const std::vector& tags, + const std::vector& labels, + const std::vector& onTags) { + if (!ct.isContinuation()) { + return Err{"expected continuation type"}; + } + ResumeThrow curr(wasm.allocator); + curr.contType = ct; curr.tag = tag; curr.operands.resize(wasm.getTag(tag)->sig.params.size()); - CHECK_ERR(visitSuspend(&curr)); + curr.handlerTags.set(tags); + curr.onTags.set(onTags); + CHECK_ERR(visitResumeThrow(&curr)); + std::vector labelNames; + labelNames.reserve(labels.size()); + for (size_t i = 0; i < labels.size(); i++) { + if (curr.onTags[i]) { + labelNames.push_back(Name()); + } else { + auto name = getLabelName(labels[i]); + CHECK_ERR(name); + labelNames.push_back(*name); + } + } + curr.handlerBlocks.set(labelNames); std::vector operands(curr.operands.begin(), curr.operands.end()); - push(builder.makeSuspend(tag, operands)); + push(builder.makeResumeThrow( + ct, tag, tags, labelNames, onTags, operands, curr.cont)); + return Ok{}; +} + +Result<> IRBuilder::makeStackSwitch(HeapType ct, Name tag) { + if (!ct.isContinuation()) { + return Err{"expected continuation type"}; + } + StackSwitch curr(wasm.allocator); + curr.contType = ct; + curr.tag = tag; + 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); // the continuation argument of + // the continuation is synthetic, + // i.e. it is provided by the + // runtime. + CHECK_ERR(visitStackSwitch(&curr)); + std::vector operands(curr.operands.begin(), curr.operands.end()); + push(builder.makeStackSwitch(ct, tag, operands, curr.cont)); return Ok{}; } diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 7194229fea0..8e7dca97cca 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2608,31 +2608,62 @@ void BinaryInstWriter::visitStringSliceWTF(StringSliceWTF* curr) { << U32LEB(BinaryConsts::StringViewWTF16Slice); } +void BinaryInstWriter::visitContNew(ContNew* curr) { + o << int8_t(BinaryConsts::ContNew); + parent.writeIndexedHeapType(curr->contType); +} + +void BinaryInstWriter::visitSuspend(Suspend* curr) { + o << int8_t(BinaryConsts::Suspend) << U32LEB(parent.getTagIndex(curr->tag)); +} + void BinaryInstWriter::visitContBind(ContBind* curr) { o << int8_t(BinaryConsts::ContBind); parent.writeIndexedHeapType(curr->contTypeBefore); parent.writeIndexedHeapType(curr->contTypeAfter); } -void BinaryInstWriter::visitContNew(ContNew* curr) { - o << int8_t(BinaryConsts::ContNew); +void BinaryInstWriter::visitResume(Resume* curr) { + o << int8_t(BinaryConsts::Resume); parent.writeIndexedHeapType(curr->contType); + + size_t handlerNum = curr->handlerTags.size(); + o << U32LEB(handlerNum); + for (size_t i = 0; i < handlerNum; i++) { + if (curr->onTags[i]) { // 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::visitResume(Resume* curr) { - o << int8_t(BinaryConsts::Resume); +void BinaryInstWriter::visitResumeThrow(ResumeThrow* curr) { + o << int8_t(BinaryConsts::ResumeThrow); parent.writeIndexedHeapType(curr->contType); + o << U32LEB(parent.getTagIndex(curr->tag)); size_t handlerNum = curr->handlerTags.size(); o << U32LEB(handlerNum); for (size_t i = 0; i < handlerNum; i++) { - o << U32LEB(parent.getTagIndex(curr->handlerTags[i])) - << U32LEB(getBreakIndex(curr->handlerBlocks[i])); + if (curr->onTags[i]) { // 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::visitSuspend(Suspend* curr) { - o << int8_t(BinaryConsts::Suspend) << U32LEB(parent.getTagIndex(curr->tag)); +void BinaryInstWriter::visitStackSwitch(StackSwitch* curr) { + o << int8_t(BinaryConsts::Switch); + parent.writeIndexedHeapType(curr->contType); + o << U32LEB(parent.getTagIndex(curr->tag)); } void BinaryInstWriter::emitScopeEnd(Expression* curr) { diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index 9bb4f425907..2a981e7626a 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -1421,7 +1421,7 @@ FeatureSet HeapType::getFeatures() const { return; case HeapType::cont: case HeapType::nocont: - feats |= FeatureSet::TypedContinuations; + feats |= FeatureSet::StackSwitching; return; } } @@ -1447,7 +1447,7 @@ FeatureSet HeapType::getFeatures() const { feats |= FeatureSet::Multivalue; } } else if (heapType->isContinuation()) { - feats |= FeatureSet::TypedContinuations; + feats |= FeatureSet::StackSwitching; } // In addition, scan their non-ref children, to add dependencies on diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index d1ca5220c3b..c4c5e83ca0e 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -508,10 +508,12 @@ struct FunctionValidator : public WalkerPass> { void visitStringEq(StringEq* curr); void visitStringWTF16Get(StringWTF16Get* curr); void visitStringSliceWTF(StringSliceWTF* curr); - void visitContBind(ContBind* curr); void visitContNew(ContNew* curr); - void visitResume(Resume* curr); + void visitContBind(ContBind* curr); void visitSuspend(Suspend* curr); + void visitResume(Resume* curr); + void visitResumeThrow(ResumeThrow* curr); + void visitStackSwitch(StackSwitch* curr); void visitFunction(Function* curr); @@ -3475,12 +3477,23 @@ void FunctionValidator::visitStringSliceWTF(StringSliceWTF* curr) { "string operations require reference-types [--enable-strings]"); } +void FunctionValidator::visitContNew(ContNew* curr) { + // TODO implement actual type-checking + shouldBeTrue(!getModule() || getModule()->features.hasStackSwitching(), + curr, + "cont.new requires stack-switching [--enable-stack-switching]"); + + shouldBeTrue((curr->contType.isContinuation() && + curr->contType.getContinuation().type.isSignature()), + curr, + "invalid type in ContNew expression"); +} + void FunctionValidator::visitContBind(ContBind* curr) { // TODO implement actual type-checking - shouldBeTrue( - !getModule() || getModule()->features.hasTypedContinuations(), - curr, - "cont.bind requires typed-continuatons [--enable-typed-continuations]"); + shouldBeTrue(!getModule() || getModule()->features.hasStackSwitching(), + curr, + "cont.bind requires stack-switching [--enable-stack-switching]"); shouldBeTrue((curr->contTypeBefore.isContinuation() && curr->contTypeBefore.getContinuation().type.isSignature()), @@ -3493,43 +3506,58 @@ void FunctionValidator::visitContBind(ContBind* curr) { "invalid second type in ContBind expression"); } -void FunctionValidator::visitContNew(ContNew* curr) { +void FunctionValidator::visitSuspend(Suspend* curr) { // TODO implement actual type-checking + shouldBeTrue(!getModule() || getModule()->features.hasStackSwitching(), + curr, + "suspend requires stack-switching [--enable-stack-switching]"); +} + +void FunctionValidator::visitResume(Resume* curr) { + // TODO implement actual type-checking + shouldBeTrue(!getModule() || getModule()->features.hasStackSwitching(), + curr, + "resume requires stack-switching [--enable-stack-switching]"); + shouldBeTrue( - !getModule() || getModule()->features.hasTypedContinuations(), + curr->sentTypes.size() == curr->handlerBlocks.size(), curr, - "cont.new requires typed-continuatons [--enable-typed-continuations]"); + "sentTypes cache in Resume instruction has not been initialized"); shouldBeTrue((curr->contType.isContinuation() && curr->contType.getContinuation().type.isSignature()), curr, - "invalid type in ContNew expression"); + "invalid type in Resume expression"); } -void FunctionValidator::visitResume(Resume* curr) { +void FunctionValidator::visitResumeThrow(ResumeThrow* curr) { // TODO implement actual type-checking shouldBeTrue( - !getModule() || getModule()->features.hasTypedContinuations(), + !getModule() || getModule()->features.hasStackSwitching(), curr, - "resume requires typed-continuatons [--enable-typed-continuations]"); + "resume_throw requires stack-switching [--enable-stack-switching]"); shouldBeTrue( curr->sentTypes.size() == curr->handlerBlocks.size(), curr, - "sentTypes cache in Resume instruction has not been initialized"); + "sentTypes cache in ResumeThrow instruction has not been initialized"); shouldBeTrue((curr->contType.isContinuation() && curr->contType.getContinuation().type.isSignature()), curr, - "invalid type in Resume expression"); + "invalid type in ResumeThrow expression"); } -void FunctionValidator::visitSuspend(Suspend* curr) { +void FunctionValidator::visitStackSwitch(StackSwitch* curr) { // TODO implement actual type-checking - shouldBeTrue( - !getModule() || getModule()->features.hasTypedContinuations(), - curr, - "suspend requires typed-continuations [--enable-typed-continuations]"); + shouldBeTrue(!getModule() || getModule()->features.hasStackSwitching(), + curr, + "switch requires stack-switching [--enable-stack-switching]"); + + shouldBeTrue((curr->contType.isContinuation() && + curr->contType.getContinuation().type.isSignature()), + curr, + "invalid type in Switch expression"); } void FunctionValidator::visitFunction(Function* curr) { @@ -4033,10 +4061,10 @@ static void validateTags(Module& module, ValidationInfo& info) { } for (auto& curr : module.tags) { if (curr->sig.results != Type(Type::none)) { - info.shouldBeTrue(module.features.hasTypedContinuations(), + info.shouldBeTrue(module.features.hasStackSwitching(), curr->name, - "Tags with result types require typed continuations " - "feature [--enable-typed-continuations]"); + "Tags with result types require stack switching " + "feature [--enable-stack-switching]"); } if (curr->sig.params.isTuple()) { info.shouldBeTrue( diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 4150af5b41a..734828db027 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -54,7 +54,7 @@ const char* RelaxedSIMDFeature = "relaxed-simd"; const char* ExtendedConstFeature = "extended-const"; const char* StringsFeature = "strings"; const char* MultiMemoryFeature = "multimemory"; -const char* TypedContinuationsFeature = "typed-continuations"; +const char* StackSwitchingFeature = "stack-switching"; const char* SharedEverythingFeature = "shared-everything"; const char* FP16Feature = "fp16"; } // namespace CustomSections @@ -1368,6 +1368,14 @@ void StringSliceWTF::finalize() { } } +void ContNew::finalize() { + if (func->type == Type::unreachable) { + type = Type::unreachable; + } else { + type = Type(contType, NonNullable); + } +} + void ContBind::finalize() { if (cont->type == Type::unreachable) { type = Type::unreachable; @@ -1376,11 +1384,10 @@ void ContBind::finalize() { } } -void ContNew::finalize() { - if (func->type == Type::unreachable) { - type = Type::unreachable; - } else { - type = Type(contType, NonNullable); +void Suspend::finalize(Module* wasm) { + if (!handleUnreachableOperands(this) && wasm) { + auto tag = wasm->getTag(this->tag); + type = tag->sig.results; } } @@ -1437,10 +1444,62 @@ void Resume::finalize(Module* wasm) { populateResumeSentTypes(this, wasm); } -void Suspend::finalize(Module* wasm) { +static void populateResumeThrowSentTypes(ResumeThrow* curr, Module* wasm) { + if (!wasm) { + return; + } + + const Signature& contSig = + curr->contType.getContinuation().type.getSignature(); + + // Let $tag be a tag with type [tgp*] -> [tgr*]. Let $ct be a continuation + // type (cont $ft), where $ft is [ctp*] -> [ctr*]. Then an instruction (resume + // $ct ... (tag $tag $block) ... ) causes $block to receive values of the + // following types when suspending to $tag: tgp* (ref $ct') where ct' = (cont + // $ft') and ft' = [tgr*] -> [ctr*]. + // + auto& ctrs = contSig.results; + curr->sentTypes.clear(); + curr->sentTypes.resize(curr->handlerTags.size()); + for (Index i = 0; i < curr->handlerTags.size(); i++) { + auto& tag = curr->handlerTags[i]; + auto& tagSig = wasm->getTag(tag)->sig; + + auto& tgps = tagSig.params; + auto& tgrs = tagSig.results; + + HeapType ftPrime{Signature(tgrs, ctrs)}; + HeapType ctPrime{Continuation(ftPrime)}; + Type ctPrimeRef(ctPrime, Nullability::NonNullable); + + if (tgps.size() > 0) { + TypeList sentValueTypes; + sentValueTypes.reserve(tgps.size() + 1); + + sentValueTypes.insert(sentValueTypes.begin(), tgps.begin(), tgps.end()); + sentValueTypes.push_back(ctPrimeRef); + curr->sentTypes[i] = Type(sentValueTypes); + } else { + curr->sentTypes[i] = ctPrimeRef; + } + } +} + +void ResumeThrow::finalize(Module* wasm) { + if (cont->type == Type::unreachable) { + type = Type::unreachable; + } else if (!handleUnreachableOperands(this)) { + const Signature& contSig = + this->contType.getContinuation().type.getSignature(); + type = contSig.results; + } + + populateResumeThrowSentTypes(this, wasm); +} + +void StackSwitch::finalize(Module* wasm) { if (!handleUnreachableOperands(this) && wasm) { - auto tag = wasm->getTag(this->tag); - type = tag->sig.results; + type = this->contType.getContinuation().type.getSignature().params; } } diff --git a/src/wasm2js.h b/src/wasm2js.h index 15ae019ea22..47d5abb2f91 100644 --- a/src/wasm2js.h +++ b/src/wasm2js.h @@ -2401,11 +2401,15 @@ Ref Wasm2JSBuilder::processExpression(Expression* curr, ValueBuilder::makeCall(ABI::wasm2js::TRAP)); } + Ref visitContNew(ContNew* curr) { + unimplemented(curr); + WASM_UNREACHABLE("unimp"); + } Ref visitContBind(ContBind* curr) { unimplemented(curr); WASM_UNREACHABLE("unimp"); } - Ref visitContNew(ContNew* curr) { + Ref visitSuspend(Suspend* curr) { unimplemented(curr); WASM_UNREACHABLE("unimp"); } @@ -2413,7 +2417,11 @@ Ref Wasm2JSBuilder::processExpression(Expression* curr, unimplemented(curr); WASM_UNREACHABLE("unimp"); } - Ref visitSuspend(Suspend* curr) { + Ref visitResumeThrow(ResumeThrow* curr) { + unimplemented(curr); + WASM_UNREACHABLE("unimp"); + } + Ref visitStackSwitch(StackSwitch* curr) { unimplemented(curr); WASM_UNREACHABLE("unimp"); } diff --git a/test/lit/basic/typed_continuations.wast b/test/lit/basic/stack_switching.wast similarity index 100% rename from test/lit/basic/typed_continuations.wast rename to test/lit/basic/stack_switching.wast diff --git a/test/lit/basic/typed_continuations_contbind.wast b/test/lit/basic/stack_switching_contbind.wast similarity index 100% rename from test/lit/basic/typed_continuations_contbind.wast rename to test/lit/basic/stack_switching_contbind.wast diff --git a/test/lit/basic/typed_continuations_contnew.wast b/test/lit/basic/stack_switching_contnew.wast similarity index 100% rename from test/lit/basic/typed_continuations_contnew.wast rename to test/lit/basic/stack_switching_contnew.wast diff --git a/test/lit/basic/typed_continuations_resume.wast b/test/lit/basic/stack_switching_resume.wast similarity index 100% rename from test/lit/basic/typed_continuations_resume.wast rename to test/lit/basic/stack_switching_resume.wast diff --git a/test/lit/basic/stack_switching_resume_throw.wast b/test/lit/basic/stack_switching_resume_throw.wast new file mode 100644 index 00000000000..7d136ea3980 --- /dev/null +++ b/test/lit/basic/stack_switching_resume_throw.wast @@ -0,0 +1,132 @@ +;; 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 $ft (func (param i32) (result i32))) + ;; CHECK-BIN: (type $ft (func (param i32) (result i32))) + (type $ft (func (param i32) (result i32))) + ;; CHECK-TEXT: (type $ct (cont $ft)) + ;; CHECK-BIN: (type $ct (cont $ft)) + (type $ct (cont $ft)) + + ;; CHECK-TEXT: (type $2 (func (result i32 (ref $ct)))) + + ;; CHECK-TEXT: (type $3 (func (param i64))) + + ;; CHECK-TEXT: (type $4 (func (param (ref $ct)) (result i32))) + + ;; CHECK-TEXT: (tag $t (param i32) (result i32)) + ;; CHECK-BIN: (type $2 (func (result i32 (ref $ct)))) + + ;; CHECK-BIN: (type $3 (func (param i64))) + + ;; CHECK-BIN: (type $4 (func (param (ref $ct)) (result i32))) + + ;; CHECK-BIN: (tag $t (param i32) (result i32)) + (tag $t (param i32) (result i32)) + ;; CHECK-TEXT: (tag $e (param i64)) + ;; CHECK-BIN: (tag $e (param i64)) + (tag $e (param i64)) + + ;; CHECK-TEXT: (func $go (type $4) (param $x (ref $ct)) (result i32) + ;; CHECK-TEXT-NEXT: (tuple.extract 2 0 + ;; CHECK-TEXT-NEXT: (block $handler (type $2) (result i32 (ref $ct)) + ;; CHECK-TEXT-NEXT: (return + ;; CHECK-TEXT-NEXT: (resume_throw $ct $e (on $t $handler) + ;; CHECK-TEXT-NEXT: (i64.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 $4) (param $x (ref $ct)) (result i32) + ;; CHECK-BIN-NEXT: (local $1 (tuple i32 (ref $ct))) + ;; CHECK-BIN-NEXT: (local $2 i32) + ;; CHECK-BIN-NEXT: (local.set $1 + ;; CHECK-BIN-NEXT: (block $label$1 (type $2) (result i32 (ref $ct)) + ;; CHECK-BIN-NEXT: (return + ;; CHECK-BIN-NEXT: (resume_throw $ct $e (on $t $label$1) + ;; CHECK-BIN-NEXT: (i64.const 123) + ;; CHECK-BIN-NEXT: (local.get $x) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (block (result i32) + ;; CHECK-BIN-NEXT: (local.set $2 + ;; CHECK-BIN-NEXT: (tuple.extract 2 0 + ;; CHECK-BIN-NEXT: (local.get $1) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (tuple.extract 2 1 + ;; CHECK-BIN-NEXT: (local.get $1) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (local.get $2) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + (func $go (param $x (ref $ct)) (result i32) + (tuple.extract 2 0 + (block $handler (result i32 (ref $ct)) + (return + (resume_throw $ct $e + (on $t $handler) + (i64.const 123) + (local.get $x) + ) + ) + ) + ) + ) +) +;; CHECK-BIN-NODEBUG: (type $0 (func (param i32) (result i32))) + +;; CHECK-BIN-NODEBUG: (type $1 (cont $0)) + +;; CHECK-BIN-NODEBUG: (type $2 (func (result i32 (ref $1)))) + +;; CHECK-BIN-NODEBUG: (type $3 (func (param i64))) + +;; CHECK-BIN-NODEBUG: (type $4 (func (param (ref $1)) (result i32))) + +;; CHECK-BIN-NODEBUG: (tag $tag$0 (param i32) (result i32)) + +;; CHECK-BIN-NODEBUG: (tag $tag$1 (param i64)) + +;; CHECK-BIN-NODEBUG: (func $0 (type $4) (param $0 (ref $1)) (result i32) +;; CHECK-BIN-NODEBUG-NEXT: (local $1 (tuple i32 (ref $1))) +;; CHECK-BIN-NODEBUG-NEXT: (local $2 i32) +;; CHECK-BIN-NODEBUG-NEXT: (local.set $1 +;; CHECK-BIN-NODEBUG-NEXT: (block $label$1 (type $2) (result i32 (ref $1)) +;; CHECK-BIN-NODEBUG-NEXT: (return +;; CHECK-BIN-NODEBUG-NEXT: (resume_throw $1 $tag$1 (on $tag$0 $label$1) +;; CHECK-BIN-NODEBUG-NEXT: (i64.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: (block (result i32) +;; CHECK-BIN-NODEBUG-NEXT: (local.set $2 +;; CHECK-BIN-NODEBUG-NEXT: (tuple.extract 2 0 +;; CHECK-BIN-NODEBUG-NEXT: (local.get $1) +;; 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 $1) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $2) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/basic/typed_continuations_suspend.wast b/test/lit/basic/stack_switching_suspend.wast similarity index 100% rename from test/lit/basic/typed_continuations_suspend.wast rename to test/lit/basic/stack_switching_suspend.wast diff --git a/test/lit/help/wasm-as.test b/test/lit/help/wasm-as.test index 5dd4b151573..46b03926ab6 100644 --- a/test/lit/help/wasm-as.test +++ b/test/lit/help/wasm-as.test @@ -104,9 +104,9 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-multimemory Disable multimemory ;; CHECK-NEXT: -;; CHECK-NEXT: --enable-typed-continuations Enable typed continuations +;; CHECK-NEXT: --enable-stack-switching Enable stack switching ;; CHECK-NEXT: -;; CHECK-NEXT: --disable-typed-continuations Disable typed continuations +;; CHECK-NEXT: --disable-stack-switching Disable stack switching ;; CHECK-NEXT: ;; CHECK-NEXT: --enable-shared-everything Enable shared-everything threads ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-ctor-eval.test b/test/lit/help/wasm-ctor-eval.test index abb80b12eb6..6cb5fb6ad39 100644 --- a/test/lit/help/wasm-ctor-eval.test +++ b/test/lit/help/wasm-ctor-eval.test @@ -111,9 +111,9 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-multimemory Disable multimemory ;; CHECK-NEXT: -;; CHECK-NEXT: --enable-typed-continuations Enable typed continuations +;; CHECK-NEXT: --enable-stack-switching Enable stack switching ;; CHECK-NEXT: -;; CHECK-NEXT: --disable-typed-continuations Disable typed continuations +;; CHECK-NEXT: --disable-stack-switching Disable stack switching ;; CHECK-NEXT: ;; CHECK-NEXT: --enable-shared-everything Enable shared-everything threads ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-dis.test b/test/lit/help/wasm-dis.test index 90bc12b9918..70801851a05 100644 --- a/test/lit/help/wasm-dis.test +++ b/test/lit/help/wasm-dis.test @@ -97,9 +97,9 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-multimemory Disable multimemory ;; CHECK-NEXT: -;; CHECK-NEXT: --enable-typed-continuations Enable typed continuations +;; CHECK-NEXT: --enable-stack-switching Enable stack switching ;; CHECK-NEXT: -;; CHECK-NEXT: --disable-typed-continuations Disable typed continuations +;; CHECK-NEXT: --disable-stack-switching Disable stack switching ;; CHECK-NEXT: ;; CHECK-NEXT: --enable-shared-everything Enable shared-everything threads ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-emscripten-finalize.test b/test/lit/help/wasm-emscripten-finalize.test index cdd378d4082..a1b979940a9 100644 --- a/test/lit/help/wasm-emscripten-finalize.test +++ b/test/lit/help/wasm-emscripten-finalize.test @@ -139,9 +139,9 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-multimemory Disable multimemory ;; CHECK-NEXT: -;; CHECK-NEXT: --enable-typed-continuations Enable typed continuations +;; CHECK-NEXT: --enable-stack-switching Enable stack switching ;; CHECK-NEXT: -;; CHECK-NEXT: --disable-typed-continuations Disable typed continuations +;; CHECK-NEXT: --disable-stack-switching Disable stack switching ;; CHECK-NEXT: ;; CHECK-NEXT: --enable-shared-everything Enable shared-everything threads ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-merge.test b/test/lit/help/wasm-merge.test index ee1fed13a92..e05b889c530 100644 --- a/test/lit/help/wasm-merge.test +++ b/test/lit/help/wasm-merge.test @@ -127,9 +127,9 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-multimemory Disable multimemory ;; CHECK-NEXT: -;; CHECK-NEXT: --enable-typed-continuations Enable typed continuations +;; CHECK-NEXT: --enable-stack-switching Enable stack switching ;; CHECK-NEXT: -;; CHECK-NEXT: --disable-typed-continuations Disable typed continuations +;; CHECK-NEXT: --disable-stack-switching Disable stack switching ;; CHECK-NEXT: ;; CHECK-NEXT: --enable-shared-everything Enable shared-everything threads ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index 50295b1f02d..41569987288 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -731,9 +731,9 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-multimemory Disable multimemory ;; CHECK-NEXT: -;; CHECK-NEXT: --enable-typed-continuations Enable typed continuations +;; CHECK-NEXT: --enable-stack-switching Enable stack switching ;; CHECK-NEXT: -;; CHECK-NEXT: --disable-typed-continuations Disable typed continuations +;; CHECK-NEXT: --disable-stack-switching Disable stack switching ;; CHECK-NEXT: ;; CHECK-NEXT: --enable-shared-everything Enable shared-everything threads ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index 0691d1c1839..86fe87a5ef3 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -740,9 +740,9 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-multimemory Disable multimemory ;; CHECK-NEXT: -;; CHECK-NEXT: --enable-typed-continuations Enable typed continuations +;; CHECK-NEXT: --enable-stack-switching Enable stack switching ;; CHECK-NEXT: -;; CHECK-NEXT: --disable-typed-continuations Disable typed continuations +;; CHECK-NEXT: --disable-stack-switching Disable stack switching ;; CHECK-NEXT: ;; CHECK-NEXT: --enable-shared-everything Enable shared-everything threads ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-reduce.test b/test/lit/help/wasm-reduce.test index cc75aed2b11..ef27f04858d 100644 --- a/test/lit/help/wasm-reduce.test +++ b/test/lit/help/wasm-reduce.test @@ -133,9 +133,9 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-multimemory Disable multimemory ;; CHECK-NEXT: -;; CHECK-NEXT: --enable-typed-continuations Enable typed continuations +;; CHECK-NEXT: --enable-stack-switching Enable stack switching ;; CHECK-NEXT: -;; CHECK-NEXT: --disable-typed-continuations Disable typed continuations +;; CHECK-NEXT: --disable-stack-switching Disable stack switching ;; CHECK-NEXT: ;; CHECK-NEXT: --enable-shared-everything Enable shared-everything threads ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-split.test b/test/lit/help/wasm-split.test index e5e73656205..142edca02bb 100644 --- a/test/lit/help/wasm-split.test +++ b/test/lit/help/wasm-split.test @@ -236,9 +236,9 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-multimemory Disable multimemory ;; CHECK-NEXT: -;; CHECK-NEXT: --enable-typed-continuations Enable typed continuations +;; CHECK-NEXT: --enable-stack-switching Enable stack switching ;; CHECK-NEXT: -;; CHECK-NEXT: --disable-typed-continuations Disable typed continuations +;; CHECK-NEXT: --disable-stack-switching Disable stack switching ;; CHECK-NEXT: ;; CHECK-NEXT: --enable-shared-everything Enable shared-everything threads ;; CHECK-NEXT: diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index 89dcaa0280d..7f7aef782cf 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -694,9 +694,9 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-multimemory Disable multimemory ;; CHECK-NEXT: -;; CHECK-NEXT: --enable-typed-continuations Enable typed continuations +;; CHECK-NEXT: --enable-stack-switching Enable stack switching ;; CHECK-NEXT: -;; CHECK-NEXT: --disable-typed-continuations Disable typed continuations +;; CHECK-NEXT: --disable-stack-switching Disable stack switching ;; CHECK-NEXT: ;; CHECK-NEXT: --enable-shared-everything Enable shared-everything threads ;; CHECK-NEXT: diff --git a/test/passes/strip-target-features_roundtrip_print-features_all-features.txt b/test/passes/strip-target-features_roundtrip_print-features_all-features.txt index 901f43bc504..d19756b8366 100644 --- a/test/passes/strip-target-features_roundtrip_print-features_all-features.txt +++ b/test/passes/strip-target-features_roundtrip_print-features_all-features.txt @@ -14,7 +14,7 @@ --enable-extended-const --enable-strings --enable-multimemory ---enable-typed-continuations +--enable-stack-switching --enable-shared-everything --enable-fp16 (module diff --git a/test/unit/test_features.py b/test/unit/test_features.py index f5c3fabc507..8c185ea700a 100644 --- a/test/unit/test_features.py +++ b/test/unit/test_features.py @@ -51,10 +51,10 @@ def check_gc(self, module, error): self.check_feature(module, error, '--enable-gc', ['--enable-reference-types']) - def check_typed_continuations(self, module, error): - # Typed continuations implies function references (which is provided by + def check_stack_switching(self, module, error): + # Stack switching implies function references (which is provided by # gc in binaryen, and implies reference types) and exceptions - self.check_feature(module, error, '--enable-typed-continuations', + self.check_feature(module, error, '--enable-stack-switching', ['--enable-gc', '--enable-reference-types', '--enable-exception-handling']) def test_v128_signature(self): @@ -290,9 +290,9 @@ def test_tag_results(self): (tag $foo (result i32)) ) ''' - self.check_typed_continuations(module, - 'Tags with result types require typed ' - 'continuations feature [--enable-typed-continuations]') + self.check_stack_switching(module, + 'Tags with result types require stack ' + 'switching feature [--enable-stack-switching]') def test_cont_type(self): module = ''' @@ -304,7 +304,7 @@ def test_cont_type(self): ) ) ''' - self.check_typed_continuations(module, 'all used types should be allowed') + self.check_stack_switching(module, 'all used types should be allowed') class TargetFeaturesSectionTest(utils.BinaryenTestCase): @@ -424,7 +424,7 @@ def test_emit_all_features(self): '--enable-extended-const', '--enable-strings', '--enable-multimemory', - '--enable-typed-continuations', + '--enable-stack-switching', '--enable-shared-everything', '--enable-fp16', ], p2.stdout.splitlines())