summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xscripts/fuzz_opt.py1
-rwxr-xr-xscripts/gen-s-parser.py2
-rw-r--r--src/gen-s-parser.inc9
-rw-r--r--src/ir/ReFinalize.cpp2
-rw-r--r--src/ir/branch-utils.h11
-rw-r--r--src/ir/cost.h5
-rw-r--r--src/ir/effects.h13
-rw-r--r--src/ir/module-utils.cpp2
-rw-r--r--src/ir/possible-contents.cpp5
-rw-r--r--src/ir/subtype-exprs.h2
-rw-r--r--src/parser/parsers.h5
-rw-r--r--src/passes/Print.cpp36
-rw-r--r--src/passes/TypeGeneralizing.cpp2
-rw-r--r--src/wasm-binary.h5
-rw-r--r--src/wasm-builder.h15
-rw-r--r--src/wasm-delegations-fields.def12
-rw-r--r--src/wasm-delegations.def1
-rw-r--r--src/wasm-interpreter.h2
-rw-r--r--src/wasm-s-parser.h1
-rw-r--r--src/wasm.h27
-rw-r--r--src/wasm/wasm-binary.cpp50
-rw-r--r--src/wasm/wasm-s-parser.cpp32
-rw-r--r--src/wasm/wasm-stack.cpp12
-rw-r--r--src/wasm/wasm-validator.cpp19
-rw-r--r--src/wasm/wasm.cpp50
-rw-r--r--src/wasm2js.h5
-rw-r--r--test/lit/basic/typed_continuations_resume.wast163
27 files changed, 489 insertions, 0 deletions
diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py
index 28cf4eb70..953108a44 100755
--- a/scripts/fuzz_opt.py
+++ b/scripts/fuzz_opt.py
@@ -315,6 +315,7 @@ INITIAL_CONTENTS_IGNORE = [
'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 9d6e58481..d873ca4f6 100755
--- a/scripts/gen-s-parser.py
+++ b/scripts/gen-s-parser.py
@@ -566,6 +566,8 @@ instructions = [
# 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 cef0a6b92..8387dbb8e 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 65f5c4724..0338f630c 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 3c0275701..28e29ede4 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<Resume>()) {
+ 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<Try>() || expr->is<Rethrow>()); // 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<Resume>()) {
+ // 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<Try>() || expr->is<Rethrow>()); // delegate or rethrow
}
diff --git a/src/ir/cost.h b/src/ir/cost.h
index 0e87317a6..821e46524 100644
--- a/src/ir/cost.h
+++ b/src/ir/cost.h
@@ -726,6 +726,11 @@ struct CostAnalyzer : public OverriddenVisitor<CostAnalyzer, CostType> {
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 1156cd823..3ff320a88 100644
--- a/src/ir/effects.h
+++ b/src/ir/effects.h
@@ -973,6 +973,19 @@ private:
// 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 afe4a4c54..304ddb04d 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<ArraySet>()) {
counts.note(set->ref->type);
+ } else if (auto* resume = curr->dynCast<Resume>()) {
+ 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 67b33553d..2f900f0ed 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 86805f88a..65558b430 100644
--- a/src/ir/subtype-exprs.h
+++ b/src/ir/subtype-exprs.h
@@ -333,6 +333,8 @@ struct SubtypingDiscoverer : public OverriddenVisitor<SubType> {
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 1ae5cc080..10894a7cf 100644
--- a/src/parser/parsers.h
+++ b/src/parser/parsers.h
@@ -164,6 +164,7 @@ Result<> makeStringIterMove(Ctx&, Index, StringIterMoveOp op);
template<typename Ctx>
Result<> makeStringSliceWTF(Ctx&, Index, StringSliceWTFOp op);
template<typename Ctx> Result<> makeStringSliceIter(Ctx&, Index);
+template<typename Ctx> Result<> makeResume(Ctx&, Index);
// Modules
template<typename Ctx> MaybeResult<Index> maybeTypeidx(Ctx& ctx);
@@ -1812,6 +1813,10 @@ template<typename Ctx> Result<> makeStringSliceIter(Ctx& ctx, Index pos) {
return ctx.makeStringSliceIter(pos);
}
+template<typename Ctx> 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 b6060a853..948e8d239 100644
--- a/src/passes/Print.cpp
+++ b/src/passes/Print.cpp
@@ -316,6 +316,7 @@ struct PrintSExpression : public UnifiedExpressionVisitor<PrintSExpression> {
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 d167b89a9..293d17cdf 100644
--- a/src/passes/TypeGeneralizing.cpp
+++ b/src/passes/TypeGeneralizing.cpp
@@ -874,6 +874,8 @@ struct TransferFn : OverriddenVisitor<TransferFn> {
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<PostWalker<TypeGeneralizing>> {
diff --git a/src/wasm-binary.h b/src/wasm-binary.h
index 84720865e..2afa29538 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 @@ public:
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 a873a7309..0c3cec5c2 100644
--- a/src/wasm-builder.h
+++ b/src/wasm-builder.h
@@ -1197,6 +1197,21 @@ public:
return ret;
}
+ Resume* makeResume(HeapType contType,
+ const std::vector<Name>& handlerTags,
+ const std::vector<Name>& handlerBlocks,
+ const std::vector<Expression*>& operands,
+ Expression* cont) {
+ auto* ret = wasm.allocator.alloc<Resume>();
+ 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 3f8072445..aeb32e689 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 e486c490f..721c56db9 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 eeca7c3cc..1d9c576bc 100644
--- a/src/wasm-interpreter.h
+++ b/src/wasm-interpreter.h
@@ -2349,6 +2349,7 @@ public:
}
return ExpressionRunner<SubType>::visitRefAs(curr);
}
+ Flow visitResume(Resume* curr) { WASM_UNREACHABLE("unimplemented"); }
void trap(const char* why) override { throw NonconstantException(); }
@@ -3915,6 +3916,7 @@ public:
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 849632ceb..b8f9d6c65 100644
--- a/src/wasm-s-parser.h
+++ b/src/wasm-s-parser.h
@@ -328,6 +328,7 @@ private:
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 8d1587d42..451039d45 100644
--- a/src/wasm.h
+++ b/src/wasm.h
@@ -743,6 +743,7 @@ public:
StringIterMoveId,
StringSliceWTFId,
StringSliceIterId,
+ ResumeId,
NumExpressionIds
};
Id _id;
@@ -1995,6 +1996,32 @@ public:
void finalize();
};
+class Resume : public SpecificExpression<Expression::ResumeId> {
+public:
+ Resume(MixedArena& allocator)
+ : handlerTags(allocator), handlerBlocks(allocator), operands(allocator),
+ sentTypes(allocator) {}
+
+ HeapType contType;
+ ArenaVector<Name> handlerTags;
+ ArenaVector<Name> 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<Type> sentTypes;
+};
+
// Globals
struct Named {
diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp
index bf14efeb1..06c6f3648 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<Resume>())->cast<Resume>());
+ break;
+ }
case BinaryConsts::AtomicPrefix: {
code = static_cast<uint8_t>(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 9dbfc88c2..cc8fe2273 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<Resume>();
+
+ 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<RefI31>();
ret->value = parseExpression(s[1]);
diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp
index 3b14b3c35..6a2fd8468 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 7ea8b0bdb..9930010a0 100644
--- a/src/wasm/wasm-validator.cpp
+++ b/src/wasm/wasm-validator.cpp
@@ -487,6 +487,7 @@ public:
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 ad44acda4..06e9de148 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 c3f3b5fa6..ae3f60e18 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 000000000..e96c9b4f5
--- /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: )