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

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

// table.init: out of bounds of the table or the element segment, and should
// not perform the operation at all.
//
// Arithmetic overflow of tableoffset + len or of segmentoffset + len should not
// affect the behavior.

// Note, the length of the element segment is 16.
const tbl_init_len = 16;

function tbl_init(min, max, backup, write, segoffs=0) {
    let ins = wasmEvalText(
        `(module
           (table (export "tbl") ${min} ${max} funcref)
           (elem func $f0 $f1 $f2 $f3 $f4 $f5 $f6 $f7 $f8 $f9 $f10 $f11 $f12 $f13 $f14 $f15)
           (func $f0 (export "f0"))
           (func $f1 (export "f1"))
           (func $f2 (export "f2"))
           (func $f3 (export "f3"))
           (func $f4 (export "f4"))
           (func $f5 (export "f5"))
           (func $f6 (export "f6"))
           (func $f7 (export "f7"))
           (func $f8 (export "f8"))
           (func $f9 (export "f9"))
           (func $f10 (export "f10"))
           (func $f11 (export "f11"))
           (func $f12 (export "f12"))
           (func $f13 (export "f13"))
           (func $f14 (export "f14"))
           (func $f15 (export "f15"))
           (func (export "run") (param $offs i32) (param $len i32)
             (table.init 0 (local.get $offs) (i32.const ${segoffs}) (local.get $len))))`);
    // A fill writing past the end of the table 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
    // table with as much data as was available.
    let offs = min - backup;
    assertErrorMessage(() => ins.exports.run(offs, write),
                       WebAssembly.RuntimeError,
                       /index out of bounds/);
    let tbl = ins.exports.tbl;
    for (let i=0; i < min; i++) {
        assertEq(tbl.get(i), null);
    }
}

// We exceed the bounds of the table but not of the element segment
tbl_init(tbl_init_len*2, tbl_init_len*4, Math.floor(tbl_init_len/2), tbl_init_len);
tbl_init(tbl_init_len*2, tbl_init_len*4, Math.floor(tbl_init_len/2)-1, tbl_init_len);

// We exceed the bounds of the element segment but not the table
tbl_init(tbl_init_len*10, tbl_init_len*20, tbl_init_len*4, tbl_init_len*2);
tbl_init(tbl_init_len*10, tbl_init_len*20, tbl_init_len*4-1, tbl_init_len*2-1);

// We arithmetically overflow the table limit but not the segment limit
tbl_init(tbl_init_len*4, tbl_init_len*4, tbl_init_len, 0xFFFFFFF0);

// We arithmetically overflow the segment limit but not the table limit
tbl_init(tbl_init_len, tbl_init_len, tbl_init_len, 0xFFFFFFFC, Math.floor(tbl_init_len/2));

// table.copy: out of bounds of the table for the source or target, and should
// perform the operation at all.  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
//
// Note we do not test the multi-table case here because that is part of the
// reftypes proposal; tests are in the gc/ subdirectory.

const tbl_copy_len = 16;

function tbl_copy(min, max, srcOffs, targetOffs, len) {
    let ins = wasmEvalText(
        `(module
           (table (export "tbl") ${min} ${max} funcref)
           (func $f0 (export "f0"))
           (func $f1 (export "f1"))
           (func $f2 (export "f2"))
           (func $f3 (export "f3"))
           (func $f4 (export "f4"))
           (func $f5 (export "f5"))
           (func $f6 (export "f6"))
           (func $f7 (export "f7"))
           (func $f8 (export "f8"))
           (func $f9 (export "f9"))
           (func $f10 (export "f10"))
           (func $f11 (export "f11"))
           (func $f12 (export "f12"))
           (func $f13 (export "f13"))
           (func $f14 (export "f14"))
           (func $f15 (export "f15"))
           (func (export "run") (param $targetOffs i32) (param $srcOffs i32) (param $len i32)
             (table.copy (local.get $targetOffs) (local.get $srcOffs) (local.get $len))))`);

    let tbl = ins.exports.tbl;

    let copyDown = srcOffs < targetOffs;
    let targetAvail = tbl.length - targetOffs;
    let srcAvail = tbl.length - srcOffs;
    let srcLim = srcOffs + Math.min(len, targetAvail, srcAvail);

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

    for (var i=0, s=0; i < tbl.length; i++ ) {
        if (i >= srcOffs && i < srcLim) {
            assertEq(tbl.get(i), ins.exports["f" + (s++)]);
            continue;
        }
        assertEq(tbl.get(i), null);
    }
}

// OOB target address, nonoverlapping
tbl_copy(tbl_copy_len*2, tbl_copy_len*4, 0, Math.floor(1.5*tbl_copy_len), tbl_copy_len);
tbl_copy(tbl_copy_len*2, tbl_copy_len*4, 0, Math.floor(1.5*tbl_copy_len)-1, tbl_copy_len-1);

// OOB source address, nonoverlapping
tbl_copy(tbl_copy_len*2, tbl_copy_len*4, Math.floor(1.5*tbl_copy_len), 0, tbl_copy_len);
tbl_copy(tbl_copy_len*2, tbl_copy_len*4, Math.floor(1.5*tbl_copy_len)-1, 0, tbl_copy_len-1);

// OOB target address, overlapping, src < target
tbl_copy(tbl_copy_len*2, tbl_copy_len*4, tbl_copy_len-5, Math.floor(1.5*tbl_copy_len), tbl_copy_len);

// OOB source address, overlapping, target < src
tbl_copy(tbl_copy_len*2, tbl_copy_len*4, Math.floor(1.5*tbl_copy_len), tbl_copy_len-5, tbl_copy_len);

// OOB both, overlapping, including src == target
tbl_copy(tbl_copy_len*2, tbl_copy_len*4, tbl_copy_len+5, Math.floor(1.5*tbl_copy_len), tbl_copy_len);
tbl_copy(tbl_copy_len*2, tbl_copy_len*4, Math.floor(1.5*tbl_copy_len), tbl_copy_len+5, tbl_copy_len);
tbl_copy(tbl_copy_len*2, tbl_copy_len*4, tbl_copy_len+5, tbl_copy_len+5, tbl_copy_len);

// Arithmetic overflow on source address.
tbl_copy(tbl_copy_len*8, tbl_copy_len*8, tbl_copy_len*7, 0, 0xFFFFFFE0);

// Arithmetic overflow on target adddress is an overlapping case.
tbl_copy(tbl_copy_len*8, tbl_copy_len*8, 0, tbl_copy_len*7, 0xFFFFFFE0);
