From 4ccb81c2dc3d5e3916ab17de9d1a466dc10a0345 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Jul 2025 13:01:42 -0700 Subject: [PATCH 001/125] wohoo --- src/wasm-interpreter.h | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 701a89199fe..282e50cb154 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -59,7 +59,7 @@ struct NonconstantException {}; // Utilities -extern Name WASM, RETURN_FLOW, RETURN_CALL_FLOW, NONCONSTANT_FLOW; +extern Name RETURN_FLOW, RETURN_CALL_FLOW, NONCONSTANT_FLOW; // Stuff that flows around during executing expressions: a literal, or a change // in control flow. @@ -240,6 +240,10 @@ class ExpressionRunner : public OverriddenVisitor { protected: RelaxedBehavior relaxedBehavior = RelaxedBehavior::NonConstant; + // We save values from visit() until they are consumed, so that we can pause/ + // resume. TODO try-catch needs save/restore too + std::vector valueStack; + public: ExpressionRunner(Module* module = nullptr, Index maxDepth = NO_LIMIT, @@ -255,7 +259,14 @@ class ExpressionRunner : public OverriddenVisitor { if (maxDepth != NO_LIMIT && depth > maxDepth) { hostLimit("interpreter recursion limit"); } + + // Save and restore the value stack around each call: once the visit + // completes, all values have been consumed, and nothing needs to be + // saved. + auto oldValueStackSize = valueStack.size(); auto ret = OverriddenVisitor::visit(curr); + valueStack.resize(oldValueStackSize); + if (!ret.breaking()) { Type type = ret.getType(); if (type.isConcrete() || curr->type.isConcrete()) { @@ -268,6 +279,7 @@ class ExpressionRunner : public OverriddenVisitor { #endif assert(Type::isSubType(type, curr->type)); } + valueStack.push_back(ret.values); } depth--; return ret; From 9646f9accb0c041f0093972fd116ca88424a5233 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Jul 2025 13:10:25 -0700 Subject: [PATCH 002/125] exc --- src/wasm-interpreter.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 282e50cb154..54773a4f94d 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -241,7 +241,7 @@ class ExpressionRunner : public OverriddenVisitor { RelaxedBehavior relaxedBehavior = RelaxedBehavior::NonConstant; // We save values from visit() until they are consumed, so that we can pause/ - // resume. TODO try-catch needs save/restore too + // resume. std::vector valueStack; public: @@ -4522,9 +4522,12 @@ class ModuleRunnerBase : public ExpressionRunner { } Flow visitTry(Try* curr) { NOTE_ENTER("Try"); + // Unwind the value stack when we jump up the call stack. + auto oldValueStackSize = self()->valueStack.size(); try { return self()->visit(curr->body); } catch (const WasmException& e) { + self()->valueStack.resize(oldValueStackSize); // If delegation is in progress and the current try is not the target of // the delegation, don't handle it and just rethrow. if (scope->currDelegateTarget.is()) { @@ -4571,9 +4574,12 @@ class ModuleRunnerBase : public ExpressionRunner { } Flow visitTryTable(TryTable* curr) { NOTE_ENTER("TryTable"); + // Unwind the value stack when we jump up the call stack. + auto oldValueStackSize = self()->valueStack.size(); try { return self()->visit(curr->body); } catch (const WasmException& e) { + self()->valueStack.resize(oldValueStackSize); auto exnData = e.exn.getExnData(); for (size_t i = 0; i < curr->catchTags.size(); i++) { auto catchTag = curr->catchTags[i]; From ddee5c43c0b04d8ce2ddac25230e2e3796ccb545 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Jul 2025 13:18:34 -0700 Subject: [PATCH 003/125] faster --- src/wasm-interpreter.h | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 54773a4f94d..1b8b6d336f6 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -242,13 +242,16 @@ class ExpressionRunner : public OverriddenVisitor { // We save values from visit() until they are consumed, so that we can pause/ // resume. - std::vector valueStack; + std::optional> valueStack; public: ExpressionRunner(Module* module = nullptr, Index maxDepth = NO_LIMIT, Index maxLoopIterations = NO_LIMIT) : module(module), maxDepth(maxDepth), maxLoopIterations(maxLoopIterations) { + if (module && module->features.hasStackSwitching()) { + valueStack.emplace(); + } } virtual ~ExpressionRunner() = default; @@ -263,9 +266,14 @@ class ExpressionRunner : public OverriddenVisitor { // Save and restore the value stack around each call: once the visit // completes, all values have been consumed, and nothing needs to be // saved. - auto oldValueStackSize = valueStack.size(); - auto ret = OverriddenVisitor::visit(curr); - valueStack.resize(oldValueStackSize); + Flow ret; + if (valueStack) { + auto oldValueStackSize = valueStack->size(); + ret = OverriddenVisitor::visit(curr); + valueStack->resize(oldValueStackSize); + } else { + ret = OverriddenVisitor::visit(curr); + } if (!ret.breaking()) { Type type = ret.getType(); @@ -279,7 +287,9 @@ class ExpressionRunner : public OverriddenVisitor { #endif assert(Type::isSubType(type, curr->type)); } - valueStack.push_back(ret.values); + if (valueStack) { + valueStack->push_back(ret.values); + } } depth--; return ret; @@ -4523,11 +4533,13 @@ class ModuleRunnerBase : public ExpressionRunner { Flow visitTry(Try* curr) { NOTE_ENTER("Try"); // Unwind the value stack when we jump up the call stack. - auto oldValueStackSize = self()->valueStack.size(); + auto oldValueStackSize = self()->valueStack ? self()->valueStack->size() : 0; try { return self()->visit(curr->body); } catch (const WasmException& e) { - self()->valueStack.resize(oldValueStackSize); + if (self()->valueStack) { + self()->valueStack->resize(oldValueStackSize); + } // If delegation is in progress and the current try is not the target of // the delegation, don't handle it and just rethrow. if (scope->currDelegateTarget.is()) { @@ -4575,11 +4587,13 @@ class ModuleRunnerBase : public ExpressionRunner { Flow visitTryTable(TryTable* curr) { NOTE_ENTER("TryTable"); // Unwind the value stack when we jump up the call stack. - auto oldValueStackSize = self()->valueStack.size(); + auto oldValueStackSize = self()->valueStack ? self()->valueStack->size() : 0; try { return self()->visit(curr->body); } catch (const WasmException& e) { - self()->valueStack.resize(oldValueStackSize); + if (self()->valueStack) { + self()->valueStack->resize(oldValueStackSize); + } auto exnData = e.exn.getExnData(); for (size_t i = 0; i < curr->catchTags.size(); i++) { auto catchTag = curr->catchTags[i]; From ed3b506271e3edab7e75fa7a857797eaf9df8c54 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Jul 2025 13:50:51 -0700 Subject: [PATCH 004/125] hope --- test/lit/exec/cont.wast | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 test/lit/exec/cont.wast diff --git a/test/lit/exec/cont.wast b/test/lit/exec/cont.wast new file mode 100644 index 00000000000..c445f25436c --- /dev/null +++ b/test/lit/exec/cont.wast @@ -0,0 +1,36 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited. + +;; RUN: wasm-opt %s -all --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s + +(module + (tag $exn) + (tag $e1) + (tag $e2) + + (type $f1 (func)) + (type $k1 (cont $f1)) + + (func $f1 (export "unhandled-1") + (suspend $e1) + ) + + (func (export "unhandled-2") + (resume $k1 (cont.new $k1 (ref.func $f1))) + ) + + (func (export "unhandled-3") + (block $h (result (ref $k1)) + (resume $k1 (on $e2 $h) (cont.new $k1 (ref.func $f1))) + (unreachable) + ) + (drop) + ) + + (func (export "handled") + (block $h (result (ref $k1)) + (resume $k1 (on $e1 $h) (cont.new $k1 (ref.func $f1))) + (unreachable) + ) + (drop) + ) +) From 2cf8b9d9fa264daf6823400c13bde54bad38280a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Jul 2025 14:11:57 -0700 Subject: [PATCH 005/125] field --- src/wasm-interpreter.h | 8 +++++++- src/wasm/wasm.cpp | 3 ++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 1b8b6d336f6..19ee1c0cdf4 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -59,7 +59,7 @@ struct NonconstantException {}; // Utilities -extern Name RETURN_FLOW, RETURN_CALL_FLOW, NONCONSTANT_FLOW; +extern Name RETURN_FLOW, RETURN_CALL_FLOW, NONCONSTANT_FLOW, SUSPEND_FLOW; // Stuff that flows around during executing expressions: a literal, or a change // in control flow. @@ -73,9 +73,15 @@ class Flow { Flow(Name breakTo, Literal value) : values{value}, breakTo(breakTo) {} Flow(Name breakTo, Literals&& values) : values(std::move(values)), breakTo(breakTo) {} + Flow(Name breakTo, Name suspendTag, Literals&& values) + : values(std::move(values)), breakTo(breakTo), suspendTag(suspendTag) { + assert(breakTo == SUSPEND_FLOW); + } Literals values; Name breakTo; // if non-null, a break is going on + Name suspendTag; // if non-null, breakTo must be SUSPEND_FLOW, and this is the + // tag being suspended // A helper function for the common case where there is only one value const Literal& getSingleValue() { diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index ee28c14f97d..5f5afdc0da3 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -24,10 +24,11 @@ namespace wasm { // shared constants -Name WASM("wasm"); +//Name WASM("wasm"); Name RETURN_FLOW("*return:)*"); Name RETURN_CALL_FLOW("*return-call:)*"); Name NONCONSTANT_FLOW("*nonconstant:)*"); +Name SUSPEND_FLOW("*suspend:)*"); namespace BinaryConsts::CustomSections { From 3d009f7f823f4fd3a7d51ff77b4de729143a3876 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Jul 2025 14:26:16 -0700 Subject: [PATCH 006/125] start --- src/shell-interface.h | 4 +++- src/tools/execution-results.h | 11 +++++++++-- src/tools/wasm-ctor-eval.cpp | 6 +++++- src/wasm-interpreter.h | 22 +++++++++++++++++----- 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/shell-interface.h b/src/shell-interface.h index 3a8b6d23314..28f492f8d32 100644 --- a/src/shell-interface.h +++ b/src/shell-interface.h @@ -191,7 +191,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); + return flow.values; } } diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index 03e9ccb47aa..796bc1a4e29 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -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_; } @@ -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) { + std::cout << "[exception thrown: unhandled suspend]" << std::endl; + return Exception{}; + } + return flow.values; } catch (const TrapException&) { return Trap{}; } catch (const WasmException& e) { diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index c7ed64cc1f5..c92b815eb6c 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -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: ") + diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 19ee1c0cdf4..36ad73d21aa 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -3196,7 +3196,12 @@ class ModuleRunnerBase : public ExpressionRunner { if (!export_ || export_->kind != ExternalKind::Function) { externalInterface->trap("callExport not found"); } - return callFunction(*export_->getInternalName(), arguments); + auto flow = callFunction(*export_->getInternalName(), arguments); + if (flow.suspendTag) { + // TODO: allow suspending through exports; return Flow from this func + externalInterface->trap("unhandled suspend"); + } + return flow.values; } Literals callExport(Name name) { return callExport(name, Literals()); } @@ -4639,7 +4644,14 @@ class ModuleRunnerBase : public ExpressionRunner { } Flow visitContNew(ContNew* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitContBind(ContBind* curr) { return Flow(NONCONSTANT_FLOW); } - Flow visitSuspend(Suspend* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitSuspend(Suspend* curr) { + Literals arguments; + Flow flow = self()->generateArguments(curr->operands, arguments); + if (flow.breaking()) { + return flow; + } + return Flow(SUSPEND_FLOW, curr->tag, std::move(arguments)); + } Flow visitResume(Resume* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitResumeThrow(ResumeThrow* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitStackSwitch(StackSwitch* curr) { return Flow(NONCONSTANT_FLOW); } @@ -4692,7 +4704,7 @@ class ModuleRunnerBase : public ExpressionRunner { return value; } - Literals callFunction(Name name, Literals arguments) { + Flow callFunction(Name name, Literals arguments) { if (callDepth > maxDepth) { hostLimit("stack limit"); } @@ -4749,7 +4761,7 @@ class ModuleRunnerBase : public ExpressionRunner { } // cannot still be breaking, it means we missed our stop - assert(!flow.breaking() || flow.breakTo == RETURN_FLOW); + assert(!flow.breaking() || flow.breakTo == RETURN_FLOW || flow.breakTo == SUSPEND_FLOW); auto type = flow.getType(); if (!Type::isSubType(type, *resultType)) { std::cerr << "calling " << name << " resulted in " << type @@ -4757,7 +4769,7 @@ class ModuleRunnerBase : public ExpressionRunner { WASM_UNREACHABLE("unexpected result type"); } - return flow.values; + return flow; } // The maximum call stack depth to evaluate into. From 2aa78ee9f8e2db5e0f1f48cde3d5e49aaae5d1fb Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Jul 2025 14:40:21 -0700 Subject: [PATCH 007/125] work --- src/wasm-interpreter.h | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 36ad73d21aa..4194314a9c1 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4652,7 +4652,24 @@ class ModuleRunnerBase : public ExpressionRunner { } return Flow(SUSPEND_FLOW, curr->tag, std::move(arguments)); } - Flow visitResume(Resume* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitResume(Resume* curr) { + auto flow = self()->visit(curr->cont); + if (flow.suspendTag) { + // See if a suspension arrived that we support. + for (size_t i = 0; i < curr->handlerTags.size(); i++) { + auto handlerTag = curr->handlerTags[i]; + if (handlerTag == flow.suspendTag) { + // Switch the flow from suspending to branching, and keep sending the + // same values (which include the tag values + a new continuation at + // the end, so we have nothing to add here). + flow.suspendTag = Name(); + flow.breakTo = curr->handlerBlocks[i]; + return flow; + } + } + } + return flow; + } Flow visitResumeThrow(ResumeThrow* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitStackSwitch(StackSwitch* curr) { return Flow(NONCONSTANT_FLOW); } From 80a805dc351b6f8076e6f2ae0e0acc608575970e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Jul 2025 14:55:13 -0700 Subject: [PATCH 008/125] work --- src/wasm-interpreter.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 4194314a9c1..cf0f141f11c 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -113,6 +113,9 @@ class Flow { } o << flow.values[i]; } + if (flow.suspendTag) { + o << " [suspend:" << flow.suspendTag << ']'; + } o << "})"; return o; } From 2ec86fd3bfa94cbd51d358643480a775528619d2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Jul 2025 15:10:23 -0700 Subject: [PATCH 009/125] add continutation literal --- src/literal.h | 19 +++++++++++++++++++ src/wasm-interpreter.h | 6 ++++-- src/wasm/literal.cpp | 18 ++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/literal.h b/src/literal.h index 10f2158a403..09fb514972d 100644 --- a/src/literal.h +++ b/src/literal.h @@ -33,6 +33,7 @@ namespace wasm { class Literals; struct GCData; struct ExnData; +struct ContData; class Literal { // store only integers, whose bits are deterministic. floats @@ -63,6 +64,8 @@ class Literal { std::shared_ptr gcData; // A reference to Exn data. std::shared_ptr exnData; + // A reference to a Continuation. + std::shared_ptr contData; }; public: @@ -93,6 +96,7 @@ class Literal { } explicit Literal(std::shared_ptr gcData, HeapType type); explicit Literal(std::shared_ptr exnData); + explicit Literal(std::shared_ptr contData); explicit Literal(std::string_view string); Literal(const Literal& other); Literal& operator=(const Literal& other); @@ -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(); } @@ -312,6 +317,7 @@ class Literal { } std::shared_ptr getGCData() const; std::shared_ptr getExnData() const; + std::shared_ptr getContData() const; // careful! int32_t* geti32Ptr() { @@ -796,6 +802,19 @@ struct ExnData { ExnData(Name tag, Literals payload) : tag(tag), payload(payload) {} }; +// The data of a (ref cont) literal. +struct ContData { + // The function this continuation begins in. + // TODO: handle cross-module calls using something other than a Name here. + Name func; + + // Information about how to resume execution, a list of instruction and data + // that we "replay" into the value and call stacks. + Literals resumeInfo; + + ContData(Name func, Literals resumeInfo) : func(func), resumeInfo(resumeInfo) {} +}; + } // namespace wasm namespace std { diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index cf0f141f11c..f2ca6f38e07 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4645,7 +4645,9 @@ class ModuleRunnerBase : public ExpressionRunner { multiValues.pop_back(); return ret; } - Flow visitContNew(ContNew* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitContNew(ContNew* curr) { + + } Flow visitContBind(ContBind* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitSuspend(Suspend* curr) { Literals arguments; @@ -4664,7 +4666,7 @@ class ModuleRunnerBase : public ExpressionRunner { if (handlerTag == flow.suspendTag) { // Switch the flow from suspending to branching, and keep sending the // same values (which include the tag values + a new continuation at - // the end, so we have nothing to add here). + // the end, so we have nothing to add here). // TODO: doc on Flow flow.suspendTag = Name(); flow.breakTo = curr->handlerBlocks[i]; return flow; diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp index c9a1f29be25..22727a68604 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -91,6 +91,12 @@ Literal::Literal(std::shared_ptr exnData) assert(exnData); } +Literal::Literal(std::shared_ptr contData) + : contData(contData), type(HeapType::exn, NonNullable) { + // The data must not be null. + assert(contData); +} + Literal::Literal(std::string_view string) : gcData(nullptr), type(Type(HeapType::string, NonNullable)) { // TODO: we could in theory internalize strings @@ -140,6 +146,10 @@ Literal::Literal(const Literal& other) : type(other.type) { func = other.func; return; } + if (type.isContinuation()) { + new (&contData) std::shared_ptr(other.contData); + return; + } switch (heapType.getBasic(Unshared)) { case HeapType::i31: i32 = other.i32; @@ -182,6 +192,8 @@ Literal::~Literal() { gcData.~shared_ptr(); } else if (isExn()) { exnData.~shared_ptr(); + } else if (isContinuation()) { + contData.~shared_ptr(); } } @@ -337,6 +349,12 @@ std::shared_ptr Literal::getExnData() const { return exnData; } +std::shared_ptr Literal::getContData() const { + assert(isExn()); + assert(contData); + return contData; +} + Literal Literal::castToF32() { assert(type == Type::i32); Literal ret(Type::f32); From 9d8566dc4b10de34e5fdda42007157daa359808b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Jul 2025 15:15:30 -0700 Subject: [PATCH 010/125] work --- src/wasm-interpreter.h | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index f2ca6f38e07..2ed11e946ff 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4646,7 +4646,21 @@ class ModuleRunnerBase : public ExpressionRunner { return ret; } Flow visitContNew(ContNew* curr) { - + Literals arguments; + Flow flow = self()->generateArguments(curr->operands, arguments); + if (flow.breaking()) { + return flow; + } + auto contFlow = self()->visit(curr->cont); + if (contFlow.breaking()) { + return contFlow; + } + if (auto* refFunc = contFlow->getSingleValue()->dynCast()) { + // The initial data is empty, as nothing has executed yet, so we don't + // need any information about how to resume (we resume by just running). + return std::make_shared(refFunc->func, {}); + } + trap("non-function in cont.new"); } Flow visitContBind(ContBind* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitSuspend(Suspend* curr) { From 06d9a651d7cb980ce5ed0edee5d94d3877194bd9 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Jul 2025 15:17:53 -0700 Subject: [PATCH 011/125] work --- src/wasm-interpreter.h | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 2ed11e946ff..f6a161ee825 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4646,23 +4646,25 @@ class ModuleRunnerBase : public ExpressionRunner { return ret; } Flow visitContNew(ContNew* curr) { + auto funcFlow = self()->visit(curr->func); + if (funcFlow.breaking()) { + return funcFlow; + } + auto* refFunc = funcFlow->getSingleValue()->cast(); + // The initial data is empty, as nothing has executed yet, so we don't + // need any information about how to resume (we resume by just running). + return std::make_shared(refFunc->func, {}); + } + Flow visitContBind(ContBind* curr) { + return Flow(NONCONSTANT_FLOW); +/* Literals arguments; Flow flow = self()->generateArguments(curr->operands, arguments); if (flow.breaking()) { return flow; } - auto contFlow = self()->visit(curr->cont); - if (contFlow.breaking()) { - return contFlow; - } - if (auto* refFunc = contFlow->getSingleValue()->dynCast()) { - // The initial data is empty, as nothing has executed yet, so we don't - // need any information about how to resume (we resume by just running). - return std::make_shared(refFunc->func, {}); - } - trap("non-function in cont.new"); +*/ } - Flow visitContBind(ContBind* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitSuspend(Suspend* curr) { Literals arguments; Flow flow = self()->generateArguments(curr->operands, arguments); From 62700c52da109c528a8be1484e6cec6838664d5c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Jul 2025 15:20:21 -0700 Subject: [PATCH 012/125] work --- src/wasm-interpreter.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index f6a161ee825..e9c99f02274 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4650,10 +4650,9 @@ class ModuleRunnerBase : public ExpressionRunner { if (funcFlow.breaking()) { return funcFlow; } - auto* refFunc = funcFlow->getSingleValue()->cast(); // The initial data is empty, as nothing has executed yet, so we don't // need any information about how to resume (we resume by just running). - return std::make_shared(refFunc->func, {}); + return std::make_shared(funcFlow.getSingleValue().getFunc(), {}); } Flow visitContBind(ContBind* curr) { return Flow(NONCONSTANT_FLOW); From 3aa2ee58668eb9c1c0a3856091e870e8522cd6e5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Jul 2025 15:22:45 -0700 Subject: [PATCH 013/125] work --- src/wasm-interpreter.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index e9c99f02274..95932c7c8bc 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4650,9 +4650,10 @@ class ModuleRunnerBase : public ExpressionRunner { if (funcFlow.breaking()) { return funcFlow; } + Name func = funcFlow.getSingleValue().getFunc(); // The initial data is empty, as nothing has executed yet, so we don't // need any information about how to resume (we resume by just running). - return std::make_shared(funcFlow.getSingleValue().getFunc(), {}); + return Literal(std::make_shared(func, Literals{})); } Flow visitContBind(ContBind* curr) { return Flow(NONCONSTANT_FLOW); From ff216af1cef238fcaca6c56555b0133ae91d4410 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Jul 2025 16:59:19 -0700 Subject: [PATCH 014/125] work --- src/literal.h | 5 ++++- src/wasm-interpreter.h | 4 +++- src/wasm/literal.cpp | 4 +--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/literal.h b/src/literal.h index 09fb514972d..9c0c4f69a27 100644 --- a/src/literal.h +++ b/src/literal.h @@ -812,7 +812,10 @@ struct ContData { // that we "replay" into the value and call stacks. Literals resumeInfo; - ContData(Name func, Literals resumeInfo) : func(func), resumeInfo(resumeInfo) {} + // The continuation type. + HeapType type; + + ContData(Name func, Literals resumeInfo, HeapType type) : func(func), resumeInfo(resumeInfo), type(type) {} }; } // namespace wasm diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 95932c7c8bc..bb30ac36a2d 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4653,7 +4653,7 @@ class ModuleRunnerBase : public ExpressionRunner { Name func = funcFlow.getSingleValue().getFunc(); // The initial data is empty, as nothing has executed yet, so we don't // need any information about how to resume (we resume by just running). - return Literal(std::make_shared(func, Literals{})); + return Literal(std::make_shared(func, Literals{}, curr->type.getHeapType())); } Flow visitContBind(ContBind* curr) { return Flow(NONCONSTANT_FLOW); @@ -4683,6 +4683,8 @@ class ModuleRunnerBase : public ExpressionRunner { // Switch the flow from suspending to branching, and keep sending the // same values (which include the tag values + a new continuation at // the end, so we have nothing to add here). // TODO: doc on Flow + // TODO: callTable is tricky, as table might change, so like in + // Asyncify, need to save funcref. flow.suspendTag = Name(); flow.breakTo = curr->handlerBlocks[i]; return flow; diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp index 22727a68604..e7dbd5cf342 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -92,9 +92,7 @@ Literal::Literal(std::shared_ptr exnData) } Literal::Literal(std::shared_ptr contData) - : contData(contData), type(HeapType::exn, NonNullable) { - // The data must not be null. - assert(contData); + : contData(contData), type(contData->type, NonNullable, Exact) { } Literal::Literal(std::string_view string) From 2de4b004c4fb1145defc50612a58a5070cf8e2b1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Jul 2025 17:07:01 -0700 Subject: [PATCH 015/125] work --- src/wasm-interpreter.h | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index bb30ac36a2d..ebd93d731b2 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -97,6 +97,8 @@ class Flow { return builder.makeConstantExpression(values); } + // Returns true if we are breaking out of normal execution. This can be + // because of a break/continue, or a continuation. bool breaking() const { return breakTo.is(); } void clearIf(Name target) { @@ -4675,6 +4677,9 @@ class ModuleRunnerBase : public ExpressionRunner { } Flow visitResume(Resume* curr) { auto flow = self()->visit(curr->cont); + +// XXX it should RESUME EXECUTION!!!!!!!! + if (flow.suspendTag) { // See if a suspension arrived that we support. for (size_t i = 0; i < curr->handlerTags.size(); i++) { @@ -4690,8 +4695,13 @@ class ModuleRunnerBase : public ExpressionRunner { return flow; } } + // No handler worked out, keep propagating. + return flow; } - return flow; + if (flow.breaking()) { + return flow; + } + return Flow(); } Flow visitResumeThrow(ResumeThrow* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitStackSwitch(StackSwitch* curr) { return Flow(NONCONSTANT_FLOW); } From 346848a94d1ef8db54c706926eade3ca075b9d3d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 28 Jul 2025 12:36:54 -0700 Subject: [PATCH 016/125] typo --- src/ir/iteration.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir/iteration.h b/src/ir/iteration.h index bfbf3bb6263..00f2a7383e4 100644 --- a/src/ir/iteration.h +++ b/src/ir/iteration.h @@ -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 // From 2bc653bc052578827eb9c82befb794575e7ef16c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 28 Jul 2025 13:00:54 -0700 Subject: [PATCH 017/125] simple debug --- src/wasm-interpreter.h | 302 ++++------------------------------ src/wasm/wasm-interpreter.cpp | 17 -- 2 files changed, 34 insertions(+), 285 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index ebd93d731b2..c812d9c4e12 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -123,55 +123,6 @@ class Flow { } }; -// Debugging helpers -#ifdef WASM_INTERPRETER_DEBUG -class Indenter { - static int indentLevel; - - const char* entryName; - -public: - Indenter(const char* entry); - ~Indenter(); - - static void print(); -}; - -#define NOTE_ENTER(x) \ - Indenter _int_blah(x); \ - { \ - Indenter::print(); \ - std::cout << "visit " << x << " : " << curr << "\n"; \ - } -#define NOTE_ENTER_(x) \ - Indenter _int_blah(x); \ - { \ - Indenter::print(); \ - std::cout << "visit " << x << "\n"; \ - } -#define NOTE_NAME(p0) \ - { \ - Indenter::print(); \ - std::cout << "name " << '(' << Name(p0) << ")\n"; \ - } -#define NOTE_EVAL1(p0) \ - { \ - Indenter::print(); \ - std::cout << "eval " #p0 " (" << p0 << ")\n"; \ - } -#define NOTE_EVAL2(p0, p1) \ - { \ - Indenter::print(); \ - std::cout << "eval " #p0 " (" << p0 << "), " #p1 " (" << p1 << ")\n"; \ - } -#else // WASM_INTERPRETER_DEBUG -#define NOTE_ENTER(x) -#define NOTE_ENTER_(x) -#define NOTE_NAME(p0) -#define NOTE_EVAL1(p0) -#define NOTE_EVAL2(p0, p1) -#endif // WASM_INTERPRETER_DEBUG - // Execute an expression template class ExpressionRunner : public OverriddenVisitor { @@ -190,14 +141,12 @@ class ExpressionRunner : public OverriddenVisitor { Index maxLoopIterations; Flow generateArguments(const ExpressionList& operands, Literals& arguments) { - NOTE_ENTER_("generateArguments"); arguments.reserve(operands.size()); for (auto expression : operands) { Flow flow = self()->visit(expression); if (flow.breaking()) { return flow; } - NOTE_EVAL1(flow.values); arguments.push_back(flow.getSingleValue()); } return Flow(); @@ -255,6 +204,16 @@ class ExpressionRunner : public OverriddenVisitor { // resume. std::optional> valueStack; +#if WASM_INTERPRETER_DEBUG + std::string indent() { + std::string ret; + for (Index i = 0; i < depth; i++) { + ret += ' '; + } + return ret; + } +#endif + public: ExpressionRunner(Module* module = nullptr, Index maxDepth = NO_LIMIT, @@ -269,6 +228,10 @@ class ExpressionRunner : public OverriddenVisitor { void setRelaxedBehavior(RelaxedBehavior value) { relaxedBehavior = value; } Flow visit(Expression* curr) { +#if WASM_INTERPRETER_DEBUG + std::cout << indent() << "visit(" << getExpressionName(curr) << ")\n"; +#endif + depth++; if (maxDepth != NO_LIMIT && depth > maxDepth) { hostLimit("interpreter recursion limit"); @@ -289,20 +252,20 @@ class ExpressionRunner : public OverriddenVisitor { if (!ret.breaking()) { Type type = ret.getType(); if (type.isConcrete() || curr->type.isConcrete()) { -#if 1 // def WASM_INTERPRETER_DEBUG if (!Type::isSubType(type, curr->type)) { - std::cerr << "expected " << ModuleType(*module, curr->type) + Fatal() << "expected " << ModuleType(*module, curr->type) << ", seeing " << ModuleType(*module, type) << " from\n" << ModuleExpression(*module, curr) << '\n'; } -#endif - assert(Type::isSubType(type, curr->type)); } if (valueStack) { valueStack->push_back(ret.values); } } depth--; +#if WASM_INTERPRETER_DEBUG + std::cout << indent() << "=> returning: " << ret << '\n'; +#endif return ret; } @@ -310,7 +273,6 @@ class ExpressionRunner : public OverriddenVisitor { Module* getModule() { return module; } Flow visitBlock(Block* curr) { - NOTE_ENTER("Block"); // special-case Block, because Block nesting (in their first element) can be // incredibly deep std::vector stack; @@ -344,12 +306,10 @@ class ExpressionRunner : public OverriddenVisitor { return flow; } Flow visitIf(If* curr) { - NOTE_ENTER("If"); Flow flow = visit(curr->condition); if (flow.breaking()) { return flow; } - NOTE_EVAL1(flow.values); if (flow.getSingleValue().geti32()) { Flow flow = visit(curr->ifTrue); if (!flow.breaking() && !curr->ifFalse) { @@ -363,7 +323,6 @@ class ExpressionRunner : public OverriddenVisitor { return Flow(); } Flow visitLoop(Loop* curr) { - NOTE_ENTER("Loop"); Index loopCount = 0; while (1) { Flow flow = visit(curr->body); @@ -381,7 +340,6 @@ class ExpressionRunner : public OverriddenVisitor { } } Flow visitBreak(Break* curr) { - NOTE_ENTER("Break"); bool condition = true; Flow flow; if (curr->value) { @@ -404,7 +362,6 @@ class ExpressionRunner : public OverriddenVisitor { return flow; } Flow visitSwitch(Switch* curr) { - NOTE_ENTER("Switch"); Flow flow; Literals values; if (curr->value) { @@ -429,8 +386,6 @@ class ExpressionRunner : public OverriddenVisitor { } Flow visitConst(Const* curr) { - NOTE_ENTER("Const"); - NOTE_EVAL1(curr->value); return Flow(curr->value); // heh } @@ -438,13 +393,11 @@ class ExpressionRunner : public OverriddenVisitor { // delegate to the Literal::* methods, except we handle traps here. Flow visitUnary(Unary* curr) { - NOTE_ENTER("Unary"); Flow flow = visit(curr->value); if (flow.breaking()) { return flow; } Literal value = flow.getSingleValue(); - NOTE_EVAL1(value); switch (curr->op) { case ClzInt32: case ClzInt64: @@ -721,7 +674,6 @@ class ExpressionRunner : public OverriddenVisitor { WASM_UNREACHABLE("invalid op"); } Flow visitBinary(Binary* curr) { - NOTE_ENTER("Binary"); Flow flow = visit(curr->left); if (flow.breaking()) { return flow; @@ -732,7 +684,6 @@ class ExpressionRunner : public OverriddenVisitor { return flow; } Literal right = flow.getSingleValue(); - NOTE_EVAL2(left, right); assert(curr->left->type.isConcrete() ? left.type == curr->left->type : true); assert(curr->right->type.isConcrete() ? right.type == curr->right->type @@ -1214,7 +1165,6 @@ class ExpressionRunner : public OverriddenVisitor { WASM_UNREACHABLE("invalid op"); } Flow visitSIMDExtract(SIMDExtract* curr) { - NOTE_ENTER("SIMDExtract"); Flow flow = self()->visit(curr->vec); if (flow.breaking()) { return flow; @@ -1243,7 +1193,6 @@ class ExpressionRunner : public OverriddenVisitor { WASM_UNREACHABLE("invalid op"); } Flow visitSIMDReplace(SIMDReplace* curr) { - NOTE_ENTER("SIMDReplace"); Flow flow = self()->visit(curr->vec); if (flow.breaking()) { return flow; @@ -1273,7 +1222,6 @@ class ExpressionRunner : public OverriddenVisitor { WASM_UNREACHABLE("invalid op"); } Flow visitSIMDShuffle(SIMDShuffle* curr) { - NOTE_ENTER("SIMDShuffle"); Flow flow = self()->visit(curr->left); if (flow.breaking()) { return flow; @@ -1287,7 +1235,6 @@ class ExpressionRunner : public OverriddenVisitor { return left.shuffleV8x16(right, curr->mask); } Flow visitSIMDTernary(SIMDTernary* curr) { - NOTE_ENTER("SIMDBitselect"); Flow flow = self()->visit(curr->a); if (flow.breaking()) { return flow; @@ -1350,7 +1297,6 @@ class ExpressionRunner : public OverriddenVisitor { WASM_UNREACHABLE("invalid op"); } Flow visitSIMDShift(SIMDShift* curr) { - NOTE_ENTER("SIMDShift"); Flow flow = self()->visit(curr->vec); if (flow.breaking()) { return flow; @@ -1390,7 +1336,6 @@ class ExpressionRunner : public OverriddenVisitor { WASM_UNREACHABLE("invalid op"); } Flow visitSelect(Select* curr) { - NOTE_ENTER("Select"); Flow ifTrue = visit(curr->ifTrue); if (ifTrue.breaking()) { return ifTrue; @@ -1403,11 +1348,9 @@ class ExpressionRunner : public OverriddenVisitor { if (condition.breaking()) { return condition; } - NOTE_EVAL1(condition.getSingleValue()); return condition.getSingleValue().geti32() ? ifTrue : ifFalse; // ;-) } Flow visitDrop(Drop* curr) { - NOTE_ENTER("Drop"); Flow value = visit(curr->value); if (value.breaking()) { return value; @@ -1415,24 +1358,20 @@ class ExpressionRunner : public OverriddenVisitor { return Flow(); } Flow visitReturn(Return* curr) { - NOTE_ENTER("Return"); Flow flow; if (curr->value) { flow = visit(curr->value); if (flow.breaking()) { return flow; } - NOTE_EVAL1(flow.getSingleValue()); } flow.breakTo = RETURN_FLOW; return flow; } Flow visitNop(Nop* curr) { - NOTE_ENTER("Nop"); return Flow(); } Flow visitUnreachable(Unreachable* curr) { - NOTE_ENTER("Unreachable"); trap("unreachable"); WASM_UNREACHABLE("unreachable"); } @@ -1499,11 +1438,9 @@ class ExpressionRunner : public OverriddenVisitor { Flow visitAtomicFence(AtomicFence* curr) { // Wasm currently supports only sequentially consistent atomics, in which // case atomic_fence can be lowered to nothing. - NOTE_ENTER("AtomicFence"); return Flow(); } Flow visitTupleMake(TupleMake* curr) { - NOTE_ENTER("tuple.make"); Literals arguments; Flow flow = generateArguments(curr->operands, arguments); if (flow.breaking()) { @@ -1516,7 +1453,6 @@ class ExpressionRunner : public OverriddenVisitor { return flow; } Flow visitTupleExtract(TupleExtract* curr) { - NOTE_ENTER("tuple.extract"); Flow flow = visit(curr->tuple); if (flow.breaking()) { return flow; @@ -1552,26 +1488,20 @@ class ExpressionRunner : public OverriddenVisitor { Flow visitPop(Pop* curr) { WASM_UNREACHABLE("unimp"); } Flow visitCallRef(CallRef* curr) { WASM_UNREACHABLE("unimp"); } Flow visitRefNull(RefNull* curr) { - NOTE_ENTER("RefNull"); return Literal::makeNull(curr->type.getHeapType()); } Flow visitRefIsNull(RefIsNull* curr) { - NOTE_ENTER("RefIsNull"); Flow flow = visit(curr->value); if (flow.breaking()) { return flow; } const auto& value = flow.getSingleValue(); - NOTE_EVAL1(value); return Literal(int32_t(value.isNull())); } Flow visitRefFunc(RefFunc* curr) { - NOTE_ENTER("RefFunc"); - NOTE_NAME(curr->func); return Literal::makeFunc(curr->func, curr->type.getHeapType()); } Flow visitRefEq(RefEq* curr) { - NOTE_ENTER("RefEq"); Flow flow = visit(curr->left); if (flow.breaking()) { return flow; @@ -1582,7 +1512,6 @@ class ExpressionRunner : public OverriddenVisitor { return flow; } auto right = flow.getSingleValue(); - NOTE_EVAL2(left, right); return Literal(int32_t(left == right)); } Flow visitTableGet(TableGet* curr) { WASM_UNREACHABLE("unimp"); } @@ -1596,25 +1525,21 @@ class ExpressionRunner : public OverriddenVisitor { Flow visitTry(Try* curr) { WASM_UNREACHABLE("unimp"); } Flow visitTryTable(TryTable* curr) { WASM_UNREACHABLE("unimp"); } Flow visitThrow(Throw* curr) { - NOTE_ENTER("Throw"); Literals arguments; Flow flow = generateArguments(curr->operands, arguments); if (flow.breaking()) { return flow; } - NOTE_EVAL1(curr->tag); throwException(WasmException{makeExnData(curr->tag, arguments)}); WASM_UNREACHABLE("throw"); } Flow visitRethrow(Rethrow* curr) { WASM_UNREACHABLE("unimp"); } Flow visitThrowRef(ThrowRef* curr) { - NOTE_ENTER("ThrowRef"); Flow flow = visit(curr->exnref); if (flow.breaking()) { return flow; } const auto& exnref = flow.getSingleValue(); - NOTE_EVAL1(exnref); if (exnref.isNull()) { trap("null ref"); } @@ -1623,24 +1548,20 @@ class ExpressionRunner : public OverriddenVisitor { WASM_UNREACHABLE("throw"); } Flow visitRefI31(RefI31* curr) { - NOTE_ENTER("RefI31"); Flow flow = visit(curr->value); if (flow.breaking()) { return flow; } const auto& value = flow.getSingleValue(); - NOTE_EVAL1(value); return Literal::makeI31(value.geti32(), curr->type.getHeapType().getShared()); } Flow visitI31Get(I31Get* curr) { - NOTE_ENTER("I31Get"); Flow flow = visit(curr->i31); if (flow.breaking()) { return flow; } const auto& value = flow.getSingleValue(); - NOTE_EVAL1(value); if (value.isNull()) { trap("null ref"); } @@ -1720,7 +1641,6 @@ class ExpressionRunner : public OverriddenVisitor { } Flow visitRefTest(RefTest* curr) { - NOTE_ENTER("RefTest"); auto cast = doCast(curr); if (auto* breaking = cast.getBreaking()) { return *breaking; @@ -1729,7 +1649,6 @@ class ExpressionRunner : public OverriddenVisitor { } } Flow visitRefCast(RefCast* curr) { - NOTE_ENTER("RefCast"); auto cast = curr->desc ? doDescCast(curr) : doCast(curr); if (auto* breaking = cast.getBreaking()) { return *breaking; @@ -1741,7 +1660,6 @@ class ExpressionRunner : public OverriddenVisitor { WASM_UNREACHABLE("unreachable"); } Flow visitRefGetDesc(RefGetDesc* curr) { - NOTE_ENTER("RefGetDesc"); Flow ref = self()->visit(curr->ref); if (ref.breaking()) { return ref; @@ -1753,7 +1671,6 @@ class ExpressionRunner : public OverriddenVisitor { return data->desc; } Flow visitBrOn(BrOn* curr) { - NOTE_ENTER("BrOn"); // BrOnCast* uses the casting infrastructure, so handle them first. switch (curr->op) { case BrOnCast: @@ -1787,7 +1704,6 @@ class ExpressionRunner : public OverriddenVisitor { return flow; } const auto& value = flow.getSingleValue(); - NOTE_EVAL1(value); if (curr->op == BrOnNull) { // BrOnNull does not propagate the value if it takes the branch. if (value.isNull()) { @@ -1808,7 +1724,6 @@ class ExpressionRunner : public OverriddenVisitor { WASM_UNREACHABLE("unexpected op"); } Flow visitStructNew(StructNew* curr) { - NOTE_ENTER("StructNew"); if (curr->type == Type::unreachable) { // We cannot proceed to compute the heap type, as there isn't one. Just // find why we are unreachable, and stop there. @@ -1854,7 +1769,6 @@ class ExpressionRunner : public OverriddenVisitor { return makeGCData(std::move(data), curr->type, desc.getSingleValue()); } Flow visitStructGet(StructGet* curr) { - NOTE_ENTER("StructGet"); Flow ref = self()->visit(curr->ref); if (ref.breaking()) { return ref; @@ -1867,7 +1781,6 @@ class ExpressionRunner : public OverriddenVisitor { return extendForPacking(data->values[curr->index], field, curr->signed_); } Flow visitStructSet(StructSet* curr) { - NOTE_ENTER("StructSet"); Flow ref = self()->visit(curr->ref); if (ref.breaking()) { return ref; @@ -1887,7 +1800,6 @@ class ExpressionRunner : public OverriddenVisitor { } Flow visitStructRMW(StructRMW* curr) { - NOTE_ENTER("StructRMW"); Flow ref = self()->visit(curr->ref); if (ref.breaking()) { return ref; @@ -1927,7 +1839,6 @@ class ExpressionRunner : public OverriddenVisitor { } Flow visitStructCmpxchg(StructCmpxchg* curr) { - NOTE_ENTER("StructCmpxchg"); Flow ref = self()->visit(curr->ref); if (ref.breaking()) { return ref; @@ -1959,7 +1870,6 @@ class ExpressionRunner : public OverriddenVisitor { static const Index DataLimit = (1 << 30) / sizeof(Literal); Flow visitArrayNew(ArrayNew* curr) { - NOTE_ENTER("ArrayNew"); Flow init; if (!curr->isWithDefault()) { init = self()->visit(curr->init); @@ -2002,7 +1912,6 @@ class ExpressionRunner : public OverriddenVisitor { Flow visitArrayNewData(ArrayNewData* curr) { WASM_UNREACHABLE("unimp"); } Flow visitArrayNewElem(ArrayNewElem* curr) { WASM_UNREACHABLE("unimp"); } Flow visitArrayNewFixed(ArrayNewFixed* curr) { - NOTE_ENTER("ArrayNewFixed"); Index num = curr->values.size(); if (num >= DataLimit) { hostLimit("allocation failure"); @@ -2031,7 +1940,6 @@ class ExpressionRunner : public OverriddenVisitor { return makeGCData(std::move(data), curr->type); } Flow visitArrayGet(ArrayGet* curr) { - NOTE_ENTER("ArrayGet"); Flow ref = self()->visit(curr->ref); if (ref.breaking()) { return ref; @@ -2052,7 +1960,6 @@ class ExpressionRunner : public OverriddenVisitor { return extendForPacking(data->values[i], field, curr->signed_); } Flow visitArraySet(ArraySet* curr) { - NOTE_ENTER("ArraySet"); Flow ref = self()->visit(curr->ref); if (ref.breaking()) { return ref; @@ -2078,7 +1985,6 @@ class ExpressionRunner : public OverriddenVisitor { return Flow(); } Flow visitArrayLen(ArrayLen* curr) { - NOTE_ENTER("ArrayLen"); Flow ref = self()->visit(curr->ref); if (ref.breaking()) { return ref; @@ -2090,7 +1996,6 @@ class ExpressionRunner : public OverriddenVisitor { return Literal(int32_t(data->values.size())); } Flow visitArrayCopy(ArrayCopy* curr) { - NOTE_ENTER("ArrayCopy"); Flow destRef = self()->visit(curr->destRef); if (destRef.breaking()) { return destRef; @@ -2139,7 +2044,6 @@ class ExpressionRunner : public OverriddenVisitor { return Flow(); } Flow visitArrayFill(ArrayFill* curr) { - NOTE_ENTER("ArrayFill"); Flow ref = self()->visit(curr->ref); if (ref.breaking()) { return ref; @@ -2180,7 +2084,6 @@ class ExpressionRunner : public OverriddenVisitor { Flow visitArrayInitData(ArrayInitData* curr) { WASM_UNREACHABLE("unimp"); } Flow visitArrayInitElem(ArrayInitElem* curr) { WASM_UNREACHABLE("unimp"); } Flow visitArrayRMW(ArrayRMW* curr) { - NOTE_ENTER("ArrayRMW"); Flow ref = self()->visit(curr->ref); if (ref.breaking()) { return ref; @@ -2225,7 +2128,6 @@ class ExpressionRunner : public OverriddenVisitor { } Flow visitArrayCmpxchg(ArrayCmpxchg* curr) { - NOTE_ENTER("ArrayCmpxchg"); Flow ref = self()->visit(curr->ref); if (ref.breaking()) { return ref; @@ -2255,13 +2157,11 @@ class ExpressionRunner : public OverriddenVisitor { return oldVal; } Flow visitRefAs(RefAs* curr) { - NOTE_ENTER("RefAs"); Flow flow = visit(curr->value); if (flow.breaking()) { return flow; } const auto& value = flow.getSingleValue(); - NOTE_EVAL1(value); switch (curr->op) { case RefAsNonNull: if (value.isNull()) { @@ -2346,7 +2246,6 @@ class ExpressionRunner : public OverriddenVisitor { return Literal(int32_t(data->values.size())); } Flow visitStringConcat(StringConcat* curr) { - NOTE_ENTER("StringConcat"); Flow flow = visit(curr->left); if (flow.breaking()) { return flow; @@ -2357,7 +2256,6 @@ class ExpressionRunner : public OverriddenVisitor { return flow; } auto right = flow.getSingleValue(); - NOTE_EVAL2(left, right); auto leftData = left.getGCData(); auto rightData = right.getGCData(); if (!leftData || !rightData) { @@ -2420,7 +2318,6 @@ class ExpressionRunner : public OverriddenVisitor { return Literal(int32_t(strData->values.size())); } Flow visitStringEq(StringEq* curr) { - NOTE_ENTER("StringEq"); Flow flow = visit(curr->left); if (flow.breaking()) { return flow; @@ -2431,7 +2328,6 @@ class ExpressionRunner : public OverriddenVisitor { return flow; } auto right = flow.getSingleValue(); - NOTE_EVAL2(left, right); auto leftData = left.getGCData(); auto rightData = right.getGCData(); int32_t result; @@ -2494,7 +2390,6 @@ class ExpressionRunner : public OverriddenVisitor { return Literal((uint32_t)value.isString()); } Flow visitStringWTF16Get(StringWTF16Get* curr) { - NOTE_ENTER("StringWTF16Get"); Flow ref = visit(curr->ref); if (ref.breaking()) { return ref; @@ -2669,8 +2564,6 @@ class ConstantExpressionRunner : public ExpressionRunner { } Flow visitLocalGet(LocalGet* curr) { - NOTE_ENTER("LocalGet"); - NOTE_EVAL1(curr->index); // Check if a constant value has been set in the context of this runner. auto iter = localValues.find(curr->index); if (iter != localValues.end()) { @@ -2679,8 +2572,6 @@ class ConstantExpressionRunner : public ExpressionRunner { return Flow(NONCONSTANT_FLOW); } Flow visitLocalSet(LocalSet* curr) { - NOTE_ENTER("LocalSet"); - NOTE_EVAL1(curr->index); if (!(flags & FlagValues::PRESERVE_SIDEEFFECTS)) { // If we are evaluating and not replacing the expression, remember the // constant value set, if any, and see if there is a value flowing through @@ -2698,8 +2589,6 @@ class ConstantExpressionRunner : public ExpressionRunner { return Flow(NONCONSTANT_FLOW); } Flow visitGlobalGet(GlobalGet* curr) { - NOTE_ENTER("GlobalGet"); - NOTE_NAME(curr->name); if (this->module != nullptr) { auto* global = this->module->getGlobal(curr->name); // Check if the global has an immutable value anyway @@ -2715,8 +2604,6 @@ class ConstantExpressionRunner : public ExpressionRunner { return Flow(NONCONSTANT_FLOW); } Flow visitGlobalSet(GlobalSet* curr) { - NOTE_ENTER("GlobalSet"); - NOTE_NAME(curr->name); if (!(flags & FlagValues::PRESERVE_SIDEEFFECTS) && this->module != nullptr) { // If we are evaluating and not replacing the expression, remember the @@ -2731,152 +2618,114 @@ class ConstantExpressionRunner : public ExpressionRunner { return Flow(NONCONSTANT_FLOW); } Flow visitCall(Call* curr) { - NOTE_ENTER("Call"); - NOTE_NAME(curr->target); return Flow(NONCONSTANT_FLOW); } Flow visitCallIndirect(CallIndirect* curr) { - NOTE_ENTER("CallIndirect"); return Flow(NONCONSTANT_FLOW); } Flow visitCallRef(CallRef* curr) { - NOTE_ENTER("CallRef"); return Flow(NONCONSTANT_FLOW); } Flow visitTableGet(TableGet* curr) { - NOTE_ENTER("TableGet"); return Flow(NONCONSTANT_FLOW); } Flow visitTableSet(TableSet* curr) { - NOTE_ENTER("TableSet"); return Flow(NONCONSTANT_FLOW); } Flow visitTableSize(TableSize* curr) { - NOTE_ENTER("TableSize"); return Flow(NONCONSTANT_FLOW); } Flow visitTableGrow(TableGrow* curr) { - NOTE_ENTER("TableGrow"); return Flow(NONCONSTANT_FLOW); } Flow visitTableFill(TableFill* curr) { - NOTE_ENTER("TableFill"); return Flow(NONCONSTANT_FLOW); } Flow visitTableCopy(TableCopy* curr) { - NOTE_ENTER("TableCopy"); return Flow(NONCONSTANT_FLOW); } Flow visitTableInit(TableInit* curr) { - NOTE_ENTER("TableInit"); return Flow(NONCONSTANT_FLOW); } Flow visitElemDrop(ElemDrop* curr) { - NOTE_ENTER("ElemDrop"); return Flow(NONCONSTANT_FLOW); } Flow visitLoad(Load* curr) { - NOTE_ENTER("Load"); return Flow(NONCONSTANT_FLOW); } Flow visitStore(Store* curr) { - NOTE_ENTER("Store"); return Flow(NONCONSTANT_FLOW); } Flow visitMemorySize(MemorySize* curr) { - NOTE_ENTER("MemorySize"); return Flow(NONCONSTANT_FLOW); } Flow visitMemoryGrow(MemoryGrow* curr) { - NOTE_ENTER("MemoryGrow"); return Flow(NONCONSTANT_FLOW); } Flow visitMemoryInit(MemoryInit* curr) { - NOTE_ENTER("MemoryInit"); return Flow(NONCONSTANT_FLOW); } Flow visitDataDrop(DataDrop* curr) { - NOTE_ENTER("DataDrop"); return Flow(NONCONSTANT_FLOW); } Flow visitMemoryCopy(MemoryCopy* curr) { - NOTE_ENTER("MemoryCopy"); return Flow(NONCONSTANT_FLOW); } Flow visitMemoryFill(MemoryFill* curr) { - NOTE_ENTER("MemoryFill"); return Flow(NONCONSTANT_FLOW); } Flow visitAtomicRMW(AtomicRMW* curr) { - NOTE_ENTER("AtomicRMW"); return Flow(NONCONSTANT_FLOW); } Flow visitAtomicCmpxchg(AtomicCmpxchg* curr) { - NOTE_ENTER("AtomicCmpxchg"); return Flow(NONCONSTANT_FLOW); } Flow visitAtomicWait(AtomicWait* curr) { - NOTE_ENTER("AtomicWait"); return Flow(NONCONSTANT_FLOW); } Flow visitAtomicNotify(AtomicNotify* curr) { - NOTE_ENTER("AtomicNotify"); return Flow(NONCONSTANT_FLOW); } Flow visitSIMDLoad(SIMDLoad* curr) { - NOTE_ENTER("SIMDLoad"); return Flow(NONCONSTANT_FLOW); } Flow visitSIMDLoadSplat(SIMDLoad* curr) { - NOTE_ENTER("SIMDLoadSplat"); return Flow(NONCONSTANT_FLOW); } Flow visitSIMDLoadExtend(SIMDLoad* curr) { - NOTE_ENTER("SIMDLoadExtend"); return Flow(NONCONSTANT_FLOW); } Flow visitSIMDLoadStoreLane(SIMDLoadStoreLane* curr) { - NOTE_ENTER("SIMDLoadStoreLane"); return Flow(NONCONSTANT_FLOW); } Flow visitArrayNewData(ArrayNewData* curr) { - NOTE_ENTER("ArrayNewData"); return Flow(NONCONSTANT_FLOW); } Flow visitArrayNewElem(ArrayNewElem* curr) { - NOTE_ENTER("ArrayNewElem"); return Flow(NONCONSTANT_FLOW); } Flow visitArrayCopy(ArrayCopy* curr) { - NOTE_ENTER("ArrayCopy"); return Flow(NONCONSTANT_FLOW); } Flow visitArrayFill(ArrayFill* curr) { - NOTE_ENTER("ArrayFill"); return Flow(NONCONSTANT_FLOW); } Flow visitArrayInitData(ArrayInitData* curr) { - NOTE_ENTER("ArrayInitData"); return Flow(NONCONSTANT_FLOW); } Flow visitArrayInitElem(ArrayInitElem* curr) { - NOTE_ENTER("ArrayInitElem"); return Flow(NONCONSTANT_FLOW); } Flow visitPop(Pop* curr) { - NOTE_ENTER("Pop"); return Flow(NONCONSTANT_FLOW); } Flow visitTry(Try* curr) { - NOTE_ENTER("Try"); return Flow(NONCONSTANT_FLOW); } Flow visitTryTable(TryTable* curr) { - NOTE_ENTER("TryTable"); return Flow(NONCONSTANT_FLOW); } Flow visitRethrow(Rethrow* curr) { - NOTE_ENTER("Rethrow"); return Flow(NONCONSTANT_FLOW); } Flow visitRefAs(RefAs* curr) { @@ -2887,27 +2736,21 @@ class ConstantExpressionRunner : public ExpressionRunner { return ExpressionRunner::visitRefAs(curr); } Flow visitContNew(ContNew* curr) { - NOTE_ENTER("ContNew"); return Flow(NONCONSTANT_FLOW); } Flow visitContBind(ContBind* curr) { - NOTE_ENTER("ContBind"); return Flow(NONCONSTANT_FLOW); } Flow visitSuspend(Suspend* curr) { - NOTE_ENTER("Suspend"); return Flow(NONCONSTANT_FLOW); } Flow visitResume(Resume* curr) { - NOTE_ENTER("Resume"); return Flow(NONCONSTANT_FLOW); } Flow visitResumeThrow(ResumeThrow* curr) { - NOTE_ENTER("ResumeThrow"); return Flow(NONCONSTANT_FLOW); } Flow visitStackSwitch(StackSwitch* curr) { - NOTE_ENTER("StackSwitch"); return Flow(NONCONSTANT_FLOW); } @@ -3464,8 +3307,6 @@ class ModuleRunnerBase : public ExpressionRunner { public: Flow visitCall(Call* curr) { - NOTE_ENTER("Call"); - NOTE_NAME(curr->target); Name target = curr->target; Literals arguments; Flow flow = self()->generateArguments(curr->operands, arguments); @@ -3490,14 +3331,14 @@ class ModuleRunnerBase : public ExpressionRunner { } Flow ret = callFunction(target, arguments); -#ifdef WASM_INTERPRETER_DEBUG - std::cout << "(returned to " << scope->function->name << ")\n"; +#if WASM_INTERPRETER_DEBUG + std::cout << self()->indent() << "(returned to " << scope->function->name + << ")\n"; #endif return ret; } Flow visitCallIndirect(CallIndirect* curr) { - NOTE_ENTER("CallIndirect"); Literals arguments; Flow flow = self()->generateArguments(curr->operands, arguments); if (flow.breaking()) { @@ -3524,14 +3365,14 @@ class ModuleRunnerBase : public ExpressionRunner { Flow ret = info.interface()->callTable( info.name, index, curr->heapType, arguments, curr->type, *self()); -#ifdef WASM_INTERPRETER_DEBUG - std::cout << "(returned to " << scope->function->name << ")\n"; +#if WASM_INTERPRETER_DEBUG + std::cout << self()->indent() << "(returned to " << scope->function->name + << ")\n"; #endif return ret; } Flow visitCallRef(CallRef* curr) { - NOTE_ENTER("CallRef"); Literals arguments; Flow flow = self()->generateArguments(curr->operands, arguments); if (flow.breaking()) { @@ -3554,14 +3395,14 @@ class ModuleRunnerBase : public ExpressionRunner { } Flow ret = callFunction(targetRef.getFunc(), arguments); -#ifdef WASM_INTERPRETER_DEBUG - std::cout << "(returned to " << scope->function->name << ")\n"; +#if WASM_INTERPRETER_DEBUG + std::cout << self()->indent() << "(returned to " << scope->function->name + << ")\n"; #endif return ret; } Flow visitTableGet(TableGet* curr) { - NOTE_ENTER("TableGet"); Flow index = self()->visit(curr->index); if (index.breaking()) { return index; @@ -3571,7 +3412,6 @@ class ModuleRunnerBase : public ExpressionRunner { return info.interface()->tableLoad(info.name, address); } Flow visitTableSet(TableSet* curr) { - NOTE_ENTER("TableSet"); Flow index = self()->visit(curr->index); if (index.breaking()) { return index; @@ -3587,7 +3427,6 @@ class ModuleRunnerBase : public ExpressionRunner { } Flow visitTableSize(TableSize* curr) { - NOTE_ENTER("TableSize"); auto info = getTableInstanceInfo(curr->table); auto* table = info.instance->wasm.getTable(info.name); Index tableSize = info.interface()->tableSize(curr->table); @@ -3595,7 +3434,6 @@ class ModuleRunnerBase : public ExpressionRunner { } Flow visitTableGrow(TableGrow* curr) { - NOTE_ENTER("TableGrow"); Flow valueFlow = self()->visit(curr->value); if (valueFlow.breaking()) { return valueFlow; @@ -3629,7 +3467,6 @@ class ModuleRunnerBase : public ExpressionRunner { } Flow visitTableFill(TableFill* curr) { - NOTE_ENTER("TableFill"); Flow destFlow = self()->visit(curr->dest); if (destFlow.breaking()) { return destFlow; @@ -3660,7 +3497,6 @@ class ModuleRunnerBase : public ExpressionRunner { } Flow visitTableCopy(TableCopy* curr) { - NOTE_ENTER("TableCopy"); Flow dest = self()->visit(curr->dest); if (dest.breaking()) { return dest; @@ -3673,9 +3509,6 @@ class ModuleRunnerBase : public ExpressionRunner { if (size.breaking()) { return size; } - NOTE_EVAL1(dest); - NOTE_EVAL1(source); - NOTE_EVAL1(size); Address destVal(dest.getSingleValue().getUnsigned()); Address sourceVal(source.getSingleValue().getUnsigned()); Address sizeVal(size.getSingleValue().getUnsigned()); @@ -3711,7 +3544,6 @@ class ModuleRunnerBase : public ExpressionRunner { } Flow visitTableInit(TableInit* curr) { - NOTE_ENTER("TableInit"); Flow dest = self()->visit(curr->dest); if (dest.breaking()) { return dest; @@ -3724,9 +3556,6 @@ class ModuleRunnerBase : public ExpressionRunner { if (size.breaking()) { return size; } - NOTE_EVAL1(dest); - NOTE_EVAL1(offset); - NOTE_EVAL1(size); auto* segment = wasm.getElementSegment(curr->segment); @@ -3765,53 +3594,40 @@ class ModuleRunnerBase : public ExpressionRunner { } Flow visitLocalGet(LocalGet* curr) { - NOTE_ENTER("LocalGet"); auto index = curr->index; - NOTE_EVAL1(index); - NOTE_EVAL1(scope->locals[index]); return scope->locals[index]; } Flow visitLocalSet(LocalSet* curr) { - NOTE_ENTER("LocalSet"); auto index = curr->index; Flow flow = self()->visit(curr->value); if (flow.breaking()) { return flow; } - NOTE_EVAL1(index); - NOTE_EVAL1(flow.getSingleValue()); assert(curr->isTee() ? Type::isSubType(flow.getType(), curr->type) : true); scope->locals[index] = flow.values; return curr->isTee() ? flow : Flow(); } Flow visitGlobalGet(GlobalGet* curr) { - NOTE_ENTER("GlobalGet"); auto name = curr->name; - NOTE_EVAL1(name); return getGlobal(name); } Flow visitGlobalSet(GlobalSet* curr) { - NOTE_ENTER("GlobalSet"); auto name = curr->name; Flow flow = self()->visit(curr->value); if (flow.breaking()) { return flow; } - NOTE_EVAL1(name); - NOTE_EVAL1(flow.getSingleValue()); getGlobal(name) = flow.values; return Flow(); } Flow visitLoad(Load* curr) { - NOTE_ENTER("Load"); Flow flow = self()->visit(curr->ptr); if (flow.breaking()) { return flow; } - NOTE_EVAL1(flow); auto info = getMemoryInstanceInfo(curr->memory); auto memorySize = info.instance->getMemorySize(info.name); auto addr = @@ -3820,12 +3636,9 @@ class ModuleRunnerBase : public ExpressionRunner { info.instance->checkAtomicAddress(addr, curr->bytes, memorySize); } auto ret = info.interface()->load(curr, addr, info.name); - NOTE_EVAL1(addr); - NOTE_EVAL1(ret); return ret; } Flow visitStore(Store* curr) { - NOTE_ENTER("Store"); Flow ptr = self()->visit(curr->ptr); if (ptr.breaking()) { return ptr; @@ -3841,14 +3654,11 @@ class ModuleRunnerBase : public ExpressionRunner { if (curr->isAtomic) { info.instance->checkAtomicAddress(addr, curr->bytes, memorySize); } - NOTE_EVAL1(addr); - NOTE_EVAL1(value); info.interface()->store(curr, addr, value.getSingleValue(), info.name); return Flow(); } Flow visitAtomicRMW(AtomicRMW* curr) { - NOTE_ENTER("AtomicRMW"); Flow ptr = self()->visit(curr->ptr); if (ptr.breaking()) { return ptr; @@ -3857,16 +3667,12 @@ class ModuleRunnerBase : public ExpressionRunner { if (value.breaking()) { return value; } - NOTE_EVAL1(ptr); auto info = getMemoryInstanceInfo(curr->memory); auto memorySize = info.instance->getMemorySize(info.name); auto addr = info.instance->getFinalAddress(curr, ptr.getSingleValue(), memorySize); - NOTE_EVAL1(addr); - NOTE_EVAL1(value); auto loaded = info.instance->doAtomicLoad( addr, curr->bytes, curr->type, info.name, memorySize); - NOTE_EVAL1(loaded); auto computed = value.getSingleValue(); switch (curr->op) { case RMWAdd: @@ -3892,12 +3698,10 @@ class ModuleRunnerBase : public ExpressionRunner { return loaded; } Flow visitAtomicCmpxchg(AtomicCmpxchg* curr) { - NOTE_ENTER("AtomicCmpxchg"); Flow ptr = self()->visit(curr->ptr); if (ptr.breaking()) { return ptr; } - NOTE_EVAL1(ptr); auto expected = self()->visit(curr->expected); if (expected.breaking()) { return expected; @@ -3911,12 +3715,8 @@ class ModuleRunnerBase : public ExpressionRunner { auto addr = info.instance->getFinalAddress(curr, ptr.getSingleValue(), memorySize); expected = Flow(wrapToSmallerSize(expected.getSingleValue(), curr->bytes)); - NOTE_EVAL1(addr); - NOTE_EVAL1(expected); - NOTE_EVAL1(replacement); auto loaded = info.instance->doAtomicLoad( addr, curr->bytes, curr->type, info.name, memorySize); - NOTE_EVAL1(loaded); if (loaded == expected.getSingleValue()) { info.instance->doAtomicStore( addr, curr->bytes, replacement.getSingleValue(), info.name, memorySize); @@ -3924,19 +3724,15 @@ class ModuleRunnerBase : public ExpressionRunner { return loaded; } Flow visitAtomicWait(AtomicWait* curr) { - NOTE_ENTER("AtomicWait"); Flow ptr = self()->visit(curr->ptr); if (ptr.breaking()) { return ptr; } - NOTE_EVAL1(ptr); auto expected = self()->visit(curr->expected); - NOTE_EVAL1(expected); if (expected.breaking()) { return expected; } auto timeout = self()->visit(curr->timeout); - NOTE_EVAL1(timeout); if (timeout.breaking()) { return timeout; } @@ -3947,7 +3743,6 @@ class ModuleRunnerBase : public ExpressionRunner { curr, ptr.getSingleValue(), bytes, memorySize); auto loaded = info.instance->doAtomicLoad( addr, bytes, curr->expectedType, info.name, memorySize); - NOTE_EVAL1(loaded); if (loaded != expected.getSingleValue()) { return Literal(int32_t(1)); // not equal } @@ -3962,14 +3757,11 @@ class ModuleRunnerBase : public ExpressionRunner { return Literal(int32_t(2)); // Timed out } Flow visitAtomicNotify(AtomicNotify* curr) { - NOTE_ENTER("AtomicNotify"); Flow ptr = self()->visit(curr->ptr); if (ptr.breaking()) { return ptr; } - NOTE_EVAL1(ptr); auto count = self()->visit(curr->notifyCount); - NOTE_EVAL1(count); if (count.breaking()) { return count; } @@ -3982,7 +3774,6 @@ class ModuleRunnerBase : public ExpressionRunner { return Literal(int32_t(0)); // none woken up } Flow visitSIMDLoad(SIMDLoad* curr) { - NOTE_ENTER("SIMDLoad"); switch (curr->op) { case Load8SplatVec128: case Load16SplatVec128: @@ -4042,7 +3833,6 @@ class ModuleRunnerBase : public ExpressionRunner { if (flow.breaking()) { return flow; } - NOTE_EVAL1(flow); Address src(flow.getSingleValue().getUnsigned()); auto info = getMemoryInstanceInfo(curr->memory); auto loadLane = [&](Address addr) { @@ -4102,7 +3892,6 @@ class ModuleRunnerBase : public ExpressionRunner { if (flow.breaking()) { return flow; } - NOTE_EVAL1(flow); auto info = getMemoryInstanceInfo(curr->memory); auto memorySize = info.instance->getMemorySize(info.name); Address src = info.instance->getFinalAddress( @@ -4118,12 +3907,10 @@ class ModuleRunnerBase : public ExpressionRunner { } } Flow visitSIMDLoadStoreLane(SIMDLoadStoreLane* curr) { - NOTE_ENTER("SIMDLoadStoreLane"); Flow ptrFlow = self()->visit(curr->ptr); if (ptrFlow.breaking()) { return ptrFlow; } - NOTE_EVAL1(ptrFlow); Flow vecFlow = self()->visit(curr->vec); if (vecFlow.breaking()) { return vecFlow; @@ -4190,14 +3977,12 @@ class ModuleRunnerBase : public ExpressionRunner { WASM_UNREACHABLE("unexpected op"); } Flow visitMemorySize(MemorySize* curr) { - NOTE_ENTER("MemorySize"); auto info = getMemoryInstanceInfo(curr->memory); auto memorySize = info.instance->getMemorySize(info.name); auto* memory = info.instance->wasm.getMemory(info.name); return Literal::makeFromInt64(memorySize, memory->addressType); } Flow visitMemoryGrow(MemoryGrow* curr) { - NOTE_ENTER("MemoryGrow"); Flow flow = self()->visit(curr->delta); if (flow.breaking()) { return flow; @@ -4236,7 +4021,6 @@ class ModuleRunnerBase : public ExpressionRunner { return ret; } Flow visitMemoryInit(MemoryInit* curr) { - NOTE_ENTER("MemoryInit"); Flow dest = self()->visit(curr->dest); if (dest.breaking()) { return dest; @@ -4249,9 +4033,6 @@ class ModuleRunnerBase : public ExpressionRunner { if (size.breaking()) { return size; } - NOTE_EVAL1(dest); - NOTE_EVAL1(offset); - NOTE_EVAL1(size); auto* segment = wasm.getDataSegment(curr->segment); @@ -4280,12 +4061,10 @@ class ModuleRunnerBase : public ExpressionRunner { return {}; } Flow visitDataDrop(DataDrop* curr) { - NOTE_ENTER("DataDrop"); droppedDataSegments.insert(curr->segment); return {}; } Flow visitMemoryCopy(MemoryCopy* curr) { - NOTE_ENTER("MemoryCopy"); Flow dest = self()->visit(curr->dest); if (dest.breaking()) { return dest; @@ -4298,9 +4077,6 @@ class ModuleRunnerBase : public ExpressionRunner { if (size.breaking()) { return size; } - NOTE_EVAL1(dest); - NOTE_EVAL1(source); - NOTE_EVAL1(size); Address destVal(dest.getSingleValue().getUnsigned()); Address sourceVal(source.getSingleValue().getUnsigned()); Address sizeVal(size.getSingleValue().getUnsigned()); @@ -4339,7 +4115,6 @@ class ModuleRunnerBase : public ExpressionRunner { return {}; } Flow visitMemoryFill(MemoryFill* curr) { - NOTE_ENTER("MemoryFill"); Flow dest = self()->visit(curr->dest); if (dest.breaking()) { return dest; @@ -4352,9 +4127,6 @@ class ModuleRunnerBase : public ExpressionRunner { if (size.breaking()) { return size; } - NOTE_EVAL1(dest); - NOTE_EVAL1(value); - NOTE_EVAL1(size); Address destVal(dest.getSingleValue().getUnsigned()); Address sizeVal(size.getSingleValue().getUnsigned()); @@ -4376,7 +4148,6 @@ class ModuleRunnerBase : public ExpressionRunner { return {}; } Flow visitArrayNewData(ArrayNewData* curr) { - NOTE_ENTER("ArrayNewData"); auto offsetFlow = self()->visit(curr->offset); if (offsetFlow.breaking()) { return offsetFlow; @@ -4417,7 +4188,6 @@ class ModuleRunnerBase : public ExpressionRunner { return self()->makeGCData(std::move(contents), curr->type); } Flow visitArrayNewElem(ArrayNewElem* curr) { - NOTE_ENTER("ArrayNewElem"); auto offsetFlow = self()->visit(curr->offset); if (offsetFlow.breaking()) { return offsetFlow; @@ -4448,7 +4218,6 @@ class ModuleRunnerBase : public ExpressionRunner { return self()->makeGCData(std::move(contents), curr->type); } Flow visitArrayInitData(ArrayInitData* curr) { - NOTE_ENTER("ArrayInit"); Flow ref = self()->visit(curr->ref); if (ref.breaking()) { return ref; @@ -4497,7 +4266,6 @@ class ModuleRunnerBase : public ExpressionRunner { return {}; } Flow visitArrayInitElem(ArrayInitElem* curr) { - NOTE_ENTER("ArrayInit"); Flow ref = self()->visit(curr->ref); if (ref.breaking()) { return ref; @@ -4547,7 +4315,6 @@ class ModuleRunnerBase : public ExpressionRunner { return {}; } Flow visitTry(Try* curr) { - NOTE_ENTER("Try"); // Unwind the value stack when we jump up the call stack. auto oldValueStackSize = self()->valueStack ? self()->valueStack->size() : 0; try { @@ -4601,7 +4368,6 @@ class ModuleRunnerBase : public ExpressionRunner { } } Flow visitTryTable(TryTable* curr) { - NOTE_ENTER("TryTable"); // Unwind the value stack when we jump up the call stack. auto oldValueStackSize = self()->valueStack ? self()->valueStack->size() : 0; try { @@ -4640,7 +4406,6 @@ class ModuleRunnerBase : public ExpressionRunner { WASM_UNREACHABLE("rethrow"); } Flow visitPop(Pop* curr) { - NOTE_ENTER("Pop"); assert(!multiValues.empty()); auto ret = multiValues.back(); assert(Type::isSubType(ret.getType(), curr->type)); @@ -4678,7 +4443,7 @@ class ModuleRunnerBase : public ExpressionRunner { Flow visitResume(Resume* curr) { auto flow = self()->visit(curr->cont); -// XXX it should RESUME EXECUTION!!!!!!!! +// XXX it should RESUME EXECUTION!!!!!!!! waka if (flow.suspendTag) { // See if a suspension arrived that we support. @@ -4780,8 +4545,9 @@ class ModuleRunnerBase : public ExpressionRunner { FunctionScope scope(function, arguments, *self()); -#ifdef WASM_INTERPRETER_DEBUG - std::cout << "entering " << function->name << "\n with arguments:\n"; +#if WASM_INTERPRETER_DEBUG + std::cout << self()->indent() << "entering " << function->name + << "\n with arguments:\n"; for (unsigned i = 0; i < arguments.size(); ++i) { std::cout << " $" << i << ": " << arguments[i] << '\n'; } @@ -4789,9 +4555,9 @@ class ModuleRunnerBase : public ExpressionRunner { flow = self()->visit(function->body); -#ifdef WASM_INTERPRETER_DEBUG - std::cout << "exiting " << function->name << " with " << flow.values - << '\n'; +#if WASM_INTERPRETER_DEBUG + std::cout << self()->indent() << "exiting " << function->name << " with " + << flow.values << '\n'; #endif if (flow.breakTo != RETURN_CALL_FLOW) { diff --git a/src/wasm/wasm-interpreter.cpp b/src/wasm/wasm-interpreter.cpp index cd6c232b5fb..37ac05556ac 100644 --- a/src/wasm/wasm-interpreter.cpp +++ b/src/wasm/wasm-interpreter.cpp @@ -2,23 +2,6 @@ namespace wasm { -#ifdef WASM_INTERPRETER_DEBUG -int Indenter::indentLevel = 0; - -Indenter::Indenter(const char* entry) : entryName(entry) { ++indentLevel; } -Indenter::~Indenter() { - print(); - std::cout << "exit " << entryName << '\n'; - --indentLevel; -} -void Indenter::print() { - std::cout << indentLevel << ':'; - for (int i = 0; i <= indentLevel; ++i) { - std::cout << ' '; - } -} -#endif // WASM_INTERPRETER_DEBUG - std::ostream& operator<<(std::ostream& o, const WasmException& exn) { auto exnData = exn.exn.getExnData(); return o << exnData->tag << " " << exnData->payload; From f3485eb4ddefd10687b4f414a46cc22c8d1abf3a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 28 Jul 2025 13:09:05 -0700 Subject: [PATCH 018/125] format --- src/literal.h | 3 ++- src/wasm-interpreter.h | 32 ++++++++++++++++++-------------- src/wasm/literal.cpp | 3 +-- src/wasm/wasm.cpp | 2 +- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/literal.h b/src/literal.h index 9c0c4f69a27..934ae00eae3 100644 --- a/src/literal.h +++ b/src/literal.h @@ -815,7 +815,8 @@ struct ContData { // The continuation type. HeapType type; - ContData(Name func, Literals resumeInfo, HeapType type) : func(func), resumeInfo(resumeInfo), type(type) {} + ContData(Name func, Literals resumeInfo, HeapType type) + : func(func), resumeInfo(resumeInfo), type(type) {} }; } // namespace wasm diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index c812d9c4e12..869cefd9c45 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -254,8 +254,8 @@ class ExpressionRunner : public OverriddenVisitor { if (type.isConcrete() || curr->type.isConcrete()) { if (!Type::isSubType(type, curr->type)) { Fatal() << "expected " << ModuleType(*module, curr->type) - << ", seeing " << ModuleType(*module, type) << " from\n" - << ModuleExpression(*module, curr) << '\n'; + << ", seeing " << ModuleType(*module, type) << " from\n" + << ModuleExpression(*module, curr) << '\n'; } } if (valueStack) { @@ -4316,7 +4316,8 @@ class ModuleRunnerBase : public ExpressionRunner { } Flow visitTry(Try* curr) { // Unwind the value stack when we jump up the call stack. - auto oldValueStackSize = self()->valueStack ? self()->valueStack->size() : 0; + auto oldValueStackSize = + self()->valueStack ? self()->valueStack->size() : 0; try { return self()->visit(curr->body); } catch (const WasmException& e) { @@ -4369,7 +4370,8 @@ class ModuleRunnerBase : public ExpressionRunner { } Flow visitTryTable(TryTable* curr) { // Unwind the value stack when we jump up the call stack. - auto oldValueStackSize = self()->valueStack ? self()->valueStack->size() : 0; + auto oldValueStackSize = + self()->valueStack ? self()->valueStack->size() : 0; try { return self()->visit(curr->body); } catch (const WasmException& e) { @@ -4420,17 +4422,18 @@ class ModuleRunnerBase : public ExpressionRunner { Name func = funcFlow.getSingleValue().getFunc(); // The initial data is empty, as nothing has executed yet, so we don't // need any information about how to resume (we resume by just running). - return Literal(std::make_shared(func, Literals{}, curr->type.getHeapType())); + return Literal( + std::make_shared(func, Literals{}, curr->type.getHeapType())); } Flow visitContBind(ContBind* curr) { return Flow(NONCONSTANT_FLOW); -/* - Literals arguments; - Flow flow = self()->generateArguments(curr->operands, arguments); - if (flow.breaking()) { - return flow; - } -*/ + /* + Literals arguments; + Flow flow = self()->generateArguments(curr->operands, arguments); + if (flow.breaking()) { + return flow; + } + */ } Flow visitSuspend(Suspend* curr) { Literals arguments; @@ -4443,7 +4446,7 @@ class ModuleRunnerBase : public ExpressionRunner { Flow visitResume(Resume* curr) { auto flow = self()->visit(curr->cont); -// XXX it should RESUME EXECUTION!!!!!!!! waka + // XXX it should RESUME EXECUTION!!!!!!!! waka if (flow.suspendTag) { // See if a suspension arrived that we support. @@ -4577,7 +4580,8 @@ class ModuleRunnerBase : public ExpressionRunner { } // cannot still be breaking, it means we missed our stop - assert(!flow.breaking() || flow.breakTo == RETURN_FLOW || flow.breakTo == SUSPEND_FLOW); + assert(!flow.breaking() || flow.breakTo == RETURN_FLOW || + flow.breakTo == SUSPEND_FLOW); auto type = flow.getType(); if (!Type::isSubType(type, *resultType)) { std::cerr << "calling " << name << " resulted in " << type diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp index e7dbd5cf342..0e1d7b874e4 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -92,8 +92,7 @@ Literal::Literal(std::shared_ptr exnData) } Literal::Literal(std::shared_ptr contData) - : contData(contData), type(contData->type, NonNullable, Exact) { -} + : contData(contData), type(contData->type, NonNullable, Exact) {} Literal::Literal(std::string_view string) : gcData(nullptr), type(Type(HeapType::string, NonNullable)) { diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 5f5afdc0da3..6222f37ab8f 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -24,7 +24,7 @@ namespace wasm { // shared constants -//Name WASM("wasm"); +// Name WASM("wasm"); Name RETURN_FLOW("*return:)*"); Name RETURN_CALL_FLOW("*return-call:)*"); Name NONCONSTANT_FLOW("*nonconstant:)*"); From 7d68e247c1e1c7f3f431ee96a89418725daf2c59 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 28 Jul 2025 13:16:04 -0700 Subject: [PATCH 019/125] work --- src/parser/wast-parser.cpp | 5 ++++- src/wasm/wasm.cpp | 1 - test/lit/exec/cont.wast | 36 ------------------------------------ 3 files changed, 4 insertions(+), 38 deletions(-) delete mode 100644 test/lit/exec/cont.wast diff --git a/src/parser/wast-parser.cpp b/src/parser/wast-parser.cpp index 0f7887f8fe2..46a0cffc163 100644 --- a/src/parser/wast-parser.cpp +++ b/src/parser/wast-parser.cpp @@ -351,8 +351,11 @@ MaybeResult assertModule(Lexer& in) { // (assert_trap action msg) // (assert_trap module msg) +// +// Also handles assert_suspension, as for us an unhandled suspension is a trap. MaybeResult assertTrap(Lexer& in) { - if (!in.takeSExprStart("assert_trap"sv)) { + if (!in.takeSExprStart("assert_trap"sv) && + !in.takeSExprStart("assert_suspension"sv)) { return {}; } auto pos = in.getPos(); diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 6222f37ab8f..27d44217eac 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -24,7 +24,6 @@ namespace wasm { // shared constants -// Name WASM("wasm"); Name RETURN_FLOW("*return:)*"); Name RETURN_CALL_FLOW("*return-call:)*"); Name NONCONSTANT_FLOW("*nonconstant:)*"); diff --git a/test/lit/exec/cont.wast b/test/lit/exec/cont.wast deleted file mode 100644 index c445f25436c..00000000000 --- a/test/lit/exec/cont.wast +++ /dev/null @@ -1,36 +0,0 @@ -;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited. - -;; RUN: wasm-opt %s -all --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s - -(module - (tag $exn) - (tag $e1) - (tag $e2) - - (type $f1 (func)) - (type $k1 (cont $f1)) - - (func $f1 (export "unhandled-1") - (suspend $e1) - ) - - (func (export "unhandled-2") - (resume $k1 (cont.new $k1 (ref.func $f1))) - ) - - (func (export "unhandled-3") - (block $h (result (ref $k1)) - (resume $k1 (on $e2 $h) (cont.new $k1 (ref.func $f1))) - (unreachable) - ) - (drop) - ) - - (func (export "handled") - (block $h (result (ref $k1)) - (resume $k1 (on $e1 $h) (cont.new $k1 (ref.func $f1))) - (unreachable) - ) - (drop) - ) -) From 0eb3346f094d6e303fd8918b4ef1aeace383ca50 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 28 Jul 2025 13:21:38 -0700 Subject: [PATCH 020/125] work --- src/wasm-interpreter.h | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 869cefd9c45..4250f437c6d 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4445,30 +4445,40 @@ class ModuleRunnerBase : public ExpressionRunner { } Flow visitResume(Resume* curr) { auto flow = self()->visit(curr->cont); + if (flow.breaking()) { + return flow; + } - // XXX it should RESUME EXECUTION!!!!!!!! waka - - if (flow.suspendTag) { + // Execute the continuation. + auto contData = flow.getSingleValue().getContData(); +#if WASM_INTERPRETER_DEBUG + std::cout << self()->indent() << "resuming func " << contData->func << '\n'; +#endif + auto* func = wasm.getFunction(contData->func); + auto funcType = func->type; + Flow ret = callFunction(target, {}); +#if WASM_INTERPRETER_DEBUG + std::cout << self()->indent() << "finished resuming, with " << ret << '\n'; +#endif + if (ret.suspendTag) { // See if a suspension arrived that we support. for (size_t i = 0; i < curr->handlerTags.size(); i++) { auto handlerTag = curr->handlerTags[i]; - if (handlerTag == flow.suspendTag) { + if (handlerTag == ret.suspendTag) { // Switch the flow from suspending to branching, and keep sending the // same values (which include the tag values + a new continuation at // the end, so we have nothing to add here). // TODO: doc on Flow // TODO: callTable is tricky, as table might change, so like in // Asyncify, need to save funcref. - flow.suspendTag = Name(); - flow.breakTo = curr->handlerBlocks[i]; - return flow; + ret.suspendTag = Name(); + ret.breakTo = curr->handlerBlocks[i]; + return ret; } } // No handler worked out, keep propagating. - return flow; - } - if (flow.breaking()) { - return flow; + return ret; } + // No suspension; all done. return Flow(); } Flow visitResumeThrow(ResumeThrow* curr) { return Flow(NONCONSTANT_FLOW); } From e0367c6ea2d9ed8e2c3758ba635eb806e14f4dd4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 28 Jul 2025 13:22:24 -0700 Subject: [PATCH 021/125] work --- src/wasm-interpreter.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 4250f437c6d..2cee7f02f51 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4451,12 +4451,11 @@ class ModuleRunnerBase : public ExpressionRunner { // Execute the continuation. auto contData = flow.getSingleValue().getContData(); + auto func = contData->func; #if WASM_INTERPRETER_DEBUG - std::cout << self()->indent() << "resuming func " << contData->func << '\n'; + std::cout << self()->indent() << "resuming func " << func << '\n'; #endif - auto* func = wasm.getFunction(contData->func); - auto funcType = func->type; - Flow ret = callFunction(target, {}); + Flow ret = callFunction(func, {}); #if WASM_INTERPRETER_DEBUG std::cout << self()->indent() << "finished resuming, with " << ret << '\n'; #endif From 77b1d2d31f9ebd78491bf390d441b57fcd705fd3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 28 Jul 2025 13:25:07 -0700 Subject: [PATCH 022/125] work --- src/wasm/literal.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp index 0e1d7b874e4..819e1270448 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -347,7 +347,7 @@ std::shared_ptr Literal::getExnData() const { } std::shared_ptr Literal::getContData() const { - assert(isExn()); + assert(isContinuation()); assert(contData); return contData; } From 86fc6a3f80c4aa1321bccba9b76be00b66db932d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 28 Jul 2025 13:31:04 -0700 Subject: [PATCH 023/125] work --- src/wasm-interpreter.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 2cee7f02f51..515638f7b53 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4435,12 +4435,28 @@ class ModuleRunnerBase : public ExpressionRunner { } */ } + + // TODO where? + // Stack of names of the entry function of currently-running continuations. + std::vector continuationEntryStack; + Flow visitSuspend(Suspend* curr) { Literals arguments; Flow flow = self()->generateArguments(curr->operands, arguments); if (flow.breaking()) { return flow; } + + // Generate a continuation to proceed from here, and add it as another + // value. The name of the function at the bottom of the stack is on + // continuationEntryStack. + assert(!continuationEntryStack.empty()); + auto func = continuationEntryStack.back(); + continuationEntryStack.pop_back(); + auto cont = Literal( + std::make_shared(func, Literals{}, curr->type.getHeapType())); + // TODO: save the stack!!1 + arguments.push_back(cont); return Flow(SUSPEND_FLOW, curr->tag, std::move(arguments)); } Flow visitResume(Resume* curr) { @@ -4452,6 +4468,7 @@ class ModuleRunnerBase : public ExpressionRunner { // Execute the continuation. auto contData = flow.getSingleValue().getContData(); auto func = contData->func; + continuationEntryStack.push_back(func); #if WASM_INTERPRETER_DEBUG std::cout << self()->indent() << "resuming func " << func << '\n'; #endif From 9a9bf7cb1adfdde13242b9dca2831bdbe3d87e5f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 28 Jul 2025 13:32:20 -0700 Subject: [PATCH 024/125] work --- src/wasm-interpreter.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 515638f7b53..6f269324157 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4450,7 +4450,9 @@ class ModuleRunnerBase : public ExpressionRunner { // Generate a continuation to proceed from here, and add it as another // value. The name of the function at the bottom of the stack is on // continuationEntryStack. - assert(!continuationEntryStack.empty()); + if (continuationEntryStack.empty()) { + trap("no continuation to suspend"); + } auto func = continuationEntryStack.back(); continuationEntryStack.pop_back(); auto cont = Literal( From 20a59c0edbbe89bfa2293e1fbdcb2a056b417aad Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 28 Jul 2025 13:48:00 -0700 Subject: [PATCH 025/125] work --- src/wasm-interpreter.h | 40 +++++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 6f269324157..5692e1aea0c 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4437,8 +4437,8 @@ class ModuleRunnerBase : public ExpressionRunner { } // TODO where? - // Stack of names of the entry function of currently-running continuations. - std::vector continuationEntryStack; + // Stack of currently-running continuations. + std::vector> continuationEntryStack; Flow visitSuspend(Suspend* curr) { Literals arguments; @@ -4453,12 +4453,17 @@ class ModuleRunnerBase : public ExpressionRunner { if (continuationEntryStack.empty()) { trap("no continuation to suspend"); } - auto func = continuationEntryStack.back(); + auto old = continuationEntryStack.back(); continuationEntryStack.pop_back(); + // Copy the continuation, and add stack info so it can be restored from + // here. auto cont = Literal( - std::make_shared(func, Literals{}, curr->type.getHeapType())); + std::make_shared(old->func, Literals{}, old->type)); // TODO: save the stack!!1 arguments.push_back(cont); + // Continuations are one-shot, so make the old one invalid. + old->func = Name(); + old->type = HeapTypes::none; return Flow(SUSPEND_FLOW, curr->tag, std::move(arguments)); } Flow visitResume(Resume* curr) { @@ -4467,10 +4472,13 @@ class ModuleRunnerBase : public ExpressionRunner { return flow; } - // Execute the continuation. + // Get and execute the continuation. auto contData = flow.getSingleValue().getContData(); - auto func = contData->func; - continuationEntryStack.push_back(func); + Name func = contData->func; + if (!func) { + trap("continuation already executed"); + } + continuationEntryStack.push_back(contData); #if WASM_INTERPRETER_DEBUG std::cout << self()->indent() << "resuming func " << func << '\n'; #endif @@ -4607,14 +4615,20 @@ class ModuleRunnerBase : public ExpressionRunner { throw NonconstantException(); } - // cannot still be breaking, it means we missed our stop - assert(!flow.breaking() || flow.breakTo == RETURN_FLOW || - flow.breakTo == SUSPEND_FLOW); auto type = flow.getType(); - if (!Type::isSubType(type, *resultType)) { - std::cerr << "calling " << name << " resulted in " << type + if (flow.breakTo == SUSPEND_FLOW) { + // When suspending, the result is a continuation. + assert(type.isContinuation()); + } else { + // We are normally executing (not suspending), and therefore cannot still + // be breaking, which would mean we missed our stop. + assert(!flow.breaking() || flow.breakTo == RETURN_FLOW); + + // In normal execution, the result is the expected one. + if (!Type::isSubType(type, *resultType)) { + Fatal() << "calling " << name << " resulted in " << type << " but the function type is " << *resultType << '\n'; - WASM_UNREACHABLE("unexpected result type"); + } } return flow; From 9dd600f6df874fae5d4b859cd622a14074abe69b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 28 Jul 2025 14:11:28 -0700 Subject: [PATCH 026/125] test --- test/spec/cont.wast | 1101 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1101 insertions(+) create mode 100644 test/spec/cont.wast diff --git a/test/spec/cont.wast b/test/spec/cont.wast new file mode 100644 index 00000000000..29c4848854a --- /dev/null +++ b/test/spec/cont.wast @@ -0,0 +1,1101 @@ +;; Unhandled tags & guards + +(module + (tag $exn) + (tag $e1) + (tag $e2) + + (type $f1 (func)) + (type $k1 (cont $f1)) + + (func $f1 (export "unhandled-1") + (suspend $e1) + ) + + (func (export "unhandled-2") + (resume $k1 (cont.new $k1 (ref.func $f1))) + ) + + (func (export "unhandled-3") + (block $h (result (ref $k1)) + (resume $k1 (on $e2 $h) (cont.new $k1 (ref.func $f1))) + (unreachable) + ) + (drop) + ) + + (func (export "handled") + (block $h (result (ref $k1)) + (resume $k1 (on $e1 $h) (cont.new $k1 (ref.func $f1))) + (unreachable) + ) + (drop) + ) + + (elem declare func $f2) + (func $f2 + (throw $exn) + ) + + (func (export "uncaught-1") + (block $h (result (ref $k1)) + (resume $k1 (on $e1 $h) (cont.new $k1 (ref.func $f2))) + (unreachable) + ) + (drop) + ) + + (func (export "uncaught-2") + (block $h (result (ref $k1)) + (resume $k1 (on $e1 $h) (cont.new $k1 (ref.func $f1))) + (unreachable) + ) + (resume_throw $k1 $exn) + ) + + (elem declare func $f3) + (func $f3 + (call $f4) + ) + (func $f4 + (suspend $e1) + ) + + (func (export "uncaught-3") + (block $h (result (ref $k1)) + (resume $k1 (on $e1 $h) (cont.new $k1 (ref.func $f3))) + (unreachable) + ) + (resume_throw $k1 $exn) + ) + + (elem declare func $r0 $r1) + (func $r0) + (func $r1 (suspend $e1) (suspend $e1)) + + (func $nl1 (param $k (ref $k1)) + (resume $k1 (local.get $k)) + (resume $k1 (local.get $k)) + ) + (func $nl2 (param $k (ref $k1)) + (block $h (result (ref $k1)) + (resume $k1 (on $e1 $h) (local.get $k)) + (unreachable) + ) + (resume $k1 (local.get $k)) + (unreachable) + ) + (func $nl3 (param $k (ref $k1)) + (local $k' (ref null $k1)) + (block $h1 (result (ref $k1)) + (resume $k1 (on $e1 $h1) (local.get $k)) + (unreachable) + ) + (local.set $k') + (block $h2 (result (ref $k1)) + (resume $k1 (on $e1 $h2) (local.get $k')) + (unreachable) + ) + (resume $k1 (local.get $k')) + (unreachable) + ) + (func $nl4 (param $k (ref $k1)) + (drop (cont.bind $k1 $k1 (local.get $k))) + (resume $k1 (local.get $k)) + ) + + (func (export "non-linear-1") + (call $nl1 (cont.new $k1 (ref.func $r0))) + ) + (func (export "non-linear-2") + (call $nl2 (cont.new $k1 (ref.func $r1))) + ) + (func (export "non-linear-3") + (call $nl3 (cont.new $k1 (ref.func $r1))) + ) + (func (export "non-linear-4") + (call $nl4 (cont.new $k1 (ref.func $r1))) + ) +) + +(assert_suspension (invoke "unhandled-1") "unhandled") +(assert_suspension (invoke "unhandled-2") "unhandled") +(assert_suspension (invoke "unhandled-3") "unhandled") +(assert_return (invoke "handled")) + +(assert_exception (invoke "uncaught-1")) +(assert_exception (invoke "uncaught-2")) +(assert_exception (invoke "uncaught-3")) + +(assert_trap (invoke "non-linear-1") "continuation already consumed") +(assert_trap (invoke "non-linear-2") "continuation already consumed") +(assert_trap (invoke "non-linear-3") "continuation already consumed") +(assert_trap (invoke "non-linear-4") "continuation already consumed") + +(assert_invalid + (module + (type $ft (func)) + (func + (cont.new $ft (ref.null $ft)) + (drop))) + "non-continuation type 0") + +(assert_invalid + (module + (type $ft (func)) + (type $ct (cont $ft)) + (func + (resume $ft (ref.null $ct)) + (unreachable))) + "non-continuation type 0") + +(assert_invalid + (module + (type $ft (func)) + (type $ct (cont $ft)) + (tag $exn) + (func + (resume_throw $ft $exn (ref.null $ct)) + (unreachable))) + "non-continuation type 0") + +(assert_invalid + (module + (type $ft (func)) + (type $ct (cont $ft)) + (func + (cont.bind $ft $ct (ref.null $ct)) + (unreachable))) + "non-continuation type 0") + +(assert_invalid + (module + (type $ft (func)) + (type $ct (cont $ft)) + (func + (cont.bind $ct $ft (ref.null $ct)) + (unreachable))) + "non-continuation type 0") + +(assert_invalid + (module + (type $ft (func)) + (type $ct (cont $ft)) + (tag $foo) + (func + (block $on_foo (result (ref $ft)) + (resume $ct (on $foo $on_foo) (ref.null $ct)) + (unreachable) + ) + (drop))) + "non-continuation type 0") + +(assert_invalid + (module + (type $ft (func)) + (type $ct (cont $ft)) + (tag $foo) + (func + (block $on_foo (result (ref $ct) (ref $ft)) + (resume $ct (on $foo $on_foo) (ref.null $ct)) + (unreachable) + ) + (drop) + (drop))) + "non-continuation type 0") + +(assert_invalid + (module + (type $ct (cont $ct))) + "non-function type 0") + +(assert_invalid + (module + (rec + (type $s0 (struct (field (ref 0) (ref 1) (ref $s0) (ref $s1)))) + (type $s1 (struct (field (ref 0) (ref 1) (ref $s0) (ref $s1)))) + ) + (type $ct (cont $s0))) + "non-function type 0") + +(module + (rec + (type $f1 (func (param (ref $f2)))) + (type $f2 (func (param (ref $f1)))) + ) + (type $c1 (cont $f1)) + (type $c2 (cont $f2)) +) + +;; Simple state example + +(module $state + (tag $get (result i32)) + (tag $set (param i32) (result i32)) + + (type $f (func (param i32) (result i32))) + (type $k (cont $f)) + + (func $runner (param $s i32) (param $k (ref $k)) (result i32) + (loop $loop + (block $on_get (result (ref $k)) + (block $on_set (result i32 (ref $k)) + (resume $k (on $get $on_get) (on $set $on_set) + (local.get $s) (local.get $k) + ) + (return) + ) + ;; on set + (local.set $k) + (local.set $s) + (br $loop) + ) + ;; on get + (local.set $k) + (br $loop) + ) + (unreachable) + ) + + (func $f (param i32) (result i32) + (drop (suspend $set (i32.const 7))) + (i32.add + (suspend $get) + (i32.mul + (i32.const 2) + (i32.add + (suspend $set (i32.const 3)) + (suspend $get) + ) + ) + ) + ) + + (elem declare func $f) + (func (export "run") (result i32) + (call $runner (i32.const 0) (cont.new $k (ref.func $f))) + ) +) + +(assert_return (invoke "run") (i32.const 19)) + + +;; Simple generator example + +(module $generator + (type $gen (func (param i64))) + (type $geny (func (param i32))) + (type $cont0 (cont $gen)) + (type $cont (cont $geny)) + + (tag $yield (param i64) (result i32)) + + ;; Hook for logging purposes + (global $hook (export "hook") (mut (ref $gen)) (ref.func $dummy)) + (func $dummy (param i64)) + + (func $gen (export "start") (param $i i64) + (loop $l + (br_if 1 (suspend $yield (local.get $i))) + (call_ref $gen (local.get $i) (global.get $hook)) + (local.set $i (i64.add (local.get $i) (i64.const 1))) + (br $l) + ) + ) + + (elem declare func $gen) + + (func (export "sum") (param $i i64) (param $j i64) (result i64) + (local $sum i64) + (local $n i64) + (local $k (ref null $cont)) + (local.get $i) + (cont.new $cont0 (ref.func $gen)) + (block $on_first_yield (param i64 (ref $cont0)) (result i64 (ref $cont)) + (resume $cont0 (on $yield $on_first_yield)) + (unreachable) + ) + (loop $on_yield (param i64) (param (ref $cont)) + (local.set $k) + (local.set $n) + (local.set $sum (i64.add (local.get $sum) (local.get $n))) + (i64.eq (local.get $n) (local.get $j)) + (local.get $k) + (resume $cont (on $yield $on_yield)) + ) + (return (local.get $sum)) + ) +) + +(register "generator") + +(assert_return (invoke "sum" (i64.const 0) (i64.const 0)) (i64.const 0)) +(assert_return (invoke "sum" (i64.const 2) (i64.const 2)) (i64.const 2)) +(assert_return (invoke "sum" (i64.const 0) (i64.const 3)) (i64.const 6)) +(assert_return (invoke "sum" (i64.const 1) (i64.const 10)) (i64.const 55)) +(assert_return (invoke "sum" (i64.const 100) (i64.const 2000)) (i64.const 1_996_050)) + + +;; Simple scheduler example + +(module $scheduler + (type $proc (func)) + (type $cont (cont $proc)) + + (tag $yield (export "yield")) + (tag $spawn (export "spawn") (param (ref $cont))) + + ;; Table as simple queue (keeping it simple, no ring buffer) + (table $queue 0 (ref null $cont)) + (global $qdelta i32 (i32.const 10)) + (global $qback (mut i32) (i32.const 0)) + (global $qfront (mut i32) (i32.const 0)) + + (func $queue-empty (result i32) + (i32.eq (global.get $qfront) (global.get $qback)) + ) + + (func $dequeue (result (ref null $cont)) + (local $i i32) + (if (call $queue-empty) + (then (return (ref.null $cont))) + ) + (local.set $i (global.get $qfront)) + (global.set $qfront (i32.add (local.get $i) (i32.const 1))) + (table.get $queue (local.get $i)) + ) + + (func $enqueue (param $k (ref $cont)) + ;; Check if queue is full + (if (i32.eq (global.get $qback) (table.size $queue)) + (then + ;; Check if there is enough space in the front to compact + (if (i32.lt_u (global.get $qfront) (global.get $qdelta)) + (then + ;; Space is below threshold, grow table instead + (drop (table.grow $queue (ref.null $cont) (global.get $qdelta))) + ) + (else + ;; Enough space, move entries up to head of table + (global.set $qback (i32.sub (global.get $qback) (global.get $qfront))) + (table.copy $queue $queue + (i32.const 0) ;; dest = new front = 0 + (global.get $qfront) ;; src = old front + (global.get $qback) ;; len = new back = old back - old front + ) + (table.fill $queue ;; null out old entries to avoid leaks + (global.get $qback) ;; start = new back + (ref.null $cont) ;; init value + (global.get $qfront) ;; len = old front = old front - new front + ) + (global.set $qfront (i32.const 0)) + ) + ) + ) + ) + (table.set $queue (global.get $qback) (local.get $k)) + (global.set $qback (i32.add (global.get $qback) (i32.const 1))) + ) + + (func $scheduler (export "scheduler") (param $main (ref $cont)) + (call $enqueue (local.get $main)) + (loop $l + (if (call $queue-empty) (then (return))) + (block $on_yield (result (ref $cont)) + (block $on_spawn (result (ref $cont) (ref $cont)) + (resume $cont (on $yield $on_yield) (on $spawn $on_spawn) + (call $dequeue) + ) + (br $l) ;; thread terminated + ) + ;; on $spawn, proc and cont on stack + (call $enqueue) ;; continuation of old thread + (call $enqueue) ;; new thread + (br $l) + ) + ;; on $yield, cont on stack + (call $enqueue) + (br $l) + ) + ) +) + +(register "scheduler") + +(module + (type $proc (func)) + (type $pproc (func (param i32))) ;; parameterised proc + (type $cont (cont $proc)) + (type $pcont (cont $pproc)) ;; parameterised continuation proc + (tag $yield (import "scheduler" "yield")) + (tag $spawn (import "scheduler" "spawn") (param (ref $cont))) + (func $scheduler (import "scheduler" "scheduler") (param $main (ref $cont))) + + (func $log (import "spectest" "print_i32") (param i32)) + + (global $width (mut i32) (i32.const 0)) + (global $depth (mut i32) (i32.const 0)) + + (elem declare func $main $thread1 $thread2 $thread3) + + (func $main + (call $log (i32.const 0)) + (suspend $spawn (cont.new $cont (ref.func $thread1))) + (call $log (i32.const 1)) + (suspend $spawn (cont.bind $pcont $cont (global.get $depth) (cont.new $pcont (ref.func $thread2)))) + (call $log (i32.const 2)) + (suspend $spawn (cont.new $cont (ref.func $thread3))) + (call $log (i32.const 3)) + ) + + (func $thread1 + (call $log (i32.const 10)) + (suspend $yield) + (call $log (i32.const 11)) + (suspend $yield) + (call $log (i32.const 12)) + (suspend $yield) + (call $log (i32.const 13)) + ) + + (func $thread2 (param $d i32) + (local $w i32) + (local.set $w (global.get $width)) + (call $log (i32.const 20)) + (br_if 0 (i32.eqz (local.get $d))) + (call $log (i32.const 21)) + (loop $l + (if (local.get $w) + (then + (call $log (i32.const 22)) + (suspend $yield) + (call $log (i32.const 23)) + (suspend $spawn + (cont.bind $pcont $cont + (i32.sub (local.get $d) (i32.const 1)) + (cont.new $pcont (ref.func $thread2)) + ) + ) + (call $log (i32.const 24)) + (local.set $w (i32.sub (local.get $w) (i32.const 1))) + (br $l) + ) + ) + ) + (call $log (i32.const 25)) + ) + + (func $thread3 + (call $log (i32.const 30)) + (suspend $yield) + (call $log (i32.const 31)) + (suspend $yield) + (call $log (i32.const 32)) + ) + + (func (export "run") (param $width i32) (param $depth i32) + (global.set $depth (local.get $depth)) + (global.set $width (local.get $width)) + (call $log (i32.const -1)) + (call $scheduler (cont.new $cont (ref.func $main))) + (call $log (i32.const -2)) + ) +) + +(assert_return (invoke "run" (i32.const 0) (i32.const 0))) +(assert_return (invoke "run" (i32.const 0) (i32.const 1))) +(assert_return (invoke "run" (i32.const 1) (i32.const 0))) +(assert_return (invoke "run" (i32.const 1) (i32.const 1))) +(assert_return (invoke "run" (i32.const 3) (i32.const 4))) + + +;; Nested example: generator in a thread + +(module $concurrent-generator + (func $log (import "spectest" "print_i64") (param i64)) + + (tag $syield (import "scheduler" "yield")) + (tag $spawn (import "scheduler" "spawn") (param (ref $cont))) + (func $scheduler (import "scheduler" "scheduler") (param $main (ref $cont))) + + (type $ghook (func (param i64))) + (func $gsum (import "generator" "sum") (param i64 i64) (result i64)) + (global $ghook (import "generator" "hook") (mut (ref $ghook))) + + (global $result (mut i64) (i64.const 0)) + (global $done (mut i32) (i32.const 0)) + + (elem declare func $main $bg-thread $syield) + + (func $syield (param $i i64) + (call $log (local.get $i)) + (suspend $syield) + ) + + (func $bg-thread + (call $log (i64.const -10)) + (loop $l + (call $log (i64.const -11)) + (suspend $syield) + (br_if $l (i32.eqz (global.get $done))) + ) + (call $log (i64.const -12)) + ) + + (func $main (param $i i64) (param $j i64) + (suspend $spawn (cont.new $cont (ref.func $bg-thread))) + (global.set $ghook (ref.func $syield)) + (global.set $result (call $gsum (local.get $i) (local.get $j))) + (global.set $done (i32.const 1)) + ) + + (type $proc (func)) + (type $pproc (func (param i64 i64))) + (type $cont (cont $proc)) + (type $pcont (cont $pproc)) + (func (export "sum") (param $i i64) (param $j i64) (result i64) + (call $log (i64.const -1)) + (call $scheduler + (cont.bind $pcont $cont (local.get $i) (local.get $j) (cont.new $pcont (ref.func $main))) + ) + (call $log (i64.const -2)) + (global.get $result) + ) +) + +(assert_return (invoke "sum" (i64.const 10) (i64.const 20)) (i64.const 165)) + + +;; cont.bind + +(module + (type $f2 (func (param i32 i32) (result i32 i32 i32 i32 i32 i32))) + (type $f4 (func (param i32 i32 i32 i32) (result i32 i32 i32 i32 i32 i32))) + (type $f6 (func (param i32 i32 i32 i32 i32 i32) (result i32 i32 i32 i32 i32 i32))) + + (type $k2 (cont $f2)) + (type $k4 (cont $f4)) + (type $k6 (cont $f6)) + + (elem declare func $f) + (func $f (param i32 i32 i32 i32 i32 i32) (result i32 i32 i32 i32 i32 i32) + (local.get 0) (local.get 1) (local.get 2) + (local.get 3) (local.get 4) (local.get 5) + ) + + (func (export "run") (result i32 i32 i32 i32 i32 i32) + (local $k6 (ref null $k6)) + (local $k4 (ref null $k4)) + (local $k2 (ref null $k2)) + (local.set $k6 (cont.new $k6 (ref.func $f))) + (local.set $k4 (cont.bind $k6 $k4 (i32.const 1) (i32.const 2) (local.get $k6))) + (local.set $k2 (cont.bind $k4 $k2 (i32.const 3) (i32.const 4) (local.get $k4))) + (resume $k2 (i32.const 5) (i32.const 6) (local.get $k2)) + ) +) + +(assert_return (invoke "run") + (i32.const 1) (i32.const 2) (i32.const 3) + (i32.const 4) (i32.const 5) (i32.const 6) +) + + +(module + (tag $e (result i32 i32 i32 i32 i32 i32)) + + (type $f0 (func (result i32 i32 i32 i32 i32 i32 i32))) + (type $f2 (func (param i32 i32) (result i32 i32 i32 i32 i32 i32 i32))) + (type $f4 (func (param i32 i32 i32 i32) (result i32 i32 i32 i32 i32 i32 i32))) + (type $f6 (func (param i32 i32 i32 i32 i32 i32) (result i32 i32 i32 i32 i32 i32 i32))) + + (type $k0 (cont $f0)) + (type $k2 (cont $f2)) + (type $k4 (cont $f4)) + (type $k6 (cont $f6)) + + (elem declare func $f) + (func $f (result i32 i32 i32 i32 i32 i32 i32) + (i32.const 0) (suspend $e) + ) + + (func (export "run") (result i32 i32 i32 i32 i32 i32 i32) + (local $k6 (ref null $k6)) + (local $k4 (ref null $k4)) + (local $k2 (ref null $k2)) + (block $l (result (ref $k6)) + (resume $k0 (on $e $l) (cont.new $k0 (ref.func $f))) + (unreachable) + ) + (local.set $k6) + (local.set $k4 (cont.bind $k6 $k4 (i32.const 1) (i32.const 2) (local.get $k6))) + (local.set $k2 (cont.bind $k4 $k2 (i32.const 3) (i32.const 4) (local.get $k4))) + (resume $k2 (i32.const 5) (i32.const 6) (local.get $k2)) + ) +) + +(assert_return (invoke "run") + (i32.const 0) (i32.const 1) (i32.const 2) (i32.const 3) + (i32.const 4) (i32.const 5) (i32.const 6) +) + +;; Subtyping +(module + (type $ft1 (func (param i32))) + (type $ct1 (sub (cont $ft1))) + + (type $ft0 (func)) + (type $ct0 (sub (cont $ft0))) + + (func $test (param $x (ref $ct1)) + (i32.const 123) + (local.get $x) + (cont.bind $ct1 $ct0) + (drop) + ) +) + +(module + (type $f1 (sub (func (result anyref)))) + (type $f2 (sub $f1 (func (result eqref)))) + (type $c1 (sub (cont $f1))) + (type $c2 (sub $c1 (cont $f2))) +) + +;; Globals +(module + (type $ft (func)) + (type $ct (cont $ft)) + + (global $k (mut (ref null $ct)) (ref.null $ct)) + (global $g (ref null $ct) (ref.null $ct)) + + (func $f) + (elem declare func $f) + + (func (export "set-global") + (global.set $k (cont.new $ct (ref.func $f)))) +) +(assert_return (invoke "set-global")) + +;; Switch +(module + (rec + (type $ft (func (param (ref null $ct)))) + (type $ct (cont $ft))) + + (func $print-i32 (import "spectest" "print_i32") (param i32)) + + (global $fi (mut i32) (i32.const 0)) + (global $gi (mut i32) (i32.const 1)) + + (tag $swap) + + (func $init (export "init") (result i32) + (resume $ct (on $swap switch) + (cont.new $ct (ref.func $g)) + (cont.new $ct (ref.func $f))) + (return (i32.const 42))) + (func $f (type $ft) + (local $nextk (ref null $ct)) + (local.set $nextk (local.get 0)) + (call $print-i32 (global.get $fi)) + (switch $ct $swap (local.get $nextk)) + (local.set $nextk) + (call $print-i32 (global.get $fi)) + (switch $ct $swap (local.get $nextk)) + (drop)) + (func $g (type $ft) + (local $nextk (ref null $ct)) + (local.set $nextk (local.get 0)) + (call $print-i32 (global.get $gi)) + (switch $ct $swap (local.get $nextk)) + (local.set $nextk) + (call $print-i32 (global.get $gi))) + (elem declare func $f $g) +) +(assert_return (invoke "init") (i32.const 42)) + +(module + (rec + (type $ft (func (param i32) (param (ref null $ct)) (result i32))) + (type $ct (cont $ft))) + + (func $print-i32 (import "spectest" "print_i32") (param i32)) + + (tag $swap (result i32)) + + (func $init (export "init") (result i32) + (resume $ct (on $swap switch) + (i32.const 1) + (cont.new $ct (ref.func $g)) + (cont.new $ct (ref.func $f)))) + (func $f (type $ft) + (local $i i32) + (local $nextk (ref null $ct)) + (local.set $i (local.get 0)) + (local.set $nextk (local.get 1)) + (call $print-i32 (local.get $i)) + (switch $ct $swap (i32.add (i32.const 1) (local.get $i)) (local.get $nextk)) + (local.set $nextk) + (local.set $i) + (call $print-i32 (local.get $i)) + (switch $ct $swap (i32.add (i32.const 1) (local.get $i)) (local.get $nextk)) + (unreachable)) + (func $g (type $ft) + (local $i i32) + (local $nextk (ref null $ct)) + (local.set $i (local.get 0)) + (local.set $nextk (local.get 1)) + (call $print-i32 (local.get $i)) + (switch $ct $swap (i32.add (i32.const 1) (local.get $i)) (local.get $nextk)) + (local.set $nextk) + (local.set $i) + (call $print-i32 (local.get $i)) + (return (local.get $i))) + (elem declare func $f $g) +) +(assert_return (invoke "init") (i32.const 4)) + + +(assert_invalid + (module + (rec + (type $ft (func (param (ref null $ct)))) + (type $ct (cont $ft))) + (type $ft2 (func)) + (type $ct2 (cont $ft2)) + + (tag $swap) + (func $f (type $ft) + (switch $ct $swap (cont.new $ct2 (ref.null $ft2))) + (drop))) + "type mismatch") + +(assert_invalid + (module + (rec + (type $ft (func (param i32) (param (ref null $ct)))) + (type $ct (cont $ft))) + + (tag $swap) + (func $f (type $ft) + (switch $ct $swap (i64.const 0) (local.get 1)) + (drop) + (drop))) + "type mismatch") + +(module + (type $ft1 (func)) + (type $ct1 (cont $ft1)) + (rec + (type $ft2 (func (param (ref null $ct2)))) + (type $ct2 (cont $ft2))) + + (tag $t) + + (func $suspend (type $ft2) + (suspend $t)) + + (func $switch (type $ft2) + (switch $ct2 $t (local.get 0)) + (drop)) + + (func (export "unhandled-suspend-t") + (resume $ct2 (on $t switch) + (cont.new $ct2 (ref.func $suspend)) + (cont.new $ct2 (ref.func $suspend)))) + (func (export "unhandled-switch-t") + (block $l (result (ref $ct1)) + (resume $ct2 (on $t $l) + (cont.new $ct2 (ref.func $switch)) + (cont.new $ct2 (ref.func $switch))) + (unreachable) + ) + (unreachable)) + + (elem declare func $suspend $switch) +) +(assert_suspension (invoke "unhandled-suspend-t") "unhandled tag") +(assert_suspension (invoke "unhandled-switch-t") "unhandled tag") + +(module + (rec + (type $ft (func (param (ref null $ct)))) + (type $ct (cont $ft))) + + (tag $t) + + (func + (cont.new $ct (ref.null $ft)) + (unreachable)) + (func + (cont.bind $ct $ct (ref.null $ct)) + (unreachable)) + (func + (resume $ct (ref.null $ct) (ref.null $ct)) + (unreachable)) + (func + (resume_throw $ct $t (ref.null $ct)) + (unreachable)) + (func + (switch $ct $t (ref.null $ct)) + (unreachable)) +) + +(module $co2 + (type $task (func (result i32))) ;; type alias task = [] -> [] + (type $ct (cont $task)) ;; type alias ct = $task + (tag $pause (export "pause")) ;; pause : [] -> [] + (tag $cancel (export "cancel")) ;; cancel : [] -> [] + ;; run : [(ref $task) (ref $task)] -> [] + ;; implements a 'seesaw' (c.f. Ganz et al. (ICFP@99)) + (func $run (export "seesaw") (param $up (ref $ct)) (param $down (ref $ct)) (result i32) + (local $result i32) + ;; run $up + (loop $run_next (result i32) + (block $on_pause (result (ref $ct)) + (resume $ct (on $pause $on_pause) + (local.get $up)) + ;; $up finished, store its result + (local.set $result) + ;; next cancel $down + (block $on_cancel + (try_table (catch $cancel $on_cancel) + ;; inject the cancel exception into $down + (resume_throw $ct $cancel (local.get $down)) + (drop) ;; drop the return value if it handled $cancel + ;; itself and returned normally... + ) + ) ;; ... otherwise catch $cancel and return $up's result. + (return (local.get $result)) + ) ;; on_pause clause, stack type: [(cont $ct)] + (local.set $up) + ;; swap $up and $down + (local.get $down) + (local.set $down (local.get $up)) + (local.set $up) + (br $run_next) + ) + ) +) +(register "co2") + +(module $client + (type $task-0 (func (param i32) (result i32))) + (type $ct-0 (cont $task-0)) + (type $task (func (result i32))) + (type $ct (cont $task)) + + (func $seesaw (import "co2" "seesaw") (param (ref $ct)) (param (ref $ct)) (result i32)) + (func $print-i32 (import "spectest" "print_i32") (param i32)) + (tag $pause (import "co2" "pause")) + + (func $even (param $niter i32) (result i32) + (local $next i32) ;; zero initialised. + (local $i i32) + (loop $print-next + (call $print-i32 (local.get $next)) + (suspend $pause) + (local.set $next (i32.add (local.get $next) (i32.const 2))) + (local.set $i (i32.add (local.get $i) (i32.const 1))) + (br_if $print-next (i32.lt_u (local.get $i) (local.get $niter))) + ) + (local.get $next) + ) + (func $odd (param $niter i32) (result i32) + (local $next i32) ;; zero initialised. + (local $i i32) + (local.set $next (i32.const 1)) + (loop $print-next + (call $print-i32 (local.get $next)) + (suspend $pause) + (local.set $next (i32.add (local.get $next) (i32.const 2))) + (local.set $i (i32.add (local.get $i) (i32.const 1))) + (br_if $print-next (i32.lt_u (local.get $i) (local.get $niter))) + ) + (local.get $next) + ) + + (func (export "main") (result i32) + (call $seesaw + (cont.bind $ct-0 $ct + (i32.const 5) (cont.new $ct-0 (ref.func $even))) + (cont.bind $ct-0 $ct + (i32.const 5) (cont.new $ct-0 (ref.func $odd))))) + + (elem declare func $even $odd) +) +(assert_return (invoke "main") (i32.const 10)) + +(module + (type $f1 (func (result i32))) + (type $c1 (cont $f1)) + (type $f2 (func (param (ref null $c1)) (result i32))) + (type $c2 (cont $f2)) + (type $f3 (func (param (ref null $c2)) (result i32))) + (type $c3 (cont $f3)) + (tag $e (result i32)) + + (func $fn_1 (param (ref null $c2)) (result i32) + (local.get 0) + (switch $c2 $e) + (i32.const 24) + ) + (elem declare func $fn_1) + + (func $fn_2 (result i32) + (cont.new $c3 (ref.func $fn_1)) + (switch $c3 $e) + (drop) + (i32.const -1) + ) + (elem declare func $fn_2) + + (func (export "main") (result i32) + (cont.new $c1 (ref.func $fn_2)) + (resume $c1 (on $e switch)) + ) +) + +(assert_return (invoke "main") (i32.const -1)) + +;; Syntax: check unfolded forms +(module + (type $ft (func)) + (type $ct (cont $ft)) + (rec + (type $ft2 (func (param (ref null $ct2)))) + (type $ct2 (cont $ft2))) + + (tag $yield (param i32)) + (tag $swap) + + ;; Check cont.new + (func (result (ref $ct)) + ref.null $ft + block (param (ref null $ft)) (result (ref $ct)) + cont.new $ct + end + ) + ;; Check cont.bind + (func (param (ref $ct)) (result (ref $ct)) + local.get 0 + block (param (ref $ct)) (result (ref $ct)) + cont.bind $ct $ct + end + ) + ;; Check suspend + (func + block + suspend $swap + end + ) + ;; Check resume + (func (param $k (ref $ct)) (result i32) + (local.get $k) + block $on_yield (param (ref $ct)) (result i32 (ref $ct)) + resume $ct (on $yield $on_yield) + i32.const 42 + return + end + local.set $k + ) + ;; Check resume_throw + (func (param $k (ref $ct)) (result i32) + block $on_yield (result i32 (ref $ct)) + i32.const 42 + local.get $k + resume_throw $ct $yield + i32.const 42 + return + end + local.set $k + ) + ;; Check switch + (func (param $k (ref $ct2)) + local.get $k + block (param (ref $ct2)) (result (ref null $ct2)) + switch $ct2 $swap + end + drop + ) +) + +;; Syntax: check instructions in tail position in unfolded form +(module + (type $ft (func)) + (type $ct (cont $ft)) + (rec + (type $ft2 (func (param (ref null $ct2)))) + (type $ct2 (cont $ft2))) + + (tag $yield (param i32)) + (tag $swap) + + ;; Check cont.new + (func (result (ref $ct)) + ref.null $ft + cont.new $ct + ) + ;; Check cont.bind + (func (param (ref $ct)) (result (ref $ct)) + local.get 0 + cont.bind $ct $ct + ) + + ;; Check resume + (func (;2;) (param $k (ref $ct)) + local.get $k + resume $ct + ) + ;; Check resume_throw + (func (param $k (ref $ct)) + i32.const 42 + local.get $k + resume_throw $ct $yield + ) + ;; Check switch + (func (param $k (ref $ct2)) (result (ref null $ct2)) + local.get $k + switch $ct2 $swap + ) + ;; Check suspend + (func + suspend $swap + ) +) + +(module + (type $ft0 (func)) + (type $ct0 (cont $ft0)) + + (type $ft1 (func (param (ref $ct0)))) + (type $ct1 (cont $ft1)) + + (tag $t) + + (func $f + (cont.new $ct1 (ref.func $g)) + (switch $ct1 $t) + ) + (elem declare func $f) + + (func $g (param (ref $ct0))) + (elem declare func $g) + + (func $entry + (cont.new $ct0 (ref.func $f)) + (resume $ct0 (on $t switch)) + ) +) + +(assert_invalid + (module + (rec + (type $ft (func (param (ref $ct)))) + (type $ct (cont $ft))) + (tag $t (param i32)) + + (func (param $k (ref $ct)) + (switch $ct $t))) + "type mismatch in switch tag") From b98eaa8e53238def9a38d7e7cd1ab7742fb1380d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 28 Jul 2025 14:21:43 -0700 Subject: [PATCH 027/125] work --- src/wasm/wasm.cpp | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 27d44217eac..fc04b2c816e 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -1529,10 +1529,16 @@ void Resume::finalize() { if (handleUnreachableOperands(this)) { return; } + if (cont->type.isNull()) { + // This will never be executed and the instruction will not be emitted. + // Model this with an uninhabitable cast type. + type = cont->type.with(NonNullable); + return; + } - assert(this->cont->type.isContinuation()); + assert(cont->type.isContinuation()); const Signature& contSig = - this->cont->type.getHeapType().getContinuation().type.getSignature(); + cont->type.getHeapType().getContinuation().type.getSignature(); type = contSig.results; } @@ -1544,10 +1550,16 @@ void ResumeThrow::finalize() { if (handleUnreachableOperands(this)) { return; } + if (cont->type.isNull()) { + // This will never be executed and the instruction will not be emitted. + // Model this with an uninhabitable cast type. + type = cont->type.with(NonNullable); + return; + } - assert(this->cont->type.isContinuation()); + assert(cont->type.isContinuation()); const Signature& contSig = - this->cont->type.getHeapType().getContinuation().type.getSignature(); + cont->type.getHeapType().getContinuation().type.getSignature(); type = contSig.results; } @@ -1559,10 +1571,16 @@ void StackSwitch::finalize() { if (handleUnreachableOperands(this)) { return; } + if (cont->type.isNull()) { + // This will never be executed and the instruction will not be emitted. + // Model this with an uninhabitable cast type. + type = cont->type.with(NonNullable); + return; + } - assert(this->cont->type.isContinuation()); + assert(cont->type.isContinuation()); Type params = - this->cont->type.getHeapType().getContinuation().type.getSignature().params; + cont->type.getHeapType().getContinuation().type.getSignature().params; assert(params.size() > 0); Type cont = params[params.size() - 1]; assert(cont.isContinuation()); From e1cf473bc54a6fbd90aa26089bfc6da6ff801ac6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 28 Jul 2025 14:26:40 -0700 Subject: [PATCH 028/125] work --- src/wasm-interpreter.h | 4 +++- test/spec/cont.wast | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 5692e1aea0c..47c7bf79f7d 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4507,7 +4507,9 @@ class ModuleRunnerBase : public ExpressionRunner { // No suspension; all done. return Flow(); } - Flow visitResumeThrow(ResumeThrow* 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/test/spec/cont.wast b/test/spec/cont.wast index 29c4848854a..e947f3b898c 100644 --- a/test/spec/cont.wast +++ b/test/spec/cont.wast @@ -124,8 +124,8 @@ (assert_return (invoke "handled")) (assert_exception (invoke "uncaught-1")) -(assert_exception (invoke "uncaught-2")) -(assert_exception (invoke "uncaught-3")) +;; TODO: resume_throw (assert_exception (invoke "uncaught-2")) +;; TODO: resume_throw (assert_exception (invoke "uncaught-3")) (assert_trap (invoke "non-linear-1") "continuation already consumed") (assert_trap (invoke "non-linear-2") "continuation already consumed") From 08ad3883069c91c2e172be17caf497dfda3d52f5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 28 Jul 2025 14:30:52 -0700 Subject: [PATCH 029/125] work --- src/literal.h | 4 ++++ src/wasm-interpreter.h | 22 +++++++++++----------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/literal.h b/src/literal.h index 934ae00eae3..ab672d460de 100644 --- a/src/literal.h +++ b/src/literal.h @@ -815,6 +815,10 @@ struct ContData { // The continuation type. HeapType type; + // Whether we executed. Continuations are one-shot, so they may not be + // executed a second time. + bool executed = false; + ContData(Name func, Literals resumeInfo, HeapType type) : func(func), resumeInfo(resumeInfo), type(type) {} }; diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 47c7bf79f7d..5c83fc5e2a1 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4438,7 +4438,7 @@ class ModuleRunnerBase : public ExpressionRunner { // TODO where? // Stack of currently-running continuations. - std::vector> continuationEntryStack; + std::vector> continuationStack; Flow visitSuspend(Suspend* curr) { Literals arguments; @@ -4449,21 +4449,21 @@ class ModuleRunnerBase : public ExpressionRunner { // Generate a continuation to proceed from here, and add it as another // value. The name of the function at the bottom of the stack is on - // continuationEntryStack. - if (continuationEntryStack.empty()) { + // continuationStack. + if (continuationStack.empty()) { trap("no continuation to suspend"); } - auto old = continuationEntryStack.back(); - continuationEntryStack.pop_back(); + auto old = continuationStack.back(); + continuationStack.pop_back(); // Copy the continuation, and add stack info so it can be restored from // here. auto cont = Literal( std::make_shared(old->func, Literals{}, old->type)); + assert(!cont->executed); // TODO: save the stack!!1 arguments.push_back(cont); - // Continuations are one-shot, so make the old one invalid. - old->func = Name(); - old->type = HeapTypes::none; + // Continuations are one-shot; mark the old one accordingly. + old->executed = true; return Flow(SUSPEND_FLOW, curr->tag, std::move(arguments)); } Flow visitResume(Resume* curr) { @@ -4474,11 +4474,11 @@ class ModuleRunnerBase : public ExpressionRunner { // Get and execute the continuation. auto contData = flow.getSingleValue().getContData(); - Name func = contData->func; - if (!func) { + if (contData->executed) { trap("continuation already executed"); } - continuationEntryStack.push_back(contData); + Name func = contData->func; + continuationStack.push_back(contData); #if WASM_INTERPRETER_DEBUG std::cout << self()->indent() << "resuming func " << func << '\n'; #endif From 32bddbec7e822501a73684b6ed00b163adfd24c8 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 28 Jul 2025 14:43:38 -0700 Subject: [PATCH 030/125] work --- src/wasm-interpreter.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 5c83fc5e2a1..360da18d16a 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4459,7 +4459,6 @@ class ModuleRunnerBase : public ExpressionRunner { // here. auto cont = Literal( std::make_shared(old->func, Literals{}, old->type)); - assert(!cont->executed); // TODO: save the stack!!1 arguments.push_back(cont); // Continuations are one-shot; mark the old one accordingly. From 1dc1bbb2a417b73cf978fb59159228dcc9c76781 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 28 Jul 2025 14:44:56 -0700 Subject: [PATCH 031/125] work --- src/wasm-interpreter.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 360da18d16a..8fd822a3a5c 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4461,8 +4461,6 @@ class ModuleRunnerBase : public ExpressionRunner { std::make_shared(old->func, Literals{}, old->type)); // TODO: save the stack!!1 arguments.push_back(cont); - // Continuations are one-shot; mark the old one accordingly. - old->executed = true; return Flow(SUSPEND_FLOW, curr->tag, std::move(arguments)); } Flow visitResume(Resume* curr) { @@ -4476,6 +4474,7 @@ class ModuleRunnerBase : public ExpressionRunner { if (contData->executed) { trap("continuation already executed"); } + contData->executed = true; Name func = contData->func; continuationStack.push_back(contData); #if WASM_INTERPRETER_DEBUG From ffbf718668780a412e9e5291dfbb2198d7cf3090 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 28 Jul 2025 14:46:45 -0700 Subject: [PATCH 032/125] work --- test/spec/cont.wast | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spec/cont.wast b/test/spec/cont.wast index e947f3b898c..c681b404c04 100644 --- a/test/spec/cont.wast +++ b/test/spec/cont.wast @@ -130,7 +130,7 @@ (assert_trap (invoke "non-linear-1") "continuation already consumed") (assert_trap (invoke "non-linear-2") "continuation already consumed") (assert_trap (invoke "non-linear-3") "continuation already consumed") -(assert_trap (invoke "non-linear-4") "continuation already consumed") +;; TODO: cont.bind (assert_trap (invoke "non-linear-4") "continuation already consumed") (assert_invalid (module From 54e6db1918d5eb30f4a59f08cc8d30aa319dc0fa Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 28 Jul 2025 14:57:33 -0700 Subject: [PATCH 033/125] work --- src/wasm-interpreter.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 8fd822a3a5c..3033fc27328 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4464,6 +4464,11 @@ class ModuleRunnerBase : public ExpressionRunner { return Flow(SUSPEND_FLOW, curr->tag, std::move(arguments)); } Flow visitResume(Resume* curr) { + Literals arguments; + Flow flow = self()->generateArguments(curr->operands, arguments); + if (flow.breaking()) { + return flow; + } auto flow = self()->visit(curr->cont); if (flow.breaking()) { return flow; @@ -4480,7 +4485,7 @@ class ModuleRunnerBase : public ExpressionRunner { #if WASM_INTERPRETER_DEBUG std::cout << self()->indent() << "resuming func " << func << '\n'; #endif - Flow ret = callFunction(func, {}); + Flow ret = callFunction(func, arguments); #if WASM_INTERPRETER_DEBUG std::cout << self()->indent() << "finished resuming, with " << ret << '\n'; #endif From 075268532590dc637d2362f2442553c5addbf7b6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 28 Jul 2025 14:58:54 -0700 Subject: [PATCH 034/125] work --- src/wasm-interpreter.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 3033fc27328..d741fe96f34 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4469,7 +4469,7 @@ class ModuleRunnerBase : public ExpressionRunner { if (flow.breaking()) { return flow; } - auto flow = self()->visit(curr->cont); + flow = self()->visit(curr->cont); if (flow.breaking()) { return flow; } From 83f26a2795bca270404d0eee66db938c19fd25eb Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 28 Jul 2025 15:00:47 -0700 Subject: [PATCH 035/125] work --- src/wasm-interpreter.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index d741fe96f34..1c6efa8472b 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4622,8 +4622,9 @@ class ModuleRunnerBase : public ExpressionRunner { auto type = flow.getType(); if (flow.breakTo == SUSPEND_FLOW) { - // When suspending, the result is a continuation. - assert(type.isContinuation()); + // When suspending, the last value is a continuation. + assert(type.size() > 0); + assert(type[type.size() - 1].isContinuation()); } else { // We are normally executing (not suspending), and therefore cannot still // be breaking, which would mean we missed our stop. From 7dc4655a59794e059f7bb05671fbfc5558746898 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 28 Jul 2025 15:11:59 -0700 Subject: [PATCH 036/125] work --- src/wasm-interpreter.h | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 1c6efa8472b..c61a92ec585 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -201,7 +201,8 @@ class ExpressionRunner : public OverriddenVisitor { RelaxedBehavior relaxedBehavior = RelaxedBehavior::NonConstant; // We save values from visit() until they are consumed, so that we can pause/ - // resume. + // resume. TODO: move into ModuleRunner, since we need FunctionState anyhow + // for locals? std::optional> valueStack; #if WASM_INTERPRETER_DEBUG @@ -4437,8 +4438,10 @@ class ModuleRunnerBase : public ExpressionRunner { } // TODO where? - // Stack of currently-running continuations. - std::vector> continuationStack; + // Currently-running continuation. TODO: stack? + std::shared_ptr currContinuation; + + // Resuming Flow visitSuspend(Suspend* curr) { Literals arguments; @@ -4448,18 +4451,19 @@ class ModuleRunnerBase : public ExpressionRunner { } // Generate a continuation to proceed from here, and add it as another - // value. The name of the function at the bottom of the stack is on - // continuationStack. - if (continuationStack.empty()) { + // value. The name of the function at the bottom of the stack is in + // currContinuation. + if (!currContinuation) { trap("no continuation to suspend"); } - auto old = continuationStack.back(); - continuationStack.pop_back(); + auto old = currContinuation; + currContinuation.reset(); // Copy the continuation, and add stack info so it can be restored from // here. auto cont = Literal( std::make_shared(old->func, Literals{}, old->type)); - // TODO: save the stack!!1 + // TODO: save the valueStack! + // TODO: save the locals on the function stacks! arguments.push_back(cont); return Flow(SUSPEND_FLOW, curr->tag, std::move(arguments)); } @@ -4481,7 +4485,7 @@ class ModuleRunnerBase : public ExpressionRunner { } contData->executed = true; Name func = contData->func; - continuationStack.push_back(contData); + currContinuation = contData; #if WASM_INTERPRETER_DEBUG std::cout << self()->indent() << "resuming func " << func << '\n'; #endif From 91ea6d8c13ec879b03c40da36660bca633104ce5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 28 Jul 2025 15:12:33 -0700 Subject: [PATCH 037/125] work --- src/wasm-interpreter.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index c61a92ec585..dcb9e361a8e 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4462,6 +4462,7 @@ class ModuleRunnerBase : public ExpressionRunner { // here. auto cont = Literal( std::make_shared(old->func, Literals{}, old->type)); + // TODO: save the call stack! // TODO: save the valueStack! // TODO: save the locals on the function stacks! arguments.push_back(cont); From 696c5ab2447b42c578c8771c228051042e048717 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 28 Jul 2025 15:17:26 -0700 Subject: [PATCH 038/125] work --- test/spec/cont_simple.wast | 41 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 test/spec/cont_simple.wast diff --git a/test/spec/cont_simple.wast b/test/spec/cont_simple.wast new file mode 100644 index 00000000000..3691bcb706b --- /dev/null +++ b/test/spec/cont_simple.wast @@ -0,0 +1,41 @@ +(module $state + (type $f (func)) + (type $k (cont $f)) + + (import "spectest" "print" (func $print (param i32))) + + (tag $more) + + (func $run (export "run") + (local $k (ref $k)) + (local.set $k + (cont.new $k (ref.func $f)) + ) + (loop $loop + (call $print (i32.const 100)) + (block $on (result (ref $k)) + (resume $k (on $more $on) + (local.get $k) + ) + (call $print (i32.const 200)) + (return) + ) + ;; on + (call $print (i32.const 300)) + (local.set $k) + (br $loop) + ) + (unreachable) + ) + + (func $f + (call $print (i32.const -1)) + (suspend $more) + (call $print (i32.const -2)) + (suspend $more) + (call $print (i32.const -3)) + ) +) + +(assert_return (invoke "run") (i32.const 19)) + From 040831f087c5f5f51d6871542dc60571f30d4369 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 28 Jul 2025 15:19:22 -0700 Subject: [PATCH 039/125] work --- src/literal.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/literal.h b/src/literal.h index ab672d460de..65644eaf532 100644 --- a/src/literal.h +++ b/src/literal.h @@ -808,6 +808,10 @@ struct ContData { // TODO: handle cross-module calls using something other than a Name here. Name func; + // The expression to resume execution at: where we suspended last, or, if this + // is the first execution, nullptr (which means to resume at the very start). + Expression* resumeExpr = nullptr; + // Information about how to resume execution, a list of instruction and data // that we "replay" into the value and call stacks. Literals resumeInfo; From e950b6ea98272369c80728dbccc5fb528101bc86 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 28 Jul 2025 15:22:26 -0700 Subject: [PATCH 040/125] work --- src/literal.h | 12 +++++++----- src/wasm-interpreter.h | 11 +++++------ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/literal.h b/src/literal.h index 65644eaf532..3138882342a 100644 --- a/src/literal.h +++ b/src/literal.h @@ -802,12 +802,17 @@ struct ExnData { ExnData(Name tag, Literals payload) : tag(tag), payload(payload) {} }; +class Expression; + // The data of a (ref cont) literal. struct ContData { // The function this continuation begins in. // TODO: handle cross-module calls using something other than a Name here. Name func; + // The continuation type. + HeapType type; + // The expression to resume execution at: where we suspended last, or, if this // is the first execution, nullptr (which means to resume at the very start). Expression* resumeExpr = nullptr; @@ -816,15 +821,12 @@ struct ContData { // that we "replay" into the value and call stacks. Literals resumeInfo; - // The continuation type. - HeapType type; - // Whether we executed. Continuations are one-shot, so they may not be // executed a second time. bool executed = false; - ContData(Name func, Literals resumeInfo, HeapType type) - : func(func), resumeInfo(resumeInfo), type(type) {} + ContData(Name func, HeapType type) + : func(func), type(type) {} }; } // namespace wasm diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index dcb9e361a8e..0e16b247d6b 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4421,10 +4421,9 @@ class ModuleRunnerBase : public ExpressionRunner { return funcFlow; } Name func = funcFlow.getSingleValue().getFunc(); - // The initial data is empty, as nothing has executed yet, so we don't - // need any information about how to resume (we resume by just running). + // Create a fresh continuation. return Literal( - std::make_shared(func, Literals{}, curr->type.getHeapType())); + std::make_shared(func, curr->type.getHeapType())); } Flow visitContBind(ContBind* curr) { return Flow(NONCONSTANT_FLOW); @@ -4460,12 +4459,12 @@ class ModuleRunnerBase : public ExpressionRunner { currContinuation.reset(); // Copy the continuation, and add stack info so it can be restored from // here. - auto cont = Literal( - std::make_shared(old->func, Literals{}, old->type)); + auto contData = std::make_shared(old->func, Literals{}, old->type); + contData->resumeExpr = curr; // TODO: save the call stack! // TODO: save the valueStack! // TODO: save the locals on the function stacks! - arguments.push_back(cont); + arguments.push_back(Literal(contData)); return Flow(SUSPEND_FLOW, curr->tag, std::move(arguments)); } Flow visitResume(Resume* curr) { From db8b502cbbe483c3fbe2852f6304b2e5226411e1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 28 Jul 2025 15:29:46 -0700 Subject: [PATCH 041/125] work --- src/wasm-interpreter.h | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 0e16b247d6b..bd8e991275b 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -61,6 +61,8 @@ struct NonconstantException {}; extern Name RETURN_FLOW, RETURN_CALL_FLOW, NONCONSTANT_FLOW, SUSPEND_FLOW; +// TODO: moveContData here? + // Stuff that flows around during executing expressions: a literal, or a change // in control flow. class Flow { @@ -215,6 +217,18 @@ class ExpressionRunner : public OverriddenVisitor { } #endif + // Suspend/resume support for continuations. + // TODO where? + // Currently-running continuation. TODO: stack? + std::shared_ptr currContinuation; + + // Set when we are resuming execution, that is, re-winding the stack. + // |currContinuation| must be set when this is true, as that is the + // continuation we are resuming. When we finish re-winding and continue normal + // execution in the continutation, |currContinuation| remain set while this + // will be cleared. + bool resuming = true; + public: ExpressionRunner(Module* module = nullptr, Index maxDepth = NO_LIMIT, @@ -244,6 +258,7 @@ class ExpressionRunner : public OverriddenVisitor { Flow ret; if (valueStack) { auto oldValueStackSize = valueStack->size(); + // if resuming, do something, and compare currContinuation->resumeExpr ret = OverriddenVisitor::visit(curr); valueStack->resize(oldValueStackSize); } else { @@ -4427,21 +4442,8 @@ class ModuleRunnerBase : public ExpressionRunner { } Flow visitContBind(ContBind* curr) { return Flow(NONCONSTANT_FLOW); - /* - Literals arguments; - Flow flow = self()->generateArguments(curr->operands, arguments); - if (flow.breaking()) { - return flow; - } - */ } - // TODO where? - // Currently-running continuation. TODO: stack? - std::shared_ptr currContinuation; - - // Resuming - Flow visitSuspend(Suspend* curr) { Literals arguments; Flow flow = self()->generateArguments(curr->operands, arguments); @@ -4486,6 +4488,7 @@ class ModuleRunnerBase : public ExpressionRunner { contData->executed = true; Name func = contData->func; currContinuation = contData; + resuming = true; #if WASM_INTERPRETER_DEBUG std::cout << self()->indent() << "resuming func " << func << '\n'; #endif From 124739aeb1775006a2e678844e2a3046b70c00fc Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 28 Jul 2025 16:35:46 -0700 Subject: [PATCH 042/125] work --- src/literal.h | 27 ---------------------- src/wasm-interpreter.h | 52 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 50 insertions(+), 29 deletions(-) diff --git a/src/literal.h b/src/literal.h index 3138882342a..fb7d5183e7b 100644 --- a/src/literal.h +++ b/src/literal.h @@ -802,33 +802,6 @@ struct ExnData { ExnData(Name tag, Literals payload) : tag(tag), payload(payload) {} }; -class Expression; - -// The data of a (ref cont) literal. -struct ContData { - // The function this continuation begins in. - // TODO: handle cross-module calls using something other than a Name here. - Name func; - - // The continuation type. - HeapType type; - - // The expression to resume execution at: where we suspended last, or, if this - // is the first execution, nullptr (which means to resume at the very start). - Expression* resumeExpr = nullptr; - - // Information about how to resume execution, a list of instruction and data - // that we "replay" into the value and call stacks. - Literals resumeInfo; - - // Whether we executed. Continuations are one-shot, so they may not be - // executed a second time. - bool executed = false; - - ContData(Name func, HeapType type) - : func(func), type(type) {} -}; - } // namespace wasm namespace std { diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index bd8e991275b..b816771f69b 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -61,8 +61,6 @@ struct NonconstantException {}; extern Name RETURN_FLOW, RETURN_CALL_FLOW, NONCONSTANT_FLOW, SUSPEND_FLOW; -// TODO: moveContData here? - // Stuff that flows around during executing expressions: a literal, or a change // in control flow. class Flow { @@ -125,6 +123,32 @@ class Flow { } }; +// Suspend/resume support. The critical data is stored inside a continuation +// Literal, using this data structure. +struct ContData { + // The function this continuation begins in. + // TODO: handle cross-module calls using something other than a Name here. + Name func; + + // The continuation type. + HeapType type; + + // The expression to resume execution at: where we suspended last, or, if this + // is the first execution, nullptr (which means to resume at the very start). + Expression* resumeExpr = nullptr; + + // Information about how to resume execution, a list of instruction and data + // that we "replay" into the value and call stacks. + Literals resumeInfo; + + // Whether we executed. Continuations are one-shot, so they may not be + // executed a second time. + bool executed = false; + + ContData(Name func, HeapType type) + : func(func), type(type) {} +}; + // Execute an expression template class ExpressionRunner : public OverriddenVisitor { @@ -297,11 +321,31 @@ class ExpressionRunner : public OverriddenVisitor { curr = curr->list[0]->cast(); stack.push_back(curr); } + + // Suspend/resume support. + auto suspend = [&](Index blockIndex) { + Literals entry; + // To return to the same place when we resume, we add an entry with two + // pieces of information: the index in the stack of blocks, and the index + // in the block. + entry.push_back(Literal(uint32_t(stack.size()))); + entry.push_back(Literal(uint32_t(blockIndex))); + assert(currContinuation); + currContinuation waka + }; + if (resuming) { + auto entry + } + Flow flow; auto* top = stack.back(); while (stack.size() > 0) { curr = stack.back(); stack.pop_back(); + if (flow.suspendTag) { + suspend(0); + return Flow(); + } if (flow.breaking()) { flow.clearIf(curr->name); continue; @@ -313,6 +357,10 @@ class ExpressionRunner : public OverriddenVisitor { continue; } flow = visit(list[i]); + if (flow.suspendTag) { + suspend(i); + return Flow(); + } if (flow.breaking()) { flow.clearIf(curr->name); break; From 6aba061bf4a155c9f59ca8715f6cf1c238c2cf7e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 28 Jul 2025 16:47:08 -0700 Subject: [PATCH 043/125] work --- src/wasm-interpreter.h | 43 ++++++++++++++++++++++++++++-------------- src/wasm/literal.cpp | 4 ++-- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index b816771f69b..7a9a73ee624 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -138,8 +138,10 @@ struct ContData { Expression* resumeExpr = nullptr; // Information about how to resume execution, a list of instruction and data - // that we "replay" into the value and call stacks. - Literals resumeInfo; + // that we "replay" into the value and call stacks. For convenience we split + // this into separate entries, each one a Literals. Typically an instruction + // will emit a single Literals for itself. + std::vector resumeInfo; // Whether we executed. Continuations are one-shot, so they may not be // executed a second time. @@ -330,11 +332,26 @@ class ExpressionRunner : public OverriddenVisitor { // in the block. entry.push_back(Literal(uint32_t(stack.size()))); entry.push_back(Literal(uint32_t(blockIndex))); +#if WASM_INTERPRETER_DEBUG + std::cout << indent() << "suspend block: " << entry << "\n"; +#endif assert(currContinuation); - currContinuation waka + currContinuation->resumeInfo.push_back(entry); }; + Index blockIndex = 0; if (resuming) { - auto entry + assert(currContinuation); + assert(!currContinuation->resumeInfo.empty()); + auto entry = currContinuation->resumeInfo.back(); +#if WASM_INTERPRETER_DEBUG + std::cout << indent() << "resume block: " << entry << "\n"; +#endif + currContinuation->resumeInfo.pop_back(); + assert(entry.size() == 2); + Index stackIndex = entry[0].geti32(); + blockIndex = entry[1].geti32(); + assert(stack.size() >= stackIndex); + stack.resize(stackIndex); } Flow flow; @@ -342,16 +359,12 @@ class ExpressionRunner : public OverriddenVisitor { while (stack.size() > 0) { curr = stack.back(); stack.pop_back(); - if (flow.suspendTag) { - suspend(0); - return Flow(); - } if (flow.breaking()) { flow.clearIf(curr->name); continue; } auto& list = curr->list; - for (size_t i = 0; i < list.size(); i++) { + for (size_t i = blockIndex; i < list.size(); i++) { if (curr != top && i == 0) { // one of the block recursions we already handled continue; @@ -366,6 +379,8 @@ class ExpressionRunner : public OverriddenVisitor { break; } } + // If there was a value here, we only need it for the top iteration. + blockIndex = 0; } return flow; } @@ -4502,11 +4517,11 @@ class ModuleRunnerBase : public ExpressionRunner { // Generate a continuation to proceed from here, and add it as another // value. The name of the function at the bottom of the stack is in // currContinuation. - if (!currContinuation) { + if (!self()->currContinuation) { trap("no continuation to suspend"); } - auto old = currContinuation; - currContinuation.reset(); + auto old = self()->currContinuation; + self()->currContinuation.reset(); // Copy the continuation, and add stack info so it can be restored from // here. auto contData = std::make_shared(old->func, Literals{}, old->type); @@ -4535,8 +4550,8 @@ class ModuleRunnerBase : public ExpressionRunner { } contData->executed = true; Name func = contData->func; - currContinuation = contData; - resuming = true; + self()->currContinuation = contData; + self()->resuming = true; #if WASM_INTERPRETER_DEBUG std::cout << self()->indent() << "resuming func " << func << '\n'; #endif diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp index 819e1270448..f59cb3bd509 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -14,18 +14,18 @@ * limitations under the License. */ -#include "literal.h" - #include #include #include "emscripten-optimizer/simple_ast.h" #include "fp16.h" #include "ir/bits.h" +#include "literal.h" #include "pretty_printing.h" #include "support/bits.h" #include "support/string.h" #include "support/utilities.h" +#include "wasm-interpreter.h" namespace wasm { From 8cfb369e8aa941c1dbd4a2cab90e9432a0cfdd57 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 28 Jul 2025 16:47:39 -0700 Subject: [PATCH 044/125] work --- src/wasm-interpreter.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 7a9a73ee624..3511b4db338 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4524,7 +4524,7 @@ class ModuleRunnerBase : public ExpressionRunner { self()->currContinuation.reset(); // Copy the continuation, and add stack info so it can be restored from // here. - auto contData = std::make_shared(old->func, Literals{}, old->type); + auto contData = std::make_shared(old->func, old->type); contData->resumeExpr = curr; // TODO: save the call stack! // TODO: save the valueStack! From 4231a844e6cbbce30741726f835e5508889b9564 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 28 Jul 2025 16:48:42 -0700 Subject: [PATCH 045/125] work --- src/wasm-interpreter.h | 2 +- test/spec/cont_simple.wast | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 3511b4db338..f075a4517f4 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -253,7 +253,7 @@ class ExpressionRunner : public OverriddenVisitor { // continuation we are resuming. When we finish re-winding and continue normal // execution in the continutation, |currContinuation| remain set while this // will be cleared. - bool resuming = true; + bool resuming = false; public: ExpressionRunner(Module* module = nullptr, diff --git a/test/spec/cont_simple.wast b/test/spec/cont_simple.wast index 3691bcb706b..c3e2574cd7e 100644 --- a/test/spec/cont_simple.wast +++ b/test/spec/cont_simple.wast @@ -1,3 +1,5 @@ +;; A coroutine with only control flow in a single basic block (no locals, no +;; params, no branching, no value stack). (module $state (type $f (func)) (type $k (cont $f)) From 9b04a61235467a901711115a1e344970f3d29cf4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 28 Jul 2025 16:58:19 -0700 Subject: [PATCH 046/125] work --- src/wasm-interpreter.h | 1 + src/wasm/literal.cpp | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index f075a4517f4..b1832ee2e09 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1,3 +1,4 @@ +#define WASM_INTERPRETER_DEBUG 1 /* * Copyright 2015 WebAssembly Community Group participants * diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp index f59cb3bd509..f6f4c3ff316 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -709,6 +709,16 @@ std::ostream& operator<<(std::ostream& o, Literal literal) { } } else if (heapType.isSignature()) { o << "funcref(" << literal.getFunc() << ")"; + } else if (heapType.isContinuation()) { + auto data = literal.getContData(); + o << "cont(" << data->func << ' ' << data->type; + if (data->resumeExpr) { + o << " resumeExpr=" << getExpressionName(data->resumeExpr); + } + if (!data->resumeInfo.empty()) { + o << " |resumeInfo|=" << data->resumeInfo.size(); + } + o << " executed=" << data->executed << ')'; } else { assert(literal.isData()); auto data = literal.getGCData(); From 387f7b6dfbfb73fd05fe06006a1c68dad87c25dc Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 11:23:46 -0700 Subject: [PATCH 047/125] work --- src/wasm-interpreter.h | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index b1832ee2e09..d5d16f3d884 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -3410,6 +3410,9 @@ class ModuleRunnerBase : public ExpressionRunner { return Flow(RETURN_CALL_FLOW, std::move(arguments)); } +#if WASM_INTERPRETER_DEBUG + std::cout << self()->indent() << "(calling " << target << ")\n"; +#endif Flow ret = callFunction(target, arguments); #if WASM_INTERPRETER_DEBUG std::cout << self()->indent() << "(returned to " << scope->function->name @@ -3443,6 +3446,9 @@ class ModuleRunnerBase : public ExpressionRunner { return Flow(RETURN_CALL_FLOW, std::move(arguments)); } +#if WASM_INTERPRETER_DEBUG + std::cout << self()->indent() << "(calling table)\n"; +#endif Flow ret = info.interface()->callTable( info.name, index, curr->heapType, arguments, curr->type, *self()); #if WASM_INTERPRETER_DEBUG @@ -3474,6 +3480,9 @@ class ModuleRunnerBase : public ExpressionRunner { return Flow(RETURN_CALL_FLOW, std::move(arguments)); } +#if WASM_INTERPRETER_DEBUG + std::cout << self()->indent() << "(calling ref " << targetRef.getFunc() << ")\n"; +#endif Flow ret = callFunction(targetRef.getFunc(), arguments); #if WASM_INTERPRETER_DEBUG std::cout << self()->indent() << "(returned to " << scope->function->name @@ -4552,7 +4561,12 @@ class ModuleRunnerBase : public ExpressionRunner { contData->executed = true; Name func = contData->func; self()->currContinuation = contData; - self()->resuming = true; + if (contData->resumeExpr) { + // There is an expression to resume execution at, so this is not the first + // time we run this function. Mark us as resuming, until we reach that + // expression. + self()->resuming = true; + } #if WASM_INTERPRETER_DEBUG std::cout << self()->indent() << "resuming func " << func << '\n'; #endif @@ -4572,6 +4586,7 @@ class ModuleRunnerBase : public ExpressionRunner { // Asyncify, need to save funcref. ret.suspendTag = Name(); ret.breakTo = curr->handlerBlocks[i]; + self()->currContinuation.reset(); return ret; } } @@ -4661,10 +4676,10 @@ class ModuleRunnerBase : public ExpressionRunner { FunctionScope scope(function, arguments, *self()); #if WASM_INTERPRETER_DEBUG - std::cout << self()->indent() << "entering " << function->name - << "\n with arguments:\n"; + std::cout << self()->indent() << "entering " << function->name << '\n' + << self()->indent() << " with arguments:\n"; for (unsigned i = 0; i < arguments.size(); ++i) { - std::cout << " $" << i << ": " << arguments[i] << '\n'; + std::cout << self()->indent() <<" $" << i << ": " << arguments[i] << '\n'; } #endif From bcf11cd7456ad27cd040f6bd8f68a0badae4ed04 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 11:27:59 -0700 Subject: [PATCH 048/125] work --- src/wasm-interpreter.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index d5d16f3d884..d63a0188c27 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4530,16 +4530,16 @@ class ModuleRunnerBase : public ExpressionRunner { if (!self()->currContinuation) { trap("no continuation to suspend"); } + // Copy the continuation (the old one cannot be resumed again) and add stack + // info so it can be restored from here. auto old = self()->currContinuation; - self()->currContinuation.reset(); - // Copy the continuation, and add stack info so it can be restored from - // here. - auto contData = std::make_shared(old->func, old->type); - contData->resumeExpr = curr; + auto new_ = std::make_shared(old->func, old->type); + self()->currContinuation = new_; + new_->resumeExpr = curr; // TODO: save the call stack! // TODO: save the valueStack! // TODO: save the locals on the function stacks! - arguments.push_back(Literal(contData)); + arguments.push_back(Literal(new_)); return Flow(SUSPEND_FLOW, curr->tag, std::move(arguments)); } Flow visitResume(Resume* curr) { From d31ebff0b89625e0a369f15436e33794908fb9ca Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 11:30:18 -0700 Subject: [PATCH 049/125] work --- test/{spec => lit/exec}/cont_simple.wast | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) rename test/{spec => lit/exec}/cont_simple.wast (82%) diff --git a/test/spec/cont_simple.wast b/test/lit/exec/cont_simple.wast similarity index 82% rename from test/spec/cont_simple.wast rename to test/lit/exec/cont_simple.wast index c3e2574cd7e..01577b47491 100644 --- a/test/spec/cont_simple.wast +++ b/test/lit/exec/cont_simple.wast @@ -1,3 +1,7 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited. + +;; RUN: wasm-opt %s -all --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s + ;; A coroutine with only control flow in a single basic block (no locals, no ;; params, no branching, no value stack). (module $state @@ -39,5 +43,3 @@ ) ) -(assert_return (invoke "run") (i32.const 19)) - From 5c10344f3ecd75ced6bb9d7b0187d4eff5d73caa Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 11:31:53 -0700 Subject: [PATCH 050/125] work --- test/lit/exec/cont_simple.wast | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/lit/exec/cont_simple.wast b/test/lit/exec/cont_simple.wast index 01577b47491..df10301d144 100644 --- a/test/lit/exec/cont_simple.wast +++ b/test/lit/exec/cont_simple.wast @@ -1,6 +1,6 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited. -;; RUN: wasm-opt %s -all --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s +;; RUN: wasm-opt %s -all --fuzz-exec-before -q -o /dev/null 2>&1 | filecheck %s ;; A coroutine with only control flow in a single basic block (no locals, no ;; params, no branching, no value stack). @@ -8,7 +8,7 @@ (type $f (func)) (type $k (cont $f)) - (import "spectest" "print" (func $print (param i32))) + (import "fuzzing-support" "log" (func $log (param i32))) (tag $more) @@ -18,16 +18,16 @@ (cont.new $k (ref.func $f)) ) (loop $loop - (call $print (i32.const 100)) + (call $log (i32.const 100)) (block $on (result (ref $k)) (resume $k (on $more $on) (local.get $k) ) - (call $print (i32.const 200)) + (call $log (i32.const 200)) (return) ) ;; on - (call $print (i32.const 300)) + (call $log (i32.const 300)) (local.set $k) (br $loop) ) @@ -35,11 +35,11 @@ ) (func $f - (call $print (i32.const -1)) + (call $log (i32.const -1)) (suspend $more) - (call $print (i32.const -2)) + (call $log (i32.const -2)) (suspend $more) - (call $print (i32.const -3)) + (call $log (i32.const -3)) ) ) From 9e13c0e9521a02c5669a2a26c87f3ea44bca7c48 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 11:41:30 -0700 Subject: [PATCH 051/125] work --- src/wasm-interpreter.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index d63a0188c27..1ab59d1954b 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -373,7 +373,7 @@ class ExpressionRunner : public OverriddenVisitor { flow = visit(list[i]); if (flow.suspendTag) { suspend(i); - return Flow(); + return flow; } if (flow.breaking()) { flow.clearIf(curr->name); From 41c096282d41c03c7d88bd07de22baef162520d3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 11:46:47 -0700 Subject: [PATCH 052/125] work --- src/wasm-interpreter.h | 4 ++-- test/lit/exec/cont_simple.wast | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 1ab59d1954b..560d13722d7 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -351,8 +351,8 @@ class ExpressionRunner : public OverriddenVisitor { assert(entry.size() == 2); Index stackIndex = entry[0].geti32(); blockIndex = entry[1].geti32(); - assert(stack.size() >= stackIndex); - stack.resize(stackIndex); + assert(stack.size() > stackIndex); + stack.resize(stackIndex + 1); } Flow flow; diff --git a/test/lit/exec/cont_simple.wast b/test/lit/exec/cont_simple.wast index df10301d144..94a41975140 100644 --- a/test/lit/exec/cont_simple.wast +++ b/test/lit/exec/cont_simple.wast @@ -17,8 +17,8 @@ (local.set $k (cont.new $k (ref.func $f)) ) + (call $log (i32.const 100)) (loop $loop - (call $log (i32.const 100)) (block $on (result (ref $k)) (resume $k (on $more $on) (local.get $k) From d9f19e734e15abb839ceaa3ee2a02d1985bb57e0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 11:54:10 -0700 Subject: [PATCH 053/125] work --- src/wasm-interpreter.h | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 560d13722d7..a9ee08a9122 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -32,6 +32,7 @@ #include "fp16.h" #include "ir/intrinsics.h" #include "ir/module-utils.h" +#include "ir/properties.h" #include "support/bits.h" #include "support/safe_integer.h" #include "support/stdckdint.h" @@ -283,13 +284,33 @@ class ExpressionRunner : public OverriddenVisitor { // completes, all values have been consumed, and nothing needs to be // saved. Flow ret; - if (valueStack) { - auto oldValueStackSize = valueStack->size(); - // if resuming, do something, and compare currContinuation->resumeExpr + if (!valueStack) { + // We cannot suspend/resume. Jus execute normally ret = OverriddenVisitor::visit(curr); - valueStack->resize(oldValueStackSize); } else { - ret = OverriddenVisitor::visit(curr); + // We may suspend/resume. To support that, note values on the stack, so we + // can save them if we do suspend. + auto oldValueStackSize = valueStack->size(); + if (!resuming) { + ret = OverriddenVisitor::visit(curr); + } else { + // We are resuming code. + if (Properties::isControlFlowStructure(curr)) { + // Each control flow structure knows how to handle itself. + ret = OverriddenVisitor::visit(curr); + } else if (curr->is()) { + // This is a resume, so we have found our way back to where we + // suspended. + assert(curr == currContinuation->resumeExpr); + // We finished resuming, and will continue from here normally. + resuming = false; + } else { + // Some other instruction. Do not execute it, and only return the + // value we stashed for it. + assert(0); + } + } + valueStack->resize(oldValueStackSize); } if (!ret.breaking()) { From 48decdefc818689de8062386d9a529187b8535b3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 11:55:06 -0700 Subject: [PATCH 054/125] work --- src/wasm-interpreter.h | 1 - test/lit/exec/cont_simple.wast | 8 ++++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index a9ee08a9122..4279f3908b3 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1,4 +1,3 @@ -#define WASM_INTERPRETER_DEBUG 1 /* * Copyright 2015 WebAssembly Community Group participants * diff --git a/test/lit/exec/cont_simple.wast b/test/lit/exec/cont_simple.wast index 94a41975140..dbc6248649b 100644 --- a/test/lit/exec/cont_simple.wast +++ b/test/lit/exec/cont_simple.wast @@ -12,6 +12,14 @@ (tag $more) + ;; CHECK: [fuzz-exec] calling run + ;; CHECK-NEXT: [LoggingExternalInterface logging 100] + ;; CHECK-NEXT: [LoggingExternalInterface logging -1] + ;; CHECK-NEXT: [LoggingExternalInterface logging 300] + ;; CHECK-NEXT: [LoggingExternalInterface logging -2] + ;; CHECK-NEXT: [LoggingExternalInterface logging 300] + ;; CHECK-NEXT: [LoggingExternalInterface logging -3] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] (func $run (export "run") (local $k (ref $k)) (local.set $k From 8469d009eed50b07a28e88159d7bfe62e147e508 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 12:39:35 -0700 Subject: [PATCH 055/125] work --- test/lit/exec/cont_simple.wast | 45 +++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/test/lit/exec/cont_simple.wast b/test/lit/exec/cont_simple.wast index dbc6248649b..24646a53977 100644 --- a/test/lit/exec/cont_simple.wast +++ b/test/lit/exec/cont_simple.wast @@ -1,9 +1,7 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited. -;; RUN: wasm-opt %s -all --fuzz-exec-before -q -o /dev/null 2>&1 | filecheck %s +;; RUN: foreach %s %t wasm-opt -all --fuzz-exec-before -q -o /dev/null 2>&1 | filecheck %s -;; A coroutine with only control flow in a single basic block (no locals, no -;; params, no branching, no value stack). (module $state (type $f (func)) (type $k (cont $f)) @@ -12,42 +10,49 @@ (tag $more) - ;; CHECK: [fuzz-exec] calling run - ;; CHECK-NEXT: [LoggingExternalInterface logging 100] - ;; CHECK-NEXT: [LoggingExternalInterface logging -1] - ;; CHECK-NEXT: [LoggingExternalInterface logging 300] - ;; CHECK-NEXT: [LoggingExternalInterface logging -2] - ;; CHECK-NEXT: [LoggingExternalInterface logging 300] - ;; CHECK-NEXT: [LoggingExternalInterface logging -3] - ;; CHECK-NEXT: [LoggingExternalInterface logging 200] - (func $run (export "run") - (local $k (ref $k)) - (local.set $k - (cont.new $k (ref.func $f)) - ) - (call $log (i32.const 100)) + (func $run (param $k (ref $k)) + ;; Run a coroutine, continuing to resume it until it is complete. + (call $log (i32.const 100)) ;; start (loop $loop (block $on (result (ref $k)) (resume $k (on $more $on) (local.get $k) ) - (call $log (i32.const 200)) + (call $log (i32.const 300)) ;; stop (return) ) ;; on - (call $log (i32.const 300)) + (call $log (i32.const 200)) ;; continue (local.set $k) (br $loop) ) (unreachable) ) - (func $f + ;; A coroutine with only control flow in a single basic block (no locals, no + ;; params, no branching, no value stack). When $run-block, below, runs this, + ;; the result should be to log -1, -2, -3 (with interleaved logging from + ;; $run itself, above, 100, 200, 200, 300). + (func $block (call $log (i32.const -1)) (suspend $more) (call $log (i32.const -2)) (suspend $more) (call $log (i32.const -3)) ) + + ;; CHECK: [fuzz-exec] calling run-block + ;; CHECK-NEXT: [LoggingExternalInterface logging 100] + ;; CHECK-NEXT: [LoggingExternalInterface logging -1] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging -2] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging -3] + ;; CHECK-NEXT: [LoggingExternalInterface logging 300] + (func $run-block (export "run-block") + (call $run + (cont.new $k (ref.func $block)) + ) + ) ) From 1a51fb561d56ae4f6718e271888d149debee7e1f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 12:42:35 -0700 Subject: [PATCH 056/125] work --- test/lit/exec/cont_simple.wast | 40 ++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/test/lit/exec/cont_simple.wast b/test/lit/exec/cont_simple.wast index 24646a53977..1359b8727c5 100644 --- a/test/lit/exec/cont_simple.wast +++ b/test/lit/exec/cont_simple.wast @@ -54,5 +54,45 @@ (cont.new $k (ref.func $block)) ) ) + + ;; Nested blocks, so when we suspend/resume we must traverse that stack + ;; properly. + (func $block-nested + (block $a + (call $log (i32.const -1)) + (suspend $more) + (block $b + (block $c + (call $log (i32.const -2)) + (suspend $more) + (call $log (i32.const -3)) + ) + (call $log (i32.const -4)) + ) + (suspend $more) + (call $log (i32.const -5)) + (suspend $more) + ) + (call $log (i32.const -6)) + ) + + ;; CHECK: [fuzz-exec] calling run-block-nested + ;; CHECK-NEXT: [LoggingExternalInterface logging 100] + ;; CHECK-NEXT: [LoggingExternalInterface logging -1] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging -2] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging -3] + ;; CHECK-NEXT: [LoggingExternalInterface logging -4] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging -5] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging -6] + ;; CHECK-NEXT: [LoggingExternalInterface logging 300] + (func $run-block-nested (export "run-block-nested") + (call $run + (cont.new $k (ref.func $block-nested)) + ) + ) ) From 679d87f6167e39bc005079abeaa441f64d9cae8f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 12:56:15 -0700 Subject: [PATCH 057/125] work --- src/wasm-interpreter.h | 27 ++++++++++++++++++++++++++- test/lit/exec/cont_simple.wast | 29 +++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 4279f3908b3..2cafff436a5 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -423,6 +423,7 @@ class ExpressionRunner : public OverriddenVisitor { return Flow(); } Flow visitLoop(Loop* curr) { + // NB: No special support is need for suspend/resume. Index loopCount = 0; while (1) { Flow flow = visit(curr->body); @@ -4424,6 +4425,7 @@ class ModuleRunnerBase : public ExpressionRunner { return {}; } Flow visitTry(Try* curr) { + assert(!self()->resuming); // TODO // Unwind the value stack when we jump up the call stack. auto oldValueStackSize = self()->valueStack ? self()->valueStack->size() : 0; @@ -4478,6 +4480,7 @@ class ModuleRunnerBase : public ExpressionRunner { } } Flow visitTryTable(TryTable* curr) { + assert(!self()->resuming); // TODO // Unwind the value stack when we jump up the call stack. auto oldValueStackSize = self()->valueStack ? self()->valueStack->size() : 0; @@ -4558,7 +4561,6 @@ class ModuleRunnerBase : public ExpressionRunner { new_->resumeExpr = curr; // TODO: save the call stack! // TODO: save the valueStack! - // TODO: save the locals on the function stacks! arguments.push_back(Literal(new_)); return Flow(SUSPEND_FLOW, curr->tag, std::move(arguments)); } @@ -4695,6 +4697,21 @@ class ModuleRunnerBase : public ExpressionRunner { FunctionScope scope(function, arguments, *self()); + if (self()->resuming) { + // Restore the local state (see below for the ordering). + assert(self()->currContinuation); + auto& resumeInfo = self()->currContinuation->resumeInfo; + for (Index i = 0; i < scope.locals.size(); i++) { + assert(!resumeInfo.empty()); + auto l = scope.locals.size() - 1 - i; + scope.locals[l] = resumeInfo.back(); + resumeInfo.pop_back(); + // Must have restored valid data. + assert(Type::isSubType(scope.locals[l].type, + function->getParams()[l])); + } + } + #if WASM_INTERPRETER_DEBUG std::cout << self()->indent() << "entering " << function->name << '\n' << self()->indent() << " with arguments:\n"; @@ -4710,6 +4727,14 @@ class ModuleRunnerBase : public ExpressionRunner { << flow.values << '\n'; #endif + if (flow.suspendTag) { + // Save the local state. + assert(self()->currContinuation); + for (auto& local : scope.locals) { + self()->currContinuation->resumeInfo.push_back(local); + } + } + if (flow.breakTo != RETURN_CALL_FLOW) { break; } diff --git a/test/lit/exec/cont_simple.wast b/test/lit/exec/cont_simple.wast index 1359b8727c5..e3bc42fc2b5 100644 --- a/test/lit/exec/cont_simple.wast +++ b/test/lit/exec/cont_simple.wast @@ -94,5 +94,34 @@ (cont.new $k (ref.func $block-nested)) ) ) + + (func $local + (local $x i32) + (local.set $x (i32.const 42)) + (suspend $more) + (call $log (local.get $x)) + (local.set $x (i32.const 1337)) + (suspend $more) + (call $log (local.get $x)) + ) + + ;; CHECK: [fuzz-exec] calling run-local + ;; CHECK-NEXT: [LoggingExternalInterface logging 100] + ;; CHECK-NEXT: [LoggingExternalInterface logging -1] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging -2] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging -3] + ;; CHECK-NEXT: [LoggingExternalInterface logging -4] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging -5] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging -6] + ;; CHECK-NEXT: [LoggingExternalInterface logging 300] + (func $run-local (export "run-local") + (call $run + (cont.new $k (ref.func $local)) + ) + ) ) From 14498433bc62642786571e808dda8185bad7d988 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 12:56:52 -0700 Subject: [PATCH 058/125] work --- src/wasm-interpreter.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 2cafff436a5..19543f1ab3d 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4707,7 +4707,7 @@ class ModuleRunnerBase : public ExpressionRunner { scope.locals[l] = resumeInfo.back(); resumeInfo.pop_back(); // Must have restored valid data. - assert(Type::isSubType(scope.locals[l].type, + assert(Type::isSubType(scope.locals[l].getType(), function->getParams()[l])); } } From e12d49ca4946db1d92daca9c7ebe83afcaf17fb6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 12:59:03 -0700 Subject: [PATCH 059/125] work --- src/wasm-interpreter.h | 2 +- test/lit/exec/cont_simple.wast | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 19543f1ab3d..6f1dca7e230 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4708,7 +4708,7 @@ class ModuleRunnerBase : public ExpressionRunner { resumeInfo.pop_back(); // Must have restored valid data. assert(Type::isSubType(scope.locals[l].getType(), - function->getParams()[l])); + function->getLocalType(l))); } } diff --git a/test/lit/exec/cont_simple.wast b/test/lit/exec/cont_simple.wast index e3bc42fc2b5..c3d06f3a0dd 100644 --- a/test/lit/exec/cont_simple.wast +++ b/test/lit/exec/cont_simple.wast @@ -107,16 +107,10 @@ ;; CHECK: [fuzz-exec] calling run-local ;; CHECK-NEXT: [LoggingExternalInterface logging 100] - ;; CHECK-NEXT: [LoggingExternalInterface logging -1] - ;; CHECK-NEXT: [LoggingExternalInterface logging 200] - ;; CHECK-NEXT: [LoggingExternalInterface logging -2] - ;; CHECK-NEXT: [LoggingExternalInterface logging 200] - ;; CHECK-NEXT: [LoggingExternalInterface logging -3] - ;; CHECK-NEXT: [LoggingExternalInterface logging -4] ;; CHECK-NEXT: [LoggingExternalInterface logging 200] - ;; CHECK-NEXT: [LoggingExternalInterface logging -5] + ;; CHECK-NEXT: [LoggingExternalInterface logging 42] ;; CHECK-NEXT: [LoggingExternalInterface logging 200] - ;; CHECK-NEXT: [LoggingExternalInterface logging -6] + ;; CHECK-NEXT: [LoggingExternalInterface logging 1337] ;; CHECK-NEXT: [LoggingExternalInterface logging 300] (func $run-local (export "run-local") (call $run From 41709bac4e6f7b93ecb5cc21bf2c94911681cdb9 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 13:03:54 -0700 Subject: [PATCH 060/125] work --- test/lit/exec/cont_simple.wast | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test/lit/exec/cont_simple.wast b/test/lit/exec/cont_simple.wast index c3d06f3a0dd..b27f1ab6b35 100644 --- a/test/lit/exec/cont_simple.wast +++ b/test/lit/exec/cont_simple.wast @@ -95,6 +95,7 @@ ) ) + ;; The local's state must be saved and restored. (func $local (local $x i32) (local.set $x (i32.const 42)) @@ -117,5 +118,37 @@ (cont.new $k (ref.func $local)) ) ) + + ;; This loop should suspend 4 times. + (func $loop + (local $x i32) + (local.set $x (i32.const 4)) + (loop $loop + (local.set $x + (i32.sub + (local.get $x) + (i32.const 1) + ) + ) + (call $log (local.get $x)) + (suspend $more) + (br_if $loop + (local.get $x) + ) + ) + ) + + ;; CHECK: [fuzz-exec] calling run-loop + ;; CHECK-NEXT: [LoggingExternalInterface logging 100] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging 42] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging 1337] + ;; CHECK-NEXT: [LoggingExternalInterface logging 300] + (func $run-loop (export "run-loop") + (call $run + (cont.new $k (ref.func $loop)) + ) + ) ) From 8b5b990bc8cc165a8fd0b0df41e2a27ca666684e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 13:05:18 -0700 Subject: [PATCH 061/125] work --- src/wasm-interpreter.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 6f1dca7e230..be6ab73ba6a 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -315,11 +315,13 @@ class ExpressionRunner : public OverriddenVisitor { if (!ret.breaking()) { Type type = ret.getType(); if (type.isConcrete() || curr->type.isConcrete()) { +#ifndef NDEBUG if (!Type::isSubType(type, curr->type)) { Fatal() << "expected " << ModuleType(*module, curr->type) << ", seeing " << ModuleType(*module, type) << " from\n" << ModuleExpression(*module, curr) << '\n'; } +#endif } if (valueStack) { valueStack->push_back(ret.values); @@ -4761,11 +4763,13 @@ class ModuleRunnerBase : public ExpressionRunner { // be breaking, which would mean we missed our stop. assert(!flow.breaking() || flow.breakTo == RETURN_FLOW); +#ifndef NDEBUG // In normal execution, the result is the expected one. if (!Type::isSubType(type, *resultType)) { Fatal() << "calling " << name << " resulted in " << type << " but the function type is " << *resultType << '\n'; } +#endif } return flow; From 04eccec21293e80ee72da5fba9d8252fa206bbfb Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 13:05:30 -0700 Subject: [PATCH 062/125] format --- src/wasm-interpreter.h | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index be6ab73ba6a..9c51b566657 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -148,8 +148,7 @@ struct ContData { // executed a second time. bool executed = false; - ContData(Name func, HeapType type) - : func(func), type(type) {} + ContData(Name func, HeapType type) : func(func), type(type) {} }; // Execute an expression @@ -3504,7 +3503,8 @@ class ModuleRunnerBase : public ExpressionRunner { } #if WASM_INTERPRETER_DEBUG - std::cout << self()->indent() << "(calling ref " << targetRef.getFunc() << ")\n"; + std::cout << self()->indent() << "(calling ref " << targetRef.getFunc() + << ")\n"; #endif Flow ret = callFunction(targetRef.getFunc(), arguments); #if WASM_INTERPRETER_DEBUG @@ -4535,12 +4535,9 @@ class ModuleRunnerBase : public ExpressionRunner { } Name func = funcFlow.getSingleValue().getFunc(); // Create a fresh continuation. - return Literal( - std::make_shared(func, curr->type.getHeapType())); - } - Flow visitContBind(ContBind* curr) { - return Flow(NONCONSTANT_FLOW); + return Literal(std::make_shared(func, curr->type.getHeapType())); } + Flow visitContBind(ContBind* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitSuspend(Suspend* curr) { Literals arguments; @@ -4620,9 +4617,7 @@ class ModuleRunnerBase : public ExpressionRunner { // No suspension; all done. return Flow(); } - Flow visitResumeThrow(ResumeThrow* 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); } @@ -4710,7 +4705,7 @@ class ModuleRunnerBase : public ExpressionRunner { resumeInfo.pop_back(); // Must have restored valid data. assert(Type::isSubType(scope.locals[l].getType(), - function->getLocalType(l))); + function->getLocalType(l))); } } @@ -4718,7 +4713,8 @@ class ModuleRunnerBase : public ExpressionRunner { std::cout << self()->indent() << "entering " << function->name << '\n' << self()->indent() << " with arguments:\n"; for (unsigned i = 0; i < arguments.size(); ++i) { - std::cout << self()->indent() <<" $" << i << ": " << arguments[i] << '\n'; + std::cout << self()->indent() << " $" << i << ": " << arguments[i] + << '\n'; } #endif From e652e830cd4259343c0c890d320f541e5b682787 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 13:14:15 -0700 Subject: [PATCH 063/125] work --- test/lit/exec/cont_simple.wast | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/lit/exec/cont_simple.wast b/test/lit/exec/cont_simple.wast index b27f1ab6b35..e362f8eb0d0 100644 --- a/test/lit/exec/cont_simple.wast +++ b/test/lit/exec/cont_simple.wast @@ -140,10 +140,14 @@ ;; CHECK: [fuzz-exec] calling run-loop ;; CHECK-NEXT: [LoggingExternalInterface logging 100] + ;; CHECK-NEXT: [LoggingExternalInterface logging 3] ;; CHECK-NEXT: [LoggingExternalInterface logging 200] - ;; CHECK-NEXT: [LoggingExternalInterface logging 42] + ;; CHECK-NEXT: [LoggingExternalInterface logging 2] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging 1] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] ;; CHECK-NEXT: [LoggingExternalInterface logging 200] - ;; CHECK-NEXT: [LoggingExternalInterface logging 1337] ;; CHECK-NEXT: [LoggingExternalInterface logging 300] (func $run-loop (export "run-loop") (call $run From 2523441cf6bdba1e9700c95db8ea95199bbcb797 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 13:14:25 -0700 Subject: [PATCH 064/125] Revert "work" This reverts commit e652e830cd4259343c0c890d320f541e5b682787. --- test/lit/exec/cont_simple.wast | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/test/lit/exec/cont_simple.wast b/test/lit/exec/cont_simple.wast index e362f8eb0d0..b27f1ab6b35 100644 --- a/test/lit/exec/cont_simple.wast +++ b/test/lit/exec/cont_simple.wast @@ -140,14 +140,10 @@ ;; CHECK: [fuzz-exec] calling run-loop ;; CHECK-NEXT: [LoggingExternalInterface logging 100] - ;; CHECK-NEXT: [LoggingExternalInterface logging 3] ;; CHECK-NEXT: [LoggingExternalInterface logging 200] - ;; CHECK-NEXT: [LoggingExternalInterface logging 2] - ;; CHECK-NEXT: [LoggingExternalInterface logging 200] - ;; CHECK-NEXT: [LoggingExternalInterface logging 1] - ;; CHECK-NEXT: [LoggingExternalInterface logging 200] - ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 42] ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging 1337] ;; CHECK-NEXT: [LoggingExternalInterface logging 300] (func $run-loop (export "run-loop") (call $run From 25ab4ce9d615a64e53cd8233d6941d3c0e94adf7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 13:26:10 -0700 Subject: [PATCH 065/125] work --- test/lit/exec/cont_simple.wast | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/lit/exec/cont_simple.wast b/test/lit/exec/cont_simple.wast index b27f1ab6b35..e362f8eb0d0 100644 --- a/test/lit/exec/cont_simple.wast +++ b/test/lit/exec/cont_simple.wast @@ -140,10 +140,14 @@ ;; CHECK: [fuzz-exec] calling run-loop ;; CHECK-NEXT: [LoggingExternalInterface logging 100] + ;; CHECK-NEXT: [LoggingExternalInterface logging 3] ;; CHECK-NEXT: [LoggingExternalInterface logging 200] - ;; CHECK-NEXT: [LoggingExternalInterface logging 42] + ;; CHECK-NEXT: [LoggingExternalInterface logging 2] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging 1] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] ;; CHECK-NEXT: [LoggingExternalInterface logging 200] - ;; CHECK-NEXT: [LoggingExternalInterface logging 1337] ;; CHECK-NEXT: [LoggingExternalInterface logging 300] (func $run-loop (export "run-loop") (call $run From 377ccb406992118cc80beb4457ad908645719489 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 13:36:22 -0700 Subject: [PATCH 066/125] format --- src/wasm-interpreter.h | 160 +++++++++++------------------------------ 1 file changed, 40 insertions(+), 120 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 9c51b566657..c349916b4ee 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1470,9 +1470,7 @@ class ExpressionRunner : public OverriddenVisitor { flow.breakTo = RETURN_FLOW; return flow; } - Flow visitNop(Nop* curr) { - return Flow(); - } + Flow visitNop(Nop* curr) { return Flow(); } Flow visitUnreachable(Unreachable* curr) { trap("unreachable"); WASM_UNREACHABLE("unreachable"); @@ -2719,117 +2717,51 @@ class ConstantExpressionRunner : public ExpressionRunner { } return Flow(NONCONSTANT_FLOW); } - Flow visitCall(Call* curr) { - return Flow(NONCONSTANT_FLOW); - } - Flow visitCallIndirect(CallIndirect* curr) { - return Flow(NONCONSTANT_FLOW); - } - Flow visitCallRef(CallRef* curr) { - return Flow(NONCONSTANT_FLOW); - } - Flow visitTableGet(TableGet* curr) { - return Flow(NONCONSTANT_FLOW); - } - Flow visitTableSet(TableSet* curr) { - return Flow(NONCONSTANT_FLOW); - } - Flow visitTableSize(TableSize* curr) { - return Flow(NONCONSTANT_FLOW); - } - Flow visitTableGrow(TableGrow* curr) { - return Flow(NONCONSTANT_FLOW); - } - Flow visitTableFill(TableFill* curr) { - return Flow(NONCONSTANT_FLOW); - } - Flow visitTableCopy(TableCopy* curr) { - return Flow(NONCONSTANT_FLOW); - } - Flow visitTableInit(TableInit* curr) { - return Flow(NONCONSTANT_FLOW); - } - Flow visitElemDrop(ElemDrop* curr) { - return Flow(NONCONSTANT_FLOW); - } - Flow visitLoad(Load* curr) { - return Flow(NONCONSTANT_FLOW); - } - Flow visitStore(Store* curr) { - return Flow(NONCONSTANT_FLOW); - } - Flow visitMemorySize(MemorySize* curr) { - return Flow(NONCONSTANT_FLOW); - } - Flow visitMemoryGrow(MemoryGrow* curr) { - return Flow(NONCONSTANT_FLOW); - } - Flow visitMemoryInit(MemoryInit* curr) { - return Flow(NONCONSTANT_FLOW); - } - Flow visitDataDrop(DataDrop* curr) { - return Flow(NONCONSTANT_FLOW); - } - Flow visitMemoryCopy(MemoryCopy* curr) { - return Flow(NONCONSTANT_FLOW); - } - Flow visitMemoryFill(MemoryFill* curr) { - return Flow(NONCONSTANT_FLOW); - } - Flow visitAtomicRMW(AtomicRMW* curr) { - return Flow(NONCONSTANT_FLOW); - } + Flow visitCall(Call* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitCallIndirect(CallIndirect* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitCallRef(CallRef* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitTableGet(TableGet* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitTableSet(TableSet* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitTableSize(TableSize* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitTableGrow(TableGrow* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitTableFill(TableFill* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitTableCopy(TableCopy* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitTableInit(TableInit* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitElemDrop(ElemDrop* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitLoad(Load* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitStore(Store* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitMemorySize(MemorySize* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitMemoryGrow(MemoryGrow* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitMemoryInit(MemoryInit* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitDataDrop(DataDrop* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitMemoryCopy(MemoryCopy* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitMemoryFill(MemoryFill* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitAtomicRMW(AtomicRMW* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitAtomicCmpxchg(AtomicCmpxchg* curr) { return Flow(NONCONSTANT_FLOW); } - Flow visitAtomicWait(AtomicWait* curr) { - return Flow(NONCONSTANT_FLOW); - } - Flow visitAtomicNotify(AtomicNotify* curr) { - return Flow(NONCONSTANT_FLOW); - } - Flow visitSIMDLoad(SIMDLoad* curr) { - return Flow(NONCONSTANT_FLOW); - } - Flow visitSIMDLoadSplat(SIMDLoad* curr) { - return Flow(NONCONSTANT_FLOW); - } - Flow visitSIMDLoadExtend(SIMDLoad* curr) { - return Flow(NONCONSTANT_FLOW); - } + Flow visitAtomicWait(AtomicWait* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitAtomicNotify(AtomicNotify* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitSIMDLoad(SIMDLoad* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitSIMDLoadSplat(SIMDLoad* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitSIMDLoadExtend(SIMDLoad* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitSIMDLoadStoreLane(SIMDLoadStoreLane* curr) { return Flow(NONCONSTANT_FLOW); } - Flow visitArrayNewData(ArrayNewData* curr) { - return Flow(NONCONSTANT_FLOW); - } - Flow visitArrayNewElem(ArrayNewElem* curr) { - return Flow(NONCONSTANT_FLOW); - } - Flow visitArrayCopy(ArrayCopy* curr) { - return Flow(NONCONSTANT_FLOW); - } - Flow visitArrayFill(ArrayFill* curr) { - return Flow(NONCONSTANT_FLOW); - } + Flow visitArrayNewData(ArrayNewData* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitArrayNewElem(ArrayNewElem* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitArrayCopy(ArrayCopy* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitArrayFill(ArrayFill* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitArrayInitData(ArrayInitData* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitArrayInitElem(ArrayInitElem* curr) { return Flow(NONCONSTANT_FLOW); } - Flow visitPop(Pop* curr) { - return Flow(NONCONSTANT_FLOW); - } - Flow visitTry(Try* curr) { - return Flow(NONCONSTANT_FLOW); - } - Flow visitTryTable(TryTable* curr) { - return Flow(NONCONSTANT_FLOW); - } - Flow visitRethrow(Rethrow* curr) { - return Flow(NONCONSTANT_FLOW); - } + Flow visitPop(Pop* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitTry(Try* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitTryTable(TryTable* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitRethrow(Rethrow* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitRefAs(RefAs* curr) { // TODO: Remove this once interpretation is implemented. if (curr->op == AnyConvertExtern || curr->op == ExternConvertAny) { @@ -2837,24 +2769,12 @@ class ConstantExpressionRunner : public ExpressionRunner { } return ExpressionRunner::visitRefAs(curr); } - Flow visitContNew(ContNew* 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); - } + Flow visitContNew(ContNew* 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 { throw NonconstantException(); } From ddadbfeb72865ba8c3b618f2065248adcd90ffe4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 13:45:14 -0700 Subject: [PATCH 067/125] work --- src/wasm-interpreter.h | 63 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 51 insertions(+), 12 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index c349916b4ee..c2d6556a54f 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -255,6 +255,25 @@ class ExpressionRunner : public OverriddenVisitor { // will be cleared. bool resuming = false; + void pushResumeInfoEntry(const Literals& entry) { + assert(currContinuation); +#if WASM_INTERPRETER_DEBUG + std::cout << indent() << "push resume entry: " << entry << "\n"; +#endif + currContinuation->resumeInfo.push_back(entry); + } + + Literals popResumeInfoEntry() { + assert(currContinuation); + assert(!currContinuation->resumeInfo.empty()); + auto entry = currContinuation->resumeInfo.back(); + currContinuation->resumeInfo.pop_back(); +#if WASM_INTERPRETER_DEBUG + std::cout << indent() << "pop resume entry: " << entry << "\n"; +#endif + return entry; + } + public: ExpressionRunner(Module* module = nullptr, Index maxDepth = NO_LIMIT, @@ -354,21 +373,11 @@ class ExpressionRunner : public OverriddenVisitor { // in the block. entry.push_back(Literal(uint32_t(stack.size()))); entry.push_back(Literal(uint32_t(blockIndex))); -#if WASM_INTERPRETER_DEBUG - std::cout << indent() << "suspend block: " << entry << "\n"; -#endif - assert(currContinuation); - currContinuation->resumeInfo.push_back(entry); + pushResumeInfoEntry(entry); }; Index blockIndex = 0; if (resuming) { - assert(currContinuation); - assert(!currContinuation->resumeInfo.empty()); - auto entry = currContinuation->resumeInfo.back(); -#if WASM_INTERPRETER_DEBUG - std::cout << indent() << "resume block: " << entry << "\n"; -#endif - currContinuation->resumeInfo.pop_back(); + auto entry = popResumeInfoEntry(); assert(entry.size() == 2); Index stackIndex = entry[0].geti32(); blockIndex = entry[1].geti32(); @@ -411,6 +420,36 @@ class ExpressionRunner : public OverriddenVisitor { if (flow.breaking()) { return flow; } + +/* + // Suspend/resume support. + auto suspend = [&](Index index) { + // To return to the same place when we resume, we add an entry that tells + // us if we were in the ifTrue arm (1) or ifFalse(0). + Literals entry = Literal(int32_t(index)); +#if WASM_INTERPRETER_DEBUG + std::cout << indent() << "suspend if: " << entry << "\n"; +#endif + assert(currContinuation); + currContinuation->resumeInfo.push_back(entry); + }; + waka + if (resuming) { + assert(currContinuation); + assert(!currContinuation->resumeInfo.empty()); + auto entry = currContinuation->resumeInfo.back(); +#if WASM_INTERPRETER_DEBUG + std::cout << indent() << "resume block: " << entry << "\n"; +#endif + currContinuation->resumeInfo.pop_back(); + assert(entry.size() == 2); + Index stackIndex = entry[0].geti32(); + blockIndex = entry[1].geti32(); + assert(stack.size() > stackIndex); + stack.resize(stackIndex + 1); + } +*/ + if (flow.getSingleValue().geti32()) { Flow flow = visit(curr->ifTrue); if (!flow.breaking() && !curr->ifFalse) { From 6360d033454955214e8d5e546de5cca17c0b07ba Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 13:52:19 -0700 Subject: [PATCH 068/125] work --- src/wasm-interpreter.h | 51 ++++++++++++++++-------------------------- 1 file changed, 19 insertions(+), 32 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index c2d6556a54f..c6447bbc4c6 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -420,47 +420,34 @@ class ExpressionRunner : public OverriddenVisitor { if (flow.breaking()) { return flow; } + auto condition = flow.getSingleValue().geti32(); -/* // Suspend/resume support. - auto suspend = [&](Index index) { - // To return to the same place when we resume, we add an entry that tells - // us if we were in the ifTrue arm (1) or ifFalse(0). - Literals entry = Literal(int32_t(index)); -#if WASM_INTERPRETER_DEBUG - std::cout << indent() << "suspend if: " << entry << "\n"; -#endif - assert(currContinuation); - currContinuation->resumeInfo.push_back(entry); + auto suspend = [&]() { + // To return to the same place when we resume, we stash the condition, + // then just use it below. + pushResumeInfoEntry({Literal(int32_t(condition))}); }; - waka if (resuming) { - assert(currContinuation); - assert(!currContinuation->resumeInfo.empty()); - auto entry = currContinuation->resumeInfo.back(); -#if WASM_INTERPRETER_DEBUG - std::cout << indent() << "resume block: " << entry << "\n"; -#endif - currContinuation->resumeInfo.pop_back(); - assert(entry.size() == 2); - Index stackIndex = entry[0].geti32(); - blockIndex = entry[1].geti32(); - assert(stack.size() > stackIndex); - stack.resize(stackIndex + 1); + auto entry = popResumeInfoEntry(); + assert(entry.size() == 1); + condition = entry[0].geti32(); } -*/ - if (flow.getSingleValue().geti32()) { - Flow flow = visit(curr->ifTrue); - if (!flow.breaking() && !curr->ifFalse) { - flow = Flow(); // if_else returns a value, but if does not + if (condition) { + flow = visit(curr->ifTrue); + } else { + if (curr->ifFalse) { + flow = visit(curr->ifFalse); + } else { + flow = Flow(); } - return flow; } - if (curr->ifFalse) { - return visit(curr->ifFalse); + if (flow.suspendTag) { + suspend(); + return flow; } - return Flow(); + return flow; } Flow visitLoop(Loop* curr) { // NB: No special support is need for suspend/resume. From 193e0913749fc961df52e3f674c2c59f083dd5d6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 14:01:21 -0700 Subject: [PATCH 069/125] work --- src/wasm-interpreter.h | 4 +-- test/lit/exec/cont_simple.wast | 66 +++++++++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index c6447bbc4c6..a78abc9d260 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -323,8 +323,8 @@ class ExpressionRunner : public OverriddenVisitor { resuming = false; } else { // Some other instruction. Do not execute it, and only return the - // value we stashed for it. - assert(0); + // value we stashed for it. TODO + Fatal() << *curr; } } valueStack->resize(oldValueStackSize); diff --git a/test/lit/exec/cont_simple.wast b/test/lit/exec/cont_simple.wast index e362f8eb0d0..0dcc79bb96d 100644 --- a/test/lit/exec/cont_simple.wast +++ b/test/lit/exec/cont_simple.wast @@ -119,7 +119,7 @@ ) ) - ;; This loop should suspend 4 times. + ;; This loop should suspend 4 times and log 3, 2, 1, 0. (func $loop (local $x i32) (local.set $x (i32.const 4)) @@ -154,5 +154,69 @@ (cont.new $k (ref.func $loop)) ) ) + + ;; We should log -1, -2, -3, -4 + (func $if + (local $x i32) + (if + (local.get $x) + (then + (unreachable) + ) + (else + ;; We should get here. + (call $log (i32.const -1)) + (local.set $x (i32.const 1)) + (suspend $more) + ;; A nested if. + (if + (local.get $x) + (then + ;; We should get here + (suspend $more) + (call $log (i32.const -2)) + ) + (else + (unreachable) + ) + ) + ) + ) + ;; If with one arm. + (if + (local.get $x) + (then + ;; We should get here. + (call $log (i32.const -3)) + (suspend $more) + (call $log (i32.const -4)) + ) + ) + (if + (i32.eqz + (local.get $x) + ) + (then + (unreachable) + ) + ) + ) + + ;; CHECK: [fuzz-exec] calling run-if + ;; CHECK-NEXT: [LoggingExternalInterface logging 100] + ;; CHECK-NEXT: [LoggingExternalInterface logging 3] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging 2] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging 1] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging 300] + (func $run-if (export "run-if") + (call $run + (cont.new $k (ref.func $if)) + ) + ) ) From f147ce657ffb598249191df3343bd2d0e00d0c60 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 14:07:07 -0700 Subject: [PATCH 070/125] work --- src/wasm-interpreter.h | 31 +++++++++++++++++++++---------- test/lit/exec/cont_simple.wast | 2 ++ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index a78abc9d260..6b51528aed6 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -416,22 +416,33 @@ class ExpressionRunner : public OverriddenVisitor { return flow; } Flow visitIf(If* curr) { + // Suspend/resume support. + auto suspend = [&](Index resumeIndex) { + // To return to the same place when we resume, we stash an index: + // 0 - suspended in the condition + // 1 - suspended in the ifTrue arm + // 2 - suspended in the ifFalse arm + pushResumeInfoEntry({Literal(int32_t(resumeIndex))}); + }; + Index resumeIndex; + if (resuming) { + auto entry = popResumeInfoEntry(); + assert(entry.size() == 1); + resumeIndex = entry[0].geti32(); + } + Flow flow = visit(curr->condition); + if (flow.suspendTag) { + suspend(0); + return flow; + } if (flow.breaking()) { return flow; } auto condition = flow.getSingleValue().geti32(); - // Suspend/resume support. - auto suspend = [&]() { - // To return to the same place when we resume, we stash the condition, - // then just use it below. - pushResumeInfoEntry({Literal(int32_t(condition))}); - }; if (resuming) { - auto entry = popResumeInfoEntry(); - assert(entry.size() == 1); - condition = entry[0].geti32(); + condition = (resumeIndex == 1); } if (condition) { @@ -444,7 +455,7 @@ class ExpressionRunner : public OverriddenVisitor { } } if (flow.suspendTag) { - suspend(); + suspend(condition ? 1 : 2); return flow; } return flow; diff --git a/test/lit/exec/cont_simple.wast b/test/lit/exec/cont_simple.wast index 0dcc79bb96d..5b67c3e209b 100644 --- a/test/lit/exec/cont_simple.wast +++ b/test/lit/exec/cont_simple.wast @@ -218,5 +218,7 @@ (cont.new $k (ref.func $if)) ) ) + + ;; TODO: suspend in if condition ) From 3436d6ef0791fa543b75d0793c055db37f284ee5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 14:11:50 -0700 Subject: [PATCH 071/125] work --- src/wasm-interpreter.h | 28 +++++++++++++++++----------- test/lit/exec/cont_simple.wast | 9 ++++----- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 6b51528aed6..54fbb7dbaa2 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -424,25 +424,31 @@ class ExpressionRunner : public OverriddenVisitor { // 2 - suspended in the ifFalse arm pushResumeInfoEntry({Literal(int32_t(resumeIndex))}); }; - Index resumeIndex; + Index resumeIndex = -1; if (resuming) { auto entry = popResumeInfoEntry(); assert(entry.size() == 1); resumeIndex = entry[0].geti32(); } - Flow flow = visit(curr->condition); - if (flow.suspendTag) { - suspend(0); - return flow; - } - if (flow.breaking()) { - return flow; - } - auto condition = flow.getSingleValue().geti32(); + Flow flow; + Index condition; - if (resuming) { + if (resuming && resumeIndex > 0) { + // We are resuming into one of the arms. Just set the right condition. condition = (resumeIndex == 1); + } else { + // We are executing normally, or we are resuming into the condition. + // Either way, enter the condition. + flow = visit(curr->condition); + if (flow.suspendTag) { + suspend(0); + return flow; + } + if (flow.breaking()) { + return flow; + } + condition = flow.getSingleValue().geti32(); } if (condition) { diff --git a/test/lit/exec/cont_simple.wast b/test/lit/exec/cont_simple.wast index 5b67c3e209b..c9a3356470d 100644 --- a/test/lit/exec/cont_simple.wast +++ b/test/lit/exec/cont_simple.wast @@ -204,14 +204,13 @@ ;; CHECK: [fuzz-exec] calling run-if ;; CHECK-NEXT: [LoggingExternalInterface logging 100] - ;; CHECK-NEXT: [LoggingExternalInterface logging 3] - ;; CHECK-NEXT: [LoggingExternalInterface logging 200] - ;; CHECK-NEXT: [LoggingExternalInterface logging 2] + ;; CHECK-NEXT: [LoggingExternalInterface logging -1] ;; CHECK-NEXT: [LoggingExternalInterface logging 200] - ;; CHECK-NEXT: [LoggingExternalInterface logging 1] ;; CHECK-NEXT: [LoggingExternalInterface logging 200] - ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging -2] + ;; CHECK-NEXT: [LoggingExternalInterface logging -3] ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging -4] ;; CHECK-NEXT: [LoggingExternalInterface logging 300] (func $run-if (export "run-if") (call $run From e2ed33e2dc698febec5ec0dea51edcc99c183d0e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 14:14:02 -0700 Subject: [PATCH 072/125] work --- test/lit/exec/cont_simple.wast | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/test/lit/exec/cont_simple.wast b/test/lit/exec/cont_simple.wast index c9a3356470d..289a1dbad4a 100644 --- a/test/lit/exec/cont_simple.wast +++ b/test/lit/exec/cont_simple.wast @@ -218,6 +218,32 @@ ) ) - ;; TODO: suspend in if condition + ;; Suspend in the if's condition. + (func $if-condition + (if + (block (result i32) + (call $log (i32.const -1)) + (suspend $more) + (call $log (i32.const -2)) + (i32.const 1) + ) + (then + (call $log (i32.const -3)) + ) + ) + ) + + ;; CHECK: [fuzz-exec] calling run-if-condition + ;; CHECK-NEXT: [LoggingExternalInterface logging 100] + ;; CHECK-NEXT: [LoggingExternalInterface logging -1] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging -2] + ;; CHECK-NEXT: [LoggingExternalInterface logging -3] + ;; CHECK-NEXT: [LoggingExternalInterface logging 300] + (func $run-if-condition (export "run-if-condition") + (call $run + (cont.new $k (ref.func $if-condition)) + ) + ) ) From 9f6f06b2d9adc0dd2d635b4ed442c3167f15a950 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 14:14:43 -0700 Subject: [PATCH 073/125] work --- src/wasm-interpreter.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 54fbb7dbaa2..42964c22bd0 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4521,8 +4521,8 @@ class ModuleRunnerBase : public ExpressionRunner { auto new_ = std::make_shared(old->func, old->type); self()->currContinuation = new_; new_->resumeExpr = curr; - // TODO: save the call stack! - // TODO: save the valueStack! + // TODO: save the call stack! (call, call_indirect, call_ref) + // TODO: save the valueStack! (suspend from an arm of a binary e.g.) arguments.push_back(Literal(new_)); return Flow(SUSPEND_FLOW, curr->tag, std::move(arguments)); } From 1c5798e2855046d75983fc927b73b40b88f2ff7d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 14:25:08 -0700 Subject: [PATCH 074/125] fixen --- src/wasm-interpreter.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 42964c22bd0..f3356662dc0 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1583,6 +1583,7 @@ class ExpressionRunner : public OverriddenVisitor { // case atomic_fence can be lowered to nothing. return Flow(); } + Flow visitPause(Nop* curr) { return Flow(); } Flow visitTupleMake(TupleMake* curr) { Literals arguments; Flow flow = generateArguments(curr->operands, arguments); @@ -4523,6 +4524,7 @@ class ModuleRunnerBase : public ExpressionRunner { new_->resumeExpr = curr; // TODO: save the call stack! (call, call_indirect, call_ref) // TODO: save the valueStack! (suspend from an arm of a binary e.g.) + // TODO: add a suspend/resume fuzzer (plant suspends in code using pass?) arguments.push_back(Literal(new_)); return Flow(SUSPEND_FLOW, curr->tag, std::move(arguments)); } From be3a0a5fe04390d1f24f02c77ded5b0c89c237f3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 14:25:31 -0700 Subject: [PATCH 075/125] work --- src/wasm-interpreter.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index f3356662dc0..54ef515162b 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1583,7 +1583,7 @@ class ExpressionRunner : public OverriddenVisitor { // case atomic_fence can be lowered to nothing. return Flow(); } - Flow visitPause(Nop* curr) { return Flow(); } + Flow visitPause(Pause* curr) { return Flow(); } Flow visitTupleMake(TupleMake* curr) { Literals arguments; Flow flow = generateArguments(curr->operands, arguments); From 7a3f570a803cf501489fa6b25f7dccd932e0c817 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 16:47:00 -0700 Subject: [PATCH 076/125] work --- src/wasm-interpreter.h | 68 ++++++++++++++++++++++++++---------------- 1 file changed, 42 insertions(+), 26 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 54ef515162b..b014c65a668 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -231,7 +231,23 @@ class ExpressionRunner : public OverriddenVisitor { // We save values from visit() until they are consumed, so that we can pause/ // resume. TODO: move into ModuleRunner, since we need FunctionState anyhow // for locals? - std::optional> valueStack; + // Each entry here is a scope, and contains all the values from children + // that we have seen. + std::vector> valueStack; + + // RAII class that adds noting of stack values in a scope. + class StackValueNoter { // StackScope? + ExpressionRunner* parent; + + StackValueNoter(ExpressionRunner* parent) : parent(parent) { + parent->valueStack.emplace_back(); + } + + ~StackValueNoter() { + assert(!parent->valueStack.empty()); + parent->valueStack.pop_back(); + } + }; #if WASM_INTERPRETER_DEBUG std::string indent() { @@ -279,9 +295,6 @@ class ExpressionRunner : public OverriddenVisitor { Index maxDepth = NO_LIMIT, Index maxLoopIterations = NO_LIMIT) : module(module), maxDepth(maxDepth), maxLoopIterations(maxLoopIterations) { - if (module && module->features.hasStackSwitching()) { - valueStack.emplace(); - } } virtual ~ExpressionRunner() = default; @@ -301,13 +314,14 @@ class ExpressionRunner : public OverriddenVisitor { // completes, all values have been consumed, and nothing needs to be // saved. Flow ret; - if (!valueStack) { - // We cannot suspend/resume. Jus execute normally + if (!currContinuation) { + // We cannot suspend/resume. Just execute normally ret = OverriddenVisitor::visit(curr); } else { // We may suspend/resume. To support that, note values on the stack, so we // can save them if we do suspend. - auto oldValueStackSize = valueStack->size(); + StackValueNoter noter(*this); + if (!resuming) { ret = OverriddenVisitor::visit(curr); } else { @@ -324,27 +338,42 @@ class ExpressionRunner : public OverriddenVisitor { } else { // Some other instruction. Do not execute it, and only return the // value we stashed for it. TODO + // To resume, get # of items, get the items, then populate a mapp of + // expression* to value, and use that right here Fatal() << *curr; } } - valueStack->resize(oldValueStackSize); + + if (ret.suspendTag) { + assert(currContinuation); + // We are suspending a continuation. We have stashed values at the back + // of valueStack, and we can save those for when we resume, together + // with the number of such values, so we know how many children to + // process. We put one entry for each value, plus their number. + // TODO: + assert(!valueStack.empty()); + auto& values = valueStack.back(); + auto num = values.size(); + while (!values.empty()) { + pushResumeInfoEntry(values.back()); // TODO: std::move? + values.pop_back(); + } + pushResumeInfoEntry({Literal(int32_t(num))}); + } } +#ifndef NDEBUG if (!ret.breaking()) { Type type = ret.getType(); if (type.isConcrete() || curr->type.isConcrete()) { -#ifndef NDEBUG if (!Type::isSubType(type, curr->type)) { Fatal() << "expected " << ModuleType(*module, curr->type) << ", seeing " << ModuleType(*module, type) << " from\n" << ModuleExpression(*module, curr) << '\n'; } -#endif - } - if (valueStack) { - valueStack->push_back(ret.values); } } +#endif depth--; #if WASM_INTERPRETER_DEBUG std::cout << indent() << "=> returning: " << ret << '\n'; @@ -4392,15 +4421,9 @@ class ModuleRunnerBase : public ExpressionRunner { } Flow visitTry(Try* curr) { assert(!self()->resuming); // TODO - // Unwind the value stack when we jump up the call stack. - auto oldValueStackSize = - self()->valueStack ? self()->valueStack->size() : 0; try { return self()->visit(curr->body); } catch (const WasmException& e) { - if (self()->valueStack) { - self()->valueStack->resize(oldValueStackSize); - } // If delegation is in progress and the current try is not the target of // the delegation, don't handle it and just rethrow. if (scope->currDelegateTarget.is()) { @@ -4447,15 +4470,9 @@ class ModuleRunnerBase : public ExpressionRunner { } Flow visitTryTable(TryTable* curr) { assert(!self()->resuming); // TODO - // Unwind the value stack when we jump up the call stack. - auto oldValueStackSize = - self()->valueStack ? self()->valueStack->size() : 0; try { return self()->visit(curr->body); } catch (const WasmException& e) { - if (self()->valueStack) { - self()->valueStack->resize(oldValueStackSize); - } auto exnData = e.exn.getExnData(); for (size_t i = 0; i < curr->catchTags.size(); i++) { auto catchTag = curr->catchTags[i]; @@ -4523,7 +4540,6 @@ class ModuleRunnerBase : public ExpressionRunner { self()->currContinuation = new_; new_->resumeExpr = curr; // TODO: save the call stack! (call, call_indirect, call_ref) - // TODO: save the valueStack! (suspend from an arm of a binary e.g.) // TODO: add a suspend/resume fuzzer (plant suspends in code using pass?) arguments.push_back(Literal(new_)); return Flow(SUSPEND_FLOW, curr->tag, std::move(arguments)); From 83f1dff2c89bbd45fb2faac057efd60bdd631ec1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 16:59:04 -0700 Subject: [PATCH 077/125] work --- src/wasm-interpreter.h | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index b014c65a668..3cef805f963 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -30,6 +30,7 @@ #include "fp16.h" #include "ir/intrinsics.h" +#include "ir/iteration.h" #include "ir/module-utils.h" #include "ir/properties.h" #include "support/bits.h" @@ -249,6 +250,10 @@ class ExpressionRunner : public OverriddenVisitor { } }; + // When we resume, we wil this map with children whose values were saved when + // we suspended. We apply them as we resume. + std::unordered_map restoredValuesMap; + #if WASM_INTERPRETER_DEBUG std::string indent() { std::string ret; @@ -335,12 +340,36 @@ class ExpressionRunner : public OverriddenVisitor { assert(curr == currContinuation->resumeExpr); // We finished resuming, and will continue from here normally. resuming = false; + // We should have consumed all the resumeInfo and all the + // restoredValues map. + assert(currContinuation->resumeInfo.empty()); + assert(restoredValuesMap.empty()); } else { - // Some other instruction. Do not execute it, and only return the - // value we stashed for it. TODO - // To resume, get # of items, get the items, then populate a mapp of - // expression* to value, and use that right here - Fatal() << *curr; + // Some other instruction. Perhaps we have a restored value for it, + // which we should then just return. + auto iter = restoredValuesMap.find(curr); + if (iter != restoredValuesMap.end()) { + ret = iter->second; + restoredValuesMap.erase(iter); + } else { + // Some of its children may have executed, and + // we have values stashed for them (see below where we suspend). Get + // those values, and populate || so that when visit() is called on + // them, we can return those values rather than run them. + auto numEntry = popResumeInfoEntry(); + assert(numEntry.size() == 1); + auto num = numEntry[0].geti32(); + for (auto* child : ChildIterator(curr)) { + if (num == 0) { + // We have restored all the children that executed (any others + // were not suspended, and we have no values for them). + break; + } + num--; + auto value = popResumeInfoEntry(); + restoredValuesMap[child] = value; + } + } } } From a95ce74cac8cf166cb5ffb9d70a5cb4e8d710abe Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 17:00:36 -0700 Subject: [PATCH 078/125] work --- src/wasm-interpreter.h | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 3cef805f963..e5f65f946a8 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -236,24 +236,6 @@ class ExpressionRunner : public OverriddenVisitor { // that we have seen. std::vector> valueStack; - // RAII class that adds noting of stack values in a scope. - class StackValueNoter { // StackScope? - ExpressionRunner* parent; - - StackValueNoter(ExpressionRunner* parent) : parent(parent) { - parent->valueStack.emplace_back(); - } - - ~StackValueNoter() { - assert(!parent->valueStack.empty()); - parent->valueStack.pop_back(); - } - }; - - // When we resume, we wil this map with children whose values were saved when - // we suspended. We apply them as we resume. - std::unordered_map restoredValuesMap; - #if WASM_INTERPRETER_DEBUG std::string indent() { std::string ret; @@ -295,6 +277,25 @@ class ExpressionRunner : public OverriddenVisitor { return entry; } +public: // where? + // RAII class that adds noting of stack values in a scope. + class StackValueNoter { // StackScope? + ExpressionRunner* parent; + + StackValueNoter(ExpressionRunner* parent) : parent(parent) { + parent->valueStack.emplace_back(); + } + + ~StackValueNoter() { + assert(!parent->valueStack.empty()); + parent->valueStack.pop_back(); + } + }; + + // When we resume, we wil this map with children whose values were saved when + // we suspended. We apply them as we resume. + std::unordered_map restoredValuesMap; + public: ExpressionRunner(Module* module = nullptr, Index maxDepth = NO_LIMIT, @@ -325,7 +326,7 @@ class ExpressionRunner : public OverriddenVisitor { } else { // We may suspend/resume. To support that, note values on the stack, so we // can save them if we do suspend. - StackValueNoter noter(*this); + StackValueNoter noter(this); if (!resuming) { ret = OverriddenVisitor::visit(curr); From 5132664f0601c8fc5621bab4187aeab0902f4516 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 17:01:09 -0700 Subject: [PATCH 079/125] work --- src/wasm-interpreter.h | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index e5f65f946a8..dd2b2859d9f 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -236,6 +236,24 @@ class ExpressionRunner : public OverriddenVisitor { // that we have seen. std::vector> valueStack; + // RAII class that adds noting of stack values in a scope. + struct StackValueNoter { // StackScope? + ExpressionRunner* parent; + + StackValueNoter(ExpressionRunner* parent) : parent(parent) { + parent->valueStack.emplace_back(); + } + + ~StackValueNoter() { + assert(!parent->valueStack.empty()); + parent->valueStack.pop_back(); + } + }; + + // When we resume, we wil this map with children whose values were saved when + // we suspended. We apply them as we resume. + std::unordered_map restoredValuesMap; + #if WASM_INTERPRETER_DEBUG std::string indent() { std::string ret; @@ -277,25 +295,6 @@ class ExpressionRunner : public OverriddenVisitor { return entry; } -public: // where? - // RAII class that adds noting of stack values in a scope. - class StackValueNoter { // StackScope? - ExpressionRunner* parent; - - StackValueNoter(ExpressionRunner* parent) : parent(parent) { - parent->valueStack.emplace_back(); - } - - ~StackValueNoter() { - assert(!parent->valueStack.empty()); - parent->valueStack.pop_back(); - } - }; - - // When we resume, we wil this map with children whose values were saved when - // we suspended. We apply them as we resume. - std::unordered_map restoredValuesMap; - public: ExpressionRunner(Module* module = nullptr, Index maxDepth = NO_LIMIT, From 5bfc1e33f1f73708ecfbb9f417b34e163902ddbf Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 17:11:57 -0700 Subject: [PATCH 080/125] work --- src/wasm-interpreter.h | 78 ++++++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index dd2b2859d9f..3122439b0b8 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1,3 +1,4 @@ +#define WASM_INTERPRETER_DEBUG 1 /* * Copyright 2015 WebAssembly Community Group participants * @@ -327,48 +328,49 @@ class ExpressionRunner : public OverriddenVisitor { // can save them if we do suspend. StackValueNoter noter(this); - if (!resuming) { + if (!resuming || Properties::isControlFlowStructure(curr)) { + // Normal execution, or resuming but this is a control flow structure + // (and every control flow structure handles itself). ret = OverriddenVisitor::visit(curr); } else { - // We are resuming code. - if (Properties::isControlFlowStructure(curr)) { - // Each control flow structure knows how to handle itself. - ret = OverriddenVisitor::visit(curr); - } else if (curr->is()) { - // This is a resume, so we have found our way back to where we - // suspended. - assert(curr == currContinuation->resumeExpr); - // We finished resuming, and will continue from here normally. - resuming = false; - // We should have consumed all the resumeInfo and all the - // restoredValues map. - assert(currContinuation->resumeInfo.empty()); - assert(restoredValuesMap.empty()); + // We are resuming code. Perhaps we have a restored value for it, + // which we should then just return. + auto iter = restoredValuesMap.find(curr); + if (iter != restoredValuesMap.end()) { + ret = iter->second; + restoredValuesMap.erase(iter); } else { - // Some other instruction. Perhaps we have a restored value for it, - // which we should then just return. - auto iter = restoredValuesMap.find(curr); - if (iter != restoredValuesMap.end()) { - ret = iter->second; - restoredValuesMap.erase(iter); - } else { - // Some of its children may have executed, and - // we have values stashed for them (see below where we suspend). Get - // those values, and populate || so that when visit() is called on - // them, we can return those values rather than run them. - auto numEntry = popResumeInfoEntry(); - assert(numEntry.size() == 1); - auto num = numEntry[0].geti32(); - for (auto* child : ChildIterator(curr)) { - if (num == 0) { - // We have restored all the children that executed (any others - // were not suspended, and we have no values for them). - break; - } - num--; - auto value = popResumeInfoEntry(); - restoredValuesMap[child] = value; + // Some of its children may have executed, and + // we have values stashed for them (see below where we suspend). Get + // those values, and populate || so that when visit() is called on + // them, we can return those values rather than run them. + auto numEntry = popResumeInfoEntry(); + assert(numEntry.size() == 1); + auto num = numEntry[0].geti32(); + for (auto* child : ChildIterator(curr)) { + if (num == 0) { + // We have restored all the children that executed (any others + // were not suspended, and we have no values for them). + break; } + num--; + auto value = popResumeInfoEntry(); + restoredValuesMap[child] = value; + } + // We are ready to return the right values for the children, and can + // visit this instruction. + if (curr->is()) { // TODO move into visitSuspend? + // This is a resume, so we have found our way back to where we + // suspended. + assert(curr == currContinuation->resumeExpr); + // We finished resuming, and will continue from here normally. + resuming = false; + // We should have consumed all the resumeInfo and all the + // restoredValues map. + assert(currContinuation->resumeInfo.empty()); + assert(restoredValuesMap.empty()); + } else { + ret = OverriddenVisitor::visit(curr); } } } From 266cc45d5c6602627b77c048a5d0af3fd98cc131 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 17:14:57 -0700 Subject: [PATCH 081/125] work --- src/wasm-interpreter.h | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 3122439b0b8..2530527e933 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -359,19 +359,7 @@ class ExpressionRunner : public OverriddenVisitor { } // We are ready to return the right values for the children, and can // visit this instruction. - if (curr->is()) { // TODO move into visitSuspend? - // This is a resume, so we have found our way back to where we - // suspended. - assert(curr == currContinuation->resumeExpr); - // We finished resuming, and will continue from here normally. - resuming = false; - // We should have consumed all the resumeInfo and all the - // restoredValues map. - assert(currContinuation->resumeInfo.empty()); - assert(restoredValuesMap.empty()); - } else { - ret = OverriddenVisitor::visit(curr); - } + ret = OverriddenVisitor::visit(curr); } } @@ -4552,6 +4540,19 @@ class ModuleRunnerBase : public ExpressionRunner { Flow visitContBind(ContBind* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitSuspend(Suspend* curr) { + if (self()->resuming) { + // This is a resume, so we have found our way back to where we + // suspended. + assert(curr == self()->currContinuation->resumeExpr); + // We finished resuming, and will continue from here normally. + self()->resuming = false; + // We should have consumed all the resumeInfo and all the + // restoredValues map. + assert(self()->currContinuation->resumeInfo.empty()); + assert(self()->restoredValuesMap.empty()); + return Flow(); + } + Literals arguments; Flow flow = self()->generateArguments(curr->operands, arguments); if (flow.breaking()) { From e557e568c11bd175ba9cad469fe9cfee09547f37 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 17:18:23 -0700 Subject: [PATCH 082/125] work --- src/wasm-interpreter.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 2530527e933..651a9a524c4 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -328,7 +328,8 @@ class ExpressionRunner : public OverriddenVisitor { // can save them if we do suspend. StackValueNoter noter(this); - if (!resuming || Properties::isControlFlowStructure(curr)) { + auto isControlFlow = Properties::isControlFlowStructure(curr); + if (!resuming || isControlFlow) { // Normal execution, or resuming but this is a control flow structure // (and every control flow structure handles itself). ret = OverriddenVisitor::visit(curr); @@ -363,7 +364,7 @@ class ExpressionRunner : public OverriddenVisitor { } } - if (ret.suspendTag) { + if (ret.suspendTag && !isControlFlow) { assert(currContinuation); // We are suspending a continuation. We have stashed values at the back // of valueStack, and we can save those for when we resume, together From 1ed07edc839a871db4701bc6260633a69d2ef2a1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 17:22:29 -0700 Subject: [PATCH 083/125] work --- src/wasm-interpreter.h | 2 +- test/lit/exec/cont_simple.wast | 35 ++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 651a9a524c4..8cf53816cac 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1,4 +1,4 @@ -#define WASM_INTERPRETER_DEBUG 1 +//#define WASM_INTERPRETER_DEBUG 1 /* * Copyright 2015 WebAssembly Community Group participants * diff --git a/test/lit/exec/cont_simple.wast b/test/lit/exec/cont_simple.wast index 289a1dbad4a..a0a007a6d3c 100644 --- a/test/lit/exec/cont_simple.wast +++ b/test/lit/exec/cont_simple.wast @@ -245,5 +245,40 @@ (cont.new $k (ref.func $if-condition)) ) ) + + ;; Check that we properly stash things on the value stack. + (func $value-stack + ;; Suspend on the left. + (call $log + (i32.sub + (block (result i32) + (suspend $more) + (i32.const 0) + ) + (i32.const 1) + ) + ) + ;; On the right. + (call $log + (i32.sub + (i32.const 0) + (block (result i32) + (suspend $more) + (i32.const 2) + ) + ) + ) + ) + + ;; CHECK: [fuzz-exec] calling run-value-stack + ;; CHECK-NEXT: [LoggingExternalInterface logging 100] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging -1] + ;; CHECK-NEXT: [LoggingExternalInterface logging 300] + (func $run-value-stack (export "run-value-stack") + (call $run + (cont.new $k (ref.func $value-stack)) + ) + ) ) From b155afb5ea5e220ed8a7f6301ed5afc85fcfa191 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 17:25:39 -0700 Subject: [PATCH 084/125] work --- test/lit/exec/cont_simple.wast | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/test/lit/exec/cont_simple.wast b/test/lit/exec/cont_simple.wast index a0a007a6d3c..6cc75269e9f 100644 --- a/test/lit/exec/cont_simple.wast +++ b/test/lit/exec/cont_simple.wast @@ -248,26 +248,42 @@ ;; Check that we properly stash things on the value stack. (func $value-stack - ;; Suspend on the left. + ;; Suspend on the left. No value is actually saved on the stack, as we + ;; resume before we execute the right side. (call $log (i32.sub (block (result i32) (suspend $more) - (i32.const 0) + (i32.const 1) + ) + (i32.const 2) + ) + ) + ;; On the right. Now + (call $log + (i32.sub + (i32.const 2) + (block (result i32) + (suspend $more) + (i32.const 4) ) - (i32.const 1) ) ) - ;; On the right. + ;; Both sides (call $log (i32.sub - (i32.const 0) (block (result i32) (suspend $more) - (i32.const 2) + (i32.const 3) + ) + (block (result i32) + (suspend $more) + (i32.const 6) ) ) ) + ;; TODO other test for nested + ;; TODO other test for select (trinary) ) ;; CHECK: [fuzz-exec] calling run-value-stack From 9970b070ad03e540855bed530fc1e07e3d74d97c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 17:39:55 -0700 Subject: [PATCH 085/125] work --- src/wasm-interpreter.h | 106 +++++++++++++++++++++++------------------ 1 file changed, 59 insertions(+), 47 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 8cf53816cac..388255baf36 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1,4 +1,4 @@ -//#define WASM_INTERPRETER_DEBUG 1 +#define WASM_INTERPRETER_DEBUG 1 /* * Copyright 2015 WebAssembly Community Group participants * @@ -326,59 +326,71 @@ class ExpressionRunner : public OverriddenVisitor { } else { // We may suspend/resume. To support that, note values on the stack, so we // can save them if we do suspend. - StackValueNoter noter(this); - auto isControlFlow = Properties::isControlFlowStructure(curr); - if (!resuming || isControlFlow) { - // Normal execution, or resuming but this is a control flow structure - // (and every control flow structure handles itself). - ret = OverriddenVisitor::visit(curr); - } else { - // We are resuming code. Perhaps we have a restored value for it, - // which we should then just return. - auto iter = restoredValuesMap.find(curr); - if (iter != restoredValuesMap.end()) { - ret = iter->second; - restoredValuesMap.erase(iter); + { + StackValueNoter noter(this); + + if (!resuming || isControlFlow) { + // Normal execution, or resuming but this is a control flow structure + // (and every control flow structure handles itself). + ret = OverriddenVisitor::visit(curr); } else { - // Some of its children may have executed, and - // we have values stashed for them (see below where we suspend). Get - // those values, and populate || so that when visit() is called on - // them, we can return those values rather than run them. - auto numEntry = popResumeInfoEntry(); - assert(numEntry.size() == 1); - auto num = numEntry[0].geti32(); - for (auto* child : ChildIterator(curr)) { - if (num == 0) { - // We have restored all the children that executed (any others - // were not suspended, and we have no values for them). - break; + // We are resuming code. Perhaps we have a restored value for it, + // which we should then just return. + auto iter = restoredValuesMap.find(curr); + if (iter != restoredValuesMap.end()) { + ret = iter->second; + restoredValuesMap.erase(iter); + } else { + // Some of its children may have executed, and + // we have values stashed for them (see below where we suspend). Get + // those values, and populate || so that when visit() is called on + // them, we can return those values rather than run them. + auto numEntry = popResumeInfoEntry(); + assert(numEntry.size() == 1); + auto num = numEntry[0].geti32(); + for (auto* child : ChildIterator(curr)) { + if (num == 0) { + // We have restored all the children that executed (any others + // were not suspended, and we have no values for them). + break; + } + num--; + auto value = popResumeInfoEntry(); + restoredValuesMap[child] = value; } - num--; - auto value = popResumeInfoEntry(); - restoredValuesMap[child] = value; + // We are ready to return the right values for the children, and can + // visit this instruction. + ret = OverriddenVisitor::visit(curr); } - // We are ready to return the right values for the children, and can - // visit this instruction. - ret = OverriddenVisitor::visit(curr); } - } - if (ret.suspendTag && !isControlFlow) { - assert(currContinuation); - // We are suspending a continuation. We have stashed values at the back - // of valueStack, and we can save those for when we resume, together - // with the number of such values, so we know how many children to - // process. We put one entry for each value, plus their number. - // TODO: - assert(!valueStack.empty()); - auto& values = valueStack.back(); - auto num = values.size(); - while (!values.empty()) { - pushResumeInfoEntry(values.back()); // TODO: std::move? - values.pop_back(); + if (!isControlFlow && ret.suspendTag) { + // We are suspending a continuation. We have stashed values at the back + // of valueStack, and we can save those for when we resume, together + // with the number of such values, so we know how many children to + // process. We put one entry for each value, plus their number. + // TODO: + assert(!valueStack.empty()); + auto& values = valueStack.back(); + auto num = values.size(); + while (!values.empty()) { + pushResumeInfoEntry(values.back()); // TODO: std::move? + values.pop_back(); + } + pushResumeInfoEntry({Literal(int32_t(num))}); + } + } + // Outside the scope of StackValueNoter, we can handle stashing our own + // value for our parent (whose values are at the top of valueStack. + if (!isControlFlow && !ret.suspendTag) { + // We are not suspending. But we might suspend later, so stash our + // return value on the valueStack. + if (ret.getType().isConcrete()) { + assert(!valueStack.empty()); + auto& values = valueStack.back(); + values.push_back(ret.values); } - pushResumeInfoEntry({Literal(int32_t(num))}); } } From b835ddcc07ce74b729bb0e959a30b15bdccc4116 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 17:47:31 -0700 Subject: [PATCH 086/125] work --- src/wasm-interpreter.h | 46 ++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 388255baf36..82d7e0f2656 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1,4 +1,4 @@ -#define WASM_INTERPRETER_DEBUG 1 +//#define WASM_INTERPRETER_DEBUG 1 /* * Copyright 2015 WebAssembly Community Group participants * @@ -326,13 +326,12 @@ class ExpressionRunner : public OverriddenVisitor { } else { // We may suspend/resume. To support that, note values on the stack, so we // can save them if we do suspend. - auto isControlFlow = Properties::isControlFlowStructure(curr); + auto isControlFlow = false; { - StackValueNoter noter(this); + StackValueNoter noter(this); // no need with conrolf low - if (!resuming || isControlFlow) { + if (!resuming) { // Normal execution, or resuming but this is a control flow structure - // (and every control flow structure handles itself). ret = OverriddenVisitor::visit(curr); } else { // We are resuming code. Perhaps we have a restored value for it, @@ -342,25 +341,28 @@ class ExpressionRunner : public OverriddenVisitor { ret = iter->second; restoredValuesMap.erase(iter); } else { - // Some of its children may have executed, and - // we have values stashed for them (see below where we suspend). Get - // those values, and populate || so that when visit() is called on - // them, we can return those values rather than run them. - auto numEntry = popResumeInfoEntry(); - assert(numEntry.size() == 1); - auto num = numEntry[0].geti32(); - for (auto* child : ChildIterator(curr)) { - if (num == 0) { - // We have restored all the children that executed (any others - // were not suspended, and we have no values for them). - break; + // every control flow structure handles itself + if (!Properties::isControlFlowStructure(curr)) { + // Some of its children may have executed, and + // we have values stashed for them (see below where we suspend). Get + // those values, and populate || so that when visit() is called on + // them, we can return those values rather than run them. + auto numEntry = popResumeInfoEntry(); + assert(numEntry.size() == 1); + auto num = numEntry[0].geti32(); + for (auto* child : ChildIterator(curr)) { + if (num == 0) { + // We have restored all the children that executed (any others + // were not suspended, and we have no values for them). + break; + } + num--; + auto value = popResumeInfoEntry(); + restoredValuesMap[child] = value; } - num--; - auto value = popResumeInfoEntry(); - restoredValuesMap[child] = value; + // We are ready to return the right values for the children, and can + // visit this instruction. } - // We are ready to return the right values for the children, and can - // visit this instruction. ret = OverriddenVisitor::visit(curr); } } From 79d2689a93e1cde207e73bb355b44210d4bc8afd Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 17:55:32 -0700 Subject: [PATCH 087/125] work --- src/wasm-interpreter.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 82d7e0f2656..f7625aeef00 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1,4 +1,4 @@ -//#define WASM_INTERPRETER_DEBUG 1 +#define WASM_INTERPRETER_DEBUG 1 /* * Copyright 2015 WebAssembly Community Group participants * @@ -326,12 +326,12 @@ class ExpressionRunner : public OverriddenVisitor { } else { // We may suspend/resume. To support that, note values on the stack, so we // can save them if we do suspend. - auto isControlFlow = false; + auto isControlFlow = Properties::isControlFlowStructure(curr); { StackValueNoter noter(this); // no need with conrolf low if (!resuming) { - // Normal execution, or resuming but this is a control flow structure + // Normal execution. ret = OverriddenVisitor::visit(curr); } else { // We are resuming code. Perhaps we have a restored value for it, @@ -341,8 +341,8 @@ class ExpressionRunner : public OverriddenVisitor { ret = iter->second; restoredValuesMap.erase(iter); } else { - // every control flow structure handles itself - if (!Properties::isControlFlowStructure(curr)) { + // every control flow structure handles itself in its visit*(). + if (!isControlFlow) { // Some of its children may have executed, and // we have values stashed for them (see below where we suspend). Get // those values, and populate || so that when visit() is called on @@ -385,7 +385,7 @@ class ExpressionRunner : public OverriddenVisitor { } // Outside the scope of StackValueNoter, we can handle stashing our own // value for our parent (whose values are at the top of valueStack. - if (!isControlFlow && !ret.suspendTag) { + if (!ret.suspendTag) { // We are not suspending. But we might suspend later, so stash our // return value on the valueStack. if (ret.getType().isConcrete()) { From 19b2b4e629dc107f3acc4cadb290410c4a8336c5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 17:56:28 -0700 Subject: [PATCH 088/125] work --- src/wasm-interpreter.h | 2 +- test/lit/exec/cont_simple.wast | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index f7625aeef00..227ca33de36 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1,4 +1,4 @@ -#define WASM_INTERPRETER_DEBUG 1 +//#define WASM_INTERPRETER_DEBUG 1 /* * Copyright 2015 WebAssembly Community Group participants * diff --git a/test/lit/exec/cont_simple.wast b/test/lit/exec/cont_simple.wast index 6cc75269e9f..98fa900b0f1 100644 --- a/test/lit/exec/cont_simple.wast +++ b/test/lit/exec/cont_simple.wast @@ -290,6 +290,11 @@ ;; CHECK-NEXT: [LoggingExternalInterface logging 100] ;; CHECK-NEXT: [LoggingExternalInterface logging 200] ;; CHECK-NEXT: [LoggingExternalInterface logging -1] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging -2] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging -3] ;; CHECK-NEXT: [LoggingExternalInterface logging 300] (func $run-value-stack (export "run-value-stack") (call $run From 73b6d7e889448467f77b71e2473c03201f15d370 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 29 Jul 2025 17:58:51 -0700 Subject: [PATCH 089/125] work --- test/lit/exec/cont_simple.wast | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/lit/exec/cont_simple.wast b/test/lit/exec/cont_simple.wast index 98fa900b0f1..001db7d4bc9 100644 --- a/test/lit/exec/cont_simple.wast +++ b/test/lit/exec/cont_simple.wast @@ -251,7 +251,7 @@ ;; Suspend on the left. No value is actually saved on the stack, as we ;; resume before we execute the right side. (call $log - (i32.sub + (i32.sub ;; 1 - 2 => -1 (block (result i32) (suspend $more) (i32.const 1) @@ -259,9 +259,9 @@ (i32.const 2) ) ) - ;; On the right. Now + ;; On the right. Now we save the 2 when we suspend. (call $log - (i32.sub + (i32.sub ;; 2 - 4 => -2 (i32.const 2) (block (result i32) (suspend $more) @@ -269,9 +269,9 @@ ) ) ) - ;; Both sides + ;; Both sides suspend. (call $log - (i32.sub + (i32.sub ;; 3 - 6 => -3 (block (result i32) (suspend $more) (i32.const 3) From 8289194e18d7894018c83615ae47001daf080822 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 30 Jul 2025 09:51:36 -0700 Subject: [PATCH 090/125] work --- test/lit/exec/cont_simple.wast | 59 ++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/test/lit/exec/cont_simple.wast b/test/lit/exec/cont_simple.wast index 001db7d4bc9..e7a03814ad7 100644 --- a/test/lit/exec/cont_simple.wast +++ b/test/lit/exec/cont_simple.wast @@ -282,8 +282,6 @@ ) ) ) - ;; TODO other test for nested - ;; TODO other test for select (trinary) ) ;; CHECK: [fuzz-exec] calling run-value-stack @@ -301,5 +299,62 @@ (cont.new $k (ref.func $value-stack)) ) ) + + (func $nested-unary + ;; Suspend at the top. + (call $log + (i32.eqz + (i32.eqz + (i32.eqz + (block (result i32) + (suspend $more) + (i32.const 1) + ) + ) + ) + ) + ) + ;; Suspend everywhere. + (call $log + (block (result i32) + (suspend $more) + (i32.eqz + (block (result i32) + (suspend $more) + (i32.eqz + (block (result i32) + (suspend $more) + (i32.eqz + (block (result i32) + (suspend $more) + (i32.const 0) + ) + ) + ) + ) + ) + ) + ) + ) + ) + + ;; CHECK: [fuzz-exec] calling run-nested-unary + ;; CHECK-NEXT: [LoggingExternalInterface logging 100] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging 1] + ;; CHECK-NEXT: [LoggingExternalInterface logging 300] + (func $run-nested-unary (export "run-nested-unary") + (call $run + (cont.new $k (ref.func $nested-unary)) + ) + ) ) + ;; TODO other test for nested + ;; TODO other test for select (trinary), and unary + From 3123fd760997b02e489dbd81ba64ab58ed6a8d1a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 30 Jul 2025 09:56:24 -0700 Subject: [PATCH 091/125] work --- test/lit/exec/cont_simple.wast | 49 ++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/test/lit/exec/cont_simple.wast b/test/lit/exec/cont_simple.wast index e7a03814ad7..add8189bcba 100644 --- a/test/lit/exec/cont_simple.wast +++ b/test/lit/exec/cont_simple.wast @@ -353,6 +353,55 @@ (cont.new $k (ref.func $nested-unary)) ) ) + + (func $nested-unary-more + (local $temp i32) + ;; Suspend before and after each operation. + (call $log + (block (result i32) + (local.set $temp + (i32.eqz + (block (result i32) + (local.set $temp + (i32.eqz + (block (result i32) + (local.set $temp + (i32.eqz + (block (result i32) + (i32.const 0) + (suspend $more) + ) + ) + ) + (suspend $more) + (local.get $temp) + ) + ) + ) + (suspend $more) + (local.get $temp) + ) + ) + ) + (suspend $more) + (local.get $temp) + ) + ) + ) + + ;; CHECK: [fuzz-exec] calling run-nested-unary-more + ;; CHECK-NEXT: [LoggingExternalInterface logging 100] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging 1] + ;; CHECK-NEXT: [LoggingExternalInterface logging 300] + (func $run-nested-unary-more (export "run-nested-unary-more") + (call $run + (cont.new $k (ref.func $nested-unary-more)) + ) + ) ) ;; TODO other test for nested From c7e87e3cd670c4efb73529cdbbbeed3ac4be866d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 30 Jul 2025 09:59:45 -0700 Subject: [PATCH 092/125] work --- test/lit/exec/cont_simple.wast | 40 +++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/test/lit/exec/cont_simple.wast b/test/lit/exec/cont_simple.wast index add8189bcba..5d7c0430811 100644 --- a/test/lit/exec/cont_simple.wast +++ b/test/lit/exec/cont_simple.wast @@ -402,8 +402,46 @@ (cont.new $k (ref.func $nested-unary-more)) ) ) + + (func $nested-binary + ;; Both sides suspend, in different places. + (call $log ;; (2 + 1) - (4 + 2) => -3 + (i32.sub + (block (result i32) + (i32.add + (block (result i32) + (suspend $more) + (i32.const 2) + ) + (i32.const 1) + ) + ) + (block (result i32) + (suspend $more) + (i32.add + (i32.const 4) + (block (result i32) + (i32.const 2) + ) + ) + ) + ) + ) + ) + + ;; CHECK: [fuzz-exec] calling run-nested-binary + ;; CHECK-NEXT: [LoggingExternalInterface logging 100] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging -3] + ;; CHECK-NEXT: [LoggingExternalInterface logging 300] + (func $run-nested-binary (export "run-nested-binary") + (call $run + (cont.new $k (ref.func $nested-binary)) + ) + ) ) ;; TODO other test for nested - ;; TODO other test for select (trinary), and unary + ;; TODO other test for select (trinary From 889e94992b9a72d14b796f5549ae780fff4d921b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 30 Jul 2025 10:00:38 -0700 Subject: [PATCH 093/125] work --- test/lit/exec/cont_simple.wast | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/lit/exec/cont_simple.wast b/test/lit/exec/cont_simple.wast index 5d7c0430811..0cfd028b392 100644 --- a/test/lit/exec/cont_simple.wast +++ b/test/lit/exec/cont_simple.wast @@ -427,6 +427,29 @@ ) ) ) + ;; Ditto, but with suspensions in other places, and add 1. + (call $log + (i32.sub + (block (result i32) + (suspend $more) + (i32.add + (block (result i32) + (i32.const 3) + ) + (i32.const 1) + ) + ) + (block (result i32) + (i32.add + (i32.const 4) + (block (result i32) + (suspend $more) + (i32.const 2) + ) + ) + ) + ) + ) ) ;; CHECK: [fuzz-exec] calling run-nested-binary @@ -434,6 +457,9 @@ ;; CHECK-NEXT: [LoggingExternalInterface logging 200] ;; CHECK-NEXT: [LoggingExternalInterface logging 200] ;; CHECK-NEXT: [LoggingExternalInterface logging -3] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging -2] ;; CHECK-NEXT: [LoggingExternalInterface logging 300] (func $run-nested-binary (export "run-nested-binary") (call $run From 518e91db014d295d180e6c9a601a051299b871ca Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 30 Jul 2025 10:02:06 -0700 Subject: [PATCH 094/125] work --- test/lit/exec/cont_simple.wast | 39 ++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/test/lit/exec/cont_simple.wast b/test/lit/exec/cont_simple.wast index 0cfd028b392..426b9324390 100644 --- a/test/lit/exec/cont_simple.wast +++ b/test/lit/exec/cont_simple.wast @@ -404,6 +404,7 @@ ) (func $nested-binary + (local $temp i32) ;; Both sides suspend, in different places. (call $log ;; (2 + 1) - (4 + 2) => -3 (i32.sub @@ -427,27 +428,35 @@ ) ) ) - ;; Ditto, but with suspensions in other places, and add 1. + ;; Ditto, but with suspensions moved in the arms, and others on the + ;; outside. Also add 1. (call $log - (i32.sub - (block (result i32) - (suspend $more) - (i32.add + (block (result i32) + (suspend $more) + (local.set $temp + (i32.sub (block (result i32) - (i32.const 3) + (suspend $more) + (i32.add + (block (result i32) + (i32.const 3) + ) + (i32.const 1) + ) ) - (i32.const 1) - ) - ) - (block (result i32) - (i32.add - (i32.const 4) (block (result i32) - (suspend $more) - (i32.const 2) + (i32.add + (i32.const 4) + (block (result i32) + (suspend $more) + (i32.const 2) + ) + ) ) ) ) + (suspend $more) + (local.get $temp) ) ) ) @@ -459,6 +468,8 @@ ;; CHECK-NEXT: [LoggingExternalInterface logging -3] ;; CHECK-NEXT: [LoggingExternalInterface logging 200] ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] ;; CHECK-NEXT: [LoggingExternalInterface logging -2] ;; CHECK-NEXT: [LoggingExternalInterface logging 300] (func $run-nested-binary (export "run-nested-binary") From 9da66708f3d938efcc593d0b9cb06e2679359072 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 30 Jul 2025 10:04:52 -0700 Subject: [PATCH 095/125] work --- test/lit/exec/cont_simple.wast | 73 ++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 3 deletions(-) diff --git a/test/lit/exec/cont_simple.wast b/test/lit/exec/cont_simple.wast index 426b9324390..eb5dfc6262a 100644 --- a/test/lit/exec/cont_simple.wast +++ b/test/lit/exec/cont_simple.wast @@ -477,8 +477,75 @@ (cont.new $k (ref.func $nested-binary)) ) ) -) - ;; TODO other test for nested - ;; TODO other test for select (trinary + (func $trinary + ;; Suspend in one of the arms. + (call $log + (select + (block (result i32) + (suspend $more) + (i32.const 1) + ) + (i32.const 2) + (i32.const 3) + ) + ) + (call $log + (select + (i32.const 4) + (block (result i32) + (suspend $more) + (i32.const 5) + ) + (i32.const 6) + ) + ) + (call $log + (select + (i32.const 7) + (i32.const 8) + (block (result i32) + (suspend $more) + (i32.const 9) + ) + ) + ) + ;; Suspend in them all. + (call $log + (select + (block (result i32) + (suspend $more) + (i32.const 10) + ) + (block (result i32) + (suspend $more) + (i32.const 11) + ) + (block (result i32) + (suspend $more) + (i32.const 12) + ) + ) + ) + ) + + ;; CHECK: [fuzz-exec] calling run-trinary + ;; CHECK-NEXT: [LoggingExternalInterface logging 100] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging 1] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging 4] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging 7] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging 10] + ;; CHECK-NEXT: [LoggingExternalInterface logging 300] + (func $run-trinary (export "run-trinary") + (call $run + (cont.new $k (ref.func $trinary)) + ) + ) +) From 6c64acaf146570df6804ca585b4bb2731f897a34 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 30 Jul 2025 10:05:04 -0700 Subject: [PATCH 096/125] format --- src/wasm-interpreter.h | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 227ca33de36..d6de36be302 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -344,9 +344,10 @@ class ExpressionRunner : public OverriddenVisitor { // every control flow structure handles itself in its visit*(). if (!isControlFlow) { // Some of its children may have executed, and - // we have values stashed for them (see below where we suspend). Get - // those values, and populate || so that when visit() is called on - // them, we can return those values rather than run them. + // we have values stashed for them (see below where we suspend). + // Get those values, and populate || so that when visit() is + // called on them, we can return those values rather than run + // them. auto numEntry = popResumeInfoEntry(); assert(numEntry.size() == 1); auto num = numEntry[0].geti32(); @@ -360,19 +361,20 @@ class ExpressionRunner : public OverriddenVisitor { auto value = popResumeInfoEntry(); restoredValuesMap[child] = value; } - // We are ready to return the right values for the children, and can - // visit this instruction. + // We are ready to return the right values for the children, and + // can visit this instruction. } ret = OverriddenVisitor::visit(curr); } } if (!isControlFlow && ret.suspendTag) { - // We are suspending a continuation. We have stashed values at the back - // of valueStack, and we can save those for when we resume, together - // with the number of such values, so we know how many children to - // process. We put one entry for each value, plus their number. - // TODO: + // We are suspending a continuation. We have stashed values at the + // back of valueStack, and we can save those for when we resume, + // together with the number of such values, so we know how many + // children to process. We put one entry for each value, plus their + // number. + // TODO: assert(!valueStack.empty()); auto& values = valueStack.back(); auto num = values.size(); @@ -380,7 +382,7 @@ class ExpressionRunner : public OverriddenVisitor { pushResumeInfoEntry(values.back()); // TODO: std::move? values.pop_back(); } - pushResumeInfoEntry({Literal(int32_t(num))}); + pushResumeInfoEntry({Literal(int32_t(num))}); } } // Outside the scope of StackValueNoter, we can handle stashing our own From a14e0bc3b17e1af14a6438b0ea4742b48e0a1f99 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 30 Jul 2025 10:16:38 -0700 Subject: [PATCH 097/125] work --- src/wasm-interpreter.h | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index d6de36be302..6e86b9d2f5c 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1,4 +1,3 @@ -//#define WASM_INTERPRETER_DEBUG 1 /* * Copyright 2015 WebAssembly Community Group participants * @@ -16,9 +15,14 @@ */ // -// Simple WebAssembly interpreter. This operates directly on the AST, -// for simplicity and clarity. A goal is for it to be possible for -// people to read this code and understand WebAssembly semantics. +// Simple WebAssembly interpreter. This operates directly (in-place) on our IR, +// and our IR is a structured form of Wasm, so this is similar to an AST +// interpreter. Operating directly on our IR makes us efficient in the +// Precompute pass, which tries to execute every bit of code. +// +// As a side benefit, interpreting the IR directly makes the code an easy way to +// understand WebAssembly semantics (see e.g. visitLoop(), which is basically +// just a simple loop). // #ifndef wasm_wasm_interpreter_h @@ -126,8 +130,15 @@ class Flow { } }; -// Suspend/resume support. The critical data is stored inside a continuation -// Literal, using this data structure. +// Suspend/resume support. +// +// As we are +// +// Key parts of this support: +// * ContData is the key data structure that represents continuations. Each +// continuation Literal has a reference to one of these. +// ... + struct ContData { // The function this continuation begins in. // TODO: handle cross-module calls using something other than a Name here. From 1d6a40569a417b845046e0ad1c6d4e6c13d99d6d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 30 Jul 2025 10:23:15 -0700 Subject: [PATCH 098/125] work --- src/wasm-interpreter.h | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 6e86b9d2f5c..2656470f8ce 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -132,7 +132,24 @@ class Flow { // Suspend/resume support. // -// As we are +// As we operate directly on our structured IR, we do not have a program counter +// (bytecode offset to execute, or such), nor can we use continuation-passing +// style. Instead, we implement suspending and resuming code in a parallel way +// to how Asyncify does so, see src/passes/Asyncify.cpp (as well as +// https://kripken.github.io/blog/wasm/2019/07/16/asyncify.html). That +// transformation modifies wasm, while we are an interpreter that executes wasm, +// but the shared idea is that to resume code we simply need to get to where we +// were when we suspended, so we have a "resuming" mode in which we walk the IR +// but do not execute normally. While resuming we basically re-wind the stack, +// using data we stashed on the side while unwinding. For example, if we unwind +// an If instruction then we note which arm of the If we unwound from, and then +// when we re-wind we enter that proper arm, etc. +// +// This is not the most efficient way to pause and resume execution (a program +// counter/goto would be much faster!) but this is very simple to implement in +// our interpreter, and in a way that does not make the interpreter slower when +// not pausing/resuming. As with Asyncify, the assumption is that pauses/resumes +// are rare, and it is acceptable for them to be less efficient. // // Key parts of this support: // * ContData is the key data structure that represents continuations. Each From f83baa9dba6e3956017ed362918dec7828b1eccf Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 30 Jul 2025 10:46:30 -0700 Subject: [PATCH 099/125] work --- src/wasm-interpreter.h | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 2656470f8ce..45c3d7addda 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -152,8 +152,25 @@ class Flow { // are rare, and it is acceptable for them to be less efficient. // // Key parts of this support: -// * ContData is the key data structure that represents continuations. Each +// * |ContData| is the key data structure that represents continuations. Each // continuation Literal has a reference to one of these. +// * Inside the interpreter itself: +// * |currContinuation| is the continuation we are currently executing, if +// any. +// * |resuming| is set when we are in the special "resuming" mode mentioned +// above. +// * When we suspend, everything on the stack will save the necessary info +// to recreate itself later during resume. That is done by calling +// |pushResumeEntry|, which saves info on the continuation, and which is +// read during resume using |popResumeEntry|. +// * |valueStack| preserves values on the stack, so that we can save them +// later if we suspend. +// * When we resume, the old |valuesStack| is converted into +// |restoredValuesMap|. When a visit() sees that we have a value to +// restore, it simply returns it. +// * The main suspend/resume logic is in |visit|. That handles everything +// except for control flow structure-specific handling, which is done in +// |visitIf| etc. (each such structure handles itself). // ... struct ContData { @@ -305,7 +322,7 @@ class ExpressionRunner : public OverriddenVisitor { // will be cleared. bool resuming = false; - void pushResumeInfoEntry(const Literals& entry) { + void pushResumeEntry(const Literals& entry) { assert(currContinuation); #if WASM_INTERPRETER_DEBUG std::cout << indent() << "push resume entry: " << entry << "\n"; @@ -313,7 +330,7 @@ class ExpressionRunner : public OverriddenVisitor { currContinuation->resumeInfo.push_back(entry); } - Literals popResumeInfoEntry() { + Literals popResumeEntry() { assert(currContinuation); assert(!currContinuation->resumeInfo.empty()); auto entry = currContinuation->resumeInfo.back(); @@ -376,7 +393,7 @@ class ExpressionRunner : public OverriddenVisitor { // Get those values, and populate || so that when visit() is // called on them, we can return those values rather than run // them. - auto numEntry = popResumeInfoEntry(); + auto numEntry = popResumeEntry(); assert(numEntry.size() == 1); auto num = numEntry[0].geti32(); for (auto* child : ChildIterator(curr)) { @@ -386,7 +403,7 @@ class ExpressionRunner : public OverriddenVisitor { break; } num--; - auto value = popResumeInfoEntry(); + auto value = popResumeEntry(); restoredValuesMap[child] = value; } // We are ready to return the right values for the children, and @@ -407,10 +424,10 @@ class ExpressionRunner : public OverriddenVisitor { auto& values = valueStack.back(); auto num = values.size(); while (!values.empty()) { - pushResumeInfoEntry(values.back()); // TODO: std::move? + pushResumeEntry(values.back()); // TODO: std::move? values.pop_back(); } - pushResumeInfoEntry({Literal(int32_t(num))}); + pushResumeEntry({Literal(int32_t(num))}); } } // Outside the scope of StackValueNoter, we can handle stashing our own @@ -466,11 +483,11 @@ class ExpressionRunner : public OverriddenVisitor { // in the block. entry.push_back(Literal(uint32_t(stack.size()))); entry.push_back(Literal(uint32_t(blockIndex))); - pushResumeInfoEntry(entry); + pushResumeEntry(entry); }; Index blockIndex = 0; if (resuming) { - auto entry = popResumeInfoEntry(); + auto entry = popResumeEntry(); assert(entry.size() == 2); Index stackIndex = entry[0].geti32(); blockIndex = entry[1].geti32(); @@ -515,11 +532,11 @@ class ExpressionRunner : public OverriddenVisitor { // 0 - suspended in the condition // 1 - suspended in the ifTrue arm // 2 - suspended in the ifFalse arm - pushResumeInfoEntry({Literal(int32_t(resumeIndex))}); + pushResumeEntry({Literal(int32_t(resumeIndex))}); }; Index resumeIndex = -1; if (resuming) { - auto entry = popResumeInfoEntry(); + auto entry = popResumeEntry(); assert(entry.size() == 1); resumeIndex = entry[0].geti32(); } From 2d12c6a8c40fa53f9fbcfb91f48eb44d457c2993 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 30 Jul 2025 10:52:11 -0700 Subject: [PATCH 100/125] work --- src/wasm-interpreter.h | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 45c3d7addda..48c9592fd62 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -171,7 +171,6 @@ class Flow { // * The main suspend/resume logic is in |visit|. That handles everything // except for control flow structure-specific handling, which is done in // |visitIf| etc. (each such structure handles itself). -// ... struct ContData { // The function this continuation begins in. @@ -181,14 +180,15 @@ struct ContData { // The continuation type. HeapType type; - // The expression to resume execution at: where we suspended last, or, if this - // is the first execution, nullptr (which means to resume at the very start). + // The expression to resume execution at, which is where we suspended. Or, if + // we are just starting to execute this continuation, this is nullptr (and we + // will resume at the very start). Expression* resumeExpr = nullptr; // Information about how to resume execution, a list of instruction and data // that we "replay" into the value and call stacks. For convenience we split // this into separate entries, each one a Literals. Typically an instruction - // will emit a single Literals for itself. + // will emit a single Literals for itself, or possibly a few bundles. std::vector resumeInfo; // Whether we executed. Continuations are one-shot, so they may not be @@ -275,14 +275,20 @@ class ExpressionRunner : public OverriddenVisitor { protected: RelaxedBehavior relaxedBehavior = RelaxedBehavior::NonConstant; - // We save values from visit() until they are consumed, so that we can pause/ - // resume. TODO: move into ModuleRunner, since we need FunctionState anyhow - // for locals? - // Each entry here is a scope, and contains all the values from children - // that we have seen. + // We save the value stack, so that we can stash it if we suspend. Normally, + // each instruction just calls visit() on its children, so the values are + // saved in those local stack frames in an efficient manner, but also we + // cannot scan those stack frames efficiently. Also saving those values in + // this location does not add significant overhead, and it is trivial to use + // when suspending. + // + // Each entry here is for an instruction in the stack of executing + // expressions, and contains all the values from its children that we have + // seen thus far. std::vector> valueStack; - // RAII class that adds noting of stack values in a scope. + // RAII helper for |valueStack|: Adds a scope for an instruction, and cleans + // it up after. struct StackValueNoter { // StackScope? ExpressionRunner* parent; From b0e480534529e122cfe5c43fb677351c2ee75617 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 30 Jul 2025 10:57:51 -0700 Subject: [PATCH 101/125] work --- src/wasm-interpreter.h | 46 ++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 48c9592fd62..7374364321f 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -275,6 +275,18 @@ class ExpressionRunner : public OverriddenVisitor { protected: RelaxedBehavior relaxedBehavior = RelaxedBehavior::NonConstant; +#if WASM_INTERPRETER_DEBUG + std::string indent() { + std::string ret; + for (Index i = 0; i < depth; i++) { + ret += ' '; + } + return ret; + } +#endif + + // Suspend/resume support. + // We save the value stack, so that we can stash it if we suspend. Normally, // each instruction just calls visit() on its children, so the values are // saved in those local stack frames in an efficient manner, but also we @@ -287,9 +299,9 @@ class ExpressionRunner : public OverriddenVisitor { // seen thus far. std::vector> valueStack; - // RAII helper for |valueStack|: Adds a scope for an instruction, and cleans - // it up after. - struct StackValueNoter { // StackScope? + // RAII helper for |valueStack|: Adds a scope for an instruction, where the + // values of its children will be saved, and cleans it up later. + struct StackValueNoter { ExpressionRunner* parent; StackValueNoter(ExpressionRunner* parent) : parent(parent) { @@ -302,32 +314,21 @@ class ExpressionRunner : public OverriddenVisitor { } }; - // When we resume, we wil this map with children whose values were saved when - // we suspended. We apply them as we resume. + // When we resume, we will apply the saved values from |valueStack| to this + // map, so we can "replay" them. Whenever visit() is asked to execute an + // expression that is in this map, then it will just return that value. std::unordered_map restoredValuesMap; -#if WASM_INTERPRETER_DEBUG - std::string indent() { - std::string ret; - for (Index i = 0; i < depth; i++) { - ret += ' '; - } - return ret; - } -#endif - - // Suspend/resume support for continuations. - // TODO where? - // Currently-running continuation. TODO: stack? + // The current continuation (this is set when executing it, resuming it, and + // suspending it, that is, both when executing normally and when + // unwinding/rewinding the stack). std::shared_ptr currContinuation; // Set when we are resuming execution, that is, re-winding the stack. - // |currContinuation| must be set when this is true, as that is the - // continuation we are resuming. When we finish re-winding and continue normal - // execution in the continutation, |currContinuation| remain set while this - // will be cleared. bool resuming = false; + // Add an entry to help us resume this continuation later. Instructions call + // this as we unwind. void pushResumeEntry(const Literals& entry) { assert(currContinuation); #if WASM_INTERPRETER_DEBUG @@ -336,6 +337,7 @@ class ExpressionRunner : public OverriddenVisitor { currContinuation->resumeInfo.push_back(entry); } + // Fetch an entry as we resume. Instructions call this as we rewind. Literals popResumeEntry() { assert(currContinuation); assert(!currContinuation->resumeInfo.empty()); From 62bf5d0e384ec937d7933b19328dbe2b532bacc4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 30 Jul 2025 11:00:49 -0700 Subject: [PATCH 102/125] work --- src/wasm-interpreter.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 7374364321f..727cacd7853 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -369,12 +369,11 @@ class ExpressionRunner : public OverriddenVisitor { hostLimit("interpreter recursion limit"); } - // Save and restore the value stack around each call: once the visit - // completes, all values have been consumed, and nothing needs to be - // saved. + // Execute the instruction. Flow ret; if (!currContinuation) { - // We cannot suspend/resume. Just execute normally + // We are not in a continuation, so we cannot suspend/resume. Just execute + // normally. ret = OverriddenVisitor::visit(curr); } else { // We may suspend/resume. To support that, note values on the stack, so we From b353b3f789c52168a8baca4279153964b2b9e486 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 30 Jul 2025 11:02:30 -0700 Subject: [PATCH 103/125] work --- src/wasm-interpreter.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 727cacd7853..0981cb5843a 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -383,7 +383,8 @@ class ExpressionRunner : public OverriddenVisitor { StackValueNoter noter(this); // no need with conrolf low if (!resuming) { - // Normal execution. + // Normal execution (but while maintaining |valueStack|, thanks to + // the StackValueNoter). ret = OverriddenVisitor::visit(curr); } else { // We are resuming code. Perhaps we have a restored value for it, From 3809704d061eb1820d05b8f47287c0fd04de4e71 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 30 Jul 2025 11:11:53 -0700 Subject: [PATCH 104/125] work --- src/wasm-interpreter.h | 91 +++++++++++++++++++++++------------------- 1 file changed, 49 insertions(+), 42 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 0981cb5843a..a63efd3d48d 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -376,58 +376,62 @@ class ExpressionRunner : public OverriddenVisitor { // normally. ret = OverriddenVisitor::visit(curr); } else { - // We may suspend/resume. To support that, note values on the stack, so we - // can save them if we do suspend. - auto isControlFlow = Properties::isControlFlowStructure(curr); - { + // We may suspend/resume. + bool hasValue = false; + if (resuming) { + // Perhaps we have a known value to just apply here. + auto iter = restoredValuesMap.find(curr); + if (iter != restoredValuesMap.end()) { + ret = iter->second; + restoredValuesMap.erase(iter); + hasValue = true; + } + } + if (!hasValue) { + // We must execute this instruction - either we are not resuming, or we + // are resuming but only some of our children executed). While we run, + // note values on the stack, so we can save them if we suspend. StackValueNoter noter(this); // no need with conrolf low + auto isControlFlow = Properties::isControlFlowStructure(curr); + if (!resuming) { - // Normal execution (but while maintaining |valueStack|, thanks to - // the StackValueNoter). + // Normal execution (but while noting values on the stack, as + // mentioned above). ret = OverriddenVisitor::visit(curr); } else { - // We are resuming code. Perhaps we have a restored value for it, - // which we should then just return. - auto iter = restoredValuesMap.find(curr); - if (iter != restoredValuesMap.end()) { - ret = iter->second; - restoredValuesMap.erase(iter); - } else { - // every control flow structure handles itself in its visit*(). - if (!isControlFlow) { - // Some of its children may have executed, and - // we have values stashed for them (see below where we suspend). - // Get those values, and populate || so that when visit() is - // called on them, we can return those values rather than run - // them. - auto numEntry = popResumeEntry(); - assert(numEntry.size() == 1); - auto num = numEntry[0].geti32(); - for (auto* child : ChildIterator(curr)) { - if (num == 0) { - // We have restored all the children that executed (any others - // were not suspended, and we have no values for them). - break; - } - num--; - auto value = popResumeEntry(); - restoredValuesMap[child] = value; + // We are resuming. Every control flow structure has its own logic for + // that, and here we handle all other instructions. + if (!isControlFlow) { + // Some children may have executed, and we have values stashed for + // them (see below where we suspend). Get those values, and populate + // |restoredValuesMap| so that when visit() is called on them, we + // can return those values rather than run them. + auto numEntry = popResumeEntry(); + assert(numEntry.size() == 1); + auto num = numEntry[0].geti32(); + for (auto* child : ChildIterator(curr)) { + if (num == 0) { + // We have restored all the children that executed (any others + // were not suspended, and we have no values for them). + break; } - // We are ready to return the right values for the children, and - // can visit this instruction. + num--; + auto value = popResumeEntry(); + restoredValuesMap[child] = value; } - ret = OverriddenVisitor::visit(curr); + // We are ready to return the right values for the children, and + // can visit this instruction. } + // We can now run the instruction, whether it is control flow or not. + ret = OverriddenVisitor::visit(curr); } if (!isControlFlow && ret.suspendTag) { - // We are suspending a continuation. We have stashed values at the - // back of valueStack, and we can save those for when we resume, - // together with the number of such values, so we know how many - // children to process. We put one entry for each value, plus their - // number. - // TODO: + // We are suspending a continuation. Control flow structures handled + // this already, and we do so here for all other instructions. All we + // need to do is stash the values of executed children from the + // value stack. We also stash the number of such children. assert(!valueStack.empty()); auto& values = valueStack.back(); auto num = values.size(); @@ -438,8 +442,11 @@ class ExpressionRunner : public OverriddenVisitor { pushResumeEntry({Literal(int32_t(num))}); } } + // Outside the scope of StackValueNoter, we can handle stashing our own - // value for our parent (whose values are at the top of valueStack. + // value for our parent (whose values are now at the top of |valueStack|). + // We do so when not suspending (suspending is handled above), and when + // there is a concrete value. if (!ret.suspendTag) { // We are not suspending. But we might suspend later, so stash our // return value on the valueStack. From f96c3a8ec0241c36f81f267a94a39a129989f963 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 30 Jul 2025 11:13:09 -0700 Subject: [PATCH 105/125] work --- src/wasm-interpreter.h | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index a63efd3d48d..9243765fa54 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -447,14 +447,10 @@ class ExpressionRunner : public OverriddenVisitor { // value for our parent (whose values are now at the top of |valueStack|). // We do so when not suspending (suspending is handled above), and when // there is a concrete value. - if (!ret.suspendTag) { - // We are not suspending. But we might suspend later, so stash our - // return value on the valueStack. - if (ret.getType().isConcrete()) { - assert(!valueStack.empty()); - auto& values = valueStack.back(); - values.push_back(ret.values); - } + if (!ret.suspendTag && ret.getType().isConcrete()) { + assert(!valueStack.empty()); + auto& values = valueStack.back(); + values.push_back(ret.values); } } From cb6fe6792e5a2a7a540d124c3c9395db1edbce5d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 30 Jul 2025 11:21:49 -0700 Subject: [PATCH 106/125] work --- src/wasm-interpreter.h | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 9243765fa54..0f7ab4d0e5a 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4606,12 +4606,11 @@ class ModuleRunnerBase : public ExpressionRunner { if (funcFlow.breaking()) { return funcFlow; } + // Create a new continuation for the target function. Name func = funcFlow.getSingleValue().getFunc(); - // Create a fresh continuation. return Literal(std::make_shared(func, curr->type.getHeapType())); } Flow visitContBind(ContBind* curr) { return Flow(NONCONSTANT_FLOW); } - Flow visitSuspend(Suspend* curr) { if (self()->resuming) { // This is a resume, so we have found our way back to where we @@ -4626,26 +4625,28 @@ class ModuleRunnerBase : public ExpressionRunner { return Flow(); } + // We were not resuming, so this is a new suspend that we must execute. Literals arguments; Flow flow = self()->generateArguments(curr->operands, arguments); if (flow.breaking()) { return flow; } - // Generate a continuation to proceed from here, and add it as another - // value. The name of the function at the bottom of the stack is in - // currContinuation. if (!self()->currContinuation) { trap("no continuation to suspend"); } - // Copy the continuation (the old one cannot be resumed again) and add stack - // info so it can be restored from here. + // Copy the continuation (the old one cannot be resumed again). auto old = self()->currContinuation; + assert(old->executed); auto new_ = std::make_shared(old->func, old->type); + // Switch to the new continuation, so that as we unwind, we will save the + // information we need to resume it later in the proper place. self()->currContinuation = new_; + // We will resume from this precise spot, when the new continuation is + // resumed. new_->resumeExpr = curr; - // TODO: save the call stack! (call, call_indirect, call_ref) - // TODO: add a suspend/resume fuzzer (plant suspends in code using pass?) + // Add the new continuation as a final value, which is the form that Resume + // will emit. arguments.push_back(Literal(new_)); return Flow(SUSPEND_FLOW, curr->tag, std::move(arguments)); } @@ -4687,10 +4688,8 @@ class ModuleRunnerBase : public ExpressionRunner { auto handlerTag = curr->handlerTags[i]; if (handlerTag == ret.suspendTag) { // Switch the flow from suspending to branching, and keep sending the - // same values (which include the tag values + a new continuation at - // the end, so we have nothing to add here). // TODO: doc on Flow - // TODO: callTable is tricky, as table might change, so like in - // Asyncify, need to save funcref. + // same values (which already includes the values + the continuation, + // see Suspend, so we have nothing to add here. ret.suspendTag = Name(); ret.breakTo = curr->handlerBlocks[i]; self()->currContinuation.reset(); From 2991f8c82569df4f47f50d19c3b60ff190290552 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 30 Jul 2025 11:24:58 -0700 Subject: [PATCH 107/125] work --- src/wasm-interpreter.h | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 0f7ab4d0e5a..eecaa88a884 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4780,14 +4780,10 @@ class ModuleRunnerBase : public ExpressionRunner { FunctionScope scope(function, arguments, *self()); if (self()->resuming) { - // Restore the local state (see below for the ordering). - assert(self()->currContinuation); - auto& resumeInfo = self()->currContinuation->resumeInfo; + // Restore the local state (see below for the ordering, we push/pop). for (Index i = 0; i < scope.locals.size(); i++) { - assert(!resumeInfo.empty()); auto l = scope.locals.size() - 1 - i; - scope.locals[l] = resumeInfo.back(); - resumeInfo.pop_back(); + scope.locals[l] = self()->popResumeEntry(); // Must have restored valid data. assert(Type::isSubType(scope.locals[l].getType(), function->getLocalType(l))); @@ -4812,9 +4808,8 @@ class ModuleRunnerBase : public ExpressionRunner { if (flow.suspendTag) { // Save the local state. - assert(self()->currContinuation); for (auto& local : scope.locals) { - self()->currContinuation->resumeInfo.push_back(local); + self()->pushResumeEntry(local); } } From c9ba2a95dcf203377fe89f4130ee74378642c01d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 30 Jul 2025 11:28:57 -0700 Subject: [PATCH 108/125] work --- test/lit/exec/cont_simple.wast | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/test/lit/exec/cont_simple.wast b/test/lit/exec/cont_simple.wast index eb5dfc6262a..fd752ca517c 100644 --- a/test/lit/exec/cont_simple.wast +++ b/test/lit/exec/cont_simple.wast @@ -478,7 +478,6 @@ ) ) - (func $trinary ;; Suspend in one of the arms. (call $log @@ -548,4 +547,32 @@ (cont.new $k (ref.func $trinary)) ) ) + + (func $multi-locals + (local $i32 i32) + (local $f64 f64) + (local.set $i32 (i32.const 42)) + (local.set $f64 (f64.const 3.14159)) + (suspend $more) + (call $log + (local.get $i32) + ) + (call $log + (i32.trunc_f64_s + (local.get $f64) + ) + ) + ) + + ;; CHECK: [fuzz-exec] calling run-multi-locals + ;; CHECK-NEXT: [LoggingExternalInterface logging 100] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging 42] + ;; CHECK-NEXT: [LoggingExternalInterface logging 3] + ;; CHECK-NEXT: [LoggingExternalInterface logging 300] + (func $run-multi-locals (export "run-multi-locals") + (call $run + (cont.new $k (ref.func $multi-locals)) + ) + ) ) From 37b6c7507813aac854cc35e0e637b24acdcaedd3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 30 Jul 2025 11:40:36 -0700 Subject: [PATCH 109/125] work --- src/wasm-interpreter.h | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index eecaa88a884..74c185d74a3 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4645,9 +4645,6 @@ class ModuleRunnerBase : public ExpressionRunner { // We will resume from this precise spot, when the new continuation is // resumed. new_->resumeExpr = curr; - // Add the new continuation as a final value, which is the form that Resume - // will emit. - arguments.push_back(Literal(new_)); return Flow(SUSPEND_FLOW, curr->tag, std::move(arguments)); } Flow visitResume(Resume* curr) { @@ -4687,11 +4684,12 @@ class ModuleRunnerBase : public ExpressionRunner { for (size_t i = 0; i < curr->handlerTags.size(); i++) { auto handlerTag = curr->handlerTags[i]; if (handlerTag == ret.suspendTag) { - // Switch the flow from suspending to branching, and keep sending the - // same values (which already includes the values + the continuation, - // see Suspend, so we have nothing to add here. + // Switch the flow from suspending to branching. ret.suspendTag = Name(); ret.breakTo = curr->handlerBlocks[i]; + // Add the continuation as the final value being sent. + ret.values.push_back(Literal(self()->currContinuation)); + // We are not longer processing that continuation. self()->currContinuation.reset(); return ret; } @@ -4830,15 +4828,10 @@ class ModuleRunnerBase : public ExpressionRunner { } auto type = flow.getType(); - if (flow.breakTo == SUSPEND_FLOW) { - // When suspending, the last value is a continuation. - assert(type.size() > 0); - assert(type[type.size() - 1].isContinuation()); - } else { + if (flow.breakTo != SUSPEND_FLOW) { // We are normally executing (not suspending), and therefore cannot still // be breaking, which would mean we missed our stop. assert(!flow.breaking() || flow.breakTo == RETURN_FLOW); - #ifndef NDEBUG // In normal execution, the result is the expected one. if (!Type::isSubType(type, *resultType)) { From 9155b9c2ce64b81d571842e09f4d6f5b75438db1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 30 Jul 2025 11:42:24 -0700 Subject: [PATCH 110/125] safe --- scripts/test/fuzzing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 5e716107fc5..def4b0b3189 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -111,6 +111,7 @@ 'precompute-stack-switching.wast', 'unsubtyping-stack-switching.wast', 'vacuum-stack-switching.wast', + 'cont_simple.wast', # TODO: fuzzer support for custom descriptors 'remove-unused-module-elements-refs-descriptors.wast', 'custom-descriptors.wast', From d14259ea3adf53555aa355608e5380a49e6a432d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 30 Jul 2025 11:58:50 -0700 Subject: [PATCH 111/125] work --- src/wasm-interpreter.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 74c185d74a3..6541167b6a3 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4827,13 +4827,17 @@ class ModuleRunnerBase : public ExpressionRunner { throw NonconstantException(); } - auto type = flow.getType(); - if (flow.breakTo != SUSPEND_FLOW) { + if (flow.breakTo == RETURN_FLOW) { + // We are no longer returning out of that function (but the value + // remains the same). + flow.breakTo = Name(); + } else if (flow.breakTo != SUSPEND_FLOW) { // We are normally executing (not suspending), and therefore cannot still // be breaking, which would mean we missed our stop. assert(!flow.breaking() || flow.breakTo == RETURN_FLOW); #ifndef NDEBUG // In normal execution, the result is the expected one. + auto type = flow.getType(); if (!Type::isSubType(type, *resultType)) { Fatal() << "calling " << name << " resulted in " << type << " but the function type is " << *resultType << '\n'; From c9589d97eed940a1e3c42df092b67fdd5c418d27 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 30 Jul 2025 12:25:50 -0700 Subject: [PATCH 112/125] work --- scripts/test/fuzzing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index def4b0b3189..895c5b02539 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -111,6 +111,7 @@ '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', From 39164a3795528034fd72cdbdcf70a6646932265e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 30 Jul 2025 12:47:53 -0700 Subject: [PATCH 113/125] work --- src/wasm-interpreter.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 6541167b6a3..29a143d9de2 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -290,9 +290,10 @@ class ExpressionRunner : public OverriddenVisitor { // We save the value stack, so that we can stash it if we suspend. Normally, // each instruction just calls visit() on its children, so the values are // saved in those local stack frames in an efficient manner, but also we - // cannot scan those stack frames efficiently. Also saving those values in - // this location does not add significant overhead, and it is trivial to use - // when suspending. + // cannot scan those stack frames efficiently. Saving those values in + // this location (in addition to the normal place) does not add significant + // overhead (and we skip it entirely when not in a coroutine), and it is + // trivial to use when suspending. // // Each entry here is for an instruction in the stack of executing // expressions, and contains all the values from its children that we have @@ -388,8 +389,8 @@ class ExpressionRunner : public OverriddenVisitor { } } if (!hasValue) { - // We must execute this instruction - either we are not resuming, or we - // are resuming but only some of our children executed). While we run, + // We must execute this instruction: Either we are not resuming, or we + // are resuming but only some of our children executed. While we run, // note values on the stack, so we can save them if we suspend. StackValueNoter noter(this); // no need with conrolf low From 2cff4a3ea84951fc99a328e1a175996b62c35c86 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 30 Jul 2025 13:25:03 -0700 Subject: [PATCH 114/125] work --- src/wasm-interpreter.h | 87 ++++++++++++++++++---------------- test/lit/exec/cont_simple.wast | 1 - 2 files changed, 45 insertions(+), 43 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 29a143d9de2..3ee79889216 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -330,22 +330,22 @@ class ExpressionRunner : public OverriddenVisitor { // Add an entry to help us resume this continuation later. Instructions call // this as we unwind. - void pushResumeEntry(const Literals& entry) { + void pushResumeEntry(const Literals& entry, const char* what) { assert(currContinuation); #if WASM_INTERPRETER_DEBUG - std::cout << indent() << "push resume entry: " << entry << "\n"; + std::cout << indent() << "push resume entry [" << what << "]: " << entry << "\n"; #endif currContinuation->resumeInfo.push_back(entry); } // Fetch an entry as we resume. Instructions call this as we rewind. - Literals popResumeEntry() { + Literals popResumeEntry(const char* what) { assert(currContinuation); assert(!currContinuation->resumeInfo.empty()); auto entry = currContinuation->resumeInfo.back(); currContinuation->resumeInfo.pop_back(); #if WASM_INTERPRETER_DEBUG - std::cout << indent() << "pop resume entry: " << entry << "\n"; + std::cout << indent() << "pop resume entry [" << what << "]: " << entry << "\n"; #endif return entry; } @@ -380,7 +380,8 @@ class ExpressionRunner : public OverriddenVisitor { // We may suspend/resume. bool hasValue = false; if (resuming) { - // Perhaps we have a known value to just apply here. + // Perhaps we have a known value to just apply here, without executing + // the instruction. auto iter = restoredValuesMap.find(curr); if (iter != restoredValuesMap.end()) { ret = iter->second; @@ -389,26 +390,24 @@ class ExpressionRunner : public OverriddenVisitor { } } if (!hasValue) { - // We must execute this instruction: Either we are not resuming, or we - // are resuming but only some of our children executed. While we run, - // note values on the stack, so we can save them if we suspend. - StackValueNoter noter(this); // no need with conrolf low - - auto isControlFlow = Properties::isControlFlowStructure(curr); - - if (!resuming) { - // Normal execution (but while noting values on the stack, as - // mentioned above). + // We must execute this instruction. Set up the logic to note the values + // of children (we mainly need this for non-control flow structures, + // but even control flow ones must add a scope on the value stack, to + // not confuse the others). + StackValueNoter noter(this); + + if (Properties::isControlFlowStructure(curr)) { + // Control flow structures have their own logic for suspend/resume. ret = OverriddenVisitor::visit(curr); } else { - // We are resuming. Every control flow structure has its own logic for - // that, and here we handle all other instructions. - if (!isControlFlow) { + // A general non-control-flow instruction, with generic suspend/ + // resume support implemented here. + if (resuming) { // Some children may have executed, and we have values stashed for // them (see below where we suspend). Get those values, and populate // |restoredValuesMap| so that when visit() is called on them, we // can return those values rather than run them. - auto numEntry = popResumeEntry(); + auto numEntry = popResumeEntry("num executed children"); assert(numEntry.size() == 1); auto num = numEntry[0].geti32(); for (auto* child : ChildIterator(curr)) { @@ -418,29 +417,30 @@ class ExpressionRunner : public OverriddenVisitor { break; } num--; - auto value = popResumeEntry(); + auto value = popResumeEntry("child value"); restoredValuesMap[child] = value; } - // We are ready to return the right values for the children, and - // can visit this instruction. } - // We can now run the instruction, whether it is control flow or not. + + // We are ready to return the right values for the children, and + // can visit this instruction. ret = OverriddenVisitor::visit(curr); - } - if (!isControlFlow && ret.suspendTag) { - // We are suspending a continuation. Control flow structures handled - // this already, and we do so here for all other instructions. All we - // need to do is stash the values of executed children from the - // value stack. We also stash the number of such children. - assert(!valueStack.empty()); - auto& values = valueStack.back(); - auto num = values.size(); - while (!values.empty()) { - pushResumeEntry(values.back()); // TODO: std::move? - values.pop_back(); + if (ret.suspendTag) { + // We are suspending a continuation. All we need to do for a + // general instruction is stash the values of executed children + // from the value stack, and their number (as we may have + // suspended after executing only some). + assert(!valueStack.empty()); + auto& values = valueStack.back(); + auto num = values.size(); + while (!values.empty()) { + // TODO: std::move, &elsewhere? + pushResumeEntry(values.back(), "child value"); + values.pop_back(); + } + pushResumeEntry({Literal(int32_t(num))}, "num executed children"); } - pushResumeEntry({Literal(int32_t(num))}); } } @@ -452,6 +452,9 @@ class ExpressionRunner : public OverriddenVisitor { assert(!valueStack.empty()); auto& values = valueStack.back(); values.push_back(ret.values); +#if WASM_INTERPRETER_DEBUG + std::cout << indent() << "added to valueStack: " << ret.values << '\n'; +#endif } } @@ -495,11 +498,11 @@ class ExpressionRunner : public OverriddenVisitor { // in the block. entry.push_back(Literal(uint32_t(stack.size()))); entry.push_back(Literal(uint32_t(blockIndex))); - pushResumeEntry(entry); + pushResumeEntry(entry, "block"); }; Index blockIndex = 0; if (resuming) { - auto entry = popResumeEntry(); + auto entry = popResumeEntry("block"); assert(entry.size() == 2); Index stackIndex = entry[0].geti32(); blockIndex = entry[1].geti32(); @@ -544,11 +547,11 @@ class ExpressionRunner : public OverriddenVisitor { // 0 - suspended in the condition // 1 - suspended in the ifTrue arm // 2 - suspended in the ifFalse arm - pushResumeEntry({Literal(int32_t(resumeIndex))}); + pushResumeEntry({Literal(int32_t(resumeIndex))}, "if"); }; Index resumeIndex = -1; if (resuming) { - auto entry = popResumeEntry(); + auto entry = popResumeEntry("if"); assert(entry.size() == 1); resumeIndex = entry[0].geti32(); } @@ -4782,7 +4785,7 @@ class ModuleRunnerBase : public ExpressionRunner { // Restore the local state (see below for the ordering, we push/pop). for (Index i = 0; i < scope.locals.size(); i++) { auto l = scope.locals.size() - 1 - i; - scope.locals[l] = self()->popResumeEntry(); + scope.locals[l] = self()->popResumeEntry("function"); // Must have restored valid data. assert(Type::isSubType(scope.locals[l].getType(), function->getLocalType(l))); @@ -4808,7 +4811,7 @@ class ModuleRunnerBase : public ExpressionRunner { if (flow.suspendTag) { // Save the local state. for (auto& local : scope.locals) { - self()->pushResumeEntry(local); + self()->pushResumeEntry(local, "function"); } } diff --git a/test/lit/exec/cont_simple.wast b/test/lit/exec/cont_simple.wast index fd752ca517c..49855cff030 100644 --- a/test/lit/exec/cont_simple.wast +++ b/test/lit/exec/cont_simple.wast @@ -21,7 +21,6 @@ (call $log (i32.const 300)) ;; stop (return) ) - ;; on (call $log (i32.const 200)) ;; continue (local.set $k) (br $loop) From b823e9ae77991c66bcf376ca7056eb96eafc7a64 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 30 Jul 2025 13:28:02 -0700 Subject: [PATCH 115/125] work --- src/wasm-interpreter.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 3ee79889216..4a524cc1479 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4835,7 +4835,9 @@ class ModuleRunnerBase : public ExpressionRunner { // We are no longer returning out of that function (but the value // remains the same). flow.breakTo = Name(); - } else if (flow.breakTo != SUSPEND_FLOW) { + } + + if (flow.breakTo != SUSPEND_FLOW) { // We are normally executing (not suspending), and therefore cannot still // be breaking, which would mean we missed our stop. assert(!flow.breaking() || flow.breakTo == RETURN_FLOW); From 94606499e19384de4b45393066b37eebaf779b40 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 30 Jul 2025 13:29:44 -0700 Subject: [PATCH 116/125] work --- test/spec/cont.wast | 872 -------------------------------------------- 1 file changed, 872 deletions(-) diff --git a/test/spec/cont.wast b/test/spec/cont.wast index c681b404c04..952fcf82a0c 100644 --- a/test/spec/cont.wast +++ b/test/spec/cont.wast @@ -227,875 +227,3 @@ (type $c2 (cont $f2)) ) -;; Simple state example - -(module $state - (tag $get (result i32)) - (tag $set (param i32) (result i32)) - - (type $f (func (param i32) (result i32))) - (type $k (cont $f)) - - (func $runner (param $s i32) (param $k (ref $k)) (result i32) - (loop $loop - (block $on_get (result (ref $k)) - (block $on_set (result i32 (ref $k)) - (resume $k (on $get $on_get) (on $set $on_set) - (local.get $s) (local.get $k) - ) - (return) - ) - ;; on set - (local.set $k) - (local.set $s) - (br $loop) - ) - ;; on get - (local.set $k) - (br $loop) - ) - (unreachable) - ) - - (func $f (param i32) (result i32) - (drop (suspend $set (i32.const 7))) - (i32.add - (suspend $get) - (i32.mul - (i32.const 2) - (i32.add - (suspend $set (i32.const 3)) - (suspend $get) - ) - ) - ) - ) - - (elem declare func $f) - (func (export "run") (result i32) - (call $runner (i32.const 0) (cont.new $k (ref.func $f))) - ) -) - -(assert_return (invoke "run") (i32.const 19)) - - -;; Simple generator example - -(module $generator - (type $gen (func (param i64))) - (type $geny (func (param i32))) - (type $cont0 (cont $gen)) - (type $cont (cont $geny)) - - (tag $yield (param i64) (result i32)) - - ;; Hook for logging purposes - (global $hook (export "hook") (mut (ref $gen)) (ref.func $dummy)) - (func $dummy (param i64)) - - (func $gen (export "start") (param $i i64) - (loop $l - (br_if 1 (suspend $yield (local.get $i))) - (call_ref $gen (local.get $i) (global.get $hook)) - (local.set $i (i64.add (local.get $i) (i64.const 1))) - (br $l) - ) - ) - - (elem declare func $gen) - - (func (export "sum") (param $i i64) (param $j i64) (result i64) - (local $sum i64) - (local $n i64) - (local $k (ref null $cont)) - (local.get $i) - (cont.new $cont0 (ref.func $gen)) - (block $on_first_yield (param i64 (ref $cont0)) (result i64 (ref $cont)) - (resume $cont0 (on $yield $on_first_yield)) - (unreachable) - ) - (loop $on_yield (param i64) (param (ref $cont)) - (local.set $k) - (local.set $n) - (local.set $sum (i64.add (local.get $sum) (local.get $n))) - (i64.eq (local.get $n) (local.get $j)) - (local.get $k) - (resume $cont (on $yield $on_yield)) - ) - (return (local.get $sum)) - ) -) - -(register "generator") - -(assert_return (invoke "sum" (i64.const 0) (i64.const 0)) (i64.const 0)) -(assert_return (invoke "sum" (i64.const 2) (i64.const 2)) (i64.const 2)) -(assert_return (invoke "sum" (i64.const 0) (i64.const 3)) (i64.const 6)) -(assert_return (invoke "sum" (i64.const 1) (i64.const 10)) (i64.const 55)) -(assert_return (invoke "sum" (i64.const 100) (i64.const 2000)) (i64.const 1_996_050)) - - -;; Simple scheduler example - -(module $scheduler - (type $proc (func)) - (type $cont (cont $proc)) - - (tag $yield (export "yield")) - (tag $spawn (export "spawn") (param (ref $cont))) - - ;; Table as simple queue (keeping it simple, no ring buffer) - (table $queue 0 (ref null $cont)) - (global $qdelta i32 (i32.const 10)) - (global $qback (mut i32) (i32.const 0)) - (global $qfront (mut i32) (i32.const 0)) - - (func $queue-empty (result i32) - (i32.eq (global.get $qfront) (global.get $qback)) - ) - - (func $dequeue (result (ref null $cont)) - (local $i i32) - (if (call $queue-empty) - (then (return (ref.null $cont))) - ) - (local.set $i (global.get $qfront)) - (global.set $qfront (i32.add (local.get $i) (i32.const 1))) - (table.get $queue (local.get $i)) - ) - - (func $enqueue (param $k (ref $cont)) - ;; Check if queue is full - (if (i32.eq (global.get $qback) (table.size $queue)) - (then - ;; Check if there is enough space in the front to compact - (if (i32.lt_u (global.get $qfront) (global.get $qdelta)) - (then - ;; Space is below threshold, grow table instead - (drop (table.grow $queue (ref.null $cont) (global.get $qdelta))) - ) - (else - ;; Enough space, move entries up to head of table - (global.set $qback (i32.sub (global.get $qback) (global.get $qfront))) - (table.copy $queue $queue - (i32.const 0) ;; dest = new front = 0 - (global.get $qfront) ;; src = old front - (global.get $qback) ;; len = new back = old back - old front - ) - (table.fill $queue ;; null out old entries to avoid leaks - (global.get $qback) ;; start = new back - (ref.null $cont) ;; init value - (global.get $qfront) ;; len = old front = old front - new front - ) - (global.set $qfront (i32.const 0)) - ) - ) - ) - ) - (table.set $queue (global.get $qback) (local.get $k)) - (global.set $qback (i32.add (global.get $qback) (i32.const 1))) - ) - - (func $scheduler (export "scheduler") (param $main (ref $cont)) - (call $enqueue (local.get $main)) - (loop $l - (if (call $queue-empty) (then (return))) - (block $on_yield (result (ref $cont)) - (block $on_spawn (result (ref $cont) (ref $cont)) - (resume $cont (on $yield $on_yield) (on $spawn $on_spawn) - (call $dequeue) - ) - (br $l) ;; thread terminated - ) - ;; on $spawn, proc and cont on stack - (call $enqueue) ;; continuation of old thread - (call $enqueue) ;; new thread - (br $l) - ) - ;; on $yield, cont on stack - (call $enqueue) - (br $l) - ) - ) -) - -(register "scheduler") - -(module - (type $proc (func)) - (type $pproc (func (param i32))) ;; parameterised proc - (type $cont (cont $proc)) - (type $pcont (cont $pproc)) ;; parameterised continuation proc - (tag $yield (import "scheduler" "yield")) - (tag $spawn (import "scheduler" "spawn") (param (ref $cont))) - (func $scheduler (import "scheduler" "scheduler") (param $main (ref $cont))) - - (func $log (import "spectest" "print_i32") (param i32)) - - (global $width (mut i32) (i32.const 0)) - (global $depth (mut i32) (i32.const 0)) - - (elem declare func $main $thread1 $thread2 $thread3) - - (func $main - (call $log (i32.const 0)) - (suspend $spawn (cont.new $cont (ref.func $thread1))) - (call $log (i32.const 1)) - (suspend $spawn (cont.bind $pcont $cont (global.get $depth) (cont.new $pcont (ref.func $thread2)))) - (call $log (i32.const 2)) - (suspend $spawn (cont.new $cont (ref.func $thread3))) - (call $log (i32.const 3)) - ) - - (func $thread1 - (call $log (i32.const 10)) - (suspend $yield) - (call $log (i32.const 11)) - (suspend $yield) - (call $log (i32.const 12)) - (suspend $yield) - (call $log (i32.const 13)) - ) - - (func $thread2 (param $d i32) - (local $w i32) - (local.set $w (global.get $width)) - (call $log (i32.const 20)) - (br_if 0 (i32.eqz (local.get $d))) - (call $log (i32.const 21)) - (loop $l - (if (local.get $w) - (then - (call $log (i32.const 22)) - (suspend $yield) - (call $log (i32.const 23)) - (suspend $spawn - (cont.bind $pcont $cont - (i32.sub (local.get $d) (i32.const 1)) - (cont.new $pcont (ref.func $thread2)) - ) - ) - (call $log (i32.const 24)) - (local.set $w (i32.sub (local.get $w) (i32.const 1))) - (br $l) - ) - ) - ) - (call $log (i32.const 25)) - ) - - (func $thread3 - (call $log (i32.const 30)) - (suspend $yield) - (call $log (i32.const 31)) - (suspend $yield) - (call $log (i32.const 32)) - ) - - (func (export "run") (param $width i32) (param $depth i32) - (global.set $depth (local.get $depth)) - (global.set $width (local.get $width)) - (call $log (i32.const -1)) - (call $scheduler (cont.new $cont (ref.func $main))) - (call $log (i32.const -2)) - ) -) - -(assert_return (invoke "run" (i32.const 0) (i32.const 0))) -(assert_return (invoke "run" (i32.const 0) (i32.const 1))) -(assert_return (invoke "run" (i32.const 1) (i32.const 0))) -(assert_return (invoke "run" (i32.const 1) (i32.const 1))) -(assert_return (invoke "run" (i32.const 3) (i32.const 4))) - - -;; Nested example: generator in a thread - -(module $concurrent-generator - (func $log (import "spectest" "print_i64") (param i64)) - - (tag $syield (import "scheduler" "yield")) - (tag $spawn (import "scheduler" "spawn") (param (ref $cont))) - (func $scheduler (import "scheduler" "scheduler") (param $main (ref $cont))) - - (type $ghook (func (param i64))) - (func $gsum (import "generator" "sum") (param i64 i64) (result i64)) - (global $ghook (import "generator" "hook") (mut (ref $ghook))) - - (global $result (mut i64) (i64.const 0)) - (global $done (mut i32) (i32.const 0)) - - (elem declare func $main $bg-thread $syield) - - (func $syield (param $i i64) - (call $log (local.get $i)) - (suspend $syield) - ) - - (func $bg-thread - (call $log (i64.const -10)) - (loop $l - (call $log (i64.const -11)) - (suspend $syield) - (br_if $l (i32.eqz (global.get $done))) - ) - (call $log (i64.const -12)) - ) - - (func $main (param $i i64) (param $j i64) - (suspend $spawn (cont.new $cont (ref.func $bg-thread))) - (global.set $ghook (ref.func $syield)) - (global.set $result (call $gsum (local.get $i) (local.get $j))) - (global.set $done (i32.const 1)) - ) - - (type $proc (func)) - (type $pproc (func (param i64 i64))) - (type $cont (cont $proc)) - (type $pcont (cont $pproc)) - (func (export "sum") (param $i i64) (param $j i64) (result i64) - (call $log (i64.const -1)) - (call $scheduler - (cont.bind $pcont $cont (local.get $i) (local.get $j) (cont.new $pcont (ref.func $main))) - ) - (call $log (i64.const -2)) - (global.get $result) - ) -) - -(assert_return (invoke "sum" (i64.const 10) (i64.const 20)) (i64.const 165)) - - -;; cont.bind - -(module - (type $f2 (func (param i32 i32) (result i32 i32 i32 i32 i32 i32))) - (type $f4 (func (param i32 i32 i32 i32) (result i32 i32 i32 i32 i32 i32))) - (type $f6 (func (param i32 i32 i32 i32 i32 i32) (result i32 i32 i32 i32 i32 i32))) - - (type $k2 (cont $f2)) - (type $k4 (cont $f4)) - (type $k6 (cont $f6)) - - (elem declare func $f) - (func $f (param i32 i32 i32 i32 i32 i32) (result i32 i32 i32 i32 i32 i32) - (local.get 0) (local.get 1) (local.get 2) - (local.get 3) (local.get 4) (local.get 5) - ) - - (func (export "run") (result i32 i32 i32 i32 i32 i32) - (local $k6 (ref null $k6)) - (local $k4 (ref null $k4)) - (local $k2 (ref null $k2)) - (local.set $k6 (cont.new $k6 (ref.func $f))) - (local.set $k4 (cont.bind $k6 $k4 (i32.const 1) (i32.const 2) (local.get $k6))) - (local.set $k2 (cont.bind $k4 $k2 (i32.const 3) (i32.const 4) (local.get $k4))) - (resume $k2 (i32.const 5) (i32.const 6) (local.get $k2)) - ) -) - -(assert_return (invoke "run") - (i32.const 1) (i32.const 2) (i32.const 3) - (i32.const 4) (i32.const 5) (i32.const 6) -) - - -(module - (tag $e (result i32 i32 i32 i32 i32 i32)) - - (type $f0 (func (result i32 i32 i32 i32 i32 i32 i32))) - (type $f2 (func (param i32 i32) (result i32 i32 i32 i32 i32 i32 i32))) - (type $f4 (func (param i32 i32 i32 i32) (result i32 i32 i32 i32 i32 i32 i32))) - (type $f6 (func (param i32 i32 i32 i32 i32 i32) (result i32 i32 i32 i32 i32 i32 i32))) - - (type $k0 (cont $f0)) - (type $k2 (cont $f2)) - (type $k4 (cont $f4)) - (type $k6 (cont $f6)) - - (elem declare func $f) - (func $f (result i32 i32 i32 i32 i32 i32 i32) - (i32.const 0) (suspend $e) - ) - - (func (export "run") (result i32 i32 i32 i32 i32 i32 i32) - (local $k6 (ref null $k6)) - (local $k4 (ref null $k4)) - (local $k2 (ref null $k2)) - (block $l (result (ref $k6)) - (resume $k0 (on $e $l) (cont.new $k0 (ref.func $f))) - (unreachable) - ) - (local.set $k6) - (local.set $k4 (cont.bind $k6 $k4 (i32.const 1) (i32.const 2) (local.get $k6))) - (local.set $k2 (cont.bind $k4 $k2 (i32.const 3) (i32.const 4) (local.get $k4))) - (resume $k2 (i32.const 5) (i32.const 6) (local.get $k2)) - ) -) - -(assert_return (invoke "run") - (i32.const 0) (i32.const 1) (i32.const 2) (i32.const 3) - (i32.const 4) (i32.const 5) (i32.const 6) -) - -;; Subtyping -(module - (type $ft1 (func (param i32))) - (type $ct1 (sub (cont $ft1))) - - (type $ft0 (func)) - (type $ct0 (sub (cont $ft0))) - - (func $test (param $x (ref $ct1)) - (i32.const 123) - (local.get $x) - (cont.bind $ct1 $ct0) - (drop) - ) -) - -(module - (type $f1 (sub (func (result anyref)))) - (type $f2 (sub $f1 (func (result eqref)))) - (type $c1 (sub (cont $f1))) - (type $c2 (sub $c1 (cont $f2))) -) - -;; Globals -(module - (type $ft (func)) - (type $ct (cont $ft)) - - (global $k (mut (ref null $ct)) (ref.null $ct)) - (global $g (ref null $ct) (ref.null $ct)) - - (func $f) - (elem declare func $f) - - (func (export "set-global") - (global.set $k (cont.new $ct (ref.func $f)))) -) -(assert_return (invoke "set-global")) - -;; Switch -(module - (rec - (type $ft (func (param (ref null $ct)))) - (type $ct (cont $ft))) - - (func $print-i32 (import "spectest" "print_i32") (param i32)) - - (global $fi (mut i32) (i32.const 0)) - (global $gi (mut i32) (i32.const 1)) - - (tag $swap) - - (func $init (export "init") (result i32) - (resume $ct (on $swap switch) - (cont.new $ct (ref.func $g)) - (cont.new $ct (ref.func $f))) - (return (i32.const 42))) - (func $f (type $ft) - (local $nextk (ref null $ct)) - (local.set $nextk (local.get 0)) - (call $print-i32 (global.get $fi)) - (switch $ct $swap (local.get $nextk)) - (local.set $nextk) - (call $print-i32 (global.get $fi)) - (switch $ct $swap (local.get $nextk)) - (drop)) - (func $g (type $ft) - (local $nextk (ref null $ct)) - (local.set $nextk (local.get 0)) - (call $print-i32 (global.get $gi)) - (switch $ct $swap (local.get $nextk)) - (local.set $nextk) - (call $print-i32 (global.get $gi))) - (elem declare func $f $g) -) -(assert_return (invoke "init") (i32.const 42)) - -(module - (rec - (type $ft (func (param i32) (param (ref null $ct)) (result i32))) - (type $ct (cont $ft))) - - (func $print-i32 (import "spectest" "print_i32") (param i32)) - - (tag $swap (result i32)) - - (func $init (export "init") (result i32) - (resume $ct (on $swap switch) - (i32.const 1) - (cont.new $ct (ref.func $g)) - (cont.new $ct (ref.func $f)))) - (func $f (type $ft) - (local $i i32) - (local $nextk (ref null $ct)) - (local.set $i (local.get 0)) - (local.set $nextk (local.get 1)) - (call $print-i32 (local.get $i)) - (switch $ct $swap (i32.add (i32.const 1) (local.get $i)) (local.get $nextk)) - (local.set $nextk) - (local.set $i) - (call $print-i32 (local.get $i)) - (switch $ct $swap (i32.add (i32.const 1) (local.get $i)) (local.get $nextk)) - (unreachable)) - (func $g (type $ft) - (local $i i32) - (local $nextk (ref null $ct)) - (local.set $i (local.get 0)) - (local.set $nextk (local.get 1)) - (call $print-i32 (local.get $i)) - (switch $ct $swap (i32.add (i32.const 1) (local.get $i)) (local.get $nextk)) - (local.set $nextk) - (local.set $i) - (call $print-i32 (local.get $i)) - (return (local.get $i))) - (elem declare func $f $g) -) -(assert_return (invoke "init") (i32.const 4)) - - -(assert_invalid - (module - (rec - (type $ft (func (param (ref null $ct)))) - (type $ct (cont $ft))) - (type $ft2 (func)) - (type $ct2 (cont $ft2)) - - (tag $swap) - (func $f (type $ft) - (switch $ct $swap (cont.new $ct2 (ref.null $ft2))) - (drop))) - "type mismatch") - -(assert_invalid - (module - (rec - (type $ft (func (param i32) (param (ref null $ct)))) - (type $ct (cont $ft))) - - (tag $swap) - (func $f (type $ft) - (switch $ct $swap (i64.const 0) (local.get 1)) - (drop) - (drop))) - "type mismatch") - -(module - (type $ft1 (func)) - (type $ct1 (cont $ft1)) - (rec - (type $ft2 (func (param (ref null $ct2)))) - (type $ct2 (cont $ft2))) - - (tag $t) - - (func $suspend (type $ft2) - (suspend $t)) - - (func $switch (type $ft2) - (switch $ct2 $t (local.get 0)) - (drop)) - - (func (export "unhandled-suspend-t") - (resume $ct2 (on $t switch) - (cont.new $ct2 (ref.func $suspend)) - (cont.new $ct2 (ref.func $suspend)))) - (func (export "unhandled-switch-t") - (block $l (result (ref $ct1)) - (resume $ct2 (on $t $l) - (cont.new $ct2 (ref.func $switch)) - (cont.new $ct2 (ref.func $switch))) - (unreachable) - ) - (unreachable)) - - (elem declare func $suspend $switch) -) -(assert_suspension (invoke "unhandled-suspend-t") "unhandled tag") -(assert_suspension (invoke "unhandled-switch-t") "unhandled tag") - -(module - (rec - (type $ft (func (param (ref null $ct)))) - (type $ct (cont $ft))) - - (tag $t) - - (func - (cont.new $ct (ref.null $ft)) - (unreachable)) - (func - (cont.bind $ct $ct (ref.null $ct)) - (unreachable)) - (func - (resume $ct (ref.null $ct) (ref.null $ct)) - (unreachable)) - (func - (resume_throw $ct $t (ref.null $ct)) - (unreachable)) - (func - (switch $ct $t (ref.null $ct)) - (unreachable)) -) - -(module $co2 - (type $task (func (result i32))) ;; type alias task = [] -> [] - (type $ct (cont $task)) ;; type alias ct = $task - (tag $pause (export "pause")) ;; pause : [] -> [] - (tag $cancel (export "cancel")) ;; cancel : [] -> [] - ;; run : [(ref $task) (ref $task)] -> [] - ;; implements a 'seesaw' (c.f. Ganz et al. (ICFP@99)) - (func $run (export "seesaw") (param $up (ref $ct)) (param $down (ref $ct)) (result i32) - (local $result i32) - ;; run $up - (loop $run_next (result i32) - (block $on_pause (result (ref $ct)) - (resume $ct (on $pause $on_pause) - (local.get $up)) - ;; $up finished, store its result - (local.set $result) - ;; next cancel $down - (block $on_cancel - (try_table (catch $cancel $on_cancel) - ;; inject the cancel exception into $down - (resume_throw $ct $cancel (local.get $down)) - (drop) ;; drop the return value if it handled $cancel - ;; itself and returned normally... - ) - ) ;; ... otherwise catch $cancel and return $up's result. - (return (local.get $result)) - ) ;; on_pause clause, stack type: [(cont $ct)] - (local.set $up) - ;; swap $up and $down - (local.get $down) - (local.set $down (local.get $up)) - (local.set $up) - (br $run_next) - ) - ) -) -(register "co2") - -(module $client - (type $task-0 (func (param i32) (result i32))) - (type $ct-0 (cont $task-0)) - (type $task (func (result i32))) - (type $ct (cont $task)) - - (func $seesaw (import "co2" "seesaw") (param (ref $ct)) (param (ref $ct)) (result i32)) - (func $print-i32 (import "spectest" "print_i32") (param i32)) - (tag $pause (import "co2" "pause")) - - (func $even (param $niter i32) (result i32) - (local $next i32) ;; zero initialised. - (local $i i32) - (loop $print-next - (call $print-i32 (local.get $next)) - (suspend $pause) - (local.set $next (i32.add (local.get $next) (i32.const 2))) - (local.set $i (i32.add (local.get $i) (i32.const 1))) - (br_if $print-next (i32.lt_u (local.get $i) (local.get $niter))) - ) - (local.get $next) - ) - (func $odd (param $niter i32) (result i32) - (local $next i32) ;; zero initialised. - (local $i i32) - (local.set $next (i32.const 1)) - (loop $print-next - (call $print-i32 (local.get $next)) - (suspend $pause) - (local.set $next (i32.add (local.get $next) (i32.const 2))) - (local.set $i (i32.add (local.get $i) (i32.const 1))) - (br_if $print-next (i32.lt_u (local.get $i) (local.get $niter))) - ) - (local.get $next) - ) - - (func (export "main") (result i32) - (call $seesaw - (cont.bind $ct-0 $ct - (i32.const 5) (cont.new $ct-0 (ref.func $even))) - (cont.bind $ct-0 $ct - (i32.const 5) (cont.new $ct-0 (ref.func $odd))))) - - (elem declare func $even $odd) -) -(assert_return (invoke "main") (i32.const 10)) - -(module - (type $f1 (func (result i32))) - (type $c1 (cont $f1)) - (type $f2 (func (param (ref null $c1)) (result i32))) - (type $c2 (cont $f2)) - (type $f3 (func (param (ref null $c2)) (result i32))) - (type $c3 (cont $f3)) - (tag $e (result i32)) - - (func $fn_1 (param (ref null $c2)) (result i32) - (local.get 0) - (switch $c2 $e) - (i32.const 24) - ) - (elem declare func $fn_1) - - (func $fn_2 (result i32) - (cont.new $c3 (ref.func $fn_1)) - (switch $c3 $e) - (drop) - (i32.const -1) - ) - (elem declare func $fn_2) - - (func (export "main") (result i32) - (cont.new $c1 (ref.func $fn_2)) - (resume $c1 (on $e switch)) - ) -) - -(assert_return (invoke "main") (i32.const -1)) - -;; Syntax: check unfolded forms -(module - (type $ft (func)) - (type $ct (cont $ft)) - (rec - (type $ft2 (func (param (ref null $ct2)))) - (type $ct2 (cont $ft2))) - - (tag $yield (param i32)) - (tag $swap) - - ;; Check cont.new - (func (result (ref $ct)) - ref.null $ft - block (param (ref null $ft)) (result (ref $ct)) - cont.new $ct - end - ) - ;; Check cont.bind - (func (param (ref $ct)) (result (ref $ct)) - local.get 0 - block (param (ref $ct)) (result (ref $ct)) - cont.bind $ct $ct - end - ) - ;; Check suspend - (func - block - suspend $swap - end - ) - ;; Check resume - (func (param $k (ref $ct)) (result i32) - (local.get $k) - block $on_yield (param (ref $ct)) (result i32 (ref $ct)) - resume $ct (on $yield $on_yield) - i32.const 42 - return - end - local.set $k - ) - ;; Check resume_throw - (func (param $k (ref $ct)) (result i32) - block $on_yield (result i32 (ref $ct)) - i32.const 42 - local.get $k - resume_throw $ct $yield - i32.const 42 - return - end - local.set $k - ) - ;; Check switch - (func (param $k (ref $ct2)) - local.get $k - block (param (ref $ct2)) (result (ref null $ct2)) - switch $ct2 $swap - end - drop - ) -) - -;; Syntax: check instructions in tail position in unfolded form -(module - (type $ft (func)) - (type $ct (cont $ft)) - (rec - (type $ft2 (func (param (ref null $ct2)))) - (type $ct2 (cont $ft2))) - - (tag $yield (param i32)) - (tag $swap) - - ;; Check cont.new - (func (result (ref $ct)) - ref.null $ft - cont.new $ct - ) - ;; Check cont.bind - (func (param (ref $ct)) (result (ref $ct)) - local.get 0 - cont.bind $ct $ct - ) - - ;; Check resume - (func (;2;) (param $k (ref $ct)) - local.get $k - resume $ct - ) - ;; Check resume_throw - (func (param $k (ref $ct)) - i32.const 42 - local.get $k - resume_throw $ct $yield - ) - ;; Check switch - (func (param $k (ref $ct2)) (result (ref null $ct2)) - local.get $k - switch $ct2 $swap - ) - ;; Check suspend - (func - suspend $swap - ) -) - -(module - (type $ft0 (func)) - (type $ct0 (cont $ft0)) - - (type $ft1 (func (param (ref $ct0)))) - (type $ct1 (cont $ft1)) - - (tag $t) - - (func $f - (cont.new $ct1 (ref.func $g)) - (switch $ct1 $t) - ) - (elem declare func $f) - - (func $g (param (ref $ct0))) - (elem declare func $g) - - (func $entry - (cont.new $ct0 (ref.func $f)) - (resume $ct0 (on $t switch)) - ) -) - -(assert_invalid - (module - (rec - (type $ft (func (param (ref $ct)))) - (type $ct (cont $ft))) - (tag $t (param i32)) - - (func (param $k (ref $ct)) - (switch $ct $t))) - "type mismatch in switch tag") From f1069e875e22304bd60b9efef710ab17600a65d9 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 30 Jul 2025 13:32:34 -0700 Subject: [PATCH 117/125] format --- src/wasm-interpreter.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 4a524cc1479..81e9b031f7e 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -333,7 +333,8 @@ class ExpressionRunner : public OverriddenVisitor { void pushResumeEntry(const Literals& entry, const char* what) { assert(currContinuation); #if WASM_INTERPRETER_DEBUG - std::cout << indent() << "push resume entry [" << what << "]: " << entry << "\n"; + std::cout << indent() << "push resume entry [" << what << "]: " << entry + << "\n"; #endif currContinuation->resumeInfo.push_back(entry); } @@ -345,7 +346,8 @@ class ExpressionRunner : public OverriddenVisitor { auto entry = currContinuation->resumeInfo.back(); currContinuation->resumeInfo.pop_back(); #if WASM_INTERPRETER_DEBUG - std::cout << indent() << "pop resume entry [" << what << "]: " << entry << "\n"; + std::cout << indent() << "pop resume entry [" << what << "]: " << entry + << "\n"; #endif return entry; } From 39edc5c2219239f8cd7a7d6f04591667308746f2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 31 Jul 2025 13:10:24 -0700 Subject: [PATCH 118/125] Update src/wasm-interpreter.h Co-authored-by: Thomas Lively --- src/wasm-interpreter.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 81e9b031f7e..03451bbf6cb 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -418,7 +418,7 @@ class ExpressionRunner : public OverriddenVisitor { // were not suspended, and we have no values for them). break; } - num--; + --num; auto value = popResumeEntry("child value"); restoredValuesMap[child] = value; } From fe9d157bd002d8c09a7e9ae59ff4f9c2133a7c1a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 31 Jul 2025 13:13:50 -0700 Subject: [PATCH 119/125] feedback: comment --- src/wasm-interpreter.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 03451bbf6cb..033d0dbddb6 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -559,6 +559,7 @@ class ExpressionRunner : public OverriddenVisitor { } Flow flow; + // The value of the if's condition (whether to take the ifTrue arm or not). Index condition; if (resuming && resumeIndex > 0) { From c1436044641be26fc49ebda4c5cd40beec1aa932 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 31 Jul 2025 13:15:03 -0700 Subject: [PATCH 120/125] Update src/wasm-interpreter.h Co-authored-by: Thomas Lively --- src/wasm-interpreter.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 033d0dbddb6..577d20d8ef2 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4705,7 +4705,7 @@ class ModuleRunnerBase : public ExpressionRunner { return ret; } // No suspension; all done. - return Flow(); + return ret; } Flow visitResumeThrow(ResumeThrow* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitStackSwitch(StackSwitch* curr) { return Flow(NONCONSTANT_FLOW); } From 8c7680ea6fedc3366f98948d45dd6d17f93d482b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 31 Jul 2025 13:57:11 -0700 Subject: [PATCH 121/125] todo --- src/shell-interface.h | 2 +- src/tools/execution-results.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shell-interface.h b/src/shell-interface.h index 28f492f8d32..81c7c8d4f8e 100644 --- a/src/shell-interface.h +++ b/src/shell-interface.h @@ -192,7 +192,7 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface { return callImport(func, arguments); } else { auto flow = instance.callFunction(func->name, arguments); - assert(!flow.suspendTag); + assert(!flow.suspendTag); // TODO: support stack switching on calls return flow.values; } } diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index 796bc1a4e29..0e1456cdee0 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -474,7 +474,7 @@ struct ExecutionResults { arguments.push_back(Literal::makeZero(param)); } auto flow = instance.callFunction(func->name, arguments); - if (flow.suspendTag) { + if (flow.suspendTag) { // TODO: support stack switching here std::cout << "[exception thrown: unhandled suspend]" << std::endl; return Exception{}; } From 021d90b74d6a889d891565085fe0c20cf4a4c333 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 31 Jul 2025 16:51:47 -0700 Subject: [PATCH 122/125] comment to clarify --- src/wasm-interpreter.h | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 577d20d8ef2..e9038736910 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -297,7 +297,23 @@ class ExpressionRunner : public OverriddenVisitor { // // Each entry here is for an instruction in the stack of executing // expressions, and contains all the values from its children that we have - // seen thus far. + // seen thus far. In other words, the invariant we preserve is this: when an + // instruction executes, the top of the stack contains the values of its + // children, e.g., + // + // (i32.add (A) (B)) + // + // After executing A and getting its value, valueStack looks like this: + // + // [[..], ..scopes for parents of the add.., [..], [value of A]] + // ^^^^^^^^^^^^ + // scope for the + // add, with one + // child so far + // + // Imagine that B then suspends. Then using the top of valueStack, we know the + // value of A, and can stash it. When we resume, we just apply that value, and + // proceed to execute B. std::vector> valueStack; // RAII helper for |valueStack|: Adds a scope for an instruction, where the @@ -446,10 +462,11 @@ class ExpressionRunner : public OverriddenVisitor { } } - // Outside the scope of StackValueNoter, we can handle stashing our own - // value for our parent (whose values are now at the top of |valueStack|). - // We do so when not suspending (suspending is handled above), and when - // there is a concrete value. + // Outside the scope of StackValueNoter, the scope of our own child values + // has been removed (we don't need those values any more). What is now on + // the top of |valueStack| is the list of child values of our parent, + // which is the place our own value can go, if we have one (and if we are + // not suspending - suspending is handled above). if (!ret.suspendTag && ret.getType().isConcrete()) { assert(!valueStack.empty()); auto& values = valueStack.back(); From 6ee86b7e05061dfdf6f363ef8c02cfd63bd4960e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 31 Jul 2025 17:08:18 -0700 Subject: [PATCH 123/125] fully support assert_suspend --- src/parser/wast-parser.cpp | 28 ++++++++++++++++++++++++---- src/parser/wat-parser.h | 2 +- src/shell-interface.h | 4 +++- src/tools/wasm-shell.cpp | 23 ++++++++++++++++++++--- src/wasm-interpreter.h | 23 +++++++++-------------- 5 files changed, 57 insertions(+), 23 deletions(-) diff --git a/src/parser/wast-parser.cpp b/src/parser/wast-parser.cpp index 46a0cffc163..9e12a855456 100644 --- a/src/parser/wast-parser.cpp +++ b/src/parser/wast-parser.cpp @@ -351,11 +351,8 @@ MaybeResult assertModule(Lexer& in) { // (assert_trap action msg) // (assert_trap module msg) -// -// Also handles assert_suspension, as for us an unhandled suspension is a trap. MaybeResult assertTrap(Lexer& in) { - if (!in.takeSExprStart("assert_trap"sv) && - !in.takeSExprStart("assert_suspension"sv)) { + if (!in.takeSExprStart("assert_trap"sv)) { return {}; } auto pos = in.getPos(); @@ -384,6 +381,25 @@ MaybeResult assertTrap(Lexer& in) { return Assertion{AssertModule{ModuleAssertionType::Trap, *mod}}; } +// (assert_suspension action msg) +MaybeResult 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(Lexer& in) { if (auto a = assertReturn(in)) { CHECK_ERR(a); @@ -405,6 +421,10 @@ MaybeResult assertion(Lexer& in) { CHECK_ERR(a); return *a; } + if (auto a = assertSuspension(in)) { + CHECK_ERR(a); + return *a; + } return {}; } diff --git a/src/parser/wat-parser.h b/src/parser/wat-parser.h index b0074faa6e1..34e9b69f8f0 100644 --- a/src/parser/wat-parser.h +++ b/src/parser/wat-parser.h @@ -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; diff --git a/src/shell-interface.h b/src/shell-interface.h index 81c7c8d4f8e..421c76f76b3 100644 --- a/src/shell-interface.h +++ b/src/shell-interface.h @@ -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; diff --git a/src/tools/wasm-shell.cpp b/src/tools/wasm-shell.cpp index 9139983c947..40e4ddc67df 100644 --- a/src/tools/wasm-shell.cpp +++ b/src/tools/wasm-shell.cpp @@ -186,8 +186,12 @@ struct Shell { struct TrapResult {}; struct HostLimitResult {}; struct ExceptionResult {}; - using ActionResult = - std::variant; + struct SuspensionResult {}; + using ActionResult = std::variant; std::string resultToString(ActionResult& result) { if (std::get_if(&result)) { @@ -196,6 +200,8 @@ struct Shell { return "exceeded host limit"; } else if (std::get_if(&result)) { return "exception"; + } else if (std::get_if(&result)) { + return "suspension"; } else if (auto* vals = std::get_if(&result)) { std::stringstream ss; ss << *vals; @@ -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&) { @@ -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(&act)) { auto it = instances.find(get->base ? *get->base : lastModule); if (it == instances.end()) { @@ -390,6 +401,12 @@ struct Shell { } err << "expected exception"; break; + case ActionAssertionType::Suspension: + if (std::get_if(&result)) { + return Ok{}; + } + err << "expected suspension"; + break; } err << ", got " << resultToString(result); return Err{err.str()}; diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index e9038736910..01fb2b05816 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -3250,20 +3250,15 @@ class ModuleRunnerBase : public ExpressionRunner { } // call an exported function - Literals callExport(Name name, const Literals& arguments) { + Flow callExport(Name name, const Literals& arguments) { Export* export_ = wasm.getExportOrNull(name); if (!export_ || export_->kind != ExternalKind::Function) { externalInterface->trap("callExport not found"); } - auto flow = callFunction(*export_->getInternalName(), arguments); - if (flow.suspendTag) { - // TODO: allow suspending through exports; return Flow from this func - externalInterface->trap("unhandled suspend"); - } - return flow.values; + return callFunction(*export_->getInternalName(), arguments); } - Literals callExport(Name name) { return callExport(name, Literals()); } + Flow callExport(Name name) { return callExport(name, Literals()); } // get an exported global Literals getExport(Name name) { @@ -4656,13 +4651,13 @@ class ModuleRunnerBase : public ExpressionRunner { return flow; } - if (!self()->currContinuation) { - trap("no continuation to suspend"); - } - // Copy the continuation (the old one cannot be resumed again). + // Copy the continuation (the old one cannot be resumed again). Note that no + // old one may exist, in which case we still emit a continuation, but it is + // meaningless (it will error when it reaches the host). auto old = self()->currContinuation; - assert(old->executed); - auto new_ = std::make_shared(old->func, old->type); + assert(!old || old->executed); + auto new_ = std::make_shared(old ? old->func : Name(), + old ? old->type : HeapType::none); // Switch to the new continuation, so that as we unwind, we will save the // information we need to resume it later in the proper place. self()->currContinuation = new_; From 96f6521aca79404a1f3a0cc891dd4a1ff804db22 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 1 Aug 2025 07:45:05 -0700 Subject: [PATCH 124/125] feedback on tests --- test/lit/exec/cont_simple.wast | 98 +++++++++++++--------------------- 1 file changed, 38 insertions(+), 60 deletions(-) diff --git a/test/lit/exec/cont_simple.wast b/test/lit/exec/cont_simple.wast index 49855cff030..7b6aedb2075 100644 --- a/test/lit/exec/cont_simple.wast +++ b/test/lit/exec/cont_simple.wast @@ -118,6 +118,34 @@ ) ) + (func $multi-locals + (local $i32 i32) + (local $f64 f64) + (local.set $i32 (i32.const 42)) + (local.set $f64 (f64.const 3.14159)) + (suspend $more) + (call $log + (local.get $i32) + ) + (call $log + (i32.trunc_f64_s + (local.get $f64) + ) + ) + ) + + ;; CHECK: [fuzz-exec] calling run-multi-locals + ;; CHECK-NEXT: [LoggingExternalInterface logging 100] + ;; CHECK-NEXT: [LoggingExternalInterface logging 200] + ;; CHECK-NEXT: [LoggingExternalInterface logging 42] + ;; CHECK-NEXT: [LoggingExternalInterface logging 3] + ;; CHECK-NEXT: [LoggingExternalInterface logging 300] + (func $run-multi-locals (export "run-multi-locals") + (call $run + (cont.new $k (ref.func $multi-locals)) + ) + ) + ;; This loop should suspend 4 times and log 3, 2, 1, 0. (func $loop (local $x i32) @@ -358,32 +386,14 @@ ;; Suspend before and after each operation. (call $log (block (result i32) - (local.set $temp - (i32.eqz - (block (result i32) - (local.set $temp - (i32.eqz - (block (result i32) - (local.set $temp - (i32.eqz - (block (result i32) - (i32.const 0) - (suspend $more) - ) - ) - ) - (suspend $more) - (local.get $temp) - ) - ) - ) - (suspend $more) - (local.get $temp) - ) - ) - ) - (suspend $more) - (local.get $temp) + i32.const 0 + suspend $more + i32.eqz + suspend $more + i32.eqz + suspend $more + i32.eqz + suspend $more ) ) ) @@ -420,9 +430,7 @@ (suspend $more) (i32.add (i32.const 4) - (block (result i32) - (i32.const 2) - ) + (i32.const 2) ) ) ) @@ -437,9 +445,7 @@ (block (result i32) (suspend $more) (i32.add - (block (result i32) - (i32.const 3) - ) + (i32.const 3) (i32.const 1) ) ) @@ -546,32 +552,4 @@ (cont.new $k (ref.func $trinary)) ) ) - - (func $multi-locals - (local $i32 i32) - (local $f64 f64) - (local.set $i32 (i32.const 42)) - (local.set $f64 (f64.const 3.14159)) - (suspend $more) - (call $log - (local.get $i32) - ) - (call $log - (i32.trunc_f64_s - (local.get $f64) - ) - ) - ) - - ;; CHECK: [fuzz-exec] calling run-multi-locals - ;; CHECK-NEXT: [LoggingExternalInterface logging 100] - ;; CHECK-NEXT: [LoggingExternalInterface logging 200] - ;; CHECK-NEXT: [LoggingExternalInterface logging 42] - ;; CHECK-NEXT: [LoggingExternalInterface logging 3] - ;; CHECK-NEXT: [LoggingExternalInterface logging 300] - (func $run-multi-locals (export "run-multi-locals") - (call $run - (cont.new $k (ref.func $multi-locals)) - ) - ) ) From 2d1dbe4d2fc06ac8746bdaa3db9ac7d32032796d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 1 Aug 2025 08:26:33 -0700 Subject: [PATCH 125/125] add todos --- src/wasm/wasm.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 3e59f91179c..4628f41c4f8 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -1530,6 +1530,7 @@ void Resume::finalize() { if (cont->type.isNull()) { // This will never be executed and the instruction will not be emitted. // Model this with an uninhabitable cast type. + // TODO: This is not quite right yet. type = cont->type.with(NonNullable); return; } @@ -1551,6 +1552,7 @@ void ResumeThrow::finalize() { if (cont->type.isNull()) { // This will never be executed and the instruction will not be emitted. // Model this with an uninhabitable cast type. + // TODO: This is not quite right yet. type = cont->type.with(NonNullable); return; } @@ -1572,6 +1574,7 @@ void StackSwitch::finalize() { if (cont->type.isNull()) { // This will never be executed and the instruction will not be emitted. // Model this with an uninhabitable cast type. + // TODO: This is not quite right yet. type = cont->type.with(NonNullable); return; }