if (!wasmThreadsSupported())
    quit(0);

const WASMPAGE = 65536;

// A shared memory should yield a SharedArrayBuffer of appropriate length

{
    let mem = new WebAssembly.Memory({initial: 2, maximum: 4, shared: true});
    assertEq(mem.buffer instanceof SharedArrayBuffer, true);
    assertEq(mem.buffer.byteLength, WASMPAGE*2);
}

// Ditto, when the memory was created by instantiation and exported

{
    let text = `(module
		 (memory (export "memory") 1 2 shared)
		 (func (export "l0") (result i32) (i32.load (i32.const 0))))`;
    let mod = new WebAssembly.Module(wasmTextToBinary(text));
    let ins = new WebAssembly.Instance(mod);
    let mem = ins.exports.memory;
    let buf = mem.buffer;
    assertEq(buf instanceof SharedArrayBuffer, true);
    assertEq(buf.byteLength, WASMPAGE);
}

// Shared memory requires a maximum size

{
    assertErrorMessage(() => new WebAssembly.Memory({initial: 2, shared: true}),
		       TypeError,
		       /'shared' is true but maximum is not specified/);
}

// Ditto, syntactically

{
    let text = `(module
		 (memory 1 shared)
		 (func (export "l0") (result i32) (i32.load (i32.const 0))))`;
    assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(text)),
		       WebAssembly.CompileError,
		       /maximum length required for shared memory/);
}

// Ditto, in the binary.  The flags field can be 0 (unshared, min only), 1
// (unshared, min and max), or 3 (shared, min and max), but not 2 (shared, min
// only).  So construct a module that has that, and make sure it's rejected.

{
    let bin = new Uint8Array([0x00, 0x61, 0x73, 0x6d,
			      0x01, 0x00, 0x00, 0x00,
			      0x05,                   // Memory
			      0x03,                   // Section size
			      0x01,                   // One memory
			      0x02,		      // Shared, min only (illegal)
			      0x01]);		      // Min
    assertErrorMessage(() => new WebAssembly.Module(bin),
		       WebAssembly.CompileError,
		       /maximum length required for shared memory/);
}

// Importing shared memory and being provided with shared should work

{
    let text = `(module
		 (memory (import "" "memory") 1 1 shared)
		 (func (export "id") (param i32) (result i32) (get_local 0)))`;
    let mod = new WebAssembly.Module(wasmTextToBinary(text));
    let mem = new WebAssembly.Memory({initial: 1, maximum: 1, shared: true});
    let ins = new WebAssembly.Instance(mod, {"": {memory: mem}});
    assertEq(ins.exports.id(0x12345678), 0x12345678);
}

// Importing shared memory but being provided with unshared should fail

{
    let text = `(module
		 (memory (import "" "memory") 1 1 shared)
		 (func (export "id") (param i32) (result i32) (get_local 0)))`;
    let mod = new WebAssembly.Module(wasmTextToBinary(text));
    let mem = new WebAssembly.Memory({initial: 1, maximum: 1});
    assertErrorMessage(() => new WebAssembly.Instance(mod, {"": {memory: mem}}),
		       WebAssembly.LinkError,
		       /unshared memory but shared required/);
}

// Importing unshared memory but being provided with shared should fail

{
    let text = `(module
		 (memory (import "" "memory") 1 1)
		 (func (export "id") (param i32) (result i32) (get_local 0)))`;
    let mod = new WebAssembly.Module(wasmTextToBinary(text));
    let mem = new WebAssembly.Memory({initial: 1, maximum: 1, shared: true});
    assertErrorMessage(() => new WebAssembly.Instance(mod, {"": {memory: mem}}),
		       WebAssembly.LinkError,
		       /shared memory but unshared required/);
}

// Importing shared memory and being provided with shared memory with
// incompatible parameters should fail

{
    let text = `(module
		 (memory (import "" "memory") 2 4 shared)
		 (func (export "id") (param i32) (result i32) (get_local 0)))`;
    let mod = new WebAssembly.Module(wasmTextToBinary(text));

    // some cases that are non-matching are allowed, eg, initial > declared min

    // initial < declared min
    let mem3 = new WebAssembly.Memory({initial: 1, maximum: 4, shared: true});
    assertErrorMessage(() => new WebAssembly.Instance(mod, {"": {memory: mem3}}),
		       WebAssembly.LinkError,
		       /imported Memory with incompatible size/);

    // initial > declared max
    let mem4 = new WebAssembly.Memory({initial: 5, maximum: 8, shared: true});
    assertErrorMessage(() => new WebAssembly.Instance(mod, {"": {memory: mem4}}),
		       WebAssembly.LinkError,
		       /imported Memory with incompatible size/);

    // max > declared max
    let mem5 = new WebAssembly.Memory({initial: 2, maximum: 8, shared: true});
    assertErrorMessage(() => new WebAssembly.Instance(mod, {"": {memory: mem5}}),
		       WebAssembly.LinkError,
		       /imported Memory with incompatible maximum size/);
}


// basic current_memory and grow_memory operation, with bounds checking near the
// valid/invalid boundary

{
    let text = `(module
		 (memory (export "memory") 2 4 shared)
		 (func (export "c") (result i32) current_memory)
		 (func (export "g") (result i32) (grow_memory (i32.const 1)))
		 (func (export "l") (param i32) (result i32) (i32.load (get_local 0)))
		 (func (export "s") (param i32) (param i32) (i32.store (get_local 0) (get_local 1))))`;
    let mod = new WebAssembly.Module(wasmTextToBinary(text));
    let ins = new WebAssembly.Instance(mod);
    let exp = ins.exports;
    let mem = exp.memory;

    let b1 = mem.buffer;
    assertEq(exp.c(), 2);
    assertEq(b1.byteLength, WASMPAGE*2);
    assertEq(mem.buffer === b1, true);   // current_memory does not affect buffer
    exp.s(WASMPAGE*2-4, 0x12345678)	 // access near end
    assertEq(exp.l(WASMPAGE*2-4), 0x12345678);
    assertErrorMessage(() => exp.l(WASMPAGE*2), // beyond current end (but below max)
		       WebAssembly.RuntimeError,
		       /index out of bounds/);
    assertEq(exp.g(), 2);
    assertEq(b1.byteLength, WASMPAGE*2); // growing does not affect existing buffer length
    let b2 = mem.buffer;
    assertEq(b1 !== b2, true);	         // growing produces a new buffer
    assertEq(b2.byteLength, WASMPAGE*3); // new buffer has appropriate length
    assertEq(exp.c(), 3);
    exp.s(WASMPAGE*3-4, 0x12344321);     // access near new end
    assertEq(exp.l(WASMPAGE*3-4), 0x12344321);
    assertErrorMessage(() => exp.l(WASMPAGE*3), // beyond current end (but below max)
		       WebAssembly.RuntimeError,
		       /index out of bounds/);
    assertEq(exp.g(), 3);
    assertEq(b2.byteLength, WASMPAGE*3); // growing does not affect existing buffer length
    let b3 = mem.buffer;
    assertEq(b2 !== b3, true);	         // growing produces a new buffer
    assertEq(b3.byteLength, WASMPAGE*4); // new buffer has appropriate length
    assertEq(exp.c(), 4);
    exp.s(WASMPAGE*4-4, 0x12121212);     // access near new end
    assertEq(exp.l(WASMPAGE*4-4), 0x12121212);
    assertErrorMessage(() => exp.l(WASMPAGE*4), // beyond current end (and beyond max)
		       WebAssembly.RuntimeError,
		       /index out of bounds/);
    assertEq(exp.g(), -1);
    assertEq(exp.c(), 4);                // failure to grow -> no change
    let b4 = mem.buffer;
    assertEq(b3 === b4, true);	         // failure to grow -> same ol' buffer
    assertEq(exp.g(), -1);               // we can fail repeatedly
}

// Test the grow() API with shared memory.  In the implementation this API
// shares almost all code with the wasm instruction, so don't bother going deep.

{
    let mem = new WebAssembly.Memory({initial: 2, maximum: 4, shared: true});
    let buf = mem.buffer;
    assertEq(mem.grow(1), 2);
    assertEq(buf.byteLength, WASMPAGE*2);
    assertEq(mem.grow(1), 3);
    assertErrorMessage(() => mem.grow(1), RangeError, /failed to grow memory/);
}

// Initializing shared memory with data

{
    let text = `(module
		 (memory (import "" "memory") 2 4 shared)
		 (data (i32.const 0) "abcdefghijklmnopqrstuvwxyz")
		 (func (export "l") (param i32) (result i32) (i32.load8_u (get_local 0))))`;
    let mod = new WebAssembly.Module(wasmTextToBinary(text));
    let mem = new WebAssembly.Memory({initial: 2, maximum: 4, shared: true});
    let ins = new WebAssembly.Instance(mod, {"": {memory: mem}});
    let exp = ins.exports;
    assertEq(exp.l(12), "a".charCodeAt(0) + 12);
}

