@@ -72,7 +72,8 @@ struct NonconstantException {};
7272
7373// Utilities
7474
75- extern Name RETURN_FLOW , RETURN_CALL_FLOW , NONCONSTANT_FLOW , SUSPEND_FLOW ;
75+ extern Name RETURN_FLOW , RETURN_CALL_FLOW , NONCONSTANT_FLOW , SUSPEND_FLOW ,
76+ STACK_SWITCH_FLOW ;
7677
7778// Stuff that flows around during executing expressions: a literal, or a change
7879// in control flow.
@@ -88,13 +89,14 @@ class Flow {
8889 : values(std::move(values)), breakTo(breakTo) {}
8990 Flow (Name breakTo, Tag* suspendTag, Literals&& values)
9091 : values(std::move(values)), breakTo(breakTo), suspendTag(suspendTag) {
91- assert (breakTo == SUSPEND_FLOW );
92+ assert (breakTo == SUSPEND_FLOW || breakTo == STACK_SWITCH_FLOW );
9293 }
9394
9495 Literals values;
9596 Name breakTo; // if non-null, a break is going on
96- Tag* suspendTag = nullptr ; // if non-null, breakTo must be SUSPEND_FLOW, and
97- // this is the tag being suspended
97+ Tag* suspendTag = nullptr ; // if non-null, breakTo must be SUSPEND_FLOW or
98+ // STACK_SWITCH_FLOW, and this is the tag being
99+ // suspended
98100
99101 // A helper function for the common case where there is only one value
100102 const Literal& getSingleValue () {
@@ -411,6 +413,16 @@ class ExpressionRunner : public OverriddenVisitor<SubType, Flow> {
411413 return Literal (allocation);
412414 }
413415
416+ // Same as makeGCData but for ContData.
417+ template <typename ... Args>
418+ std::shared_ptr<ContData> makeContData (Args&&... args) {
419+ auto allocation = std::make_shared<ContData>(std::forward<Args>(args)...);
420+ #if __has_feature(leak_sanitizer) || __has_feature(address_sanitizer)
421+ __lsan_ignore_object (allocation.get ());
422+ #endif
423+ return allocation;
424+ }
425+
414426 static Literal applyRMW (AtomicRMWOp op, Literal lhs, Literal rhs) {
415427 switch (op) {
416428 case RMWAdd:
@@ -5032,8 +5044,7 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
50325044 if (funcValue.isNull ()) {
50335045 trap (" null ref" );
50345046 }
5035- return Literal (
5036- std::make_shared<ContData>(funcValue, curr->type .getHeapType ()));
5047+ return Literal (self ()->makeContData (funcValue, curr->type .getHeapType ()));
50375048 }
50385049 Flow visitContBind (ContBind* curr) {
50395050 Literals arguments;
@@ -5045,37 +5056,64 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
50455056 trap (" null ref" );
50465057 }
50475058
5048- // Create a new continuation, copying the old but with the new type +
5059+ // Create a new continuation, moving the old but with the new type +
50495060 // arguments.
50505061 auto old = contValue.getContData ();
50515062 if (old->executed ) {
50525063 trap (" continuation already executed" );
50535064 }
5054- auto newData = *old;
5065+ auto newData = std::move (*old);
5066+ old->resumeInfo .clear ();
5067+ old->resumeArguments .clear ();
50555068 newData.type = curr->type .getHeapType ();
50565069 for (auto arg : arguments) {
50575070 newData.resumeArguments .push_back (arg);
50585071 }
50595072 // The old one is done.
50605073 old->executed = true ;
5061- return Literal (std::make_shared<ContData> (newData));
5074+ return Literal (self ()-> makeContData ( std::move (newData) ));
50625075 }
50635076
50645077 void maybeThrowAfterResuming (std::shared_ptr<ContData>& currContinuation) {
50655078 // We may throw by creating a tag, or an exnref.
50665079 auto * tag = currContinuation->exceptionTag ;
5067- auto exnref = currContinuation->exception .type != Type::none;
5080+ currContinuation->exceptionTag = nullptr ;
5081+ auto exn = currContinuation->exception ;
5082+ currContinuation->exception = Literal ();
5083+ auto exnref = exn.type != Type::none;
50685084 assert (!(tag && exnref));
50695085 if (tag) {
50705086 // resume_throw
5071- throwException (WasmException{
5072- self ()->makeExnData (tag, currContinuation->resumeArguments )});
5087+ auto resumeArguments = std::move (currContinuation->resumeArguments );
5088+ currContinuation->resumeArguments .clear ();
5089+ throwException (WasmException{self ()->makeExnData (tag, resumeArguments)});
50735090 } else if (exnref) {
50745091 // resume_throw_ref
5075- throwException (WasmException{currContinuation-> exception });
5092+ throwException (WasmException{exn });
50765093 }
50775094 }
50785095
5096+ Flow finishResumingAt (Expression* curr) {
5097+ #if WASM_INTERPRETER_DEBUG
5098+ std::cout << self ()->indent ()
5099+ << " returned to suspend; continuing normally\n " ;
5100+ #endif
5101+ // This is a resume, so we have found our way back to where we
5102+ // suspended.
5103+ auto currContinuation = self ()->getCurrContinuation ();
5104+ assert (curr == currContinuation->resumeExpr );
5105+ // We finished resuming, and will continue from here normally.
5106+ self ()->continuationStore ->resuming = false ;
5107+ // We should have consumed all the resumeInfo and all the
5108+ // restoredValues map.
5109+ assert (currContinuation->resumeInfo .empty ());
5110+ assert (self ()->restoredValuesMap .empty ());
5111+ maybeThrowAfterResuming (currContinuation);
5112+ auto resumeArguments = std::move (currContinuation->resumeArguments );
5113+ currContinuation->resumeArguments .clear ();
5114+ return resumeArguments;
5115+ }
5116+
50795117 Flow visitSuspend (Suspend* curr) {
50805118 // Process the arguments, whether or not we are resuming. If we are resuming
50815119 // then we don't need these values (we sent them as part of the suspension),
@@ -5084,22 +5122,7 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
50845122 VISIT_ARGUMENTS (flow, curr->operands , arguments)
50855123
50865124 if (self ()->isResuming ()) {
5087- #if WASM_INTERPRETER_DEBUG
5088- std::cout << self ()->indent ()
5089- << " returned to suspend; continuing normally\n " ;
5090- #endif
5091- // This is a resume, so we have found our way back to where we
5092- // suspended.
5093- auto currContinuation = self ()->getCurrContinuation ();
5094- assert (curr == currContinuation->resumeExpr );
5095- // We finished resuming, and will continue from here normally.
5096- self ()->continuationStore ->resuming = false ;
5097- // We should have consumed all the resumeInfo and all the
5098- // restoredValues map.
5099- assert (currContinuation->resumeInfo .empty ());
5100- assert (self ()->restoredValuesMap .empty ());
5101- maybeThrowAfterResuming (currContinuation);
5102- return currContinuation->resumeArguments ;
5125+ return finishResumingAt (curr);
51035126 }
51045127
51055128 // We were not resuming, so this is a new suspend that we must execute.
@@ -5120,7 +5143,7 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
51205143 // Note we cannot update the type yet, so it will be wrong in debug
51215144 // logging. To update it, we must find the block that receives this value,
51225145 // which means we cannot do it here (we don't even know what that block is).
5123- auto new_ = std::make_shared<ContData> ();
5146+ auto new_ = self ()-> makeContData ();
51245147
51255148 // Switch to the new continuation, so that as we unwind, we will save the
51265149 // information we need to resume it later in the proper place.
@@ -5142,7 +5165,6 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
51425165 trap (" null ref" );
51435166 }
51445167 auto contData = cont.getContData ();
5145- auto func = contData->func ;
51465168
51475169 // If we are resuming a nested suspend then we should just rewind the call
51485170 // stack, and therefore do not change or test the state here.
@@ -5175,31 +5197,46 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
51755197 self ()->pushCurrContinuation (contData);
51765198 self ()->continuationStore ->resuming = true ;
51775199#if WASM_INTERPRETER_DEBUG
5178- std::cout << self ()->indent () << " resuming func " << func. getFunc ()
5179- << ' \n ' ;
5200+ std::cout << self ()->indent () << " resuming func "
5201+ << contData-> func . getFunc () << ' \n ' ;
51805202#endif
51815203 }
51825204
5183- Flow ret = func.getFuncData ()->doCall (arguments);
5205+ // Loop to handle (on $tag switch) clauses, which switch directly to a new
5206+ // target continuation under this same resume handler without growing the
5207+ // call stack.
5208+ while (true ) {
5209+ Flow ret = contData->func .getFuncData ()->doCall (arguments);
51845210
51855211#if WASM_INTERPRETER_DEBUG
5186- if (!self ()->isResuming ()) {
5187- std::cout << self ()->indent () << " finished resuming, with " << ret
5188- << ' \n ' ;
5189- }
5212+ if (!self ()->isResuming ()) {
5213+ std::cout << self ()->indent () << " finished resuming, with " << ret
5214+ << ' \n ' ;
5215+ }
51905216#endif
5191- if (!ret.suspendTag ) {
5192- // No suspension: the coroutine finished normally. Mark it as no longer
5193- // active.
5194- self ()->popCurrContinuation ();
5195- } else {
5217+ if (!ret.suspendTag ) {
5218+ // No suspension: the coroutine finished normally. Mark it as no longer
5219+ // active.
5220+ self ()->popCurrContinuation ();
5221+ return ret;
5222+ }
5223+
51965224 // We are suspending. See if a suspension arrived that we support.
5225+ bool switched = false ;
51975226 for (size_t i = 0 ; i < curr->handlerTags .size (); i++) {
51985227 auto * handlerTag = allTags[curr->handlerTags [i]];
5199- if (handlerTag == ret.suspendTag ) {
5228+ if (handlerTag != ret.suspendTag ) {
5229+ continue ;
5230+ }
5231+ auto handlerBlock = curr->handlerBlocks [i];
5232+ // A non-empty handlerBlock is an (on $tag $label) clause, which only
5233+ // handles suspend instructions (SUSPEND_FLOW). An empty handlerBlock is
5234+ // an (on $tag switch) clause, which only handles switch instructions
5235+ // (STACK_SWITCH_FLOW).
5236+ if (ret.breakTo == SUSPEND_FLOW && handlerBlock.is ()) {
52005237 // Switch the flow from suspending to branching.
52015238 ret.suspendTag = nullptr ;
5202- ret.breakTo = curr-> handlerBlocks [i] ;
5239+ ret.breakTo = handlerBlock ;
52035240 // We can now update the continuation type, which was wrong until now
52045241 // (see comment in visitSuspend). The type is taken from the block we
52055242 // branch to (which we find in a quite inefficient manner).
@@ -5231,15 +5268,93 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
52315268 self ()->popCurrContinuation ();
52325269 return ret;
52335270 }
5271+ if (ret.breakTo == STACK_SWITCH_FLOW && !handlerBlock.is ()) {
5272+ // Finalize the suspended continuation from the switching coroutine
5273+ // (its type was already set in visitStackSwitch).
5274+ auto newCont = self ()->getCurrContinuation ();
5275+ newCont->func = contData->func ;
5276+ self ()->popCurrContinuation ();
5277+
5278+ // Extract the target continuation that visitStackSwitch appended to
5279+ // the end of the flow's values.
5280+ assert (!ret.values .empty ());
5281+ auto targetCont = ret.values .back ();
5282+ ret.values .pop_back ();
5283+ contData = targetCont.getContData ();
5284+ assert (!contData->executed );
5285+ contData->executed = true ;
5286+ // Append the forwarded payload arguments and the newly suspended
5287+ // sender continuation to the target continuation's arguments
5288+ // (preserving any arguments bound earlier via cont.bind).
5289+ for (auto arg : ret.values ) {
5290+ contData->resumeArguments .push_back (arg);
5291+ }
5292+ contData->resumeArguments .push_back (Literal (newCont));
5293+ arguments = contData->resumeArguments ;
5294+ // Switch the active continuation in this prompt to the target
5295+ // continuation and loop to immediately resume it.
5296+ self ()->pushCurrContinuation (contData);
5297+ self ()->continuationStore ->resuming = true ;
5298+ switched = true ;
5299+ break ;
5300+ }
5301+ }
5302+ if (switched) {
5303+ continue ;
52345304 }
52355305 // No handler worked out, keep propagating.
5306+ return ret;
52365307 }
5237- // No suspension; all done.
5238- return ret;
52395308 }
52405309 Flow visitResume (Resume* curr) { return doResume (curr); }
52415310 Flow visitResumeThrow (ResumeThrow* curr) { return doResume (curr); }
5242- Flow visitStackSwitch (StackSwitch* curr) { return Flow (NONCONSTANT_FLOW ); }
5311+ Flow visitStackSwitch (StackSwitch* curr) {
5312+ // Process the operands and target continuation whether or not we are
5313+ // resuming so that saved child values are consumed when rewinding the
5314+ // stack.
5315+ Literals arguments;
5316+ VISIT_ARGUMENTS (flow, curr->operands , arguments)
5317+ VISIT_REUSE (flow, curr->cont );
5318+
5319+ if (self ()->isResuming ()) {
5320+ return finishResumingAt (curr);
5321+ }
5322+
5323+ // We are not resuming, so this is a new switch that we must execute.
5324+ auto cont = flow.getSingleValue ();
5325+ if (cont.isNull ()) {
5326+ trap (" null ref" );
5327+ }
5328+ auto contData = cont.getContData ();
5329+ if (contData->executed ) {
5330+ trap (" continuation already executed" );
5331+ }
5332+
5333+ // Send the target continuation as the final value on the unwinding flow so
5334+ // the matching (on $tag switch) handler in doResume can switch to it.
5335+ arguments.push_back (cont);
5336+ auto old = self ()->getCurrContinuationOrNull ();
5337+ auto * tag = allTags[curr->tag ];
5338+ if (!old) {
5339+ return Flow (STACK_SWITCH_FLOW , tag, std::move (arguments));
5340+ }
5341+ assert (old->executed );
5342+ auto new_ = self ()->makeContData ();
5343+ // Unlike suspend, where the suspended continuation's type depends on the
5344+ // target block of the handler that catches it, a switch statically knows
5345+ // the type of the suspended continuation it creates: it is the last
5346+ // parameter of the target continuation's signature, which receives this
5347+ // suspended continuation when switched to.
5348+ auto sig =
5349+ curr->cont ->type .getHeapType ().getContinuation ().type .getSignature ();
5350+ new_->type = sig.params [sig.params .size () - 1 ].getHeapType ();
5351+ // Replace the current continuation so unwinding saves resume state into the
5352+ // new continuation, and record this switch as the point to resume from.
5353+ self ()->popCurrContinuation ();
5354+ self ()->pushCurrContinuation (new_);
5355+ new_->resumeExpr = curr;
5356+ return Flow (STACK_SWITCH_FLOW , tag, std::move (arguments));
5357+ }
52435358
52445359 void trap (std::string_view why) override {
52455360 // Traps break all current continuations - they will never be resumable.
@@ -5311,6 +5426,7 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
53115426 // set), so resuming is done. (And throw, if resume_throw.)
53125427 self ()->continuationStore ->resuming = false ;
53135428 maybeThrowAfterResuming (currContinuation);
5429+ currContinuation->resumeArguments .clear ();
53145430 }
53155431 }
53165432
@@ -5440,7 +5556,7 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
54405556 flow.breakTo = Name ();
54415557 }
54425558
5443- if (flow.breakTo != SUSPEND_FLOW ) {
5559+ if (flow.breakTo != SUSPEND_FLOW && flow. breakTo != STACK_SWITCH_FLOW ) {
54445560 // We are normally executing (not suspending), and therefore cannot still
54455561 // be breaking, which would mean we missed our stop.
54465562 assert (!flow.breaking () || flow.breakTo == RETURN_FLOW );
0 commit comments