From 774fdbb2f691e367113169d2641810402b8806ad Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 15 Jul 2019 14:50:04 -0700 Subject: 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. --- test/unit/input/asyncify-coroutine.wast | 44 ++++ test/unit/input/asyncify-pure.txt | 17 ++ test/unit/input/asyncify-pure.wast | 59 ++++++ test/unit/input/asyncify-sleep.wast | 200 ++++++++++++++++++ test/unit/input/asyncify-stackOverflow.wast | 22 ++ test/unit/input/asyncify.js | 304 +++++++++++++++++++++++++++ test/unit/input/bysyncify-coroutine.wast | 44 ---- test/unit/input/bysyncify-pure.txt | 17 -- test/unit/input/bysyncify-pure.wast | 59 ------ test/unit/input/bysyncify-sleep.wast | 200 ------------------ test/unit/input/bysyncify-stackOverflow.wast | 22 -- test/unit/input/bysyncify.js | 304 --------------------------- test/unit/test_asyncify.py | 29 +++ test/unit/test_bysyncify.py | 29 --- 14 files changed, 675 insertions(+), 675 deletions(-) create mode 100644 test/unit/input/asyncify-coroutine.wast create mode 100644 test/unit/input/asyncify-pure.txt create mode 100644 test/unit/input/asyncify-pure.wast create mode 100644 test/unit/input/asyncify-sleep.wast create mode 100644 test/unit/input/asyncify-stackOverflow.wast create mode 100644 test/unit/input/asyncify.js delete mode 100644 test/unit/input/bysyncify-coroutine.wast delete mode 100644 test/unit/input/bysyncify-pure.txt delete mode 100644 test/unit/input/bysyncify-pure.wast delete mode 100644 test/unit/input/bysyncify-sleep.wast delete mode 100644 test/unit/input/bysyncify-stackOverflow.wast delete mode 100644 test/unit/input/bysyncify.js create mode 100644 test/unit/test_asyncify.py delete mode 100644 test/unit/test_bysyncify.py (limited to 'test/unit') diff --git a/test/unit/input/asyncify-coroutine.wast b/test/unit/input/asyncify-coroutine.wast new file mode 100644 index 000000000..ace97c6e6 --- /dev/null +++ b/test/unit/input/asyncify-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/asyncify-pure.txt b/test/unit/input/asyncify-pure.txt new file mode 100644 index 000000000..1f85db1f1 --- /dev/null +++ b/test/unit/input/asyncify-pure.txt @@ -0,0 +1,17 @@ +(i32.const 100) +(i32.const 10) +(i32.const 1) +(i32.const 20) +(i32.const 1000) +(i32.const 2000) +(i32.const 4000) +(i32.const 200) +(i32.const 300) +(i32.const 400) +(i32.const 1000) +(i32.const 3000) +(i32.const 4000) +(i32.const 30) +(i32.const 2) +(i32.const 40) +(i32.const 500) diff --git a/test/unit/input/asyncify-pure.wast b/test/unit/input/asyncify-pure.wast new file mode 100644 index 000000000..27c9da111 --- /dev/null +++ b/test/unit/input/asyncify-pure.wast @@ -0,0 +1,59 @@ +(module + (memory 1 1) + (import "spectest" "print" (func $print (param i32))) + (import "asyncify" "start_unwind" (func $asyncify_start_unwind (param i32))) + (import "asyncify" "stop_unwind" (func $asyncify_stop_unwind)) + (import "asyncify" "start_rewind" (func $asyncify_start_rewind (param i32))) + (import "asyncify" "stop_rewind" (func $asyncify_stop_rewind)) + (global $sleeping (mut i32) (i32.const 0)) + (start $runtime) + (func $main + (call $print (i32.const 10)) + (call $before) + (call $print (i32.const 20)) + (call $sleep) + (call $print (i32.const 30)) + (call $after) + (call $print (i32.const 40)) + ) + (func $before + (call $print (i32.const 1)) + ) + (func $sleep + (call $print (i32.const 1000)) + (if + (i32.eqz (global.get $sleeping)) + (block + (call $print (i32.const 2000)) + (global.set $sleeping (i32.const 1)) + (i32.store (i32.const 16) (i32.const 24)) + (i32.store (i32.const 20) (i32.const 1024)) + (call $asyncify_start_unwind (i32.const 16)) + ) + (block + (call $print (i32.const 3000)) + (call $asyncify_stop_rewind) + (global.set $sleeping (i32.const 0)) + ) + ) + (call $print (i32.const 4000)) + ) + (func $after + (call $print (i32.const 2)) + ) + (func $runtime + (call $print (i32.const 100)) + ;; call main the first time, let the stack unwind + (call $main) + (call $print (i32.const 200)) + (call $asyncify_stop_unwind) + (call $print (i32.const 300)) + ;; ...can do some async stuff around here... + ;; set the rewind in motion + (call $asyncify_start_rewind (i32.const 16)) + (call $print (i32.const 400)) + (call $main) + (call $print (i32.const 500)) + ) +) + diff --git a/test/unit/input/asyncify-sleep.wast b/test/unit/input/asyncify-sleep.wast new file mode 100644 index 000000000..91fb5a327 --- /dev/null +++ b/test/unit/input/asyncify-sleep.wast @@ -0,0 +1,200 @@ +(module + (memory 1 2) + (type $ii (func (param i32) (result i32))) + (import "env" "sleep" (func $sleep)) + (import "env" "tunnel" (func $tunnel (param $x i32) (result i32))) + (export "memory" (memory 0)) + (export "factorial-recursive" (func $factorial-recursive)) + (global $temp (mut i32) (i32.const 0)) + (table 10 funcref) + (elem (i32.const 5) $tablefunc) + (func "minimal" (result i32) + (call $sleep) + (i32.const 21) + ) + (func "repeat" (result i32) + ;; sleep twice, then return 42 + (call $sleep) + (call $sleep) + (i32.const 42) + ) + (func "local" (result i32) + (local $x i32) + (local.set $x (i32.load (i32.const 0))) ;; a zero that the optimizer won't see + (local.set $x + (i32.add (local.get $x) (i32.const 10)) ;; add 10 + ) + (call $sleep) + (local.get $x) + ) + (func "local2" (result i32) + (local $x i32) + (local.set $x (i32.load (i32.const 0))) ;; a zero that the optimizer won't see + (local.set $x + (i32.add (local.get $x) (i32.const 10)) ;; add 10 + ) + (call $sleep) + (local.set $x + (i32.add (local.get $x) (i32.const 12)) ;; add 12 more + ) + (local.get $x) + ) + (func "params" (param $x i32) (param $y i32) (result i32) + (local.set $x + (i32.add (local.get $x) (i32.const 17)) ;; add 10 + ) + (local.set $y + (i32.add (local.get $y) (i32.const 1)) ;; add 12 more + ) + (call $sleep) + (i32.add (local.get $x) (local.get $y)) + ) + (func $pre + (global.set $temp (i32.const 1)) + ) + (func $inner (param $x i32) + (if (i32.eqz (local.get $x)) (call $post)) + (if (local.get $x) (call $sleep)) + (if (i32.eqz (local.get $x)) (call $post)) + ) + (func $post + (global.set $temp + (i32.mul + (global.get $temp) + (i32.const 3) + ) + ) + ) + (func "deeper" (param $x i32) (result i32) + (call $pre) + (call $inner (local.get $x)) + (call $post) + (global.get $temp) + ) + (func $factorial-recursive (param $x i32) (result i32) + (if + (i32.eq + (local.get $x) + (i32.const 1) + ) + (return (i32.const 1)) + ) + (call $sleep) + (return + (i32.mul + (local.get $x) + (call $factorial-recursive + (i32.sub + (local.get $x) + (i32.const 1) + ) + ) + ) + ) + ) + (func "factorial-loop" (param $x i32) (result i32) + (local $i i32) + (local $ret i32) + (local.set $ret (i32.const 1)) + (local.set $i (i32.const 2)) + (loop $l + (if + (i32.gt_u + (local.get $i) + (local.get $x) + ) + (return (local.get $ret)) + ) + (local.set $ret + (i32.mul + (local.get $ret) + (local.get $i) + ) + ) + (call $sleep) + (local.set $i + (i32.add + (local.get $i) + (i32.const 1) + ) + ) + (br $l) + ) + ) + (func "end_tunnel" (param $x i32) (result i32) + (local.set $x + (i32.add (local.get $x) (i32.const 22)) + ) + (call $sleep) + (i32.add (local.get $x) (i32.const 5)) + ) + (func "do_tunnel" (param $x i32) (result i32) + (local.set $x + (i32.add (local.get $x) (i32.const 11)) + ) + (local.set $x + (call $tunnel (local.get $x)) ;; calls js which calls back into wasm for end_tunnel + ) + (call $sleep) + (i32.add (local.get $x) (i32.const 33)) + ) + (func $tablefunc (param $y i32) (result i32) + (local.set $y + (i32.add (local.get $y) (i32.const 10)) + ) + (call $sleep) + (i32.add (local.get $y) (i32.const 30)) + ) + (func "call_indirect" (param $x i32) (param $y i32) (result i32) + (local.set $x + (i32.add (local.get $x) (i32.const 1)) + ) + (call $sleep) + (local.set $x + (i32.add (local.get $x) (i32.const 3)) + ) + (local.set $y + (call_indirect (type $ii) (local.get $y) (local.get $x)) ;; call function pointer x + 4, which will be 5 + ) + (local.set $y + (i32.add (local.get $y) (i32.const 90)) + ) + (call $sleep) + (i32.add (local.get $y) (i32.const 300)) ;; total is 10+30+90+300=430 + y's original value + ) + (func "if_else" (param $x i32) (param $y i32) (result i32) + (if (i32.eq (local.get $x) (i32.const 1)) + (local.set $y + (i32.add (local.get $y) (i32.const 10)) + ) + (local.set $y + (i32.add (local.get $y) (i32.const 20)) + ) + ) + (if (i32.eq (local.get $x) (i32.const 1)) + (local.set $y + (i32.add (local.get $y) (i32.const 40)) + ) + (call $sleep) + ) + (if (i32.eq (local.get $x) (i32.const 1)) + (call $sleep) + (local.set $y + (i32.add (local.get $y) (i32.const 90)) + ) + ) + (if (i32.eq (local.get $x) (i32.const 1)) + (call $sleep) + (call $sleep) + ) + (local.set $y + (i32.add (local.get $y) (i32.const 160)) + ) + (call $sleep) + (local.set $y + (i32.add (local.get $y) (i32.const 250)) + ) + (local.get $y) + ) +) + diff --git a/test/unit/input/asyncify-stackOverflow.wast b/test/unit/input/asyncify-stackOverflow.wast new file mode 100644 index 000000000..a36a06b40 --- /dev/null +++ b/test/unit/input/asyncify-stackOverflow.wast @@ -0,0 +1,22 @@ +(module + (memory 1 2) + (import "env" "sleep" (func $sleep)) + (export "memory" (memory 0)) + (func "many_locals" (param $x i32) (result i32) + (local $y i32) + (local $z i32) + (local.set $y + (i32.add (local.get $x) (i32.const 10)) + ) + (local.set $z + (i32.add (local.get $y) (i32.const 20)) + ) + (call $sleep) + (select + (local.get $y) + (local.get $z) + (local.get $x) + ) + ) +) + 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'); + diff --git a/test/unit/input/bysyncify-coroutine.wast b/test/unit/input/bysyncify-coroutine.wast deleted file mode 100644 index ace97c6e6..000000000 --- a/test/unit/input/bysyncify-coroutine.wast +++ /dev/null @@ -1,44 +0,0 @@ -(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-pure.txt b/test/unit/input/bysyncify-pure.txt deleted file mode 100644 index 1f85db1f1..000000000 --- a/test/unit/input/bysyncify-pure.txt +++ /dev/null @@ -1,17 +0,0 @@ -(i32.const 100) -(i32.const 10) -(i32.const 1) -(i32.const 20) -(i32.const 1000) -(i32.const 2000) -(i32.const 4000) -(i32.const 200) -(i32.const 300) -(i32.const 400) -(i32.const 1000) -(i32.const 3000) -(i32.const 4000) -(i32.const 30) -(i32.const 2) -(i32.const 40) -(i32.const 500) diff --git a/test/unit/input/bysyncify-pure.wast b/test/unit/input/bysyncify-pure.wast deleted file mode 100644 index b79bfe2b0..000000000 --- a/test/unit/input/bysyncify-pure.wast +++ /dev/null @@ -1,59 +0,0 @@ -(module - (memory 1 1) - (import "spectest" "print" (func $print (param i32))) - (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)) - (start $runtime) - (func $main - (call $print (i32.const 10)) - (call $before) - (call $print (i32.const 20)) - (call $sleep) - (call $print (i32.const 30)) - (call $after) - (call $print (i32.const 40)) - ) - (func $before - (call $print (i32.const 1)) - ) - (func $sleep - (call $print (i32.const 1000)) - (if - (i32.eqz (global.get $sleeping)) - (block - (call $print (i32.const 2000)) - (global.set $sleeping (i32.const 1)) - (i32.store (i32.const 16) (i32.const 24)) - (i32.store (i32.const 20) (i32.const 1024)) - (call $bysyncify_start_unwind (i32.const 16)) - ) - (block - (call $print (i32.const 3000)) - (call $bysyncify_stop_rewind) - (global.set $sleeping (i32.const 0)) - ) - ) - (call $print (i32.const 4000)) - ) - (func $after - (call $print (i32.const 2)) - ) - (func $runtime - (call $print (i32.const 100)) - ;; call main the first time, let the stack unwind - (call $main) - (call $print (i32.const 200)) - (call $bysyncify_stop_unwind) - (call $print (i32.const 300)) - ;; ...can do some async stuff around here... - ;; set the rewind in motion - (call $bysyncify_start_rewind (i32.const 16)) - (call $print (i32.const 400)) - (call $main) - (call $print (i32.const 500)) - ) -) - diff --git a/test/unit/input/bysyncify-sleep.wast b/test/unit/input/bysyncify-sleep.wast deleted file mode 100644 index 91fb5a327..000000000 --- a/test/unit/input/bysyncify-sleep.wast +++ /dev/null @@ -1,200 +0,0 @@ -(module - (memory 1 2) - (type $ii (func (param i32) (result i32))) - (import "env" "sleep" (func $sleep)) - (import "env" "tunnel" (func $tunnel (param $x i32) (result i32))) - (export "memory" (memory 0)) - (export "factorial-recursive" (func $factorial-recursive)) - (global $temp (mut i32) (i32.const 0)) - (table 10 funcref) - (elem (i32.const 5) $tablefunc) - (func "minimal" (result i32) - (call $sleep) - (i32.const 21) - ) - (func "repeat" (result i32) - ;; sleep twice, then return 42 - (call $sleep) - (call $sleep) - (i32.const 42) - ) - (func "local" (result i32) - (local $x i32) - (local.set $x (i32.load (i32.const 0))) ;; a zero that the optimizer won't see - (local.set $x - (i32.add (local.get $x) (i32.const 10)) ;; add 10 - ) - (call $sleep) - (local.get $x) - ) - (func "local2" (result i32) - (local $x i32) - (local.set $x (i32.load (i32.const 0))) ;; a zero that the optimizer won't see - (local.set $x - (i32.add (local.get $x) (i32.const 10)) ;; add 10 - ) - (call $sleep) - (local.set $x - (i32.add (local.get $x) (i32.const 12)) ;; add 12 more - ) - (local.get $x) - ) - (func "params" (param $x i32) (param $y i32) (result i32) - (local.set $x - (i32.add (local.get $x) (i32.const 17)) ;; add 10 - ) - (local.set $y - (i32.add (local.get $y) (i32.const 1)) ;; add 12 more - ) - (call $sleep) - (i32.add (local.get $x) (local.get $y)) - ) - (func $pre - (global.set $temp (i32.const 1)) - ) - (func $inner (param $x i32) - (if (i32.eqz (local.get $x)) (call $post)) - (if (local.get $x) (call $sleep)) - (if (i32.eqz (local.get $x)) (call $post)) - ) - (func $post - (global.set $temp - (i32.mul - (global.get $temp) - (i32.const 3) - ) - ) - ) - (func "deeper" (param $x i32) (result i32) - (call $pre) - (call $inner (local.get $x)) - (call $post) - (global.get $temp) - ) - (func $factorial-recursive (param $x i32) (result i32) - (if - (i32.eq - (local.get $x) - (i32.const 1) - ) - (return (i32.const 1)) - ) - (call $sleep) - (return - (i32.mul - (local.get $x) - (call $factorial-recursive - (i32.sub - (local.get $x) - (i32.const 1) - ) - ) - ) - ) - ) - (func "factorial-loop" (param $x i32) (result i32) - (local $i i32) - (local $ret i32) - (local.set $ret (i32.const 1)) - (local.set $i (i32.const 2)) - (loop $l - (if - (i32.gt_u - (local.get $i) - (local.get $x) - ) - (return (local.get $ret)) - ) - (local.set $ret - (i32.mul - (local.get $ret) - (local.get $i) - ) - ) - (call $sleep) - (local.set $i - (i32.add - (local.get $i) - (i32.const 1) - ) - ) - (br $l) - ) - ) - (func "end_tunnel" (param $x i32) (result i32) - (local.set $x - (i32.add (local.get $x) (i32.const 22)) - ) - (call $sleep) - (i32.add (local.get $x) (i32.const 5)) - ) - (func "do_tunnel" (param $x i32) (result i32) - (local.set $x - (i32.add (local.get $x) (i32.const 11)) - ) - (local.set $x - (call $tunnel (local.get $x)) ;; calls js which calls back into wasm for end_tunnel - ) - (call $sleep) - (i32.add (local.get $x) (i32.const 33)) - ) - (func $tablefunc (param $y i32) (result i32) - (local.set $y - (i32.add (local.get $y) (i32.const 10)) - ) - (call $sleep) - (i32.add (local.get $y) (i32.const 30)) - ) - (func "call_indirect" (param $x i32) (param $y i32) (result i32) - (local.set $x - (i32.add (local.get $x) (i32.const 1)) - ) - (call $sleep) - (local.set $x - (i32.add (local.get $x) (i32.const 3)) - ) - (local.set $y - (call_indirect (type $ii) (local.get $y) (local.get $x)) ;; call function pointer x + 4, which will be 5 - ) - (local.set $y - (i32.add (local.get $y) (i32.const 90)) - ) - (call $sleep) - (i32.add (local.get $y) (i32.const 300)) ;; total is 10+30+90+300=430 + y's original value - ) - (func "if_else" (param $x i32) (param $y i32) (result i32) - (if (i32.eq (local.get $x) (i32.const 1)) - (local.set $y - (i32.add (local.get $y) (i32.const 10)) - ) - (local.set $y - (i32.add (local.get $y) (i32.const 20)) - ) - ) - (if (i32.eq (local.get $x) (i32.const 1)) - (local.set $y - (i32.add (local.get $y) (i32.const 40)) - ) - (call $sleep) - ) - (if (i32.eq (local.get $x) (i32.const 1)) - (call $sleep) - (local.set $y - (i32.add (local.get $y) (i32.const 90)) - ) - ) - (if (i32.eq (local.get $x) (i32.const 1)) - (call $sleep) - (call $sleep) - ) - (local.set $y - (i32.add (local.get $y) (i32.const 160)) - ) - (call $sleep) - (local.set $y - (i32.add (local.get $y) (i32.const 250)) - ) - (local.get $y) - ) -) - diff --git a/test/unit/input/bysyncify-stackOverflow.wast b/test/unit/input/bysyncify-stackOverflow.wast deleted file mode 100644 index a36a06b40..000000000 --- a/test/unit/input/bysyncify-stackOverflow.wast +++ /dev/null @@ -1,22 +0,0 @@ -(module - (memory 1 2) - (import "env" "sleep" (func $sleep)) - (export "memory" (memory 0)) - (func "many_locals" (param $x i32) (result i32) - (local $y i32) - (local $z i32) - (local.set $y - (i32.add (local.get $x) (i32.const 10)) - ) - (local.set $z - (i32.add (local.get $y) (i32.const 20)) - ) - (call $sleep) - (select - (local.get $y) - (local.get $z) - (local.get $x) - ) - ) -) - diff --git a/test/unit/input/bysyncify.js b/test/unit/input/bysyncify.js deleted file mode 100644 index be6d32903..000000000 --- a/test/unit/input/bysyncify.js +++ /dev/null @@ -1,304 +0,0 @@ - -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.bysyncify_start_unwind(DATA_ADDR); - } 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); - } - } - }); - - 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.bysyncify_stop_unwind(); - - 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(); - } - - 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(); - - 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.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(); - }; - } - - 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.bysyncify_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; - ['bysyncify_stop_unwind', 'bysyncify_start_rewind', 'bysyncify_stop_rewind', 'bysyncify_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'); - diff --git a/test/unit/test_asyncify.py b/test/unit/test_asyncify.py new file mode 100644 index 000000000..479d24cab --- /dev/null +++ b/test/unit/test_asyncify.py @@ -0,0 +1,29 @@ +import os + +from scripts.test.shared import WASM_OPT, WASM_DIS, WASM_SHELL, NODEJS, run_process +from utils import BinaryenTestCase + + +class AsyncifyTest(BinaryenTestCase): + def test_asyncify_js(self): + def test(args): + print(args) + run_process(WASM_OPT + args + [self.input_path('asyncify-sleep.wast'), '--asyncify', '-o', 'a.wasm']) + run_process(WASM_OPT + args + [self.input_path('asyncify-coroutine.wast'), '--asyncify', '-o', 'b.wasm']) + run_process(WASM_OPT + args + [self.input_path('asyncify-stackOverflow.wast'), '--asyncify', '-o', 'c.wasm']) + print(' file size: %d' % os.path.getsize('a.wasm')) + run_process([NODEJS, self.input_path('asyncify.js')]) + + test(['-g']) + test([]) + test(['-O1']) + test(['--optimize-level=1']) + test(['-O3']) + test(['-Os', '-g']) + + def test_asyncify_pure_wasm(self): + run_process(WASM_OPT + [self.input_path('asyncify-pure.wast'), '--asyncify', '-o', 'a.wasm']) + run_process(WASM_DIS + ['a.wasm', '-o', 'a.wast']) + output = run_process(WASM_SHELL + ['a.wast'], capture_output=True).stdout + with open(self.input_path('asyncify-pure.txt')) as f: + self.assertEqual(f.read(), output) diff --git a/test/unit/test_bysyncify.py b/test/unit/test_bysyncify.py deleted file mode 100644 index 407f3f7f9..000000000 --- a/test/unit/test_bysyncify.py +++ /dev/null @@ -1,29 +0,0 @@ -import os - -from scripts.test.shared import WASM_OPT, WASM_DIS, WASM_SHELL, NODEJS, run_process -from utils import BinaryenTestCase - - -class BysyncifyTest(BinaryenTestCase): - def test_bysyncify_js(self): - def test(args): - print(args) - 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']) - run_process(WASM_OPT + args + [self.input_path('bysyncify-stackOverflow.wast'), '--bysyncify', '-o', 'c.wasm']) - print(' file size: %d' % os.path.getsize('a.wasm')) - run_process([NODEJS, self.input_path('bysyncify.js')]) - - test(['-g']) - test([]) - test(['-O1']) - test(['--optimize-level=1']) - test(['-O3']) - test(['-Os', '-g']) - - def test_bysyncify_pure_wasm(self): - run_process(WASM_OPT + [self.input_path('bysyncify-pure.wast'), '--bysyncify', '-o', 'a.wasm']) - run_process(WASM_DIS + ['a.wasm', '-o', 'a.wast']) - output = run_process(WASM_SHELL + ['a.wast'], capture_output=True).stdout - with open(self.input_path('bysyncify-pure.txt')) as f: - self.assertEqual(f.read(), output) -- cgit v1.2.3