diff options
Diffstat (limited to 'test/unit/input/bysyncify.js')
-rw-r--r-- | test/unit/input/bysyncify.js | 352 |
1 files changed, 229 insertions, 123 deletions
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'); |