// |jit-test| skip-if: !wasmBulkMemSupported()

let conf = getBuildConfiguration();
if (conf.debug &&
    (conf["arm-simulator"] || conf["arm64-simulator"] ||
     conf["mips32-simulator"] || conf["mips64-simulator"]))
{
    // Will timeout, so just quit early.
    quit(0);
}

// Sundry test cases for the "partial write" bounds checking semantics.

const PAGESIZE = 65536;

// memory.fill: out of bounds, but should perform a partial fill.
//
// Arithmetic overflow of memory offset + len should not affect the behavior, we
// should still fill up to the limit.

function mem_fill(min, max, shared, backup, write=backup*2) {
    let ins = wasmEvalText(
        `(module
           (memory (export "mem") ${min} ${max} ${shared})
           (func (export "run") (param $offs i32) (param $val i32) (param $len i32)
             (memory.fill (local.get $offs) (local.get $val) (local.get $len))))`);
    // A fill past the end should throw *and* have filled all the way up to the end
    let offs = min*PAGESIZE - backup;
    let val = 37;
    assertErrorMessage(() => ins.exports.run(offs, val, write),
                       WebAssembly.RuntimeError,
                       /index out of bounds/);
    let v = new Uint8Array(ins.exports.mem.buffer);
    for (let i=0; i < backup; i++)
        assertEq(v[offs+i], val);
    for (let i=0; i < offs; i++)
        assertEq(v[i], 0);
}

mem_fill(1, 1, "", 256);
mem_fill(1, 1, "", 257);
mem_fill(1, 1, "", 257, 0xFFFFFFFF); // offs + len overflows 32-bit

mem_fill(2, 4, "shared", 256);
mem_fill(2, 4, "shared", 257);
mem_fill(2, 4, "shared", 257, 0xFFFFFFFF); // offs + len overflows 32-bit

// memory.init: out of bounds of the memory or the segment, but should perform
// the operation up to the appropriate bound.
//
// Arithmetic overflow of memoffset + len or of bufferoffset + len should not
// affect the behavior.

// Note, the length of the data segment is 16.
const mem_init_len = 16;

function mem_init(min, max, shared, backup, write) {
    let ins = wasmEvalText(
        `(module
           (memory (export "mem") ${min} ${max} ${shared})
           (data passive "\\42\\42\\42\\42\\42\\42\\42\\42\\42\\42\\42\\42\\42\\42\\42\\42")
           (func (export "run") (param $offs i32) (param $len i32)
             (memory.init 0 (local.get $offs) (i32.const 0) (local.get $len))))`);
    // A fill writing past the end of the memory should throw *and* have filled
    // all the way up to the end.
    //
    // A fill reading past the end of the segment should throw *and* have filled
    // memory with as much data as was available.
    let offs = min*PAGESIZE - backup;
    assertErrorMessage(() => ins.exports.run(offs, write),
                       WebAssembly.RuntimeError,
                       /index out of bounds/);
    let v = new Uint8Array(ins.exports.mem.buffer);
    for (let i=0; i < Math.min(backup, mem_init_len); i++)
        assertEq(v[offs + i], 0x42);
    for (let i=Math.min(backup, mem_init_len); i < backup; i++)
        assertEq(v[offs + i], 0);
    for (let i=0; i < offs; i++)
        assertEq(v[i], 0);
}

// We exceed the bounds of the memory but not of the data segment
mem_init(1, 1, "", Math.floor(mem_init_len/2), mem_init_len);
mem_init(1, 1, "", Math.floor(mem_init_len/2)+1, mem_init_len);
mem_init(2, 4, "shared", Math.floor(mem_init_len/2), mem_init_len);
mem_init(2, 4, "shared", Math.floor(mem_init_len/2)+1, mem_init_len);

// We exceed the bounds of the data segment but not the memory
mem_init(1, 1, "", mem_init_len*4, mem_init_len*2-2);
mem_init(1, 1, "", mem_init_len*4-1, mem_init_len*2-1);
mem_init(2, 4, "shared", mem_init_len*4, mem_init_len*2-2);
mem_init(2, 4, "shared", mem_init_len*4-1, mem_init_len*2-1);

// We arithmetically overflow the memory limit but not the segment limit
mem_init(1, "", "", Math.floor(mem_init_len/2), 0xFFFFFF00);

// We arithmetically overflow the segment limit but not the memory limit
mem_init(1, "", "", PAGESIZE, 0xFFFFFFFC);

// memory.copy: out of bounds of the memory for the source or target, but should
// perform the operation up to the appropriate bound.  Major cases:
//
// - non-overlapping regions
// - overlapping regions with src >= dest
// - overlapping regions with src == dest
// - overlapping regions with src < dest
// - arithmetic overflow on src addresses
// - arithmetic overflow on target addresses
//
// for each of those,
//
// - src address oob
// - target address oob
// - both oob

function mem_copy(min, max, shared, srcOffs, targetOffs, len, copyDown=false) {
    let ins = wasmEvalText(
        `(module
           (memory (export "mem") ${min} ${max} ${shared})
           (func (export "run") (param $targetOffs i32) (param $srcOffs i32) (param $len i32)
             (memory.copy (local.get $targetOffs) (local.get $srcOffs) (local.get $len))))`);

    let v = new Uint8Array(ins.exports.mem.buffer);

    let targetAvail = v.length - targetOffs;
    let srcAvail = v.length - srcOffs;
    let targetLim = targetOffs + Math.min(len, targetAvail, srcAvail);
    let srcLim = srcOffs + Math.min(len, targetAvail, srcAvail);
    let immediateOOB = copyDown && (srcOffs + len > v.length || targetOffs + len > v.length);

    for (let i=srcOffs, j=0; i < srcLim; i++, j++)
        v[i] = j;
    assertErrorMessage(() => ins.exports.run(targetOffs, srcOffs, len),
                       WebAssembly.RuntimeError,
                       /index out of bounds/);

    // :lth wants lambda-lifting and closure optimizations
    var t = 0;
    var s = 0;
    var i = 0;
    function checkTarget() {
        if (i >= targetOffs && i < targetLim) {
            assertEq(v[i], (t++) & 0xFF);
            if (i >= srcOffs && i < srcLim)
                s++;
            return true;
        }
        return false;
    }
    function checkSource() {
        if (i >= srcOffs && i < srcLim) {
            assertEq(v[i], (s++) & 0xFF);
            if (i >= targetOffs && i < targetLim)
                t++;
            return true;
        }
        return false;
    }

    for (i=0; i < v.length; i++ ) {
        if (immediateOOB) {
            if (checkSource())
                continue;
        } else {
            if (copyDown && (checkSource() || checkTarget()))
                continue;
            if (!copyDown && (checkTarget() || checkSource()))
                continue;
        }
        assertEq(v[i], 0);
    }
}

// OOB target address, nonoverlapping
mem_copy(1, 1, "", 0, PAGESIZE-20, 40);
mem_copy(1, 1, "", 0, PAGESIZE-21, 39);
mem_copy(2, 4, "shared", 0, 2*PAGESIZE-20, 40);
mem_copy(2, 4, "shared", 0, 2*PAGESIZE-21, 39);

// OOB source address, nonoverlapping
mem_copy(1, 1, "", PAGESIZE-20, 0, 40);
mem_copy(1, 1, "", PAGESIZE-21, 0, 39);
mem_copy(2, 4, "shared", 2*PAGESIZE-20, 0, 40);
mem_copy(2, 4, "shared", 2*PAGESIZE-21, 0, 39);

// OOB target address, overlapping, src < target
mem_copy(1, 1, "", PAGESIZE-50, PAGESIZE-20, 40, true);

// OOB source address, overlapping, target < src
mem_copy(1, 1, "", PAGESIZE-20, PAGESIZE-50, 40);

// OOB both, overlapping, including target == src
mem_copy(1, 1, "", PAGESIZE-30, PAGESIZE-20, 40, true);
mem_copy(1, 1, "", PAGESIZE-20, PAGESIZE-30, 40);
mem_copy(1, 1, "", PAGESIZE-20, PAGESIZE-20, 40);

// Arithmetic overflow on source address.
mem_copy(1, "", "", PAGESIZE-20, 0, 0xFFFFF000);

// Arithmetic overflow on target adddress is an overlapping case.
mem_copy(1, 1, "", PAGESIZE-0x1000, PAGESIZE-20, 0xFFFFFF00, true);

