// 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 wait/wake 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

	// 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

	// 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/; // A generic message

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

// isLockFree(n) may return true only if there is an integer array
// on which atomic operations is allowed whose byte size is n,
// ie, it must return false for n=8.
//
// SpiderMonkey has isLockFree(1), isLockFree(2), isLockFree(4) on all
// supported platforms, only the last is guaranteed by the spec.

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, false];

function testIsLockFree() {
    // 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');
	assertEq(v, a);
    }

    // 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), false);
    assertEq(Atomics.isLockFree(9), false);
    assertEq(Atomics.isLockFree(10), false);
    assertEq(Atomics.isLockFree(11), false);
    assertEq(Atomics.isLockFree(12), false);
}

function testIsLockFree2() {
    assertEq(Atomics.isLockFree(0), false);
    assertEq(Atomics.isLockFree(0/-1), false);
    assertEq(Atomics.isLockFree(3.5), false);
    assertEq(Atomics.isLockFree(Number.NaN), false);  // NaN => +0
    assertEq(Atomics.isLockFree(Number.POSITIVE_INFINITY), false);
    assertEq(Atomics.isLockFree(Number.NEGATIVE_INFINITY), false);
    assertEq(Atomics.isLockFree(-4), false);
    assertEq(Atomics.isLockFree('4'), true);
    assertEq(Atomics.isLockFree('-4'), false);
    assertEq(Atomics.isLockFree('4.5'), true);
    assertEq(Atomics.isLockFree('5.5'), false);
    assertEq(Atomics.isLockFree(new Number(4)), true);
    assertEq(Atomics.isLockFree(new String('4')), true);
    assertEq(Atomics.isLockFree(new Boolean(true)), true);
    var thrown = false;
    try {
	Atomics.isLockFree(Symbol('1'));
    } catch (e) {
	thrown = e;
    }
    assertEq(thrown instanceof TypeError, true);
    assertEq(Atomics.isLockFree(true), true);
    assertEq(Atomics.isLockFree(false), false);
    assertEq(Atomics.isLockFree(undefined), false);
    assertEq(Atomics.isLockFree(null), false);
    assertEq(Atomics.isLockFree({toString: () => '4'}), true);
    assertEq(Atomics.isLockFree({valueOf: () => 4}), true);
    assertEq(Atomics.isLockFree({valueOf: () => 5}), false);
    assertEq(Atomics.isLockFree({password: "qumquat"}), 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 testWeirdIndices() {
    var a = new Int8Array(new SharedArrayBuffer(16));
    a[3] = 10;
    assertEq(Atomics.load(a, "0x03"), 10);
    assertEq(Atomics.load(a, {valueOf: () => 3}), 10);
}

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_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();
    testIsLockFree2();
    testWeirdIndices();
}

if (this.Atomics && this.SharedArrayBuffer)
    runTests();
