// Copyright 2015 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Flags: --allow-natives-syntax --harmony-sharedarraybuffer (function TestFailsWithNonSharedArray() { var ab = new ArrayBuffer(16); var i8a = new Int8Array(ab); var i16a = new Int16Array(ab); var i32a = new Int32Array(ab); var ui8a = new Uint8Array(ab); var ui8ca = new Uint8ClampedArray(ab); var ui16a = new Uint16Array(ab); var ui32a = new Uint32Array(ab); var f32a = new Float32Array(ab); var f64a = new Float64Array(ab); [i8a, i16a, i32a, ui8a, ui8ca, ui16a, ui32a, f32a, f64a].forEach(function( ta) { assertThrows(function() { Atomics.futexWait(ta, 0, 0); }); assertThrows(function() { Atomics.futexWake(ta, 0, 1); }); assertThrows(function() { Atomics.futexWakeOrRequeue(ta, 0, 1, 0, 0); }); }); })(); (function TestFailsWithNonSharedInt32Array() { var sab = new SharedArrayBuffer(16); var i8a = new Int8Array(sab); var i16a = new Int16Array(sab); var ui8a = new Uint8Array(sab); var ui8ca = new Uint8ClampedArray(sab); var ui16a = new Uint16Array(sab); var ui32a = new Uint32Array(sab); var f32a = new Float32Array(sab); var f64a = new Float64Array(sab); [i8a, i16a, ui8a, ui8ca, ui16a, ui32a, f32a, f64a].forEach(function( ta) { assertThrows(function() { Atomics.futexWait(ta, 0, 0); }); assertThrows(function() { Atomics.futexWake(ta, 0, 1); }); assertThrows(function() { Atomics.futexWakeOrRequeue(ta, 0, 1, 0, 0); }); }); })(); (function TestInvalidIndex() { var sab = new SharedArrayBuffer(16); var i32a = new Int32Array(sab); // Valid indexes are 0-3. [-1, 4, 100].forEach(function(invalidIndex) { assertEquals(undefined, Atomics.futexWait(i32a, invalidIndex, 0)); assertEquals(undefined, Atomics.futexWake(i32a, invalidIndex, 0)); var validIndex = 0; assertEquals(undefined, Atomics.futexWakeOrRequeue(i32a, invalidIndex, 0, 0, validIndex)); assertEquals(undefined, Atomics.futexWakeOrRequeue(i32a, validIndex, 0, 0, invalidIndex)); }); i32a = new Int32Array(sab, 8); [-1, 2, 100].forEach(function(invalidIndex) { assertEquals(undefined, Atomics.futexWait(i32a, invalidIndex, 0)); assertEquals(undefined, Atomics.futexWake(i32a, invalidIndex, 0)); var validIndex = 0; assertEquals(undefined, Atomics.futexWakeOrRequeue(i32a, invalidIndex, 0, 0, validIndex)); assertEquals(undefined, Atomics.futexWakeOrRequeue(i32a, validIndex, 0, 0, invalidIndex)); }); })(); (function TestWaitTimeout() { var i32a = new Int32Array(new SharedArrayBuffer(16)); var waitMs = 100; var startTime = new Date(); assertEquals(Atomics.TIMEDOUT, Atomics.futexWait(i32a, 0, 0, waitMs)); var endTime = new Date(); assertTrue(endTime - startTime >= waitMs); })(); (function TestWaitNotEqual() { var sab = new SharedArrayBuffer(16); var i32a = new Int32Array(sab); assertEquals(Atomics.NOTEQUAL, Atomics.futexWait(i32a, 0, 42)); i32a = new Int32Array(sab, 8); i32a[0] = 1; assertEquals(Atomics.NOTEQUAL, Atomics.futexWait(i32a, 0, 0)); })(); (function TestWaitNegativeTimeout() { var i32a = new Int32Array(new SharedArrayBuffer(16)); assertEquals(Atomics.TIMEDOUT, Atomics.futexWait(i32a, 0, 0, -1)); assertEquals(Atomics.TIMEDOUT, Atomics.futexWait(i32a, 0, 0, -Infinity)); })(); //// WORKER ONLY TESTS if (this.Worker) { var TestWaitWithTimeout = function(timeout) { var sab = new SharedArrayBuffer(16); var i32a = new Int32Array(sab); var workerScript = `onmessage = function(msg) { var i32a = new Int32Array(msg.sab, msg.offset); var result = Atomics.futexWait(i32a, 0, 0, ${timeout}); postMessage(result); };`; var worker = new Worker(workerScript); worker.postMessage({sab: sab, offset: offset}, [sab]); // Spin until the worker is waiting on the futex. while (%AtomicsFutexNumWaitersForTesting(i32a, 0) != 1) {} Atomics.futexWake(i32a, 0, 1); assertEquals(Atomics.OK, worker.getMessage()); worker.terminate(); var worker2 = new Worker(workerScript); var offset = 8; var i32a2 = new Int32Array(sab, offset); worker2.postMessage({sab: sab, offset: offset}, [sab]); // Spin until the worker is waiting on the futex. while (%AtomicsFutexNumWaitersForTesting(i32a2, 0) != 1) {} Atomics.futexWake(i32a2, 0, 1); assertEquals(Atomics.OK, worker2.getMessage()); worker2.terminate(); // Futex should work when index and buffer views are different, but // the real address is the same. var worker3 = new Worker(workerScript); i32a2 = new Int32Array(sab, 4); worker3.postMessage({sab: sab, offset: 8}, [sab]); // Spin until the worker is waiting on the futex. while (%AtomicsFutexNumWaitersForTesting(i32a2, 1) != 1) {} Atomics.futexWake(i32a2, 1, 1); assertEquals(Atomics.OK, worker3.getMessage()); worker3.terminate(); }; // Test various infinite timeouts TestWaitWithTimeout(undefined); TestWaitWithTimeout(NaN); TestWaitWithTimeout(Infinity); (function TestWakeMulti() { var sab = new SharedArrayBuffer(20); var i32a = new Int32Array(sab); // SAB values: // i32a[id], where id in range [0, 3]: // 0 => Worker |id| is still waiting on the futex // 1 => Worker |id| is not waiting on futex, but has not be reaped by the // main thread. // 2 => Worker |id| has been reaped. // // i32a[4]: // always 0. Each worker is waiting on this index. var workerScript = `onmessage = function(msg) { var id = msg.id; var i32a = new Int32Array(msg.sab); // Wait on i32a[4] (should be zero). var result = Atomics.futexWait(i32a, 4, 0); // Set i32a[id] to 1 to notify the main thread which workers were // woken up. Atomics.store(i32a, id, 1); postMessage(result); };`; var id; var workers = []; for (id = 0; id < 4; id++) { workers[id] = new Worker(workerScript); workers[id].postMessage({sab: sab, id: id}, [sab]); } // Spin until all workers are waiting on the futex. while (%AtomicsFutexNumWaitersForTesting(i32a, 4) != 4) {} // Wake up three waiters. assertEquals(3, Atomics.futexWake(i32a, 4, 3)); var wokenCount = 0; var waitingId = 0 + 1 + 2 + 3; while (wokenCount < 3) { for (id = 0; id < 4; id++) { // Look for workers that have not yet been reaped. Set i32a[id] to 2 // when they've been processed so we don't look at them again. if (Atomics.compareExchange(i32a, id, 1, 2) == 1) { assertEquals(Atomics.OK, workers[id].getMessage()); workers[id].terminate(); waitingId -= id; wokenCount++; } } } assertEquals(3, wokenCount); assertEquals(0, Atomics.load(i32a, waitingId)); assertEquals(1, %AtomicsFutexNumWaitersForTesting(i32a, 4)); // Finally wake the last waiter. assertEquals(1, Atomics.futexWake(i32a, 4, 1)); assertEquals(Atomics.OK, workers[waitingId].getMessage()); workers[waitingId].terminate(); assertEquals(0, %AtomicsFutexNumWaitersForTesting(i32a, 4)); })(); (function TestWakeOrRequeue() { var sab = new SharedArrayBuffer(24); var i32a = new Int32Array(sab); // SAB values: // i32a[id], where id in range [0, 3]: // 0 => Worker |id| is still waiting on the futex // 1 => Worker |id| is not waiting on futex, but has not be reaped by the // main thread. // 2 => Worker |id| has been reaped. // // i32a[4]: // always 0. Each worker will initially wait on this index. // // i32a[5]: // always 0. Requeued workers will wait on this index. var workerScript = `onmessage = function(msg) { var id = msg.id; var i32a = new Int32Array(msg.sab); var result = Atomics.futexWait(i32a, 4, 0, Infinity); Atomics.store(i32a, id, 1); postMessage(result); };`; var workers = []; for (id = 0; id < 4; id++) { workers[id] = new Worker(workerScript); workers[id].postMessage({sab: sab, id: id}, [sab]); } // Spin until all workers are waiting on the futex. while (%AtomicsFutexNumWaitersForTesting(i32a, 4) != 4) {} var index1 = 4; var index2 = 5; // If futexWakeOrRequeue is called with the incorrect value, it shouldn't // wake any waiters. assertEquals(Atomics.NOTEQUAL, Atomics.futexWakeOrRequeue(i32a, index1, 1, 42, index2)); assertEquals(4, %AtomicsFutexNumWaitersForTesting(i32a, index1)); assertEquals(0, %AtomicsFutexNumWaitersForTesting(i32a, index2)); // Now wake with the correct value. assertEquals(1, Atomics.futexWakeOrRequeue(i32a, index1, 1, 0, index2)); // The workers that are still waiting should atomically be transferred to // the new index. assertEquals(3, %AtomicsFutexNumWaitersForTesting(i32a, index2)); // The woken worker may not have been scheduled yet. Look for which thread // has set its i32a value to 1. var wokenCount = 0; while (wokenCount < 1) { for (id = 0; id < 4; id++) { if (Atomics.compareExchange(i32a, id, 1, 2) == 1) { wokenCount++; } } } assertEquals(0, %AtomicsFutexNumWaitersForTesting(i32a, index1)); // Wake the remaining waiters. assertEquals(3, Atomics.futexWake(i32a, index2, 3)); // As above, wait until the workers have been scheduled. wokenCount = 0; while (wokenCount < 3) { for (id = 0; id < 4; id++) { if (Atomics.compareExchange(i32a, id, 1, 2) == 1) { wokenCount++; } } } assertEquals(0, %AtomicsFutexNumWaitersForTesting(i32a, index1)); assertEquals(0, %AtomicsFutexNumWaitersForTesting(i32a, index2)); for (id = 0; id < 4; ++id) { assertEquals(Atomics.OK, workers[id].getMessage()); } // Test futexWakeOrRequeue on offset typed array var offset = 16; sab = new SharedArrayBuffer(24); i32a = new Int32Array(sab); var i32a2 = new Int32Array(sab, offset); for (id = 0; id < 4; id++) { workers[id].postMessage({sab: sab, id: id}, [sab]); } while (%AtomicsFutexNumWaitersForTesting(i32a2, 0) != 4) { } index1 = 0; index2 = 1; assertEquals(4, %AtomicsFutexNumWaitersForTesting(i32a2, index1)); assertEquals(0, %AtomicsFutexNumWaitersForTesting(i32a2, index2)); assertEquals(2, Atomics.futexWakeOrRequeue(i32a2, index1, 2, 0, index2)); assertEquals(2, %AtomicsFutexNumWaitersForTesting(i32a2, index2)); assertEquals(0, %AtomicsFutexNumWaitersForTesting(i32a2, index1)); assertEquals(2, Atomics.futexWake(i32a2, index2, 2)); for (id = 0; id < 4; ++id) { assertEquals(Atomics.OK, workers[id].getMessage()); workers[id].terminate(); } })(); }