Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Typed continuations: resume instructions #6083

Merged
merged 27 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f9ab9d4
[Typed continuations] resume instruction
frank-emrich Oct 3, 2023
2a932d3
Merge remote-tracking branch 'upstream/main' into wasmfx-resume
frank-emrich Nov 14, 2023
e96014a
dummy implementation for TypeGeneralizing pass
frank-emrich Nov 14, 2023
6b8787d
new implementation of operateOnScopeNameUsesAndSentTypes
frank-emrich Nov 14, 2023
a3cc291
OverriddenScopeNameUseVisitor
frank-emrich Nov 14, 2023
dfbb193
pass nullptr to func in operateOnScopeNameUsesAndSentValues
frank-emrich Nov 15, 2023
ef0a683
resume instructions have implicitTrap effect
frank-emrich Nov 15, 2023
53e40d4
move comment on own line
frank-emrich Nov 15, 2023
8f85434
manually remove (tag ...) lines erroneously added by updater script
frank-emrich Nov 15, 2023
b475268
experiment: move SentTypesVisitor outside of lambda
frank-emrich Nov 15, 2023
3c1906e
combine both experiments
frank-emrich Nov 15, 2023
6ab20ad
give up on OverriddenScopeNameUseVisitor
frank-emrich Nov 15, 2023
3556171
add typed_continuations_resume.wast to fuzzer ignore list
frank-emrich Nov 16, 2023
bc86090
remove visitTry, visitRethrow from SentTypesVisitor
frank-emrich Nov 21, 2023
97d54f7
Merge remote-tracking branch 'upstream/main' into wasmfx-resume
frank-emrich Nov 21, 2023
cf17036
dummy implementation of SubtypingDiscoverer::visitResume
frank-emrich Nov 21, 2023
db5b02a
Merge remote-tracking branch 'upstream/main' into wasmfx-resume
frank-emrich Dec 13, 2023
115d0e2
when printing resume instructions, keep tag clauses on same line
frank-emrich Dec 13, 2023
143bb2d
remove SentTypesVisitor in favor of simple loop
frank-emrich Dec 23, 2023
6749e08
printing: move as much into PrintExpressionContents as possible
frank-emrich Jan 7, 2024
17be964
Merge remote-tracking branch 'upstream/main' into wasmfx-resume
frank-emrich Jan 7, 2024
c262d3c
rephrase comment
frank-emrich Jan 7, 2024
fbcb7fb
Make Resume class more consistent with TryTable
frank-emrich Jan 7, 2024
06a26c1
include Resume's sentTypes field in field delegations
frank-emrich Jan 7, 2024
d41256d
address minor nits
frank-emrich Jan 8, 2024
ab6ff8b
move invariant checks to FunctionValidator::visitResume
frank-emrich Jan 8, 2024
179faa4
move & update typed_continuations_resume.wast
frank-emrich Jan 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions scripts/fuzz_opt.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
]
Expand Down
2 changes: 2 additions & 0 deletions scripts/gen-s-parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)"),
Expand Down
9 changes: 9 additions & 0 deletions src/gen-s-parser.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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':
Expand Down Expand Up @@ -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':
Expand Down
2 changes: 2 additions & 0 deletions src/ir/ReFinalize.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"); }
Expand Down
11 changes: 11 additions & 0 deletions src/ir/branch-utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}
Expand Down
5 changes: 5 additions & 0 deletions src/ir/cost.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
13 changes: 13 additions & 0 deletions src/ir/effects.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this because we're modeling suspending as throwing? Probably worth a comment if so.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was just inspired by the following happening at the end of visitCall here :

// When EH is enabled, any call can throw.
if (parent.features.hasExceptionHandling() && parent.tryDepth == 0) {
    parent.throws_ = true;
}

I'm not sure if it's really necessary for Resume. @aheejin would you mind having a look?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can a resume instruction throw an exception? If not, we can remove this. (Calls can, which is why they have it.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, if the resumed continuation throws, it will bubble out from the resume instruction, just like an exception from a called function would.

parent.throws_ = true;
}
}
};

public:
Expand Down
2 changes: 2 additions & 0 deletions src/ir/module-utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down
5 changes: 5 additions & 0 deletions src/ir/possible-contents.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions src/ir/subtype-exprs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"); }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should just note that the body is a subtype of the resume's type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think It's slightly more complicated than that: We may also need to report subtyping relations induced by values being sent to handler blocks: If a resume instruction has a (tag $t $h) clause, then the n param types of $t must match the first n result types of block $h. I guess this means that we would have to record these relationships here and skipped it so far.

};

} // namespace wasm
Expand Down
5 changes: 5 additions & 0 deletions src/parser/parsers.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -1782,6 +1783,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
// =======
Expand Down
36 changes: 36 additions & 0 deletions src/passes/Print.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/passes/TypeGeneralizing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>> {
Expand Down
5 changes: 5 additions & 0 deletions src/wasm-binary.h
Original file line number Diff line number Diff line change
Expand Up @@ -1296,6 +1296,10 @@ enum ASTNodes {
StringEncodeLossyUTF8Array = 0xb6,
StringEncodeWTF8Array = 0xb7,
StringNewUTF8ArrayTry = 0xb8,

// typed continuation opcodes
Resume = 0xe3,

};

enum MemoryAccess {
Expand Down Expand Up @@ -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);

Expand Down
15 changes: 15 additions & 0 deletions src/wasm-builder.h
Original file line number Diff line number Diff line change
Expand Up @@ -1197,6 +1197,21 @@ class Builder {
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) {
Expand Down
12 changes: 12 additions & 0 deletions src/wasm-delegations-fields.def
Original file line number Diff line number Diff line change
Expand Up @@ -945,6 +945,18 @@ switch (DELEGATE_ID) {
DELEGATE_END(StringSliceIter);
break;
}

case Expression::Id::ResumeId: {
DELEGATE_START(Resume);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems we need to add sentTypes here as well. See #6181 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

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
Expand Down
1 change: 1 addition & 0 deletions src/wasm-delegations.def
Original file line number Diff line number Diff line change
Expand Up @@ -105,5 +105,6 @@ DELEGATE(StringIterNext);
DELEGATE(StringIterMove);
DELEGATE(StringSliceWTF);
DELEGATE(StringSliceIter);
DELEGATE(Resume);

#undef DELEGATE
2 changes: 2 additions & 0 deletions src/wasm-interpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -2349,6 +2349,7 @@ class ConstantExpressionRunner : public ExpressionRunner<SubType> {
}
return ExpressionRunner<SubType>::visitRefAs(curr);
}
Flow visitResume(Resume* curr) { WASM_UNREACHABLE("unimplemented"); }

void trap(const char* why) override { throw NonconstantException(); }

Expand Down Expand Up @@ -3915,6 +3916,7 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
multiValues.pop_back();
return ret;
}
Flow visitResume(Resume* curr) { return Flow(NONCONSTANT_FLOW); }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be WASM_UNREACHABLE("unimplemented"). NONCONSTANT_FLOW is only used in the ExpressionRunner up above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


void trap(const char* why) override { externalInterface->trap(why); }

Expand Down
1 change: 1 addition & 0 deletions src/wasm-s-parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
27 changes: 27 additions & 0 deletions src/wasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,7 @@ class Expression {
StringIterMoveId,
StringSliceWTFId,
StringSliceIterId,
ResumeId,
NumExpressionIds
};
Id _id;
Expand Down Expand Up @@ -1995,6 +1996,32 @@ class StringSliceIter
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 {
Expand Down
Loading
Loading