Skip to content

Commit 62dae74

Browse files
authored
[Stack Switching] Supporting sending/receiving values from continuations (#7775)
With this, the next part of the spec test can be added.
1 parent d752025 commit 62dae74

File tree

3 files changed

+211
-11
lines changed

3 files changed

+211
-11
lines changed

src/wasm-interpreter.h

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,11 @@ struct ContData {
191191
// will emit a single Literals for itself, or possibly a few bundles.
192192
std::vector<Literals> resumeInfo;
193193

194+
// The arguments sent when resuming (on first execution these appear as
195+
// parameters to the function; on later resumes, they are returned from the
196+
// suspend).
197+
Literals resumeArguments;
198+
194199
// Whether we executed. Continuations are one-shot, so they may not be
195200
// executed a second time.
196201
bool executed = false;
@@ -403,6 +408,10 @@ class ExpressionRunner : public OverriddenVisitor<SubType, Flow> {
403408
auto iter = restoredValuesMap.find(curr);
404409
if (iter != restoredValuesMap.end()) {
405410
ret = iter->second;
411+
#if WASM_INTERPRETER_DEBUG
412+
std::cout << indent() << "consume restored value: " << ret.values
413+
<< '\n';
414+
#endif
406415
restoredValuesMap.erase(iter);
407416
hasValue = true;
408417
}
@@ -437,6 +446,10 @@ class ExpressionRunner : public OverriddenVisitor<SubType, Flow> {
437446
--num;
438447
auto value = popResumeEntry("child value");
439448
restoredValuesMap[child] = value;
449+
#if WASM_INTERPRETER_DEBUG
450+
std::cout << indent() << "prepare restored value: " << value
451+
<< '\n';
452+
#endif
440453
}
441454
}
442455

@@ -4636,6 +4649,15 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
46364649
}
46374650
Flow visitContBind(ContBind* curr) { return Flow(NONCONSTANT_FLOW); }
46384651
Flow visitSuspend(Suspend* curr) {
4652+
// Process the arguments, whether or not we are resuming. If we are resuming
4653+
// then we don't need these values (we sent them as part of the suspension),
4654+
// but must still handle them, so we finish re-winding the stack.
4655+
Literals arguments;
4656+
Flow flow = self()->generateArguments(curr->operands, arguments);
4657+
if (flow.breaking()) {
4658+
return flow;
4659+
}
4660+
46394661
if (self()->resuming) {
46404662
// This is a resume, so we have found our way back to where we
46414663
// suspended.
@@ -4646,15 +4668,10 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
46464668
// restoredValues map.
46474669
assert(self()->currContinuation->resumeInfo.empty());
46484670
assert(self()->restoredValuesMap.empty());
4649-
return Flow();
4671+
return self()->currContinuation->resumeArguments;
46504672
}
46514673

46524674
// We were not resuming, so this is a new suspend that we must execute.
4653-
Literals arguments;
4654-
Flow flow = self()->generateArguments(curr->operands, arguments);
4655-
if (flow.breaking()) {
4656-
return flow;
4657-
}
46584675

46594676
// Copy the continuation (the old one cannot be resumed again). Note that no
46604677
// old one may exist, in which case we still emit a continuation, but it is
@@ -4688,6 +4705,7 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
46884705
trap("continuation already executed");
46894706
}
46904707
contData->executed = true;
4708+
contData->resumeArguments = arguments;
46914709
Name func = contData->func;
46924710
self()->currContinuation = contData;
46934711
if (contData->resumeExpr) {
@@ -4699,12 +4717,24 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
46994717
#if WASM_INTERPRETER_DEBUG
47004718
std::cout << self()->indent() << "resuming func " << func << '\n';
47014719
#endif
4702-
Flow ret = callFunction(func, arguments);
4720+
Flow ret;
4721+
{
4722+
// Create a stack value scope. This ensures that we always have a scope,
4723+
// and so the code that pushes/pops doesn't need to check if a scope
4724+
// exists. (We do not need the values in this scope, of course, as no
4725+
// expression is above them, so we cannot suspend and need these values).
4726+
typename ExpressionRunner<SubType>::StackValueNoter noter(this);
4727+
ret = callFunction(func, arguments);
4728+
}
47034729
#if WASM_INTERPRETER_DEBUG
47044730
std::cout << self()->indent() << "finished resuming, with " << ret << '\n';
47054731
#endif
4706-
if (ret.suspendTag) {
4707-
// See if a suspension arrived that we support.
4732+
if (!ret.suspendTag) {
4733+
// No suspension: the coroutine finished normally. Mark it as no longer
4734+
// active.
4735+
self()->currContinuation.reset();
4736+
} else {
4737+
// We are suspending. See if a suspension arrived that we support.
47084738
for (size_t i = 0; i < curr->handlerTags.size(); i++) {
47094739
auto handlerTag = curr->handlerTags[i];
47104740
if (handlerTag == ret.suspendTag) {
@@ -4719,7 +4749,6 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
47194749
}
47204750
}
47214751
// No handler worked out, keep propagating.
4722-
return ret;
47234752
}
47244753
// No suspension; all done.
47254754
return ret;

test/lit/exec/cont_simple.wast

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,20 @@
22

33
;; RUN: foreach %s %t wasm-opt -all --fuzz-exec-before -q -o /dev/null 2>&1 | filecheck %s
44

5-
(module $state
5+
(module
66
(type $f (func))
77
(type $k (cont $f))
88

9+
(type $f-i32 (func (result i32)))
10+
(type $k-i32 (cont $f-i32))
11+
12+
(type $f-get-i32 (func (param i32)))
13+
(type $k-get-i32 (cont $f-get-i32))
14+
915
(import "fuzzing-support" "log" (func $log (param i32)))
1016

1117
(tag $more)
18+
(tag $more-i32 (result i32))
1219

1320
(func $run (param $k (ref $k))
1421
;; Run a coroutine, continuing to resume it until it is complete.
@@ -552,4 +559,116 @@
552559
(cont.new $k (ref.func $trinary))
553560
)
554561
)
562+
563+
(func $run-i32 (param $k-i32 (ref $k-i32)) (result i32)
564+
;; As $run, but the coroutine returns an i32.
565+
(call $log (i32.const 100)) ;; start
566+
(loop $loop
567+
(block $on (result (ref $k-i32))
568+
(resume $k-i32 (on $more $on)
569+
(local.get $k-i32)
570+
)
571+
(call $log (i32.const 300)) ;; stop
572+
(return)
573+
)
574+
(call $log (i32.const 200)) ;; continue
575+
(local.set $k-i32)
576+
(br $loop)
577+
)
578+
(unreachable)
579+
)
580+
581+
(func $ret-i32 (result i32)
582+
;; Just immediately return.
583+
(i32.const 42)
584+
)
585+
586+
;; CHECK: [fuzz-exec] calling run-ret-i32
587+
;; CHECK-NEXT: [LoggingExternalInterface logging 100]
588+
;; CHECK-NEXT: [LoggingExternalInterface logging 300]
589+
;; CHECK-NEXT: [fuzz-exec] note result: run-ret-i32 => 42
590+
(func $run-ret-i32 (export "run-ret-i32") (result i32)
591+
(call $run-i32
592+
(cont.new $k-i32 (ref.func $ret-i32))
593+
)
594+
)
595+
596+
(func $pause-i32 (result i32)
597+
(local $x i32)
598+
;; Pause before returning.
599+
(local.set $x
600+
(i32.const 1336)
601+
)
602+
(suspend $more)
603+
(i32.add
604+
(local.get $x)
605+
(i32.const 1)
606+
)
607+
)
608+
609+
;; CHECK: [fuzz-exec] calling run-pause-i32
610+
;; CHECK-NEXT: [LoggingExternalInterface logging 100]
611+
;; CHECK-NEXT: [LoggingExternalInterface logging 200]
612+
;; CHECK-NEXT: [LoggingExternalInterface logging 300]
613+
;; CHECK-NEXT: [fuzz-exec] note result: run-pause-i32 => 1337
614+
(func $run-pause-i32 (export "run-pause-i32") (result i32)
615+
(call $run-i32
616+
(cont.new $k-i32 (ref.func $pause-i32))
617+
)
618+
)
619+
620+
(func $run-get-i32 (param $x i32) (param $k-get-i32 (ref $k-get-i32))
621+
;; As $run, but the coroutine receives an i32.
622+
(call $log (i32.const 100)) ;; start
623+
(loop $loop
624+
(block $on (result (ref $k-get-i32))
625+
(resume $k-get-i32 (on $more-i32 $on)
626+
(local.get $x)
627+
(local.get $k-get-i32)
628+
)
629+
(call $log (i32.const 300)) ;; stop
630+
(return)
631+
)
632+
(call $log (i32.const 200)) ;; continue
633+
;; Modify $x, so we can see differences in the loggings.
634+
(local.set $x
635+
(i32.sub
636+
(local.get $x)
637+
(i32.const 1)
638+
)
639+
)
640+
(local.set $k-get-i32)
641+
(br $loop)
642+
)
643+
(unreachable)
644+
)
645+
646+
(func $param (param $x i32)
647+
(call $log (local.get $x))
648+
(local.set $x
649+
(i32.add
650+
(local.get $x)
651+
(i32.const 1295)
652+
)
653+
)
654+
(call $log (suspend $more-i32))
655+
(call $log (local.get $x))
656+
(call $log (suspend $more-i32))
657+
)
658+
659+
;; CHECK: [fuzz-exec] calling run-param
660+
;; CHECK-NEXT: [LoggingExternalInterface logging 100]
661+
;; CHECK-NEXT: [LoggingExternalInterface logging 42]
662+
;; CHECK-NEXT: [LoggingExternalInterface logging 200]
663+
;; CHECK-NEXT: [LoggingExternalInterface logging 41]
664+
;; CHECK-NEXT: [LoggingExternalInterface logging 1337]
665+
;; CHECK-NEXT: [LoggingExternalInterface logging 200]
666+
;; CHECK-NEXT: [LoggingExternalInterface logging 40]
667+
;; CHECK-NEXT: [LoggingExternalInterface logging 300]
668+
(func $run-param (export "run-param")
669+
(call $run-get-i32
670+
(i32.const 42)
671+
(cont.new $k-get-i32 (ref.func $param))
672+
)
673+
)
555674
)

test/spec/cont.wast

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,3 +227,55 @@
227227
(type $c2 (cont $f2))
228228
)
229229

230+
;; Simple state example
231+
232+
(module $state
233+
(tag $get (result i32))
234+
(tag $set (param i32) (result i32))
235+
236+
(type $f (func (param i32) (result i32)))
237+
(type $k (cont $f))
238+
239+
(func $runner (param $s i32) (param $k (ref $k)) (result i32)
240+
(loop $loop
241+
(block $on_get (result (ref $k))
242+
(block $on_set (result i32 (ref $k))
243+
(resume $k (on $get $on_get) (on $set $on_set)
244+
(local.get $s) (local.get $k)
245+
)
246+
(return)
247+
)
248+
;; on set
249+
(local.set $k)
250+
(local.set $s)
251+
(br $loop)
252+
)
253+
;; on get
254+
(local.set $k)
255+
(br $loop)
256+
)
257+
(unreachable)
258+
)
259+
260+
(func $f (param i32) (result i32)
261+
(drop (suspend $set (i32.const 7)))
262+
(i32.add
263+
(suspend $get)
264+
(i32.mul
265+
(i32.const 2)
266+
(i32.add
267+
(suspend $set (i32.const 3))
268+
(suspend $get)
269+
)
270+
)
271+
)
272+
)
273+
274+
(elem declare func $f)
275+
(func (export "run") (result i32)
276+
(call $runner (i32.const 0) (cont.new $k (ref.func $f)))
277+
)
278+
)
279+
280+
(assert_return (invoke "run") (i32.const 19))
281+

0 commit comments

Comments
 (0)