summaryrefslogtreecommitdiffstats
path: root/js/src/tests/test262/built-ins/Atomics/waitAsync/shell.js
blob: 409797a047c00b41fd3b470b78c89675869ec40d (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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
// GENERATED, DO NOT EDIT
// file: asyncHelpers.js
// Copyright (C) 2022 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
description: |
    A collection of assertion and wrapper functions for testing asynchronous built-ins.
defines: [asyncTest]
---*/

function asyncTest(testFunc) {
  if (!Object.hasOwn(globalThis, "$DONE")) {
    throw new Test262Error("asyncTest called without async flag");
  }
  if (typeof testFunc !== "function") {
    $DONE(new Test262Error("asyncTest called with non-function argument"));
    return;
  }
  try {
    testFunc().then(
      function () {
        $DONE();
      },
      function (error) {
        $DONE(error);
      }
    );
  } catch (syncError) {
    $DONE(syncError);
  }
}

assert.throwsAsync = async function (expectedErrorConstructor, func, message) {
  var innerThenable;
  if (message === undefined) {
    message = "";
  } else {
    message += " ";
  }
  if (typeof func === "function") {
    try {
      innerThenable = func();
      if (
        innerThenable === null ||
        typeof innerThenable !== "object" ||
        typeof innerThenable.then !== "function"
      ) {
        message +=
          "Expected to obtain an inner promise that would reject with a" +
          expectedErrorConstructor.name +
          " but result was not a thenable";
        throw new Test262Error(message);
      }
    } catch (thrown) {
      message +=
        "Expected a " +
        expectedErrorConstructor.name +
        " to be thrown asynchronously but an exception was thrown synchronously while obtaining the inner promise";
      throw new Test262Error(message);
    }
  } else {
    message +=
      "assert.throwsAsync called with an argument that is not a function";
    throw new Test262Error(message);
  }

  try {
    return innerThenable.then(
      function () {
        message +=
          "Expected a " +
          expectedErrorConstructor.name +
          " to be thrown asynchronously but no exception was thrown at all";
        throw new Test262Error(message);
      },
      function (thrown) {
        var expectedName, actualName;
        if (typeof thrown !== "object" || thrown === null) {
          message += "Thrown value was not an object!";
          throw new Test262Error(message);
        } else if (thrown.constructor !== expectedErrorConstructor) {
          expectedName = expectedErrorConstructor.name;
          actualName = thrown.constructor.name;
          if (expectedName === actualName) {
            message +=
              "Expected a " +
              expectedName +
              " but got a different error constructor with the same name";
          } else {
            message +=
              "Expected a " + expectedName + " but got a " + actualName;
          }
          throw new Test262Error(message);
        }
      }
    );
  } catch (thrown) {
    if (typeof thrown !== "object" || thrown === null) {
      message +=
        "Expected a " +
        expectedErrorConstructor.name +
        " to be thrown asynchronously but innerThenable synchronously threw a value that was not an object ";
    } else {
      message +=
        "Expected a " +
        expectedErrorConstructor.name +
        " to be thrown asynchronously but a " +
        thrown.constructor.name +
        " was thrown synchronously";
    }
    throw new Test262Error(message);
  }
};

// file: atomicsHelper.js
// Copyright (C) 2017 Mozilla Corporation.  All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
description: >
    Collection of functions used to interact with Atomics.* operations across agent boundaries.
defines:
  - $262.agent.getReportAsync
  - $262.agent.getReport
  - $262.agent.safeBroadcastAsync
  - $262.agent.safeBroadcast
  - $262.agent.setTimeout
  - $262.agent.tryYield
  - $262.agent.trySleep
---*/

/**
 * @return {String} A report sent from an agent.
 */
{
  // This is only necessary because the original
  // $262.agent.getReport API was insufficient.
  //
  // All runtimes currently have their own
  // $262.agent.getReport which is wrong, so we
  // will pave over it with a corrected version.
  //
  // Binding $262.agent is necessary to prevent
  // breaking SpiderMonkey's $262.agent.getReport
  let getReport = $262.agent.getReport.bind($262.agent);

  $262.agent.getReport = function() {
    var r;
    while ((r = getReport()) == null) {
      $262.agent.sleep(1);
    }
    return r;
  };

  if (this.setTimeout === undefined) {
    (function(that) {
      that.setTimeout = function(callback, delay) {
        let p = Promise.resolve();
        let start = Date.now();
        let end = start + delay;
        function check() {
          if ((end - Date.now()) > 0) {
            p.then(check);
          }
          else {
            callback();
          }
        }
        p.then(check);
      }
    })(this);
  }

  $262.agent.setTimeout = setTimeout;

  $262.agent.getReportAsync = function() {
    return new Promise(function(resolve) {
      (function loop() {
        let result = getReport();
        if (!result) {
          setTimeout(loop, 1000);
        } else {
          resolve(result);
        }
      })();
    });
  };
}

/**
 *
 * Share a given Int32Array or BigInt64Array to all running agents. Ensure that the
 * provided TypedArray is a "shared typed array".
 *
 * NOTE: Migrating all tests to this API is necessary to prevent tests from hanging
 * indefinitely when a SAB is sent to a worker but the code in the worker attempts to
 * create a non-sharable TypedArray (something that is not Int32Array or BigInt64Array).
 * When that scenario occurs, an exception is thrown and the agent worker can no
 * longer communicate with any other threads that control the SAB. If the main
 * thread happens to be spinning in the $262.agent.waitUntil() while loop, it will never
 * meet its termination condition and the test will hang indefinitely.
 *
 * Because we've defined $262.agent.broadcast(SAB) in
 * https://github.com/tc39/test262/blob/HEAD/INTERPRETING.md, there are host implementations
 * that assume compatibility, which must be maintained.
 *
 *
 * $262.agent.safeBroadcast(TA) should not be included in
 * https://github.com/tc39/test262/blob/HEAD/INTERPRETING.md
 *
 *
 * @param {(Int32Array|BigInt64Array)} typedArray An Int32Array or BigInt64Array with a SharedArrayBuffer
 */
$262.agent.safeBroadcast = function(typedArray) {
  let Constructor = Object.getPrototypeOf(typedArray).constructor;
  let temp = new Constructor(
    new SharedArrayBuffer(Constructor.BYTES_PER_ELEMENT)
  );
  try {
    // This will never actually wait, but that's fine because we only
    // want to ensure that this typedArray CAN be waited on and is shareable.
    Atomics.wait(temp, 0, Constructor === Int32Array ? 1 : BigInt(1));
  } catch (error) {
    throw new Test262Error(`${Constructor.name} cannot be used as a shared typed array. (${error})`);
  }

  $262.agent.broadcast(typedArray.buffer);
};

$262.agent.safeBroadcastAsync = async function(ta, index, expected) {
  await $262.agent.broadcast(ta.buffer);
  await $262.agent.waitUntil(ta, index, expected);
  await $262.agent.tryYield();
  return await Atomics.load(ta, index);
};


/**
 * With a given Int32Array or BigInt64Array, wait until the expected number of agents have
 * reported themselves by calling:
 *
 *    Atomics.add(typedArray, index, 1);
 *
 * @param {(Int32Array|BigInt64Array)} typedArray An Int32Array or BigInt64Array with a SharedArrayBuffer
 * @param {number} index    The index of which all agents will report.
 * @param {number} expected The number of agents that are expected to report as active.
 */
$262.agent.waitUntil = function(typedArray, index, expected) {

  var agents = 0;
  while ((agents = Atomics.load(typedArray, index)) !== expected) {
    /* nothing */
  }
  assert.sameValue(agents, expected, "Reporting number of 'agents' equals the value of 'expected'");
};

/**
 * Timeout values used throughout the Atomics tests. All timeouts are specified in milliseconds.
 *
 * @property {number} yield Used for `$262.agent.tryYield`. Must not be used in other functions.
 * @property {number} small Used when agents will always timeout and `Atomics.wake` is not part
 *                          of the test semantics. Must be larger than `$262.agent.timeouts.yield`.
 * @property {number} long  Used when some agents may timeout and `Atomics.wake` is called on some
 *                          agents. The agents are required to wait and this needs to be observable
 *                          by the main thread.
 * @property {number} huge  Used when `Atomics.wake` is called on all waiting agents. The waiting
 *                          must not timeout. The agents are required to wait and this needs to be
 *                          observable by the main thread. All waiting agents must be woken by the
 *                          main thread.
 *
 * Usage for `$262.agent.timeouts.small`:
 *   const WAIT_INDEX = 0;
 *   const RUNNING = 1;
 *   const TIMEOUT = $262.agent.timeouts.small;
 *   const i32a = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 2));
 *
 *   $262.agent.start(`
 *     $262.agent.receiveBroadcast(function(sab) {
 *       const i32a = new Int32Array(sab);
 *       Atomics.add(i32a, ${RUNNING}, 1);
 *
 *       $262.agent.report(Atomics.wait(i32a, ${WAIT_INDEX}, 0, ${TIMEOUT}));
 *
 *       $262.agent.leaving();
 *     });
 *   `);
 *   $262.agent.safeBroadcast(i32a.buffer);
 *
 *   // Wait until the agent was started and then try to yield control to increase
 *   // the likelihood the agent has called `Atomics.wait` and is now waiting.
 *   $262.agent.waitUntil(i32a, RUNNING, 1);
 *   $262.agent.tryYield();
 *
 *   // The agent is expected to time out.
 *   assert.sameValue($262.agent.getReport(), "timed-out");
 *
 *
 * Usage for `$262.agent.timeouts.long`:
 *   const WAIT_INDEX = 0;
 *   const RUNNING = 1;
 *   const NUMAGENT = 2;
 *   const TIMEOUT = $262.agent.timeouts.long;
 *   const i32a = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 2));
 *
 *   for (let i = 0; i < NUMAGENT; i++) {
 *     $262.agent.start(`
 *       $262.agent.receiveBroadcast(function(sab) {
 *         const i32a = new Int32Array(sab);
 *         Atomics.add(i32a, ${RUNNING}, 1);
 *
 *         $262.agent.report(Atomics.wait(i32a, ${WAIT_INDEX}, 0, ${TIMEOUT}));
 *
 *         $262.agent.leaving();
 *       });
 *     `);
 *   }
 *   $262.agent.safeBroadcast(i32a.buffer);
 *
 *   // Wait until the agents were started and then try to yield control to increase
 *   // the likelihood the agents have called `Atomics.wait` and are now waiting.
 *   $262.agent.waitUntil(i32a, RUNNING, NUMAGENT);
 *   $262.agent.tryYield();
 *
 *   // Wake exactly one agent.
 *   assert.sameValue(Atomics.wake(i32a, WAIT_INDEX, 1), 1);
 *
 *   // When it doesn't matter how many agents were woken at once, a while loop
 *   // can be used to make the test more resilient against intermittent failures
 *   // in case even though `tryYield` was called, the agents haven't started to
 *   // wait.
 *   //
 *   // // Repeat until exactly one agent was woken.
 *   // var woken = 0;
 *   // while ((woken = Atomics.wake(i32a, WAIT_INDEX, 1)) !== 0) ;
 *   // assert.sameValue(woken, 1);
 *
 *   // One agent was woken and the other one timed out.
 *   const reports = [$262.agent.getReport(), $262.agent.getReport()];
 *   assert(reports.includes("ok"));
 *   assert(reports.includes("timed-out"));
 *
 *
 * Usage for `$262.agent.timeouts.huge`:
 *   const WAIT_INDEX = 0;
 *   const RUNNING = 1;
 *   const NUMAGENT = 2;
 *   const TIMEOUT = $262.agent.timeouts.huge;
 *   const i32a = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 2));
 *
 *   for (let i = 0; i < NUMAGENT; i++) {
 *     $262.agent.start(`
 *       $262.agent.receiveBroadcast(function(sab) {
 *         const i32a = new Int32Array(sab);
 *         Atomics.add(i32a, ${RUNNING}, 1);
 *
 *         $262.agent.report(Atomics.wait(i32a, ${WAIT_INDEX}, 0, ${TIMEOUT}));
 *
 *         $262.agent.leaving();
 *       });
 *     `);
 *   }
 *   $262.agent.safeBroadcast(i32a.buffer);
 *
 *   // Wait until the agents were started and then try to yield control to increase
 *   // the likelihood the agents have called `Atomics.wait` and are now waiting.
 *   $262.agent.waitUntil(i32a, RUNNING, NUMAGENT);
 *   $262.agent.tryYield();
 *
 *   // Wake all agents.
 *   assert.sameValue(Atomics.wake(i32a, WAIT_INDEX), NUMAGENT);
 *
 *   // When it doesn't matter how many agents were woken at once, a while loop
 *   // can be used to make the test more resilient against intermittent failures
 *   // in case even though `tryYield` was called, the agents haven't started to
 *   // wait.
 *   //
 *   // // Repeat until all agents were woken.
 *   // for (var wokenCount = 0; wokenCount < NUMAGENT; ) {
 *   //   var woken = 0;
 *   //   while ((woken = Atomics.wake(i32a, WAIT_INDEX)) !== 0) ;
 *   //   // Maybe perform an action on the woken agents here.
 *   //   wokenCount += woken;
 *   // }
 *
 *   // All agents were woken and none timeout.
 *   for (var i = 0; i < NUMAGENT; i++) {
 *     assert($262.agent.getReport(), "ok");
 *   }
 */
$262.agent.timeouts = {
  yield: 100,
  small: 200,
  long: 1000,
  huge: 10000,
};

/**
 * Try to yield control to the agent threads.
 *
 * Usage:
 *   const VALUE = 0;
 *   const RUNNING = 1;
 *   const i32a = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 2));
 *
 *   $262.agent.start(`
 *     $262.agent.receiveBroadcast(function(sab) {
 *       const i32a = new Int32Array(sab);
 *       Atomics.add(i32a, ${RUNNING}, 1);
 *
 *       Atomics.store(i32a, ${VALUE}, 1);
 *
 *       $262.agent.leaving();
 *     });
 *   `);
 *   $262.agent.safeBroadcast(i32a.buffer);
 *
 *   // Wait until agent was started and then try to yield control.
 *   $262.agent.waitUntil(i32a, RUNNING, 1);
 *   $262.agent.tryYield();
 *
 *   // Note: This result is not guaranteed, but should hold in practice most of the time.
 *   assert.sameValue(Atomics.load(i32a, VALUE), 1);
 *
 * The default implementation simply waits for `$262.agent.timeouts.yield` milliseconds.
 */
$262.agent.tryYield = function() {
  $262.agent.sleep($262.agent.timeouts.yield);
};

/**
 * Try to sleep the current agent for the given amount of milliseconds. It is acceptable,
 * but not encouraged, to ignore this sleep request and directly continue execution.
 *
 * The default implementation calls `$262.agent.sleep(ms)`.
 *
 * @param {number} ms Time to sleep in milliseconds.
 */
$262.agent.trySleep = function(ms) {
  $262.agent.sleep(ms);
};

// file: detachArrayBuffer.js
// Copyright (C) 2016 the V8 project authors.  All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
description: |
    A function used in the process of asserting correctness of TypedArray objects.

    $262.detachArrayBuffer is defined by a host.
defines: [$DETACHBUFFER]
---*/

function $DETACHBUFFER(buffer) {
  if (!$262 || typeof $262.detachArrayBuffer !== "function") {
    throw new Test262Error("No method available to detach an ArrayBuffer");
  }
  $262.detachArrayBuffer(buffer);
}