// |jit-test| skip-if: !largeArrayBufferSupported(); allow-oom

var pagesz = PageSizeInBytes;
var pages_limit = MaxPagesIn32BitMemory;

// 40000 is well above 2GB but not anything that might run into boundary
// conditions near 4GB.
var pages_vanilla = 40000;

for ( let [pages,maxpages] of [[pages_vanilla, pages_vanilla+100],
                               [pages_limit - 3, pages_limit],
                               [pages_limit, pages_limit]] ) {
    assertEq(pages == maxpages || maxpages - pages >= 3, true)
    let ins = wasmEvalText(`
(module
  (memory (export "mem") ${pages} ${maxpages})

  (data (i32.const ${(pages-5)*pagesz}) "yabbadabbado")
  (data $flintstone "yabbadabbado")

  (func (export "get_constaddr") (result i32)
    (i32.load (i32.const ${pages*pagesz-4})))

  (func (export "get_varaddr") (param $p i32) (result i32)
    (i32.load (local.get $p)))

  (func (export "set_constaddr") (param $v i32)
    (i32.store (i32.const ${pages*pagesz-8}) (local.get $v)))

  (func (export "set_varaddr") (param $p i32) (param $v i32)
    (i32.store (local.get $p) (local.get $v)))

  (func (export "get_constaddr_large_offset") (result i32)
    (i32.load offset=${(pages-100)*pagesz-4} (i32.const ${pagesz*100})))

  (func (export "get_varaddr_large_offset") (param $p i32) (result i32)
    (i32.load offset=${(pages-100)*pagesz-4} (local.get $p)))

  (func (export "get_constaddr_small_offset") (result i32)
    (i32.load offset=${pagesz-4} (i32.const ${(pages-1)*pagesz})))

  (func (export "get_varaddr_small_offset") (param $p i32) (result i32)
    (i32.load offset=${pagesz-4} (local.get $p)))

  (func (export "set_constaddr_large_offset") (param $v i32)
    (i32.store offset=${(pages-100)*pagesz-16} (i32.const ${pagesz*100}) (local.get $v)))

  (func (export "set_varaddr_large_offset") (param $p i32) (param $v i32)
    (i32.store offset=${(pages-100)*pagesz-20} (local.get $p) (local.get $v)))

  (func (export "set_constaddr_small_offset") (param $v i32)
    (i32.store offset=${pagesz-24} (i32.const ${(pages-1)*pagesz}) (local.get $v)))

  (func (export "set_varaddr_small_offset") (param $p i32) (param $v i32)
    (i32.store offset=${pagesz-28} (local.get $p) (local.get $v)))

  (func (export "copy") (param $dest i32) (param $src i32)
    (memory.copy (local.get $dest) (local.get $src) (i32.const 12)))

  (func (export "init") (param $dest i32)
    (memory.init $flintstone (local.get $dest) (i32.const 0) (i32.const 12)))

  (func (export "fill") (param $dest i32) (param $val i32) (param $len i32)
    (memory.fill (local.get 0) (local.get 1) (local.get 2)))

  (func (export "grow1") (result i32)
    (memory.grow (i32.const 1)))
)`);

    let buf = new Int32Array(ins.exports.mem.buffer);

    let checkFlintstoneAt = function (addr) {
        assertEq(buf[addr/4], 0x62626179)   // "yabb", little-endian
        assertEq(buf[addr/4+1], 0x62616461) // "adab", ditto
        assertEq(buf[addr/4+2], 0x6f646162) // "bado"
    }

    buf[pages*pagesz/4-1] = 0xdeadbeef;
    assertEq(ins.exports.get_constaddr(), 0xdeadbeef|0);
    assertEq(ins.exports.get_varaddr(pages*pagesz-4), 0xdeadbeef|0);

    assertEq(ins.exports.get_constaddr_large_offset(), 0xdeadbeef|0);
    assertEq(ins.exports.get_varaddr_large_offset(pagesz*100), 0xdeadbeef|0);

    assertEq(ins.exports.get_constaddr_small_offset(), 0xdeadbeef|0);
    assertEq(ins.exports.get_varaddr_small_offset((pages-1)*pagesz), 0xdeadbeef|0);

    ins.exports.set_constaddr(0xcafebab0);
    assertEq(buf[pages*pagesz/4-2], 0xcafebab0|0);

    ins.exports.set_varaddr(pages*pagesz-12, 0xcafebab1);
    assertEq(buf[pages*pagesz/4-3], 0xcafebab1|0);

    ins.exports.set_constaddr_large_offset(0xcafebab2);
    assertEq(buf[pages*pagesz/4-4], 0xcafebab2|0);

    ins.exports.set_varaddr_large_offset(pagesz*100, 0xcafebab3);
    assertEq(buf[pages*pagesz/4-5], 0xcafebab3|0);

    ins.exports.set_constaddr_small_offset(0xcafebab4);
    assertEq(buf[pages*pagesz/4-6], 0xcafebab4|0);

    ins.exports.set_varaddr_small_offset((pages-1)*pagesz, 0xcafebab5);
    assertEq(buf[pages*pagesz/4-7], 0xcafebab5|0);

    if (pages*pagesz < 0x1_0000_0000) {
        assertErrorMessage(() => ins.exports.get_varaddr(pages*pagesz),
                           WebAssembly.RuntimeError,
                           /index out of bounds/);
    }

    assertErrorMessage(() => ins.exports.get_varaddr_large_offset(pagesz*100+4),
                       WebAssembly.RuntimeError,
                       /index out of bounds/);

    assertErrorMessage(() => ins.exports.get_varaddr_small_offset((pages-1)*pagesz+4),
                       WebAssembly.RuntimeError,
                       /index out of bounds/);

    ins.exports.set_varaddr(pages*pagesz-4, 0); // Should work
    if (pages*pagesz < 0x1_0000_0000) {
        assertErrorMessage(() => ins.exports.set_varaddr(pages*pagesz, 0),
                           WebAssembly.RuntimeError,
                           /index out of bounds/);
    }

    ins.exports.set_varaddr_large_offset(pagesz*100+16, 0); // Should work
    assertErrorMessage(() => ins.exports.set_varaddr_large_offset(pagesz*100+20, 0),
                       WebAssembly.RuntimeError,
                       /index out of bounds/);

    ins.exports.set_varaddr_small_offset((pages-1)*pagesz+24); // Should work
    assertErrorMessage(() => ins.exports.set_varaddr_small_offset((pages-1)*pagesz+28, 0),
                       WebAssembly.RuntimeError,
                       /index out of bounds/);

    // Active init
    checkFlintstoneAt((pages-5)*pagesz);

    // memory.init
    ins.exports.init((pages-6)*pagesz);
    checkFlintstoneAt((pages-6)*pagesz);

    ins.exports.init(pages*pagesz-12); // Should work
    // Dest goes OOB
    assertErrorMessage(() => ins.exports.init(pages*pagesz-6),
                       WebAssembly.RuntimeError,
                       /index out of bounds/);

    // memory.copy

    // Dest and src are in bounds
    ins.exports.copy((pages-10)*pagesz, (pages-5)*pagesz);
    checkFlintstoneAt((pages-10)*pagesz);

    // Dest goes OOB
    assertErrorMessage(() => ins.exports.copy((pages)*pagesz-6, (pages-5)*pagesz, 12),
                       WebAssembly.RuntimeError,
                       /index out of bounds/);

    ins.exports.copy((pages)*pagesz-12, (pages-1)*pagesz); // Should work
    // Src goes OOB
    assertErrorMessage(() => ins.exports.copy((pages)*pagesz-12, (pages)*pagesz-6),
                       WebAssembly.RuntimeError,
                       /index out of bounds/);


    // memory.fill
    let lastpg = (pages-1)*pagesz;
    ins.exports.fill(lastpg, 0x37, pagesz);
    for ( let i=0; i < pagesz/4; i++ )
        assertEq(buf[lastpg/4+i], 0x37373737);

    assertErrorMessage(() => ins.exports.fill(lastpg, 0x42, pagesz+1),
                       WebAssembly.RuntimeError,
                       /index out of bounds/);

    done:
    if (pages < maxpages) {
        let res = 0;

        res = ins.exports.grow1();
        if (res == -1) break done;
        assertEq(res, pages);

        res = ins.exports.grow1();
        if (res == -1) break done;
        assertEq(res, pages+1);

        res = ins.exports.grow1();
        if (res == -1) break done;
        assertEq(res, pages+2);

        assertEq(ins.exports.get_varaddr((pages+2)*pagesz), 0);

        let i = 0;
        while (ins.exports.grow1() != -1) {
            i++;
        }
        // We can't assert equality because we might OOM before we get to the
        // max, but we can assert we did not go beyond that.
        assertEq(i <= maxpages-pages-3, true);
    }
}

// Very large offsets should be allowed (but may of course be OOB).  Observe
// that offset=0xffffffff is accepted by the validator even though this access
// could never succeed.
{
    let ins = wasmEvalText(`
(module
  (memory (export "mem") 1)
  (func (export "get1") (param $p i32) (result i32)
    (i32.load offset=0x80000000 (local.get $p)))
  (func (export "get2") (param $p i32) (result i32)
    (i32.load offset=0xfffffffc (local.get $p)))
  (func (export "get3") (param $p i32) (result i32)
    (i32.load offset=0xffffffff (local.get $p))))
`);
    assertErrorMessage(() => ins.exports.get1(0),
                       WebAssembly.RuntimeError,
                       /index out of bounds/);
    assertErrorMessage(() => ins.exports.get2(0),
                       WebAssembly.RuntimeError,
                       /index out of bounds/);
    assertErrorMessage(() => ins.exports.get3(0),
                       WebAssembly.RuntimeError,
                       /index out of bounds/);
}

{
    let ins = wasmEvalText(`
(module
  (memory (export "mem") 32768)
  (func (export "get1") (param $p i32) (result i32)
    (i32.load8_s offset=0x7fffffff (local.get $p))))
`);
    assertEq(ins.exports.get1(0), 0);
    assertErrorMessage(() => ins.exports.get1(1),
                       WebAssembly.RuntimeError,
                       /index out of bounds/);
    assertErrorMessage(() => ins.exports.get1(-1),
                       WebAssembly.RuntimeError,
                       /index out of bounds/);
}

{
    let ins = wasmEvalText(`
(module
  (memory (export "mem") 32769)
  (func (export "get1") (param $p i32) (result i32)
    (i32.load8_s offset=0x80000000 (local.get $p))))
`);
    assertEq(ins.exports.get1(0), 0);
    assertEq(ins.exports.get1(65535), 0);
    assertErrorMessage(() => ins.exports.get1(65536),
                       WebAssembly.RuntimeError,
                       /index out of bounds/);
}

{
    let ins = wasmEvalText(`
(module
  (memory (export "mem") ${pages_limit})
  (func (export "get1") (param $p i32) (result i32)
    (i32.load8_s offset=${pages_limit*pagesz-1} (local.get $p))))
`);
    assertEq(ins.exports.get1(0), 0);
    assertErrorMessage(() => ins.exports.get1(1),
                       WebAssembly.RuntimeError,
                       /index out of bounds/);
    assertErrorMessage(() => ins.exports.get1(-1),
                       WebAssembly.RuntimeError,
                       /index out of bounds/);
}

// Fail to grow at the declared max
// Import and export

{
    let ins = wasmEvalText(`
(module
  (memory (export "mem") ${pages_limit} ${pages_limit})

  (func (export "set_it") (param $addr i32) (param $val i32)
    (i32.store (local.get $addr) (local.get $val)))

  (func (export "grow1") (result i32)
    (memory.grow (i32.const 1)))
)`);

    assertEq(ins.exports.grow1(), -1); // Because initial == max

    let ins2 = wasmEvalText(`
(module
  (import "" "mem" (memory 1))

  (func (export "get_it") (param $addr i32) (result i32)
    (i32.load (local.get $addr)))
)`,
                            {"":ins.exports})

    ins.exports.set_it((pages_limit*pagesz)-4, 0xbaadf00d);
    assertEq(ins2.exports.get_it((pages_limit*pagesz)-4), 0xbaadf00d|0)
}

// Fail to grow at the max heap size even if there is headroom
// in the declared max
if (pages_limit < 65536) {
    let ins = wasmEvalText(`
(module
  (memory (export "mem") ${pages_limit} ${pages_limit+2})

  (func (export "grow1") (result i32)
    (memory.grow (i32.const 1)))
)`);

    assertEq(ins.exports.grow1(), -1); // Because current is at the max heap size
}

// Fail to instantiate when the minimum is larger than the max heap size
if (pages_limit < 65536) {
    assertErrorMessage(() => wasmEvalText(`
(module (memory (export "mem") ${pages_limit+1} ${pages_limit+1}))
`),
                       WebAssembly.RuntimeError,
                       /too many memory pages/);
}
