summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/passes/Bysyncify.cpp43
-rw-r--r--test/passes/bysyncify.txt89
-rw-r--r--test/passes/bysyncify.wast5
-rw-r--r--test/passes/bysyncify_optimize-level=1.txt10
-rw-r--r--test/passes/bysyncify_pass-arg=bysyncify-imports@env.import,env.import2.txt (renamed from test/passes/bysyncify_pass-arg=bysyncify@env.import,env.import2.txt)58
-rw-r--r--test/passes/bysyncify_pass-arg=bysyncify-imports@env.import,env.import2.wast (renamed from test/passes/bysyncify_pass-arg=bysyncify@env.import,env.import2.wast)0
-rw-r--r--test/unit/input/bysyncify-coroutine.wast44
-rw-r--r--test/unit/input/bysyncify-sleep.wast (renamed from test/unit/input/bysyncify.wast)0
-rw-r--r--test/unit/input/bysyncify.js352
-rw-r--r--test/unit/test_bysyncify.py3
10 files changed, 439 insertions, 165 deletions
diff --git a/src/passes/Bysyncify.cpp b/src/passes/Bysyncify.cpp
index ffbf6db1c..f9f679f2e 100644
--- a/src/passes/Bysyncify.cpp
+++ b/src/passes/Bysyncify.cpp
@@ -126,13 +126,21 @@
// proper place, and the end to the proper end based on how much memory
// you reserved. Note that bysyncify will grow the stack up.
//
-// The pass will also create three functions that let you control unwinding
+// The pass will also create four functions that let you control unwinding
// and rewinding:
//
// * bysyncify_start_unwind(data : i32): call this to start unwinding the
// stack from the current location. "data" must point to a data
// structure as described above (with fields containing valid data).
//
+// * bysyncify_stop_unwind(): call this to note that unwinding has
+// concluded. If no other code will run before you start to rewind,
+// this is not strictly necessary, however, if you swap between
+// coroutines, or even just want to run some normal code during a
+// "sleep", then you must call this at the proper time. Otherwise,
+// the code will think it is still unwinding when it should not be,
+// which means it will keep unwinding in a meaningless way.
+//
// * bysyncify_start_rewind(data : i32): call this to start rewinding the
// stack vack up to the location stored in the provided data. This prepares
// for the rewind; to start it, you must call the first function in the
@@ -141,17 +149,18 @@
// * bysyncify_stop_rewind(): call this to note that rewinding has
// concluded, and normal execution can resume.
//
-// These three functions are exported so that you can call them from the
+// These four functions are exported so that you can call them from the
// outside. If you want to manage things from inside the wasm, then you
// couldn't have called them before they were created by this pass. To work
// around that, you can create imports to bysyncify.start_unwind,
-// bysyncify.start_rewind, and bysyncify.stop_rewind; if those exist when
-// this pass runs then it will turn those into direct calls to the functions
-// that it creates.
+// bysyncify.stop_unwind, bysyncify.start_rewind, and bysyncify.stop_rewind;
+// if those exist when this pass runs then it will turn those into direct
+// calls to the functions that it creates.
//
// To use this API, call bysyncify_start_unwind when you want to. The call
// stack will then be unwound, and so execution will resume in the JS or
-// other host environment on the outside that called into wasm. Then when
+// other host environment on the outside that called into wasm. When you
+// return there after unwinding, call bysyncify_stop_unwind. Then when
// you want to rewind, call bysyncify_start_rewind, and then call the same
// initial function you called before, so that unwinding can begin. The
// unwinding will reach the same function from which you started, since
@@ -163,15 +172,16 @@
// To customize this, you can provide an argument to wasm-opt (or another
// tool that can run this pass),
//
-// --pass-arg=bysyncify@module1.base1,module2.base2,module3.base3
+// --pass-arg=bysyncify-imports@module1.base1,module2.base2,module3.base3
//
// Each module.base in that comma-separated list will be considered to
// be an import that can unwind/rewind, and all others are assumed not to
// (aside from the bysyncify.* imports, which are always assumed to). To
// say that no import (aside from bysyncify.*) can do so (that is, the
// opposite of the default behavior), you can simply provide an import
-// that doesn't exist (say,
-// --pass-arg=bysyncify@no.imports
+// that doesn't exist, for example:
+
+// --pass-arg=bysyncify-imports@no.imports
//
#include "ir/effects.h"
@@ -190,11 +200,13 @@ namespace {
static const Name BYSYNCIFY_STATE = "__bysyncify_state";
static const Name BYSYNCIFY_DATA = "__bysyncify_data";
static const Name BYSYNCIFY_START_UNWIND = "bysyncify_start_unwind";
+static const Name BYSYNCIFY_STOP_UNWIND = "bysyncify_stop_unwind";
static const Name BYSYNCIFY_START_REWIND = "bysyncify_start_rewind";
static const Name BYSYNCIFY_STOP_REWIND = "bysyncify_stop_rewind";
static const Name BYSYNCIFY_UNWIND = "__bysyncify_unwind";
static const Name BYSYNCIFY = "bysyncify";
static const Name START_UNWIND = "start_unwind";
+static const Name STOP_UNWIND = "stop_unwind";
static const Name START_REWIND = "start_rewind";
static const Name STOP_REWIND = "stop_rewind";
static const Name BYSYNCIFY_GET_CALL_INDEX = "__bysyncify_get_call_index";
@@ -234,10 +246,10 @@ public:
ModuleUtils::ParallelFunctionMap<Info> scanner(
module, [&](Function* func, Info& info) {
if (func->imported()) {
- // The bysyncify imports to start an unwind or rewind can definitely
- // change the state.
+ // The bysyncify imports can definitely change the state.
if (func->module == BYSYNCIFY &&
- (func->base == START_UNWIND || func->base == START_REWIND)) {
+ (func->base == START_UNWIND || func->base == STOP_UNWIND ||
+ func->base == START_REWIND || func->base == STOP_REWIND)) {
info.canChangeState = true;
} else {
info.canChangeState =
@@ -252,6 +264,8 @@ public:
// Redirect the imports to the functions we'll add later.
if (target->base == START_UNWIND) {
curr->target = BYSYNCIFY_START_UNWIND;
+ } else if (target->base == STOP_UNWIND) {
+ curr->target = BYSYNCIFY_STOP_UNWIND;
} else if (target->base == START_REWIND) {
curr->target = BYSYNCIFY_START_REWIND;
} else if (target->base == STOP_REWIND) {
@@ -328,7 +342,9 @@ public:
// We only implement these at the very end, but we know that they
// definitely change the state.
if (curr->target == BYSYNCIFY_START_UNWIND ||
+ curr->target == BYSYNCIFY_STOP_UNWIND ||
curr->target == BYSYNCIFY_START_REWIND ||
+ curr->target == BYSYNCIFY_STOP_REWIND ||
curr->target == BYSYNCIFY_GET_CALL_INDEX ||
curr->target == BYSYNCIFY_CHECK_CALL_INDEX) {
canChangeState = true;
@@ -775,7 +791,7 @@ struct Bysyncify : public Pass {
// Find which imports can change the state.
const char* ALL_IMPORTS_CAN_CHANGE_STATE = "__bysyncify_all_imports";
auto stateChangingImports = runner->options.getArgumentOrDefault(
- "bysyncify", ALL_IMPORTS_CAN_CHANGE_STATE);
+ "bysyncify-imports", ALL_IMPORTS_CAN_CHANGE_STATE);
bool allImportsCanChangeState =
stateChangingImports == ALL_IMPORTS_CAN_CHANGE_STATE;
std::string separator = ",";
@@ -895,6 +911,7 @@ private:
true,
State::Unwinding,
builder.makeGlobalSet(BYSYNCIFY_DATA, builder.makeLocalGet(0, i32)));
+ makeFunction(BYSYNCIFY_STOP_UNWIND, false, State::Normal, nullptr);
makeFunction(
BYSYNCIFY_START_REWIND,
true,
diff --git a/test/passes/bysyncify.txt b/test/passes/bysyncify.txt
index 1ec35057e..2ab682149 100644
--- a/test/passes/bysyncify.txt
+++ b/test/passes/bysyncify.txt
@@ -6,6 +6,7 @@
(global $__bysyncify_state (mut i32) (i32.const 0))
(global $__bysyncify_data (mut i32) (i32.const 0))
(export "bysyncify_start_unwind" (func $bysyncify_start_unwind))
+ (export "bysyncify_stop_unwind" (func $bysyncify_stop_unwind))
(export "bysyncify_start_rewind" (func $bysyncify_start_rewind))
(export "bysyncify_stop_rewind" (func $bysyncify_stop_rewind))
(func $do_sleep (; 0 ;) (type $FUNCSIG$v)
@@ -151,16 +152,40 @@
(i32.const 2)
)
)
- (if
- (i32.eq
- (global.get $__bysyncify_state)
- (i32.const 0)
- )
- (block
+ (block
+ (if
+ (i32.eq
+ (global.get $__bysyncify_state)
+ (i32.const 0)
+ )
(global.set $sleeping
(i32.const 0)
)
- (call $bysyncify_stop_rewind)
+ )
+ (if
+ (if (result i32)
+ (i32.eq
+ (global.get $__bysyncify_state)
+ (i32.const 0)
+ )
+ (i32.const 1)
+ (i32.eq
+ (local.get $3)
+ (i32.const 1)
+ )
+ )
+ (block
+ (call $bysyncify_stop_rewind)
+ (if
+ (i32.eq
+ (global.get $__bysyncify_state)
+ (i32.const 1)
+ )
+ (br $__bysyncify_unwind
+ (i32.const 1)
+ )
+ )
+ )
)
)
)
@@ -458,6 +483,31 @@
)
)
(block
+ (call $bysyncify_stop_unwind)
+ (if
+ (i32.eq
+ (global.get $__bysyncify_state)
+ (i32.const 1)
+ )
+ (br $__bysyncify_unwind
+ (i32.const 0)
+ )
+ )
+ )
+ )
+ (if
+ (if (result i32)
+ (i32.eq
+ (global.get $__bysyncify_state)
+ (i32.const 0)
+ )
+ (i32.const 1)
+ (i32.eq
+ (local.get $1)
+ (i32.const 1)
+ )
+ )
+ (block
(call $bysyncify_start_rewind
(i32.const 4)
)
@@ -467,7 +517,7 @@
(i32.const 1)
)
(br $__bysyncify_unwind
- (i32.const 0)
+ (i32.const 1)
)
)
)
@@ -481,7 +531,7 @@
(i32.const 1)
(i32.eq
(local.get $1)
- (i32.const 1)
+ (i32.const 2)
)
)
(block
@@ -492,7 +542,7 @@
(i32.const 1)
)
(br $__bysyncify_unwind
- (i32.const 1)
+ (i32.const 2)
)
)
)
@@ -546,7 +596,12 @@
(local.get $0)
)
)
- (func $bysyncify_start_rewind (; 7 ;) (param $0 i32)
+ (func $bysyncify_stop_unwind (; 7 ;)
+ (global.set $__bysyncify_state
+ (i32.const 0)
+ )
+ )
+ (func $bysyncify_start_rewind (; 8 ;) (param $0 i32)
(if
(i32.gt_u
(i32.load
@@ -565,7 +620,7 @@
(local.get $0)
)
)
- (func $bysyncify_stop_rewind (; 8 ;)
+ (func $bysyncify_stop_rewind (; 9 ;)
(global.set $__bysyncify_state
(i32.const 0)
)
@@ -583,6 +638,7 @@
(global $__bysyncify_state (mut i32) (i32.const 0))
(global $__bysyncify_data (mut i32) (i32.const 0))
(export "bysyncify_start_unwind" (func $bysyncify_start_unwind))
+ (export "bysyncify_stop_unwind" (func $bysyncify_stop_unwind))
(export "bysyncify_start_rewind" (func $bysyncify_start_rewind))
(export "bysyncify_stop_rewind" (func $bysyncify_stop_rewind))
(func $calls-import (; 3 ;) (type $FUNCSIG$v)
@@ -2683,7 +2739,12 @@
(local.get $0)
)
)
- (func $bysyncify_start_rewind (; 20 ;) (param $0 i32)
+ (func $bysyncify_stop_unwind (; 20 ;)
+ (global.set $__bysyncify_state
+ (i32.const 0)
+ )
+ )
+ (func $bysyncify_start_rewind (; 21 ;) (param $0 i32)
(if
(i32.gt_u
(i32.load
@@ -2702,7 +2763,7 @@
(local.get $0)
)
)
- (func $bysyncify_stop_rewind (; 21 ;)
+ (func $bysyncify_stop_rewind (; 22 ;)
(global.set $__bysyncify_state
(i32.const 0)
)
diff --git a/test/passes/bysyncify.wast b/test/passes/bysyncify.wast
index 7c92bd86f..00ecc7fde 100644
--- a/test/passes/bysyncify.wast
+++ b/test/passes/bysyncify.wast
@@ -2,6 +2,7 @@
(module
(memory 1 2)
(import "bysyncify" "start_unwind" (func $bysyncify_start_unwind (param i32)))
+ (import "bysyncify" "stop_unwind" (func $bysyncify_stop_unwind))
(import "bysyncify" "start_rewind" (func $bysyncify_start_rewind (param i32)))
(import "bysyncify" "stop_rewind" (func $bysyncify_stop_rewind))
(global $sleeping (mut i32) (i32.const 0))
@@ -34,8 +35,10 @@
;; work will sleep, so we exit through here while it is paused
)
;; the second event called from the main event loop: to resume $work,
- ;; initiate a rewind, and then do the call to start things back up
+ ;; stop the unwind, then prepare a rewind, and initiate it by doing
+ ;; the call to rewind the call stack back up to where it was
(func $second_event
+ (call $bysyncify_stop_unwind)
(call $bysyncify_start_rewind (i32.const 4))
(call $work)
)
diff --git a/test/passes/bysyncify_optimize-level=1.txt b/test/passes/bysyncify_optimize-level=1.txt
index 6eb7217e4..73456a7ae 100644
--- a/test/passes/bysyncify_optimize-level=1.txt
+++ b/test/passes/bysyncify_optimize-level=1.txt
@@ -10,6 +10,7 @@
(global $__bysyncify_state (mut i32) (i32.const 0))
(global $__bysyncify_data (mut i32) (i32.const 0))
(export "bysyncify_start_unwind" (func $bysyncify_start_unwind))
+ (export "bysyncify_stop_unwind" (func $bysyncify_stop_unwind))
(export "bysyncify_start_rewind" (func $bysyncify_start_rewind))
(export "bysyncify_stop_rewind" (func $bysyncify_stop_rewind))
(func $calls-import (; 3 ;) (type $FUNCSIG$v)
@@ -1514,7 +1515,12 @@
(local.get $0)
)
)
- (func $bysyncify_start_rewind (; 20 ;) (param $0 i32)
+ (func $bysyncify_stop_unwind (; 20 ;)
+ (global.set $__bysyncify_state
+ (i32.const 0)
+ )
+ )
+ (func $bysyncify_start_rewind (; 21 ;) (param $0 i32)
(if
(i32.gt_u
(i32.load
@@ -1533,7 +1539,7 @@
(local.get $0)
)
)
- (func $bysyncify_stop_rewind (; 21 ;)
+ (func $bysyncify_stop_rewind (; 22 ;)
(global.set $__bysyncify_state
(i32.const 0)
)
diff --git a/test/passes/bysyncify_pass-arg=bysyncify@env.import,env.import2.txt b/test/passes/bysyncify_pass-arg=bysyncify-imports@env.import,env.import2.txt
index e850da6cf..81a37a643 100644
--- a/test/passes/bysyncify_pass-arg=bysyncify@env.import,env.import2.txt
+++ b/test/passes/bysyncify_pass-arg=bysyncify-imports@env.import,env.import2.txt
@@ -6,6 +6,7 @@
(global $__bysyncify_state (mut i32) (i32.const 0))
(global $__bysyncify_data (mut i32) (i32.const 0))
(export "bysyncify_start_unwind" (func $bysyncify_start_unwind))
+ (export "bysyncify_stop_unwind" (func $bysyncify_stop_unwind))
(export "bysyncify_start_rewind" (func $bysyncify_start_rewind))
(export "bysyncify_stop_rewind" (func $bysyncify_stop_rewind))
(func $do_sleep (; 0 ;) (type $FUNCSIG$v)
@@ -151,16 +152,40 @@
(i32.const 2)
)
)
- (if
- (i32.eq
- (global.get $__bysyncify_state)
- (i32.const 0)
- )
- (block
+ (block
+ (if
+ (i32.eq
+ (global.get $__bysyncify_state)
+ (i32.const 0)
+ )
(global.set $sleeping
(i32.const 0)
)
- (call $bysyncify_stop_rewind)
+ )
+ (if
+ (if (result i32)
+ (i32.eq
+ (global.get $__bysyncify_state)
+ (i32.const 0)
+ )
+ (i32.const 1)
+ (i32.eq
+ (local.get $3)
+ (i32.const 1)
+ )
+ )
+ (block
+ (call $bysyncify_stop_rewind)
+ (if
+ (i32.eq
+ (global.get $__bysyncify_state)
+ (i32.const 1)
+ )
+ (br $__bysyncify_unwind
+ (i32.const 1)
+ )
+ )
+ )
)
)
)
@@ -546,7 +571,12 @@
(local.get $0)
)
)
- (func $bysyncify_start_rewind (; 7 ;) (param $0 i32)
+ (func $bysyncify_stop_unwind (; 7 ;)
+ (global.set $__bysyncify_state
+ (i32.const 0)
+ )
+ )
+ (func $bysyncify_start_rewind (; 8 ;) (param $0 i32)
(if
(i32.gt_u
(i32.load
@@ -565,7 +595,7 @@
(local.get $0)
)
)
- (func $bysyncify_stop_rewind (; 8 ;)
+ (func $bysyncify_stop_rewind (; 9 ;)
(global.set $__bysyncify_state
(i32.const 0)
)
@@ -583,6 +613,7 @@
(global $__bysyncify_state (mut i32) (i32.const 0))
(global $__bysyncify_data (mut i32) (i32.const 0))
(export "bysyncify_start_unwind" (func $bysyncify_start_unwind))
+ (export "bysyncify_stop_unwind" (func $bysyncify_stop_unwind))
(export "bysyncify_start_rewind" (func $bysyncify_start_rewind))
(export "bysyncify_stop_rewind" (func $bysyncify_stop_rewind))
(func $calls-import (; 3 ;) (type $FUNCSIG$v)
@@ -1971,7 +2002,12 @@
(local.get $0)
)
)
- (func $bysyncify_start_rewind (; 20 ;) (param $0 i32)
+ (func $bysyncify_stop_unwind (; 20 ;)
+ (global.set $__bysyncify_state
+ (i32.const 0)
+ )
+ )
+ (func $bysyncify_start_rewind (; 21 ;) (param $0 i32)
(if
(i32.gt_u
(i32.load
@@ -1990,7 +2026,7 @@
(local.get $0)
)
)
- (func $bysyncify_stop_rewind (; 21 ;)
+ (func $bysyncify_stop_rewind (; 22 ;)
(global.set $__bysyncify_state
(i32.const 0)
)
diff --git a/test/passes/bysyncify_pass-arg=bysyncify@env.import,env.import2.wast b/test/passes/bysyncify_pass-arg=bysyncify-imports@env.import,env.import2.wast
index 7c92bd86f..7c92bd86f 100644
--- a/test/passes/bysyncify_pass-arg=bysyncify@env.import,env.import2.wast
+++ b/test/passes/bysyncify_pass-arg=bysyncify-imports@env.import,env.import2.wast
diff --git a/test/unit/input/bysyncify-coroutine.wast b/test/unit/input/bysyncify-coroutine.wast
new file mode 100644
index 000000000..ace97c6e6
--- /dev/null
+++ b/test/unit/input/bysyncify-coroutine.wast
@@ -0,0 +1,44 @@
+(module
+ (memory 1 2)
+ ;; import a "yield" function that receives the current value,
+ ;; then pauses execution until it is resumed later.
+ (import "env" "yield" (func $yield (param i32)))
+ (export "memory" (memory 0))
+ ;; simple linear progression in a loop
+ (func "linear" (result i32)
+ (local $x i32)
+ (loop $l
+ (call $yield (local.get $x))
+ (local.set $x
+ (i32.add (local.get $x) (i32.const 10))
+ )
+ (br $l)
+ )
+ )
+ ;; exponential in a loop
+ (func "exponential" (result i32)
+ (local $x i32)
+ (local.set $x
+ (i32.const 1)
+ )
+ (loop $l
+ (call $yield (local.get $x))
+ (local.set $x
+ (i32.mul (local.get $x) (i32.const 2))
+ )
+ (br $l)
+ )
+ )
+ ;; just some weird numbers, no loop
+ (func "weird" (result i32)
+ (call $yield (i32.const 42))
+ (call $yield (i32.const 1337))
+ (call $yield (i32.const 0))
+ (call $yield (i32.const -1000))
+ (call $yield (i32.const 42))
+ (call $yield (i32.const 314159))
+ (call $yield (i32.const 21828))
+ (unreachable)
+ )
+)
+
diff --git a/test/unit/input/bysyncify.wast b/test/unit/input/bysyncify-sleep.wast
index 91fb5a327..91fb5a327 100644
--- a/test/unit/input/bysyncify.wast
+++ b/test/unit/input/bysyncify-sleep.wast
diff --git a/test/unit/input/bysyncify.js b/test/unit/input/bysyncify.js
index 97d7a0da5..4fd6f4b84 100644
--- a/test/unit/input/bysyncify.js
+++ b/test/unit/input/bysyncify.js
@@ -5,151 +5,257 @@ function assert(x, y) {
var fs = require('fs');
-// Get and compile the wasm.
-
-var binary = fs.readFileSync('a.wasm');
-
-var module = new WebAssembly.Module(binary);
-
-var DATA_ADDR = 4;
-
-var sleeps = 0;
-
-var sleeping = false;
-
-var instance = new WebAssembly.Instance(module, {
- env: {
- sleep: function() {
- logMemory();
-assert(view[0] == 0);
- if (!sleeping) {
- // We are called in order to start a sleep/unwind.
- console.log('sleep...');
- sleeps++;
- // Unwinding.
- exports.bysyncify_start_unwind(DATA_ADDR);
- // Fill in the data structure. The first value has the stack location,
- // which for simplicity we can start right after the data structure itself.
- view[DATA_ADDR >> 2] = DATA_ADDR + 8;
- // The end of the stack will not be reached here anyhow.
- view[DATA_ADDR + 4 >> 2] = 1024;
- sleeping = true;
- } else {
- // We are called as part of a resume/rewind. Stop sleeping.
- console.log('resume...');
- exports.bysyncify_stop_rewind();
- // The stack should have been all used up, and so returned to the original state.
- assert(view[DATA_ADDR >> 2] == DATA_ADDR + 8);
- assert(view[DATA_ADDR + 4 >> 2] == 1024);
- sleeping = false;
+function sleepTests() {
+ console.log('\nsleep tests\n\n');
+
+ // Get and compile the wasm.
+
+ var binary = fs.readFileSync('a.wasm');
+
+ var module = new WebAssembly.Module(binary);
+
+ var DATA_ADDR = 4;
+
+ var sleeps = 0;
+
+ var sleeping = false;
+
+ var instance = new WebAssembly.Instance(module, {
+ env: {
+ sleep: function() {
+ logMemory();
+ if (!sleeping) {
+ // We are called in order to start a sleep/unwind.
+ console.log('sleep...');
+ sleeps++;
+ // Unwinding.
+ exports.bysyncify_start_unwind(DATA_ADDR);
+ // Fill in the data structure. The first value has the stack location,
+ // which for simplicity we can start right after the data structure itself.
+ view[DATA_ADDR >> 2] = DATA_ADDR + 8;
+ // The end of the stack will not be reached here anyhow.
+ view[DATA_ADDR + 4 >> 2] = 1024;
+ sleeping = true;
+ } else {
+ // We are called as part of a resume/rewind. Stop sleeping.
+ console.log('resume...');
+ exports.bysyncify_stop_rewind();
+ // The stack should have been all used up, and so returned to the original state.
+ assert(view[DATA_ADDR >> 2] == DATA_ADDR + 8);
+ assert(view[DATA_ADDR + 4 >> 2] == 1024);
+ sleeping = false;
+ }
+ logMemory();
+ },
+ tunnel: function(x) {
+ console.log('tunneling, sleep == ' + sleeping);
+ return exports.end_tunnel(x);
}
- logMemory();
- },
- tunnel: function(x) {
- console.log('tunneling, sleep == ' + sleeping);
- return exports.end_tunnel(x);
}
- }
-});
+ });
-var exports = instance.exports;
-var view = new Int32Array(exports.memory.buffer);
+ var exports = instance.exports;
+ var view = new Int32Array(exports.memory.buffer);
-function logMemory() {
- // Log the relevant memory locations for debugging purposes.
- console.log('memory: ', view[0 >> 2], view[4 >> 2], view[8 >> 2], view[12 >> 2], view[16 >> 2], view[20 >> 2], view[24 >> 2]);
-}
+ function logMemory() {
+ // Log the relevant memory locations for debugging purposes.
+ console.log('memory: ', view[0 >> 2], view[4 >> 2], view[8 >> 2], view[12 >> 2], view[16 >> 2], view[20 >> 2], view[24 >> 2]);
+ }
-function runTest(name, expectedSleeps, expectedResult, params) {
- params = params || [];
+ function runTest(name, expectedSleeps, expectedResult, params) {
+ params = params || [];
- console.log('\n==== testing ' + name + ' ====');
+ console.log('\n==== testing ' + name + ' ====');
- sleeps = 0;
+ sleeps = 0;
- logMemory();
+ logMemory();
- // Run until the sleep.
- var result = exports[name].apply(null, params);
- logMemory();
+ // Run until the sleep.
+ var result = exports[name].apply(null, params);
+ logMemory();
- if (expectedSleeps > 0) {
- assert(!result, 'results during sleep are meaningless, just 0');
+ if (expectedSleeps > 0) {
+ assert(!result, 'results during sleep are meaningless, just 0');
+ exports.bysyncify_stop_unwind(DATA_ADDR);
+
+ for (var i = 0; i < expectedSleeps - 1; i++) {
+ console.log('rewind, run until the next sleep');
+ exports.bysyncify_start_rewind(DATA_ADDR);
+ result = exports[name](); // no need for params on later times
+ assert(!result, 'results during sleep are meaningless, just 0');
+ logMemory();
+ exports.bysyncify_stop_unwind(DATA_ADDR);
+ }
- for (var i = 0; i < expectedSleeps - 1; i++) {
- console.log('rewind, run until the next sleep');
+ console.log('rewind and run til the end.');
exports.bysyncify_start_rewind(DATA_ADDR);
- result = exports[name](); // no need for params on later times
- assert(!result, 'results during sleep are meaningless, just 0');
- assert(!result, 'bad first sleep result');
- logMemory();
+ result = exports[name]();
}
- console.log('rewind and run til the end.');
- exports.bysyncify_start_rewind(DATA_ADDR);
- result = exports[name]();
- }
+ console.log('final result: ' + result);
+ assert(result == expectedResult, 'bad final result');
+ logMemory();
- console.log('final result: ' + result);
- assert(result == expectedResult, 'bad final result');
- logMemory();
+ assert(sleeps == expectedSleeps, 'expectedSleeps');
+ }
- assert(sleeps == expectedSleeps, 'expectedSleeps');
+ //================
+ // Tests
+ //================
+
+ // A minimal single sleep.
+ runTest('minimal', 1, 21);
+
+ // Two sleeps.
+ runTest('repeat', 2, 42);
+
+ // A value in a local is preserved across a sleep.
+ runTest('local', 1, 10);
+
+ // A local with more operations done on it.
+ runTest('local2', 1, 22);
+
+ // A local with more operations done on it.
+ runTest('params', 1, 18);
+ runTest('params', 1, 21, [1, 2]);
+
+ // Calls to multiple other functions, only one of whom
+ // sleeps, and keep locals and globals valid throughout.
+ runTest('deeper', 0, 27, [0]);
+ runTest('deeper', 1, 3, [1]);
+
+ // A recursive factorial, that sleeps on each iteration
+ // above 1.
+ runTest('factorial-recursive', 0, 1, [1]);
+ runTest('factorial-recursive', 1, 2, [2]);
+ runTest('factorial-recursive', 2, 6, [3]);
+ runTest('factorial-recursive', 3, 24, [4]);
+ runTest('factorial-recursive', 4, 120, [5]);
+
+ // A looping factorial, that sleeps on each iteration
+ // above 1.
+ runTest('factorial-loop', 0, 1, [1]);
+ runTest('factorial-loop', 1, 2, [2]);
+ runTest('factorial-loop', 2, 6, [3]);
+ runTest('factorial-loop', 3, 24, [4]);
+ runTest('factorial-loop', 4, 120, [5]);
+
+ // Test calling into JS in the middle (which can work if
+ // the JS just forwards the call and has no side effects or
+ // state of its own that needs to be saved).
+ runTest('do_tunnel', 2, 72, [1]);
+
+ // Test indirect function calls.
+ runTest('call_indirect', 3, 432, [1, 2]);
+
+ // Test indirect function calls.
+ runTest('if_else', 3, 1460, [1, 1000]);
+ runTest('if_else', 3, 2520, [2, 2000]);
}
-//================
-// Tests
-//================
-
-// A minimal single sleep.
-runTest('minimal', 1, 21);
-
-// Two sleeps.
-runTest('repeat', 2, 42);
-
-// A value in a local is preserved across a sleep.
-runTest('local', 1, 10);
-
-// A local with more operations done on it.
-runTest('local2', 1, 22);
-
-// A local with more operations done on it.
-runTest('params', 1, 18);
-runTest('params', 1, 21, [1, 2]);
-
-// Calls to multiple other functions, only one of whom
-// sleeps, and keep locals and globals valid throughout.
-runTest('deeper', 0, 27, [0]);
-runTest('deeper', 1, 3, [1]);
-
-// A recursive factorial, that sleeps on each iteration
-// above 1.
-runTest('factorial-recursive', 0, 1, [1]);
-runTest('factorial-recursive', 1, 2, [2]);
-runTest('factorial-recursive', 2, 6, [3]);
-runTest('factorial-recursive', 3, 24, [4]);
-runTest('factorial-recursive', 4, 120, [5]);
+function coroutineTests() {
+ console.log('\ncoroutine tests\n\n');
+
+ // Get and compile the wasm.
+
+ var binary = fs.readFileSync('b.wasm');
+
+ var module = new WebAssembly.Module(binary);
+
+ // Create a coroutine, for a specific export to
+ // call, and whose unwind/rewind data is in
+ // a specific range.
+ function Coroutine(name, dataStart, dataEnd) {
+ this.name = name;
+ this.start = function() {
+ exports[name]();
+ };
+ this.startUnwind = function() {
+ // Initialize the data.
+ view[dataStart >> 2] = dataStart + 8;
+ view[dataStart + 4 >> 2] = dataEnd;
+ exports.bysyncify_start_unwind(dataStart);
+ // (With C etc. coroutines we would also have
+ // a C stack to pause and resume here.)
+ };
+ this.stopUnwind = function() {
+ exports.bysyncify_stop_unwind();
+ };
+ this.startRewind = function() {
+ exports.bysyncify_start_rewind(dataStart);
+ exports[name]();
+ };
+ this.stopRewind = function() {
+ exports.bysyncify_stop_rewind();
+ };
+ }
-// A looping factorial, that sleeps on each iteration
-// above 1.
-runTest('factorial-loop', 0, 1, [1]);
-runTest('factorial-loop', 1, 2, [2]);
-runTest('factorial-loop', 2, 6, [3]);
-runTest('factorial-loop', 3, 24, [4]);
-runTest('factorial-loop', 4, 120, [5]);
+ var Runtime = {
+ coroutines: [
+ new Coroutine('linear', 1000, 2000),
+ new Coroutine('exponential', 2000, 3000),
+ new Coroutine('weird', 3000, 4000)
+ ],
+ active: null,
+ rewinding: false,
+ run: function(iters) {
+ Runtime.coroutines.forEach(function(coroutine) {
+ console.log('starting ' + coroutine.name);
+ Runtime.active = coroutine;
+ coroutine.start();
+ coroutine.stopUnwind();
+ Runtime.active = null;
+ });
+ for (var i = 0; i < iters; i++) {
+ Runtime.coroutines.forEach(function(coroutine) {
+ console.log('resuming ' + coroutine.name);
+ Runtime.active = coroutine;
+ Runtime.rewinding = true;
+ coroutine.startRewind();
+ Runtime.active = null;
+ });
+ }
+ },
+ values: [],
+ yield: function(value) {
+ console.log('yield reached', Runtime.rewinding, value);
+ var coroutine = Runtime.active;
+ if (Runtime.rewinding) {
+ coroutine.stopRewind();
+ Runtime.rewinding = false;
+ } else {
+ Runtime.values.push(value);
+ coroutine.startUnwind();
+ console.log('pausing ' + coroutine.name);
+ }
+ },
+ };
-// Test calling into JS in the middle (which can work if
-// the JS just forwards the call and has no side effects or
-// state of its own that needs to be saved).
-runTest('do_tunnel', 2, 72, [1]);
+ var instance = new WebAssembly.Instance(module, {
+ env: {
+ yield: Runtime.yield
+ }
+ });
+
+ var exports = instance.exports;
+ var view = new Int32Array(exports.memory.buffer);
+
+ Runtime.run(4);
+ console.log(Runtime.values);
+ assert(JSON.stringify(Runtime.values) === JSON.stringify([
+ 0, 1, 42,
+ 10, 2, 1337,
+ 20, 4, 0,
+ 30, 8, -1000,
+ 40, 16, 42
+ ]), 'check yielded values')
+}
-// Test indirect function calls.
-runTest('call_indirect', 3, 432, [1, 2]);
+// Main
-// Test indirect function calls.
-runTest('if_else', 3, 1460, [1, 1000]);
-runTest('if_else', 3, 2520, [2, 2000]);
+sleepTests();
+coroutineTests();
-// All done.
console.log('\ntests completed successfully');
diff --git a/test/unit/test_bysyncify.py b/test/unit/test_bysyncify.py
index 5373a4def..29a8ae7cb 100644
--- a/test/unit/test_bysyncify.py
+++ b/test/unit/test_bysyncify.py
@@ -8,7 +8,8 @@ class BysyncifyTest(BinaryenTestCase):
def test_bysyncify(self):
def test(args):
print(args)
- run_process(WASM_OPT + args + [self.input_path('bysyncify.wast'), '--bysyncify', '-o', 'a.wasm'])
+ run_process(WASM_OPT + args + [self.input_path('bysyncify-sleep.wast'), '--bysyncify', '-o', 'a.wasm'])
+ run_process(WASM_OPT + args + [self.input_path('bysyncify-coroutine.wast'), '--bysyncify', '-o', 'b.wasm'])
print(' file size: %d' % os.path.getsize('a.wasm'))
run_process([NODEJS, self.input_path('bysyncify.js')])