Skip to content

[Stack Switching] Add basic support for resume/suspend in the interpreter #7771

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

Merged
merged 128 commits into from
Aug 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
128 commits
Select commit Hold shift + click to select a range
4ccb81c
wohoo
kripken Jul 25, 2025
9646f9a
exc
kripken Jul 25, 2025
ddee5c4
faster
kripken Jul 25, 2025
ed3b506
hope
kripken Jul 25, 2025
7e6b8b8
Merge remote-tracking branch 'origin/main' into resumey
kripken Jul 25, 2025
2cf8b9d
field
kripken Jul 25, 2025
3d009f7
start
kripken Jul 25, 2025
2aa78ee
work
kripken Jul 25, 2025
80a805d
work
kripken Jul 25, 2025
2ec86fd
add continutation literal
kripken Jul 25, 2025
9d8566d
work
kripken Jul 25, 2025
06d9a65
work
kripken Jul 25, 2025
62700c5
work
kripken Jul 25, 2025
3aa2ee5
work
kripken Jul 25, 2025
ff216af
work
kripken Jul 25, 2025
2de4b00
work
kripken Jul 26, 2025
346848a
typo
kripken Jul 28, 2025
2bc653b
simple debug
kripken Jul 28, 2025
f3485eb
format
kripken Jul 28, 2025
7d68e24
work
kripken Jul 28, 2025
0eb3346
work
kripken Jul 28, 2025
e0367c6
work
kripken Jul 28, 2025
77b1d2d
work
kripken Jul 28, 2025
86fc6a3
work
kripken Jul 28, 2025
9a9bf7c
work
kripken Jul 28, 2025
20a59c0
work
kripken Jul 28, 2025
9dd600f
test
kripken Jul 28, 2025
b98eaa8
work
kripken Jul 28, 2025
e1cf473
work
kripken Jul 28, 2025
08ad388
work
kripken Jul 28, 2025
32bddbe
work
kripken Jul 28, 2025
1dc1bbb
work
kripken Jul 28, 2025
ffbf718
work
kripken Jul 28, 2025
54e6db1
work
kripken Jul 28, 2025
0752685
work
kripken Jul 28, 2025
83f26a2
work
kripken Jul 28, 2025
7dc4655
work
kripken Jul 28, 2025
91ea6d8
work
kripken Jul 28, 2025
696c5ab
work
kripken Jul 28, 2025
040831f
work
kripken Jul 28, 2025
e950b6e
work
kripken Jul 28, 2025
db8b502
work
kripken Jul 28, 2025
124739a
work
kripken Jul 28, 2025
6aba061
work
kripken Jul 28, 2025
8cfb369
work
kripken Jul 28, 2025
4231a84
work
kripken Jul 28, 2025
9b04a61
work
kripken Jul 28, 2025
387f7b6
work
kripken Jul 29, 2025
bcf11cd
work
kripken Jul 29, 2025
d31ebff
work
kripken Jul 29, 2025
5c10344
work
kripken Jul 29, 2025
9e13c0e
work
kripken Jul 29, 2025
41c0962
work
kripken Jul 29, 2025
d9f19e7
work
kripken Jul 29, 2025
48decde
work
kripken Jul 29, 2025
8469d00
work
kripken Jul 29, 2025
1a51fb5
work
kripken Jul 29, 2025
679d87f
work
kripken Jul 29, 2025
1449843
work
kripken Jul 29, 2025
e12d49c
work
kripken Jul 29, 2025
41709ba
work
kripken Jul 29, 2025
8b5b990
work
kripken Jul 29, 2025
04eccec
format
kripken Jul 29, 2025
e652e83
work
kripken Jul 29, 2025
2523441
Revert "work"
kripken Jul 29, 2025
25ab4ce
work
kripken Jul 29, 2025
377ccb4
format
kripken Jul 29, 2025
ddadbfe
work
kripken Jul 29, 2025
6360d03
work
kripken Jul 29, 2025
193e091
work
kripken Jul 29, 2025
f147ce6
work
kripken Jul 29, 2025
3436d6e
work
kripken Jul 29, 2025
e2ed33e
work
kripken Jul 29, 2025
9f6f06b
work
kripken Jul 29, 2025
1c2d8c4
Merge remote-tracking branch 'origin/main' into resumey
kripken Jul 29, 2025
1c5798e
fixen
kripken Jul 29, 2025
be3a0a5
work
kripken Jul 29, 2025
7a3f570
work
kripken Jul 29, 2025
83f1dff
work
kripken Jul 29, 2025
a95ce74
work
kripken Jul 30, 2025
5132664
work
kripken Jul 30, 2025
5bfc1e3
work
kripken Jul 30, 2025
266cc45
work
kripken Jul 30, 2025
e557e56
work
kripken Jul 30, 2025
1ed07ed
work
kripken Jul 30, 2025
b155afb
work
kripken Jul 30, 2025
9970b07
work
kripken Jul 30, 2025
b835ddc
work
kripken Jul 30, 2025
79d2689
work
kripken Jul 30, 2025
19b2b4e
work
kripken Jul 30, 2025
73b6d7e
work
kripken Jul 30, 2025
8289194
work
kripken Jul 30, 2025
3123fd7
work
kripken Jul 30, 2025
c7e87e3
work
kripken Jul 30, 2025
889e949
work
kripken Jul 30, 2025
518e91d
work
kripken Jul 30, 2025
9da6670
work
kripken Jul 30, 2025
6c64aca
format
kripken Jul 30, 2025
a14e0bc
work
kripken Jul 30, 2025
1d6a405
work
kripken Jul 30, 2025
f83baa9
work
kripken Jul 30, 2025
2d12c6a
work
kripken Jul 30, 2025
b0e4805
work
kripken Jul 30, 2025
62bf5d0
work
kripken Jul 30, 2025
b353b3f
work
kripken Jul 30, 2025
3809704
work
kripken Jul 30, 2025
f96c3a8
work
kripken Jul 30, 2025
cb6fe67
work
kripken Jul 30, 2025
2991f8c
work
kripken Jul 30, 2025
c9ba2a9
work
kripken Jul 30, 2025
37b6c75
work
kripken Jul 30, 2025
9155b9c
safe
kripken Jul 30, 2025
d14259e
work
kripken Jul 30, 2025
c9589d9
work
kripken Jul 30, 2025
39164a3
work
kripken Jul 30, 2025
2cff4a3
work
kripken Jul 30, 2025
b823e9a
work
kripken Jul 30, 2025
9460649
work
kripken Jul 30, 2025
f1069e8
format
kripken Jul 30, 2025
39edc5c
Update src/wasm-interpreter.h
kripken Jul 31, 2025
fe9d157
feedback: comment
kripken Jul 31, 2025
c143604
Update src/wasm-interpreter.h
kripken Jul 31, 2025
8c7680e
todo
kripken Jul 31, 2025
9269042
Merge remote-tracking branch 'myself/resumey' into resumey
kripken Jul 31, 2025
021d90b
comment to clarify
kripken Jul 31, 2025
6ee86b7
fully support assert_suspend
kripken Aug 1, 2025
96f6521
feedback on tests
kripken Aug 1, 2025
2d1dbe4
add todos
kripken Aug 1, 2025
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
2 changes: 2 additions & 0 deletions scripts/test/fuzzing.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@
'precompute-stack-switching.wast',
'unsubtyping-stack-switching.wast',
'vacuum-stack-switching.wast',
'cont.wast',
'cont_simple.wast',
# TODO: fuzzer support for custom descriptors
'remove-unused-module-elements-refs-descriptors.wast',
'custom-descriptors.wast',
Expand Down
2 changes: 1 addition & 1 deletion src/ir/iteration.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ namespace wasm {
// In general, it is preferable not to use this class and to directly access the
// children (using e.g. iff->ifTrue etc.), as that is faster. However, in cases
// where speed does not matter, this can be convenient. TODO: reimplement these
// to avoid materializing all the chilren at once.
// to avoid materializing all the children at once.
//
// ChildIterator - Iterates over all children
//
Expand Down
6 changes: 6 additions & 0 deletions src/literal.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ namespace wasm {
class Literals;
struct GCData;
struct ExnData;
struct ContData;

class Literal {
// store only integers, whose bits are deterministic. floats
Expand Down Expand Up @@ -63,6 +64,8 @@ class Literal {
std::shared_ptr<GCData> gcData;
// A reference to Exn data.
std::shared_ptr<ExnData> exnData;
// A reference to a Continuation.
std::shared_ptr<ContData> contData;
};

public:
Expand Down Expand Up @@ -93,6 +96,7 @@ class Literal {
}
explicit Literal(std::shared_ptr<GCData> gcData, HeapType type);
explicit Literal(std::shared_ptr<ExnData> exnData);
explicit Literal(std::shared_ptr<ContData> contData);
explicit Literal(std::string_view string);
Literal(const Literal& other);
Literal& operator=(const Literal& other);
Expand All @@ -105,6 +109,7 @@ class Literal {
// a null or i31). This includes structs, arrays, and also strings.
bool isData() const { return type.isData(); }
bool isExn() const { return type.isExn(); }
bool isContinuation() const { return type.isContinuation(); }
bool isString() const { return type.isString(); }

bool isNull() const { return type.isNull(); }
Expand Down Expand Up @@ -312,6 +317,7 @@ class Literal {
}
std::shared_ptr<GCData> getGCData() const;
std::shared_ptr<ExnData> getExnData() const;
std::shared_ptr<ContData> getContData() const;

// careful!
int32_t* geti32Ptr() {
Expand Down
23 changes: 23 additions & 0 deletions src/parser/wast-parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,25 @@ MaybeResult<Assertion> assertTrap(Lexer& in) {
return Assertion{AssertModule{ModuleAssertionType::Trap, *mod}};
}

// (assert_suspension action msg)
MaybeResult<Assertion> assertSuspension(Lexer& in) {
if (!in.takeSExprStart("assert_suspension"sv)) {
return {};
}
if (auto a = maybeAction(in)) {
CHECK_ERR(a);
auto msg = in.takeString();
if (!msg) {
return in.err("expected error message");
}
if (!in.takeRParen()) {
return in.err("expected end of assertion");
}
return Assertion{AssertAction{ActionAssertionType::Suspension, *a}};
}
return in.err("invalid assert_suspension");
}

MaybeResult<Assertion> assertion(Lexer& in) {
if (auto a = assertReturn(in)) {
CHECK_ERR(a);
Expand All @@ -402,6 +421,10 @@ MaybeResult<Assertion> assertion(Lexer& in) {
CHECK_ERR(a);
return *a;
}
if (auto a = assertSuspension(in)) {
CHECK_ERR(a);
return *a;
}
return {};
}

Expand Down
2 changes: 1 addition & 1 deletion src/parser/wat-parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ struct AssertReturn {
ExpectedResults expected;
};

enum class ActionAssertionType { Trap, Exhaustion, Exception };
enum class ActionAssertionType { Trap, Exhaustion, Exception, Suspension };

struct AssertAction {
ActionAssertionType type;
Expand Down
8 changes: 6 additions & 2 deletions src/shell-interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,9 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface {
std::cout << "exit()\n";
throw ExitException();
} else if (auto* inst = getImportInstance(import)) {
return inst->callExport(import->base, arguments);
auto flow = inst->callExport(import->base, arguments);
assert(!flow.suspendTag); // TODO: support stack switching on calls
return flow.values;
}
Fatal() << "callImport: unknown import: " << import->module.str << "."
<< import->name.str;
Expand Down Expand Up @@ -191,7 +193,9 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface {
if (func->imported()) {
return callImport(func, arguments);
} else {
return instance.callFunction(func->name, arguments);
auto flow = instance.callFunction(func->name, arguments);
assert(!flow.suspendTag); // TODO: support stack switching on calls
return flow.values;
}
}

Expand Down
11 changes: 9 additions & 2 deletions src/tools/execution-results.h
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,9 @@ struct LoggingExternalInterface : public ShellExternalInterface {
}

// Call the function.
return instance->callFunction(func->name, arguments);
auto flow = instance->callFunction(func->name, arguments);
assert(!flow.suspendTag);
return flow.values;
}

void setModuleRunner(ModuleRunner* instance_) { instance = instance_; }
Expand Down Expand Up @@ -471,7 +473,12 @@ struct ExecutionResults {
}
arguments.push_back(Literal::makeZero(param));
}
return instance.callFunction(func->name, arguments);
auto flow = instance.callFunction(func->name, arguments);
if (flow.suspendTag) { // TODO: support stack switching here
std::cout << "[exception thrown: unhandled suspend]" << std::endl;
return Exception{};
}
return flow.values;
} catch (const TrapException&) {
return Trap{};
} catch (const WasmException& e) {
Expand Down
6 changes: 5 additions & 1 deletion src/tools/wasm-ctor-eval.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,11 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface {
targetFunc.toString());
}
if (!func->imported()) {
return instance.callFunction(targetFunc, arguments);
auto flow = instance.callFunction(targetFunc, arguments);
if (flow.suspendTag) {
throw FailToEvalException("unhandled suspend");
}
return flow.values;
} else {
throw FailToEvalException(
std::string("callTable on imported function: ") +
Expand Down
23 changes: 20 additions & 3 deletions src/tools/wasm-shell.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,12 @@ struct Shell {
struct TrapResult {};
struct HostLimitResult {};
struct ExceptionResult {};
using ActionResult =
std::variant<Literals, TrapResult, HostLimitResult, ExceptionResult>;
struct SuspensionResult {};
using ActionResult = std::variant<Literals,
TrapResult,
HostLimitResult,
ExceptionResult,
SuspensionResult>;

std::string resultToString(ActionResult& result) {
if (std::get_if<TrapResult>(&result)) {
Expand All @@ -196,6 +200,8 @@ struct Shell {
return "exceeded host limit";
} else if (std::get_if<ExceptionResult>(&result)) {
return "exception";
} else if (std::get_if<SuspensionResult>(&result)) {
return "suspension";
} else if (auto* vals = std::get_if<Literals>(&result)) {
std::stringstream ss;
ss << *vals;
Expand All @@ -213,8 +219,9 @@ struct Shell {
return TrapResult{};
}
auto& instance = it->second;
Flow flow;
try {
return instance->callExport(invoke->name, invoke->args);
flow = instance->callExport(invoke->name, invoke->args);
} catch (TrapException&) {
return TrapResult{};
} catch (HostLimitException&) {
Expand All @@ -224,6 +231,10 @@ struct Shell {
} catch (...) {
WASM_UNREACHABLE("unexpected error");
}
if (flow.suspendTag) {
return SuspensionResult{};
}
return flow.values;
} else if (auto* get = std::get_if<GetAction>(&act)) {
auto it = instances.find(get->base ? *get->base : lastModule);
if (it == instances.end()) {
Expand Down Expand Up @@ -390,6 +401,12 @@ struct Shell {
}
err << "expected exception";
break;
case ActionAssertionType::Suspension:
if (std::get_if<SuspensionResult>(&result)) {
return Ok{};
}
err << "expected suspension";
break;
}
err << ", got " << resultToString(result);
return Err{err.str()};
Expand Down
Loading
Loading