// In order not to run afoul of C++ UB we have our own non-C++ definitions of
// operations (they are actually jitted) that can operate racily on shared
// memory, see jit/shared/AtomicOperations-shared-jit.cpp.
//
// Operations on fixed-width 1, 2, 4, and 8 byte data are adequately tested
// elsewhere.  Here we specifically test our safe-when-racy replacements of
// memcpy and memmove.
//
// There are two primitives in the engine, memcpy_down and memcpy_up.  These are
// equivalent except when data overlap, in which case memcpy_down handles
// overlapping copies that move from higher to lower addresses and memcpy_up
// handles ditto from lower to higher.  memcpy uses memcpy_down always while
// memmove selects the one to use dynamically based on its arguments.

// Basic memcpy algorithm to be tested:
//
// - if src and target have the same alignment
//   - byte copy up to word alignment
//   - block copy as much as possible
//   - word copy as much as possible
//   - byte copy any tail
// - else if on a platform that can deal with unaligned access
//   (ie, x86, ARM64, and ARM if the proper flag is set)
//   - block copy as much as possible
//   - word copy as much as possible
//   - byte copy any tail
// - else // on a platform that can't deal with unaligned access
//   (ie ARM without the flag or x86 DEBUG builds with the
//   JS_NO_UNALIGNED_MEMCPY env var)
//   - block copy with byte copies
//   - word copy with byte copies
//   - byte copy any tail

var target_buf = new SharedArrayBuffer(1024);
var src_buf = new SharedArrayBuffer(1024);

///////////////////////////////////////////////////////////////////////////
//
// Different src and target buffer, this is memcpy "move down".  The same
// code is used in the engine for overlapping buffers when target addresses
// are lower than source addresses.

fill(src_buf);

// Basic 1K perfectly aligned copy, copies blocks only.
{
    let target = new Uint8Array(target_buf);
    let src = new Uint8Array(src_buf);
    clear(target_buf);
    target.set(src);
    check(target_buf, 0, 1024, 0);
}

// Buffers are equally aligned but not on a word boundary and not ending on a
// word boundary either, so this will copy first some bytes, then some blocks,
// then some words, and then some bytes.
{
    let fill = 0x79;
    clear(target_buf, fill);
    let target = new Uint8Array(target_buf, 1, 1022);
    let src = new Uint8Array(src_buf, 1, 1022);
    target.set(src);
    check_fill(target_buf, 0, 1, fill);
    check(target_buf, 1, 1023, 1);
    check_fill(target_buf, 1023, 1024, fill);
}

// Buffers are unequally aligned, we'll copy bytes only on some platforms and
// unaligned blocks/words on others.
{
    clear(target_buf);
    let target = new Uint8Array(target_buf, 0, 1023);
    let src = new Uint8Array(src_buf, 1);
    target.set(src);
    check(target_buf, 0, 1023, 1);
    check_zero(target_buf, 1023, 1024);
}

///////////////////////////////////////////////////////////////////////////
//
// Overlapping src and target buffer and the target addresses are always
// higher than the source addresses, this is memcpy "move up"

// Buffers are equally aligned but not on a word boundary and not ending on a
// word boundary either, so this will copy first some bytes, then some blocks,
// then some words, and then some bytes.
{
    fill(target_buf);
    let target = new Uint8Array(target_buf, 9, 999);
    let src = new Uint8Array(target_buf, 1, 999);
    target.set(src);
    check(target_buf, 9, 1008, 1);
    check(target_buf, 1008, 1024, 1008 & 255);
}

// Buffers are unequally aligned, we'll copy bytes only on some platforms and
// unaligned blocks/words on others.
{
    fill(target_buf);
    let target = new Uint8Array(target_buf, 2, 1022);
    let src = new Uint8Array(target_buf, 1, 1022);
    target.set(src);
    check(target_buf, 2, 1024, 1);
}

///////////////////////////////////////////////////////////////////////////
//
// Copy 0 to 127 bytes from and to a variety of addresses to check that we
// handle limits properly in these edge cases.

// Too slow in debug-noopt builds but we don't want to flag the test as slow,
// since that means it'll never be run.

if (this.getBuildConfiguration && !getBuildConfiguration().debug)
{
    let t = new Uint8Array(target_buf);
    for (let my_src_buf of [src_buf, target_buf]) {
        for (let size=0; size < 127; size++) {
            for (let src_offs=0; src_offs < 8; src_offs++) {
                for (let target_offs=0; target_offs < 8; target_offs++) {
                    clear(target_buf, Math.random()*255);
                    let target = new Uint8Array(target_buf, target_offs, size);

                    // Zero is boring
                    let bias = (Math.random() * 100 % 12) | 0;

                    // Note src may overlap target partially
                    let src = new Uint8Array(my_src_buf, src_offs, size);
                    for ( let i=0; i < size; i++ )
                        src[i] = i+bias;

                    // We expect these values to be unchanged by the copy
                    let below = target_offs > 0 ? t[target_offs - 1] : 0;
                    let above = t[target_offs + size];

                    // Copy
                    target.set(src);

                    // Verify
                    check(target_buf, target_offs, target_offs + size, bias);
                    if (target_offs > 0)
                        assertEq(t[target_offs-1], below);
                    assertEq(t[target_offs+size], above);
                }
            }
        }
    }
}


// Utilities

function clear(buf, fill) {
    let a = new Uint8Array(buf);
    for ( let i=0; i < a.length; i++ )
        a[i] = fill;
}

function fill(buf) {
    let a = new Uint8Array(buf);
    for ( let i=0; i < a.length; i++ )
        a[i] = i & 255
}

function check(buf, from, to, startingWith) {
    let a = new Uint8Array(buf);
    for ( let i=from; i < to; i++ ) {
        assertEq(a[i], startingWith);
        startingWith = (startingWith + 1) & 255;
    }
}

function check_zero(buf, from, to) {
    check_fill(buf, from, to, 0);
}

function check_fill(buf, from, to, fill) {
    let a = new Uint8Array(buf);
    for ( let i=from; i < to; i++ )
        assertEq(a[i], fill);
}
