@@ -71,7 +71,8 @@ struct NonconstantException {};
7171
7272// Utilities
7373
74- extern Name RETURN_FLOW, RETURN_CALL_FLOW, NONCONSTANT_FLOW, SUSPEND_FLOW;
74+ extern Name RETURN_FLOW, RETURN_CALL_FLOW, NONCONSTANT_FLOW, SUSPEND_FLOW,
75+ THREAD_SUSPEND_FLOW;
7576
7677// Stuff that flows around during executing expressions: a literal, or a change
7778// in control flow.
@@ -87,13 +88,15 @@ class Flow {
8788 : values(std::move(values)), breakTo(breakTo) {}
8889 Flow (Name breakTo, Tag* suspendTag, Literals&& values)
8990 : values(std::move(values)), breakTo(breakTo), suspendTag(suspendTag) {
90- assert (breakTo == SUSPEND_FLOW);
91+ assert (breakTo == SUSPEND_FLOW || breakTo == THREAD_SUSPEND_FLOW );
9192 }
9293
9394 Literals values;
9495 Name breakTo; // if non-null, a break is going on
9596 Tag* suspendTag = nullptr ; // if non-null, breakTo must be SUSPEND_FLOW, and
96- // this is the tag being suspended
97+ // this is the tag being suspended. If breakTo is
98+ // THREAD_SUSPEND_FLOW, this represents the thread
99+ // suspending and this field is not used.
97100
98101 // A helper function for the common case where there is only one value
99102 const Literal& getSingleValue () {
@@ -281,6 +284,10 @@ struct ContData {
281284 // resume_throw_ref).
282285 Literal exception;
283286
287+ // If set, this continuation was suspended into a wait queue by a thread
288+ // and has not yet been woken up.
289+ bool isWaiting = false ;
290+
284291 // Whether we executed. Continuations are one-shot, so they may not be
285292 // executed a second time.
286293 bool executed = false ;
@@ -303,6 +310,13 @@ struct ContinuationStore {
303310
304311 // Set when we are resuming execution, that is, re-winding the stack.
305312 bool resuming = false ;
313+
314+ // The wait queue for threads waiting on addresses (represented by GCData and
315+ // field index).
316+ std::unordered_map<
317+ std::shared_ptr<GCData>,
318+ std::unordered_map<Index, std::vector<std::shared_ptr<ContData>>>>
319+ waitQueues;
306320};
307321
308322// Execute an expression
@@ -2244,13 +2258,90 @@ class ExpressionRunner : public OverriddenVisitor<SubType, Flow> {
22442258 }
22452259
22462260 Flow visitStructWait (StructWait* curr) {
2247- WASM_UNREACHABLE (" struct.wait not implemented" );
2248- return Flow ();
2261+ VISIT (ref, curr->ref )
2262+ VISIT (expected, curr->expected )
2263+ VISIT (timeout,
2264+ curr->timeout ) // We ignore timeout in the simulation for simplicity
2265+
2266+ auto data = ref.getSingleValue ().getGCData ();
2267+ if (!data) {
2268+ trap (" null ref" );
2269+ }
2270+
2271+ auto & field = data->values [curr->index ];
2272+ if (field != expected.getSingleValue ()) {
2273+ return Literal (int32_t (1 )); // not-equal, don't wait
2274+ }
2275+
2276+ if (self ()->isResuming ()) {
2277+ // We have been notified and resumed.
2278+ // Clear the resume state and continue.
2279+ auto currContinuation = self ()->getCurrContinuation ();
2280+ assert (curr == currContinuation->resumeExpr );
2281+ self ()->continuationStore ->resuming = false ;
2282+ assert (currContinuation->resumeInfo .empty ());
2283+ assert (self ()->restoredValuesMap .empty ());
2284+ return Literal (int32_t (0 )); // ok, woken up
2285+ }
2286+
2287+ // We need to wait. Create a continuation and suspend the thread.
2288+ auto old = self ()->getCurrContinuationOrNull ();
2289+ if (!old) {
2290+ // Not executing within a continuation, cannot suspend.
2291+ // For wasm-shell simulation, we assume threads are started with
2292+ // ContNew/ContBind.
2293+ return Flow (THREAD_SUSPEND_FLOW); // This will cause a trap up the stack
2294+ // natively if not caught.
2295+ }
2296+ assert (old->executed );
2297+
2298+ auto new_ = std::make_shared<ContData>();
2299+ self ()->popCurrContinuation ();
2300+ self ()->pushCurrContinuation (new_);
2301+ new_->resumeExpr = curr;
2302+ new_->isWaiting = true ;
2303+
2304+ self ()->continuationStore ->waitQueues [data][curr->index ].push_back (new_);
2305+
2306+ return Flow (THREAD_SUSPEND_FLOW);
22492307 }
22502308
22512309 Flow visitStructNotify (StructNotify* curr) {
2252- WASM_UNREACHABLE (" struct.notify not implemented" );
2253- return Flow ();
2310+ VISIT (ref, curr->ref )
2311+ VISIT (count, curr->count )
2312+
2313+ auto data = ref.getSingleValue ().getGCData ();
2314+ if (!data) {
2315+ trap (" null ref" );
2316+ }
2317+
2318+ int32_t countVal = count.getSingleValue ().geti32 ();
2319+ int32_t woken = 0 ;
2320+
2321+ auto & store = self ()->continuationStore ;
2322+ auto it1 = store->waitQueues .find (data);
2323+ if (it1 != store->waitQueues .end ()) {
2324+ auto & fieldQueues = it1->second ;
2325+ auto it2 = fieldQueues.find (curr->index );
2326+ if (it2 != fieldQueues.end ()) {
2327+ auto & queue = it2->second ;
2328+ while (!queue.empty () && woken < countVal) {
2329+ // The waking thread will be executed by the wasm-shell scheduler.
2330+ // In the reference interpreter, awake continuations should be
2331+ // tracked. Since wasm-shell handles interleaved threads, we don't
2332+ // automatically execute them here. Wait! wasm-shell scheduler needs
2333+ // to know which threads are ready. Our ContinuationStore wait queues
2334+ // structure just pops them. The scheduler wrapper will need a way to
2335+ // track all active threads.
2336+ auto wokeCont = queue.front ();
2337+ wokeCont->isWaiting = false ;
2338+ queue.erase (queue.begin ());
2339+ woken++;
2340+ }
2341+ }
2342+ }
2343+
2344+ return Literal (woken);
22542345 }
22552346
22562347 // Arbitrary deterministic limit on size. If we need to allocate a Literals
@@ -2714,6 +2805,10 @@ class ExpressionRunner : public OverriddenVisitor<SubType, Flow> {
27142805
27152806 virtual void hostLimit (std::string_view why) { WASM_UNREACHABLE (" unimp" ); }
27162807
2808+ virtual void invokeMain (const std::string& startName) {
2809+ WASM_UNREACHABLE (" unimp" );
2810+ }
2811+
27172812 virtual void throwException (const WasmException& exn) {
27182813 WASM_UNREACHABLE (" unimp" );
27192814 }
@@ -3257,6 +3352,42 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
32573352
32583353 Flow callExport (Name name) { return callExport (name, Literals ()); }
32593354
3355+ std::shared_ptr<ContData> getSuspendedContinuation () {
3356+ return this ->getCurrContinuationOrNull ();
3357+ }
3358+
3359+ Flow resumeContinuation (std::shared_ptr<ContData> contData,
3360+ Literals arguments = {}) {
3361+ if (contData->executed ) {
3362+ this ->trap (" continuation already executed" );
3363+ }
3364+ contData->executed = true ;
3365+
3366+ if (contData->resumeArguments .empty ()) {
3367+ contData->resumeArguments = arguments;
3368+ }
3369+
3370+ this ->pushCurrContinuation (contData);
3371+ this ->continuationStore ->resuming = true ;
3372+ #if WASM_INTERPRETER_DEBUG
3373+ std::cout << this ->indent () << " resuming func " << contData->func .getFunc ()
3374+ << ' \n ' ;
3375+ #endif
3376+
3377+ Flow ret = contData->func .getFuncData ()->doCall (arguments);
3378+
3379+ if (this ->isResuming ()) {
3380+ // if we didn't suspend again natively, clear resuming flag
3381+ this ->continuationStore ->resuming = false ;
3382+ }
3383+
3384+ if (ret.breakTo != THREAD_SUSPEND_FLOW && !ret.suspendTag ) {
3385+ // The coroutine finished normally.
3386+ this ->popCurrContinuation ();
3387+ }
3388+ return ret;
3389+ }
3390+
32603391 Literal getExportedFunction (Name name) {
32613392 Export* export_ = wasm.getExportOrNull (name);
32623393 if (!export_ || export_->kind != ExternalKind::Function) {
0 commit comments