summaryrefslogtreecommitdiff
path: root/test/unit/input/asyncify.js
diff options
context:
space:
mode:
authorAlon Zakai <azakai@google.com>2019-07-15 14:50:04 -0700
committerGitHub <noreply@github.com>2019-07-15 14:50:04 -0700
commit774fdbb2f691e367113169d2641810402b8806ad (patch)
tree4847e46b5bb54b3a798817e4d5506c3ce0b60dd4 /test/unit/input/asyncify.js
parentcdab4ef130626958790cb2601209f14d192fa10a (diff)
downloadbinaryen-774fdbb2f691e367113169d2641810402b8806ad.tar.gz
binaryen-774fdbb2f691e367113169d2641810402b8806ad.tar.bz2
binaryen-774fdbb2f691e367113169d2641810402b8806ad.zip
Bysyncify => Asyncify (#2226)
After some discussion this seems like a less confusing name: what the pass does is "asyncify" code, after all. The one downside is the name overlaps with the old emscripten "Asyncify" utility, which we'll need to clarify in the docs there. This keeps the old --bysyncify flag around for now, which is helpful for avoiding temporary breakage on CI as we move the emscripten side as well.
Diffstat (limited to 'test/unit/input/asyncify.js')
-rw-r--r--test/unit/input/asyncify.js304
1 files changed, 304 insertions, 0 deletions
diff --git a/test/unit/input/asyncify.js b/test/unit/input/asyncify.js
new file mode 100644
index 000000000..0dcd87302
--- /dev/null
+++ b/test/unit/input/asyncify.js
@@ -0,0 +1,304 @@
+
+function assert(x, y) {
+ if (!x) throw (y || 'assertion failed') + '\n' + new Error().stack;
+}
+
+var fs = require('fs');
+
+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++;
+ sleeping = true;
+ // Unwinding.
+ // 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;
+ exports.asyncify_start_unwind(DATA_ADDR);
+ } else {
+ // We are called as part of a resume/rewind. Stop sleeping.
+ console.log('resume...');
+ exports.asyncify_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);
+ }
+ }
+ });
+
+ 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 runTest(name, expectedSleeps, expectedResult, params) {
+ params = params || [];
+
+ console.log('\n==== testing ' + name + ' ====');
+
+ sleeps = 0;
+
+ 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');
+ exports.asyncify_stop_unwind();
+
+ for (var i = 0; i < expectedSleeps - 1; i++) {
+ console.log('rewind, run until the next sleep');
+ exports.asyncify_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.asyncify_stop_unwind();
+ }
+
+ console.log('rewind and run til the end.');
+ exports.asyncify_start_rewind(DATA_ADDR);
+ result = exports[name]();
+ }
+
+ console.log('final result: ' + result);
+ assert(result == expectedResult, 'bad final result');
+ logMemory();
+
+ 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]);
+}
+
+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.asyncify_start_unwind(dataStart);
+ // (With C etc. coroutines we would also have
+ // a C stack to pause and resume here.)
+ };
+ this.stopUnwind = function() {
+ exports.asyncify_stop_unwind();
+ };
+ this.startRewind = function() {
+ exports.asyncify_start_rewind(dataStart);
+ exports[name]();
+ };
+ this.stopRewind = function() {
+ exports.asyncify_stop_rewind();
+ };
+ }
+
+ 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);
+ }
+ },
+ };
+
+ 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')
+}
+
+function stackOverflowAssertTests() {
+ console.log('\nstack overflow assertion tests\n\n');
+
+ // Get and compile the wasm.
+
+ var binary = fs.readFileSync('c.wasm');
+
+ var module = new WebAssembly.Module(binary);
+
+ var DATA_ADDR = 4;
+
+ var instance = new WebAssembly.Instance(module, {
+ env: {
+ sleep: function() {
+ console.log('sleep...');
+ exports.asyncify_start_unwind(DATA_ADDR);
+ view[DATA_ADDR >> 2] = DATA_ADDR + 8;
+ // The end of the stack will be reached as the stack is tiny.
+ view[DATA_ADDR + 4 >> 2] = view[DATA_ADDR >> 2] + 1;
+ }
+ }
+ });
+
+ var exports = instance.exports;
+ var view = new Int32Array(exports.memory.buffer);
+ exports.many_locals();
+ assert(view[DATA_ADDR >> 2] > view[DATA_ADDR + 4 >> 2], 'should have wrote past the end of the stack');
+ // All API calls should now fail, since we wrote past the end of the
+ // stack
+ var fails = 0;
+ ['asyncify_stop_unwind', 'asyncify_start_rewind', 'asyncify_stop_rewind', 'asyncify_start_unwind'].forEach(function(name) {
+ try {
+ exports[name](DATA_ADDR);
+ console.log('no fail on', name);
+ } catch (e) {
+ console.log('expected fail on', name);
+ fails++;
+ }
+ });
+ assert(fails == 4, 'all 4 should have failed');
+}
+
+// Main
+
+sleepTests();
+coroutineTests();
+stackOverflowAssertTests();
+
+console.log('\ntests completed successfully');
+