summaryrefslogtreecommitdiff
path: root/test/unit/input/bysyncify.js
blob: 97d7a0da5d413eb9ec7d4006b457905bad1a457e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155

function assert(x, y) {
  if (!x) throw (y || 'assertion failed') + '\n' + new Error().stack;
}

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;
      }
      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');

    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');
      assert(!result, 'bad first sleep result');
      logMemory();
    }

    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]);

// All done.
console.log('\ntests completed successfully');