diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 28cf4eb70d6..953108a4421 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -315,6 +315,7 @@ def is_git_repo(): 'multi-memory-lowering-import-error.wast', # the fuzzer does not support typed continuations 'typed_continuations.wast', + 'typed_continuations_resume.wast', # New EH implementation is in progress 'exception-handling.wast', ] diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py index 9d6e58481ff..d873ca4f64d 100755 --- a/scripts/gen-s-parser.py +++ b/scripts/gen-s-parser.py @@ -566,6 +566,8 @@ # Typed function references instructions ("call_ref", "makeCallRef(s, /*isReturn=*/false)"), ("return_call_ref", "makeCallRef(s, /*isReturn=*/true)"), + # Typed continuations instructions + ("resume", "makeResume(s)"), # GC ("i31.new", "makeRefI31(s)"), # deprecated ("ref.i31", "makeRefI31(s)"), diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc index cef0a6b926b..8387dbb8e9f 100644 --- a/src/gen-s-parser.inc +++ b/src/gen-s-parser.inc @@ -3004,6 +3004,9 @@ switch (buf[0]) { default: goto parse_error; } } + case 's': + if (op == "resume"sv) { return makeResume(s); } + goto parse_error; case 't': { switch (buf[3]) { case 'h': @@ -8104,6 +8107,12 @@ switch (buf[0]) { default: goto parse_error; } } + case 's': + if (op == "resume"sv) { + CHECK_ERR(makeResume(ctx, pos)); + return Ok{}; + } + goto parse_error; case 't': { switch (buf[3]) { case 'h': diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp index 65f5c472463..0338f630ccc 100644 --- a/src/ir/ReFinalize.cpp +++ b/src/ir/ReFinalize.cpp @@ -182,6 +182,8 @@ void ReFinalize::visitStringSliceIter(StringSliceIter* curr) { curr->finalize(); } +void ReFinalize::visitResume(Resume* curr) { curr->finalize(); } + void ReFinalize::visitExport(Export* curr) { WASM_UNREACHABLE("unimp"); } void ReFinalize::visitGlobal(Global* curr) { WASM_UNREACHABLE("unimp"); } void ReFinalize::visitTable(Table* curr) { WASM_UNREACHABLE("unimp"); } diff --git a/src/ir/branch-utils.h b/src/ir/branch-utils.h index 3c02757015a..28e29ede402 100644 --- a/src/ir/branch-utils.h +++ b/src/ir/branch-utils.h @@ -82,6 +82,13 @@ void operateOnScopeNameUsesAndSentTypes(Expression* expr, T func) { func(name, tt->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 } @@ -106,6 +113,10 @@ void operateOnScopeNameUsesAndSentValues(Expression* expr, T func) { // The values are supplied by throwing instructions, so we are unable to // know what they will be here. func(name, nullptr); + } else if (auto* res = expr->dynCast()) { + // 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 { assert(expr->is() || expr->is()); // delegate or rethrow } diff --git a/src/ir/cost.h b/src/ir/cost.h index 0e87317a624..821e46524c1 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -726,6 +726,11 @@ struct CostAnalyzer : public OverriddenVisitor { return 8 + visit(curr->ref) + visit(curr->num); } + CostType visitResume(Resume* curr) { + // Inspired by indirect calls, but twice the cost. + return 12 + visit(curr->cont); + } + private: CostType nullCheckCost(Expression* ref) { // A nullable type requires a bounds check in most VMs. diff --git a/src/ir/effects.h b/src/ir/effects.h index 1156cd82350..3ff320a8883 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -973,6 +973,19 @@ class EffectAnalyzer { // traps when ref is null. parent.implicitTrap = true; } + + void visitResume(Resume* 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; + } + } }; public: diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index afe4a4c5426..304ddb04ded 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -341,6 +341,8 @@ struct CodeScanner counts.include(get->type); } else if (auto* set = curr->dynCast()) { counts.note(set->ref->type); + } else if (auto* resume = curr->dynCast()) { + counts.note(resume->contType); } else if (Properties::isControlFlowStructure(curr)) { counts.noteControlFlow(Signature(Type::none, curr->type)); } diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 67b33553d55..2f900f0edc0 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -1199,6 +1199,11 @@ struct InfoCollector void visitReturn(Return* curr) { addResult(curr->value); } + void visitResume(Resume* curr) { + // TODO: optimize when possible + addRoot(curr); + } + void visitFunction(Function* func) { // Functions with a result can flow a value out from their body. addResult(func->body); diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index 86805f88a7b..65558b43011 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -333,6 +333,8 @@ struct SubtypingDiscoverer : public OverriddenVisitor { void visitStringIterMove(StringIterMove* curr) {} void visitStringSliceWTF(StringSliceWTF* curr) {} void visitStringSliceIter(StringSliceIter* curr) {} + + void visitResume(Resume* curr) { WASM_UNREACHABLE("not implemented"); } }; } // namespace wasm diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 28110a628e2..6e346ec2217 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -164,6 +164,7 @@ Result<> makeStringIterMove(Ctx&, Index, StringIterMoveOp op); template Result<> makeStringSliceWTF(Ctx&, Index, StringSliceWTFOp op); template Result<> makeStringSliceIter(Ctx&, Index); +template Result<> makeResume(Ctx&, Index); // Modules template MaybeResult maybeTypeidx(Ctx& ctx); @@ -1782,6 +1783,10 @@ template Result<> makeStringSliceIter(Ctx& ctx, Index pos) { return ctx.makeStringSliceIter(pos); } +template Result<> makeResume(Ctx& ctx, Index pos) { + return ctx.in.err("unimplemented instruction"); +} + // ======= // Modules // ======= diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index b6060a85363..948e8d239dd 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -316,6 +316,7 @@ struct PrintSExpression : public UnifiedExpressionVisitor { void visitLoop(Loop* curr); void visitTry(Try* curr); void visitTryTable(TryTable* curr); + void visitResume(Resume* curr); void maybePrintUnreachableReplacement(Expression* curr, Type type); void maybePrintUnreachableOrNullReplacement(Expression* curr, Type type); void visitCallRef(CallRef* curr) { @@ -2438,6 +2439,24 @@ struct PrintExpressionContents void visitStringSliceIter(StringSliceIter* curr) { printMedium(o, "stringview_iter.slice"); } + + void visitResume(Resume* curr) { + printMedium(o, "resume"); + + o << ' '; + printHeapType(curr->contType); + + // We deliberate keep all (tag ...) clauses on the same line as the resume + // itself to work around a quirk in update_lit_checks.py + for (Index i = 0; i < curr->handlerTags.size(); i++) { + o << " ("; + printMedium(o, "tag "); + printName(curr->handlerTags[i], o); + o << ' '; + printName(curr->handlerBlocks[i], o); + o << ')'; + } + } }; void PrintSExpression::setModule(Module* module) { @@ -2786,6 +2805,23 @@ void PrintSExpression::visitTryTable(TryTable* curr) { controlFlowDepth--; } +void PrintSExpression::visitResume(Resume* 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::maybePrintUnreachableReplacement(Expression* curr, Type type) { // See the parallel function diff --git a/src/passes/TypeGeneralizing.cpp b/src/passes/TypeGeneralizing.cpp index d167b89a9f4..293d17cdf15 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -874,6 +874,8 @@ struct TransferFn : OverriddenVisitor { void visitStringIterMove(StringIterMove* curr) { WASM_UNREACHABLE("TODO"); } void visitStringSliceWTF(StringSliceWTF* curr) { WASM_UNREACHABLE("TODO"); } void visitStringSliceIter(StringSliceIter* curr) { WASM_UNREACHABLE("TODO"); } + + void visitResume(Resume* curr) { WASM_UNREACHABLE("TODO"); } }; struct TypeGeneralizing : WalkerPass> { diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 84720865ea1..2afa2953834 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1296,6 +1296,10 @@ enum ASTNodes { StringEncodeLossyUTF8Array = 0xb6, StringEncodeWTF8Array = 0xb7, StringNewUTF8ArrayTry = 0xb8, + + // typed continuation opcodes + Resume = 0xe3, + }; enum MemoryAccess { @@ -1922,6 +1926,7 @@ class WasmBinaryReader { void visitCallRef(CallRef* curr); void visitRefAsCast(RefCast* curr, uint32_t code); void visitRefAs(RefAs* curr, uint8_t code); + void visitResume(Resume* curr); [[noreturn]] void throwError(std::string text); diff --git a/src/wasm-builder.h b/src/wasm-builder.h index a873a73094b..0c3cec5c2b6 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -1197,6 +1197,21 @@ class Builder { return ret; } + Resume* makeResume(HeapType contType, + const std::vector& handlerTags, + const std::vector& handlerBlocks, + const std::vector& operands, + Expression* cont) { + auto* ret = wasm.allocator.alloc(); + ret->contType = contType; + ret->handlerTags.set(handlerTags); + ret->handlerBlocks.set(handlerBlocks); + ret->operands.set(operands); + ret->cont = cont; + ret->finalize(&wasm); + return ret; + } + // Additional helpers Drop* makeDrop(Expression* value) { diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def index 3f8072445c0..aeb32e68915 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -945,6 +945,18 @@ switch (DELEGATE_ID) { DELEGATE_END(StringSliceIter); break; } + + case Expression::Id::ResumeId: { + DELEGATE_START(Resume); + DELEGATE_FIELD_TYPE_VECTOR(Resume, sentTypes); + DELEGATE_FIELD_CHILD(Resume, cont); + DELEGATE_FIELD_CHILD_VECTOR(Resume, operands); + DELEGATE_FIELD_SCOPE_NAME_USE_VECTOR(Resume, handlerBlocks); + DELEGATE_FIELD_NAME_KIND_VECTOR(Resume, handlerTags, ModuleItemKind::Tag); + DELEGATE_FIELD_HEAPTYPE(Resume, contType); + DELEGATE_END(Resume); + break; + } } #undef DELEGATE_ID diff --git a/src/wasm-delegations.def b/src/wasm-delegations.def index e486c490fdb..721c56db9e2 100644 --- a/src/wasm-delegations.def +++ b/src/wasm-delegations.def @@ -105,5 +105,6 @@ DELEGATE(StringIterNext); DELEGATE(StringIterMove); DELEGATE(StringSliceWTF); DELEGATE(StringSliceIter); +DELEGATE(Resume); #undef DELEGATE diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index eeca7c3ccde..1d9c576bc8b 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2349,6 +2349,7 @@ class ConstantExpressionRunner : public ExpressionRunner { } return ExpressionRunner::visitRefAs(curr); } + Flow visitResume(Resume* curr) { WASM_UNREACHABLE("unimplemented"); } void trap(const char* why) override { throw NonconstantException(); } @@ -3915,6 +3916,7 @@ class ModuleRunnerBase : public ExpressionRunner { multiValues.pop_back(); return ret; } + Flow visitResume(Resume* curr) { return Flow(NONCONSTANT_FLOW); } void trap(const char* why) override { externalInterface->trap(why); } diff --git a/src/wasm-s-parser.h b/src/wasm-s-parser.h index 849632ceb2d..b8f9d6c6581 100644 --- a/src/wasm-s-parser.h +++ b/src/wasm-s-parser.h @@ -328,6 +328,7 @@ class SExpressionWasmBuilder { Expression* makeStringIterMove(Element& s, StringIterMoveOp op); Expression* makeStringSliceWTF(Element& s, StringSliceWTFOp op); Expression* makeStringSliceIter(Element& s); + Expression* makeResume(Element& s); // Helper functions Type parseBlockType(Element& s, Index& i); diff --git a/src/wasm.h b/src/wasm.h index 8d1587d4223..451039d4511 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -743,6 +743,7 @@ class Expression { StringIterMoveId, StringSliceWTFId, StringSliceIterId, + ResumeId, NumExpressionIds }; Id _id; @@ -1995,6 +1996,32 @@ class StringSliceIter void finalize(); }; +class Resume : public SpecificExpression { +public: + Resume(MixedArena& allocator) + : handlerTags(allocator), handlerBlocks(allocator), operands(allocator), + sentTypes(allocator) {} + + HeapType contType; + ArenaVector handlerTags; + ArenaVector handlerBlocks; + + ExpressionList operands; + Expression* cont; + + // 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; +}; + // Globals struct Named { diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index bf14efeb182..06c6f364827 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -4046,6 +4046,10 @@ BinaryConsts::ASTNodes WasmBinaryReader::readExpression(Expression*& curr) { visitCallRef(call); break; } + case BinaryConsts::Resume: { + visitResume((curr = allocator.alloc())->cast()); + break; + } case BinaryConsts::AtomicPrefix: { code = static_cast(getU32LEB()); if (maybeVisitLoad(curr, code, /*isAtomic=*/true)) { @@ -7758,6 +7762,52 @@ void WasmBinaryReader::visitRefAs(RefAs* curr, uint8_t code) { curr->finalize(); } +void WasmBinaryReader::visitResume(Resume* curr) { + BYN_TRACE("zz node: Resume\n"); + + auto contTypeIndex = getU32LEB(); + curr->contType = getTypeByIndex(contTypeIndex); + if (!curr->contType.isContinuation()) { + throwError("non-continuation type in resume instruction " + + curr->contType.toString()); + } + + 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); + + BYN_TRACE("handler num: " << numHandlers << std::endl); + for (size_t i = 0; i < numHandlers; i++) { + BYN_TRACE("read one tag handler pair \n"); + auto tagIndex = getU32LEB(); + auto tag = getTagName(tagIndex); + + auto handlerIndex = getU32LEB(); + auto handler = getBreakTarget(handlerIndex).name; + + curr->handlerTags[i] = tag; + curr->handlerBlocks[i] = handler; + + // We don't know the final name yet + tagRefs[tagIndex].push_back(&curr->handlerTags[i]); + } + + curr->cont = popNonVoidExpression(); + + auto numArgs = + curr->contType.getContinuation().type.getSignature().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::throwError(std::string text) { throw ParseException(text, 0, pos); } diff --git a/src/wasm/wasm-s-parser.cpp b/src/wasm/wasm-s-parser.cpp index 9dbfc88c2d3..cc8fe22734d 100644 --- a/src/wasm/wasm-s-parser.cpp +++ b/src/wasm/wasm-s-parser.cpp @@ -2988,6 +2988,38 @@ Expression* SExpressionWasmBuilder::makeCallRef(Element& s, bool isReturn) { target, operands, sigType.getSignature().results, isReturn); } +Expression* SExpressionWasmBuilder::makeResume(Element& s) { + auto ret = allocator.alloc(); + + ret->contType = parseHeapType(*s[1]); + if (!ret->contType.isContinuation()) { + throw ParseException("expected continuation type", s[1]->line, s[1]->col); + } + + Index i = 2; + while (i < s.size() && elementStartsWith(*s[i], "tag")) { + Element& inner = *s[i++]; + if (inner.size() < 3) { + throw ParseException("invalid tag block", inner.line, inner.col); + } + Name tag = getTagName(*inner[1]); + if (!wasm.getTagOrNull(tag)) { + throw ParseException("bad tag name", inner[1]->line, inner[1]->col); + } + ret->handlerTags.push_back(tag); + ret->handlerBlocks.push_back(getLabel(*inner[2])); + } + + while (i < s.size() - 1) { + ret->operands.push_back(parseExpression(s[i++])); + } + + ret->cont = parseExpression(s[i]); + + ret->finalize(&wasm); + return ret; +} + Expression* SExpressionWasmBuilder::makeRefI31(Element& s) { auto ret = allocator.alloc(); ret->value = parseExpression(s[1]); diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 3b14b3c35ba..6a2fd8468c5 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2488,6 +2488,18 @@ void BinaryInstWriter::visitStringSliceIter(StringSliceIter* curr) { << U32LEB(BinaryConsts::StringViewIterSlice); } +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++) { + o << U32LEB(parent.getTagIndex(curr->handlerTags[i])) + << U32LEB(getBreakIndex(curr->handlerBlocks[i])); + } +} + void BinaryInstWriter::emitScopeEnd(Expression* curr) { assert(!breakStack.empty()); breakStack.pop_back(); diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 7ea8b0bdb4b..9930010a010 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -487,6 +487,7 @@ struct FunctionValidator : public WalkerPass> { void visitStringIterMove(StringIterMove* curr); void visitStringSliceWTF(StringSliceWTF* curr); void visitStringSliceIter(StringSliceIter* curr); + void visitResume(Resume* curr); void visitFunction(Function* curr); @@ -3285,6 +3286,24 @@ void FunctionValidator::visitStringSliceIter(StringSliceIter* curr) { "string operations require reference-types [--enable-strings]"); } +void FunctionValidator::visitResume(Resume* curr) { + // TODO implement actual type-checking + shouldBeTrue( + !getModule() || getModule()->features.hasTypedContinuations(), + curr, + "resume requires typed-continuatons [--enable-typed-continuations]"); + + shouldBeTrue( + curr->sentTypes.size() == curr->handlerBlocks.size(), + curr, + "sentTypes cache in Resume instruction has not been initialised"); + + shouldBeTrue((curr->contType.isContinuation() && + curr->contType.getContinuation().type.isSignature()), + curr, + "invalid type in Resume expression"); +} + void FunctionValidator::visitFunction(Function* curr) { if (curr->getResults().isTuple()) { shouldBeTrue(getModule()->features.hasMultivalue(), diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index ad44acda47e..06e9de14828 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -17,6 +17,7 @@ #include "wasm.h" #include "ir/branch-utils.h" #include "wasm-traversal.h" +#include "wasm-type.h" namespace wasm { @@ -1350,6 +1351,55 @@ void StringSliceIter::finalize() { } } +static void populateResumeSentTypes(Resume* 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 Resume::finalize(Module* wasm) { + const Signature& contSig = + this->contType.getContinuation().type.getSignature(); + type = contSig.results; + + populateResumeSentTypes(this, wasm); +} + 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 c3f3b5fa60d..ae3f60e18b0 100644 --- a/src/wasm2js.h +++ b/src/wasm2js.h @@ -2428,6 +2428,11 @@ Ref Wasm2JSBuilder::processFunctionBody(Module* m, WASM_UNREACHABLE("unimp"); } + Ref visitResume(Resume* curr) { + unimplemented(curr); + WASM_UNREACHABLE("unimp"); + } + private: Ref makePointer(Expression* ptr, Address offset) { auto ret = visit(ptr, EXPRESSION_RESULT); diff --git a/test/lit/basic/typed_continuations_resume.wast b/test/lit/basic/typed_continuations_resume.wast new file mode 100644 index 00000000000..e96c9b4f5d9 --- /dev/null +++ b/test/lit/basic/typed_continuations_resume.wast @@ -0,0 +1,163 @@ +;; 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-BINARY: (type $ft (func (param i32) (result i32))) + ;; 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-BINARY: (type $ct (cont $ft)) + ;; CHECK-TEXT: (type $ct (cont $ft)) + ;; CHECK-BIN: (type $ct (cont $ft)) + (type $ct (cont $ft)) + ;; CHECK-BINARY: (type $2 (func (result i32))) + + ;; CHECK-BINARY: (type $3 (func (param (ref $ct)) (result i32))) + + ;; CHECK-BINARY: (tag $t (result i32)) + ;; CHECK-TEXT: (type $2 (func (result i32 (ref $ct)))) + + ;; CHECK-TEXT: (type $3 (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 (ref $ct)) (result i32))) + + ;; CHECK-BIN: (tag $t (param i32) (result i32)) + (tag $t (param i32) (result i32)) + + ;; CHECK-BINARY: (func $go (type $3) (param $x (ref $ct)) (result i32) + ;; CHECK-BINARY-NEXT: (drop + ;; CHECK-BINARY-NEXT: (block $label$1 (result (ref $ct)) + ;; CHECK-BINARY-NEXT: (return + ;; CHECK-BINARY-NEXT: (resume $ct (tag $t $label$1) + ;; CHECK-BINARY-NEXT: (i32.const 123) + ;; CHECK-BINARY-NEXT: (local.get $x) + ;; CHECK-BINARY-NEXT: ) + ;; CHECK-BINARY-NEXT: ) + ;; CHECK-BINARY-NEXT: ) + ;; CHECK-BINARY-NEXT: ) + ;; CHECK-BINARY-NEXT: (i32.const 123) + ;; CHECK-BINARY-NEXT: ) + ;; CHECK-TEXT: (func $go (type $3) (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 $ct (tag $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 $3) (param $x (ref $ct)) (result i32) + ;; CHECK-BIN-NEXT: (local $1 (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 $ct (tag $t $label$1) + ;; 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: (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 $ct + (tag $t $handler) + (i32.const 123) + (local.get $x) + ) + ) + ) + ) + ) +) +;; CHECK-NODEBUG: (type $0 (func (param i32) (result i32))) + +;; CHECK-NODEBUG: (type $1 (cont $0)) + +;; CHECK-NODEBUG: (type $2 (func (result i32))) + +;; CHECK-NODEBUG: (type $3 (func (param (ref $1)) (result i32))) + +;; CHECK-NODEBUG: (tag $tag$0 (result i32)) + +;; CHECK-NODEBUG: (func $0 (type $3) (param $0 (ref $1)) (result i32) +;; CHECK-NODEBUG-NEXT: (drop +;; CHECK-NODEBUG-NEXT: (block $label$1 (result (ref $1)) +;; CHECK-NODEBUG-NEXT: (return +;; CHECK-NODEBUG-NEXT: (resume $1 (tag $tag$0 $label$1) +;; CHECK-NODEBUG-NEXT: (i32.const 123) +;; CHECK-NODEBUG-NEXT: (local.get $0) +;; CHECK-NODEBUG-NEXT: ) +;; CHECK-NODEBUG-NEXT: ) +;; CHECK-NODEBUG-NEXT: ) +;; CHECK-NODEBUG-NEXT: ) +;; CHECK-NODEBUG-NEXT: (i32.const 123) +;; CHECK-NODEBUG-NEXT: ) +;; 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 (ref $1)) (result i32))) + +;; CHECK-BIN-NODEBUG: (tag $tag$0 (param i32) (result i32)) + +;; CHECK-BIN-NODEBUG: (func $0 (type $3) (param $0 (ref $1)) (result i32) +;; CHECK-BIN-NODEBUG-NEXT: (local $1 (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 $1 (tag $tag$0 $label$1) +;; 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: (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: )