| // Basic functional tests for the Atomics primitives. |
| // |
| // These do not test atomicity, just that calling and coercions and |
| // indexing and exception behavior all work right. |
| // |
| // These do not test the futex operations. |
| |
| load(libdir + "asserts.js"); |
| |
| var DEBUG = false; // Set to true for useful printouts |
| |
| function dprint(...xs) { |
| if (!DEBUG) |
| return; |
| var s = ""; |
| for ( var x in xs ) |
| s += String(xs[x]); |
| print(s); |
| } |
| |
| // Clone a function so that we get reliable inlining of primitives with --ion-eager. |
| // For eg testMethod and testFunction that are polymorphic in the array a, |
| // the inliner gets confused and stops inlining after Int8 -- not what we want. |
| function CLONE(f) { |
| return this.eval("(" + f.toSource() + ")"); |
| } |
| |
| function testMethod(a, ...indices) { |
| dprint("Method: " + a.constructor.name); |
| var poison; |
| switch (a.BYTES_PER_ELEMENT) { |
| case 1: poison = 0x5A; break; |
| case 2: poison = 0x5A5A; break; |
| case 4: poison = 0x5A5A5A5A; break; |
| } |
| for ( var i=0 ; i < indices.length ; i++ ) { |
| var x = indices[i]; |
| if (x > 0) |
| a[x-1] = poison; |
| if (x < a.length-1) |
| a[x+1] = poison; |
| |
| // val = 0 |
| assertEq(Atomics.compareExchange(a, x, 0, 37), 0); |
| // val = 37 |
| assertEq(Atomics.compareExchange(a, x, 37, 5), 37); |
| // val = 5 |
| assertEq(Atomics.compareExchange(a, x, 7, 8), 5); // ie should fail |
| // val = 5 |
| assertEq(Atomics.compareExchange(a, x, 5, 9), 5); |
| // val = 9 |
| assertEq(Atomics.compareExchange(a, x, 5, 0), 9); // should also fail |
| |
| // val = 9 |
| assertEq(Atomics.exchange(a, x, 4), 9); |
| // val = 4 |
| assertEq(Atomics.exchange(a, x, 9), 4); |
| |
| // val = 9 |
| assertEq(Atomics.load(a, x), 9); |
| // val = 9 |
| assertEq(Atomics.store(a, x, 14), 14); // What about coercion? |
| // val = 14 |
| assertEq(Atomics.load(a, x), 14); |
| // val = 14 |
| Atomics.store(a, x, 0); |
| // val = 0 |
| |
| Atomics.fence(); |
| |
| // val = 0 |
| assertEq(Atomics.add(a, x, 3), 0); |
| // val = 3 |
| assertEq(Atomics.sub(a, x, 2), 3); |
| // val = 1 |
| assertEq(Atomics.or(a, x, 6), 1); |
| // val = 7 |
| assertEq(Atomics.and(a, x, 14), 7); |
| // val = 6 |
| assertEq(Atomics.xor(a, x, 5), 6); |
| // val = 3 |
| assertEq(Atomics.load(a, x), 3); |
| // val = 3 |
| Atomics.store(a, x, 0); |
| // val = 0 |
| |
| // Check adjacent elements were not affected |
| if (x > 0) { |
| assertEq(a[x-1], poison); |
| a[x-1] = 0; |
| } |
| if (x < a.length-1) { |
| assertEq(a[x+1], poison); |
| a[x+1] = 0; |
| } |
| } |
| } |
| |
| function testFunction(a, ...indices) { |
| dprint("Function: " + a.constructor.name); |
| var poison; |
| switch (a.BYTES_PER_ELEMENT) { |
| case 1: poison = 0x5A; break; |
| case 2: poison = 0x5A5A; break; |
| case 4: poison = 0x5A5A5A5A; break; |
| } |
| for ( var i=0 ; i < indices.length ; i++ ) { |
| var x = indices[i]; |
| if (x > 0) |
| a[x-1] = poison; |
| if (x < a.length-1) |
| a[x+1] = poison; |
| |
| // val = 0 |
| assertEq(gAtomics_compareExchange(a, x, 0, 37), 0); |
| // val = 37 |
| assertEq(gAtomics_compareExchange(a, x, 37, 5), 37); |
| // val = 5 |
| assertEq(gAtomics_compareExchange(a, x, 7, 8), 5); // ie should fail |
| // val = 5 |
| assertEq(gAtomics_compareExchange(a, x, 5, 9), 5); |
| // val = 9 |
| assertEq(gAtomics_compareExchange(a, x, 5, 0), 9); // should also fail |
| |
| // val = 9 |
| assertEq(gAtomics_exchange(a, x, 4), 9); |
| // val = 4 |
| assertEq(gAtomics_exchange(a, x, 9), 4); |
| |
| // val = 9 |
| assertEq(gAtomics_load(a, x), 9); |
| // val = 9 |
| assertEq(gAtomics_store(a, x, 14), 14); // What about coercion? |
| // val = 14 |
| assertEq(gAtomics_load(a, x), 14); |
| // val = 14 |
| gAtomics_store(a, x, 0); |
| // val = 0 |
| |
| gAtomics_fence(); |
| |
| // val = 0 |
| assertEq(gAtomics_add(a, x, 3), 0); |
| // val = 3 |
| assertEq(gAtomics_sub(a, x, 2), 3); |
| // val = 1 |
| assertEq(gAtomics_or(a, x, 6), 1); |
| // val = 7 |
| assertEq(gAtomics_and(a, x, 14), 7); |
| // val = 6 |
| assertEq(gAtomics_xor(a, x, 5), 6); |
| // val = 3 |
| assertEq(gAtomics_load(a, x), 3); |
| // val = 3 |
| gAtomics_store(a, x, 0); |
| // val = 0 |
| |
| // Check adjacent elements were not affected |
| if (x > 0) { |
| assertEq(a[x-1], poison); |
| a[x-1] = 0; |
| } |
| if (x < a.length-1) { |
| assertEq(a[x+1], poison); |
| a[x+1] = 0; |
| } |
| } |
| } |
| |
| function testTypeCAS(a) { |
| dprint("Type: " + a.constructor.name); |
| |
| var thrown = false; |
| try { |
| Atomics.compareExchange([0], 0, 0, 1); |
| } |
| catch (e) { |
| thrown = true; |
| assertEq(e instanceof TypeError, true); |
| } |
| assertEq(thrown, true); |
| |
| // All these variants should be OK |
| Atomics.compareExchange(a, 0, 0.7, 1.8); |
| Atomics.compareExchange(a, 0, "0", 1); |
| Atomics.compareExchange(a, 0, 0, "1"); |
| Atomics.compareExchange(a, 0, 0); |
| } |
| |
| function testTypeBinop(a, op) { |
| dprint("Type: " + a.constructor.name); |
| |
| var thrown = false; |
| try { |
| op([0], 0, 1); |
| } |
| catch (e) { |
| thrown = true; |
| assertEq(e instanceof TypeError, true); |
| } |
| assertEq(thrown, true); |
| |
| // These are all OK |
| op(a, 0, 0.7); |
| op(a, 0, "0"); |
| op(a, 0); |
| } |
| |
| var globlength = 0; // Will be set later |
| |
| function testRangeCAS(a) { |
| dprint("Range: " + a.constructor.name); |
| |
| var msg = /out-of-range index for atomic access/; |
| |
| assertErrorMessage(() => Atomics.compareExchange(a, -1, 0, 1), RangeError, msg); |
| assertEq(a[0], 0); |
| |
| assertErrorMessage(() => Atomics.compareExchange(a, "hi", 0, 1), RangeError, msg); |
| assertEq(a[0], 0); |
| |
| assertErrorMessage(() => Atomics.compareExchange(a, a.length + 5, 0, 1), RangeError, msg); |
| assertEq(a[0], 0); |
| |
| assertErrorMessage(() => Atomics.compareExchange(a, globlength, 0, 1), RangeError, msg); |
| assertEq(a[0], 0); |
| } |
| |
| // Ad-hoc tests for extreme and out-of-range values. |
| // None of these should throw |
| |
| function testInt8Extremes(a) { |
| dprint("Int8 extremes"); |
| |
| a[10] = 0; |
| a[11] = 0; |
| |
| Atomics.store(a, 10, 255); |
| assertEq(a[10], -1); |
| assertEq(Atomics.load(a, 10), -1); |
| |
| Atomics.add(a, 10, 255); // should coerce to -1 |
| assertEq(a[10], -2); |
| assertEq(Atomics.load(a, 10), -2); |
| |
| Atomics.add(a, 10, -1); |
| assertEq(a[10], -3); |
| assertEq(Atomics.load(a, 10), -3); |
| |
| Atomics.sub(a, 10, 255); // should coerce to -1 |
| assertEq(a[10], -2); |
| assertEq(Atomics.load(a, 10), -2); |
| |
| Atomics.sub(a, 10, 256); // should coerce to 0 |
| assertEq(a[10], -2); |
| assertEq(Atomics.load(a, 10), -2); |
| |
| Atomics.and(a, 10, -1); // Preserve all |
| assertEq(a[10], -2); |
| assertEq(Atomics.load(a, 10), -2); |
| |
| Atomics.and(a, 10, 256); // Preserve none |
| assertEq(a[10], 0); |
| assertEq(Atomics.load(a, 10), 0); |
| |
| Atomics.store(a, 10, 255); |
| assertEq(Atomics.exchange(a, 10, 0), -1); |
| |
| assertEq(a[11], 0); |
| } |
| |
| function testUint8Extremes(a) { |
| dprint("Uint8 extremes"); |
| |
| a[10] = 0; |
| a[11] = 0; |
| |
| Atomics.store(a, 10, 255); |
| assertEq(a[10], 255); |
| assertEq(Atomics.load(a, 10), 255); |
| |
| Atomics.add(a, 10, 255); |
| assertEq(a[10], 254); |
| assertEq(Atomics.load(a, 10), 254); |
| |
| Atomics.add(a, 10, -1); |
| assertEq(a[10], 253); |
| assertEq(Atomics.load(a, 10), 253); |
| |
| Atomics.sub(a, 10, 255); |
| assertEq(a[10], 254); |
| assertEq(Atomics.load(a, 10), 254); |
| |
| Atomics.and(a, 10, -1); // Preserve all |
| assertEq(a[10], 254); |
| assertEq(Atomics.load(a, 10), 254); |
| |
| Atomics.and(a, 10, 256); // Preserve none |
| assertEq(a[10], 0); |
| assertEq(Atomics.load(a, 10), 0); |
| |
| Atomics.store(a, 10, 255); |
| assertEq(Atomics.exchange(a, 10, 0), 255); |
| |
| assertEq(a[11], 0); |
| } |
| |
| function testInt16Extremes(a) { |
| dprint("Int16 extremes"); |
| |
| a[10] = 0; |
| a[11] = 0; |
| |
| Atomics.store(a, 10, 65535); |
| assertEq(a[10], -1); |
| assertEq(Atomics.load(a, 10), -1); |
| |
| Atomics.add(a, 10, 65535); // should coerce to -1 |
| assertEq(a[10], -2); |
| assertEq(Atomics.load(a, 10), -2); |
| |
| Atomics.add(a, 10, -1); |
| assertEq(a[10], -3); |
| assertEq(Atomics.load(a, 10), -3); |
| |
| Atomics.sub(a, 10, 65535); // should coerce to -1 |
| assertEq(a[10], -2); |
| assertEq(Atomics.load(a, 10), -2); |
| |
| Atomics.sub(a, 10, 65536); // should coerce to 0 |
| assertEq(a[10], -2); |
| assertEq(Atomics.load(a, 10), -2); |
| |
| Atomics.and(a, 10, -1); // Preserve all |
| assertEq(a[10], -2); |
| assertEq(Atomics.load(a, 10), -2); |
| |
| Atomics.and(a, 10, 65536); // Preserve none |
| assertEq(a[10], 0); |
| assertEq(Atomics.load(a, 10), 0); |
| |
| assertEq(a[11], 0); |
| } |
| |
| function testUint32(a) { |
| var k = 0; |
| for ( var i=0 ; i < 20 ; i++ ) { |
| a[i] = i+5; |
| k += a[i]; |
| } |
| |
| var sum = 0; |
| for ( var i=0 ; i < 20 ; i++ ) |
| sum += Atomics.add(a, i, 1); |
| |
| assertEq(sum, k); |
| } |
| |
| // This test is a reliable test of sign extension in the JIT where |
| // testInt8Extremes is not (because there may not be enough type |
| // information without a loop - see bug 1181062 for a description |
| // of the general problem). |
| |
| function exchangeLoop(ta) { |
| var sum = 0; |
| for ( var i=0 ; i < 100000 ; i++ ) |
| sum += Atomics.exchange(ta, i & 15, 255); |
| return sum; |
| } |
| |
| function adHocExchange() { |
| var a = new Int8Array(new SharedArrayBuffer(16)); |
| for ( var i=0 ; i < a.length ; i++ ) |
| a[i] = 255; |
| assertEq(exchangeLoop(a), -100000); |
| } |
| |
| var sizes = [ 1, 2, 3, 4, 5, 6, 7, 8, |
| 9, 10, 11, 12]; |
| var answers = [ true, true, false, true, false, false, false, {}, |
| false, false, false, false]; |
| |
| function testIsLockFree() { |
| var saved8 = "Invalid"; |
| |
| // This ought to defeat most compile-time resolution. |
| for ( var i=0 ; i < sizes.length ; i++ ) { |
| var v = Atomics.isLockFree(sizes[i]); |
| var a = answers[i]; |
| assertEq(typeof v, 'boolean'); |
| if (typeof a == 'boolean') |
| assertEq(v, a); |
| else |
| saved8 = v; |
| } |
| |
| // This ought to be optimizable. |
| assertEq(Atomics.isLockFree(1), true); |
| assertEq(Atomics.isLockFree(2), true); |
| assertEq(Atomics.isLockFree(3), false); |
| assertEq(Atomics.isLockFree(4), true); |
| assertEq(Atomics.isLockFree(5), false); |
| assertEq(Atomics.isLockFree(6), false); |
| assertEq(Atomics.isLockFree(7), false); |
| assertEq(Atomics.isLockFree(8), saved8); |
| assertEq(Atomics.isLockFree(9), false); |
| assertEq(Atomics.isLockFree(10), false); |
| assertEq(Atomics.isLockFree(11), false); |
| assertEq(Atomics.isLockFree(12), false); |
| } |
| |
| function testUint8Clamped(sab) { |
| var ta = new Uint8ClampedArray(sab); |
| var thrown = false; |
| try { |
| CLONE(testMethod)(ta, 0); |
| } |
| catch (e) { |
| thrown = true; |
| assertEq(e instanceof TypeError, true); |
| } |
| assertEq(thrown, true); |
| } |
| |
| function isLittleEndian() { |
| var xxx = new ArrayBuffer(2); |
| var xxa = new Int16Array(xxx); |
| var xxb = new Int8Array(xxx); |
| xxa[0] = 37; |
| var is_little = xxb[0] == 37; |
| return is_little; |
| } |
| |
| function runTests() { |
| var is_little = isLittleEndian(); |
| |
| // Currently the SharedArrayBuffer needs to be a multiple of 4K bytes in size. |
| var sab = new SharedArrayBuffer(4096); |
| |
| // Test that two arrays created on the same storage alias |
| var t1 = new Int8Array(sab); |
| var t2 = new Uint16Array(sab); |
| |
| assertEq(t1[0], 0); |
| assertEq(t2[0], 0); |
| t1[0] = 37; |
| if (is_little) |
| assertEq(t2[0], 37); |
| else |
| assertEq(t2[0], 37 << 16); |
| t1[0] = 0; |
| |
| // Test that invoking as Atomics.whatever() works, on correct arguments. |
| CLONE(testMethod)(new Int8Array(sab), 0, 42, 4095); |
| CLONE(testMethod)(new Uint8Array(sab), 0, 42, 4095); |
| CLONE(testMethod)(new Int16Array(sab), 0, 42, 2047); |
| CLONE(testMethod)(new Uint16Array(sab), 0, 42, 2047); |
| CLONE(testMethod)(new Int32Array(sab), 0, 42, 1023); |
| CLONE(testMethod)(new Uint32Array(sab), 0, 42, 1023); |
| |
| // Test that invoking as v = Atomics.whatever; v() works, on correct arguments. |
| gAtomics_compareExchange = Atomics.compareExchange; |
| gAtomics_exchange = Atomics.exchange; |
| gAtomics_load = Atomics.load; |
| gAtomics_store = Atomics.store; |
| gAtomics_fence = Atomics.fence; |
| gAtomics_add = Atomics.add; |
| gAtomics_sub = Atomics.sub; |
| gAtomics_and = Atomics.and; |
| gAtomics_or = Atomics.or; |
| gAtomics_xor = Atomics.xor; |
| |
| CLONE(testFunction)(new Int8Array(sab), 0, 42, 4095); |
| CLONE(testFunction)(new Uint8Array(sab), 0, 42, 4095); |
| CLONE(testFunction)(new Int16Array(sab), 0, 42, 2047); |
| CLONE(testFunction)(new Uint16Array(sab), 0, 42, 2047); |
| CLONE(testFunction)(new Int32Array(sab), 0, 42, 1023); |
| CLONE(testFunction)(new Uint32Array(sab), 0, 42, 1023); |
| |
| // Test various range and type conditions |
| var v8 = new Int8Array(sab); |
| var v32 = new Int32Array(sab); |
| |
| CLONE(testTypeCAS)(v8); |
| CLONE(testTypeCAS)(v32); |
| |
| CLONE(testTypeBinop)(v8, Atomics.add); |
| CLONE(testTypeBinop)(v8, Atomics.sub); |
| CLONE(testTypeBinop)(v8, Atomics.and); |
| CLONE(testTypeBinop)(v8, Atomics.or); |
| CLONE(testTypeBinop)(v8, Atomics.xor); |
| |
| CLONE(testTypeBinop)(v32, Atomics.add); |
| CLONE(testTypeBinop)(v32, Atomics.sub); |
| CLONE(testTypeBinop)(v32, Atomics.and); |
| CLONE(testTypeBinop)(v32, Atomics.or); |
| CLONE(testTypeBinop)(v32, Atomics.xor); |
| |
| // Test out-of-range references |
| globlength = v8.length + 5; |
| CLONE(testRangeCAS)(v8); |
| globlength = v32.length + 5; |
| CLONE(testRangeCAS)(v32); |
| |
| // Test extreme values |
| testInt8Extremes(new Int8Array(sab)); |
| testUint8Extremes(new Uint8Array(sab)); |
| testInt16Extremes(new Int16Array(sab)); |
| testUint32(new Uint32Array(sab)); |
| |
| // Test that Uint8ClampedArray is not accepted. |
| testUint8Clamped(sab); |
| |
| // Misc ad-hoc tests |
| adHocExchange(); |
| |
| // Misc |
| testIsLockFree(); |
| } |
| |
| if (this.Atomics && this.SharedArrayBuffer) |
| runTests(); |