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

// Do not include this in the preamble, it must be loaded after lib/wasm.js
load(scriptdir + "ad-hack-preamble.js")

// Simple unary operators.  Place parameter in memory at offset 16,
// read the result at offset 0.

function expandConstantUnopInputs(op, memtype, inputs) {
    let s = '';
    let ident = 0;
    for ( let a of inputs ) {
        let constval = `${memtype.layoutName} ${a.map(jsValueToWasmName).join(' ')}`;
        s += `
    (func (export "run_const${ident}")
      (v128.store (i32.const 0)
        (${op} (v128.const ${constval}))))
`;
        ident++;
    }
    return s;
}

function insAndMemUnop(op, memtype, resultmemtype, inputs) {
    var ins = wasmEvalText(`
  (module
    (memory (export "mem") 1 1)

    (func (export "run")
      (v128.store (i32.const 0)
        (call $doit (v128.load (i32.const 16)))))

    (func $doit (param $a v128) (result v128)
      (${op} (local.get $a)))

    ${expandConstantUnopInputs(op, memtype, inputs)})`);
    var mem = new memtype(ins.exports.mem.buffer);
    var resultmem = !resultmemtype || memtype == resultmemtype ? mem : new resultmemtype(ins.exports.mem.buffer);
    return [ins, mem, resultmem];
}

function ineg(bits) { return (a) => sign_extend(!a ? a : -a,bits) }
function iabs(bits) { return (a) => zero_extend(a < 0 ? -a : a, bits) }
function fneg(a) { return -a }
function fabs(a) { return Math.abs(a) }
function fsqrt(a) { return Math.fround(Math.sqrt(Math.fround(a))) }
function dsqrt(a) { return Math.sqrt(a) }
function bitnot(a) { return (~a) & 255 }
function ffloor(x) { return Math.fround(Math.floor(x)) }
function fceil(x) { return Math.fround(Math.ceil(x)) }
function ftrunc(x) { return Math.fround(Math.sign(x)*Math.floor(Math.abs(x))) }
function fnearest(x) { return Math.fround(Math.round(x)) }
function dfloor(x) { return Math.floor(x) }
function dceil(x) { return Math.ceil(x) }
function dtrunc(x) { return Math.sign(x)*Math.floor(Math.abs(x)) }
function dnearest(x) { return Math.round(x) }

for ( let [op, memtype, rop, resultmemtype] of
      [['i8x16.neg', Int8Array, ineg(8)],
       ['i16x8.neg', Int16Array, ineg(16)],
       ['i32x4.neg', Int32Array, ineg(32)],
       ['i64x2.neg', BigInt64Array, ineg(64)],
       ['i8x16.abs', Int8Array, iabs(8), Uint8Array],
       ['i16x8.abs', Int16Array, iabs(16), Uint16Array],
       ['i32x4.abs', Int32Array, iabs(32), Uint32Array],
       ['f32x4.neg', Float32Array, fneg],
       ['f64x2.neg', Float64Array, fneg],
       ['f32x4.abs', Float32Array, fabs],
       ['f64x2.abs', Float64Array, fabs],
       ['f32x4.sqrt', Float32Array, fsqrt],
       ['f64x2.sqrt', Float64Array, dsqrt],
       ['f32x4.ceil', Float32Array, fceil],
       ['f32x4.floor', Float32Array, ffloor],
       ['f32x4.trunc', Float32Array, ftrunc],
       ['f32x4.nearest', Float32Array, fnearest],
       ['f64x2.ceil', Float64Array, dceil],
       ['f64x2.floor', Float64Array, dfloor],
       ['f64x2.trunc', Float64Array, dtrunc],
       ['f64x2.nearest', Float64Array, dnearest],
       ['v128.not', Uint8Array, bitnot],
      ])
{
    let [ins, mem, resultmem] = insAndMemUnop(op, memtype, resultmemtype, memtype.inputs);
    let len = 16/memtype.BYTES_PER_ELEMENT;
    let xs = iota(len);
    let zero = xs.map(_ => 0);
    let bitsForF32 = memtype == Float32Array ? new Uint32Array(mem.buffer) : null;
    let bitsForF64 = memtype == Float64Array ? new BigInt64Array(mem.buffer) : null;

    function testIt(a, r) {
        set(mem, len, a);
        ins.exports.run();
        assertSame(get(resultmem, 0, len), r);

        // Test signalling NaN superficially by replacing QNaN inputs with SNaN
        if (bitsForF32 != null && a.some(isNaN)) {
            a.forEach((x, i) => { if (isNaN(x)) { bitsForF32[len+i] = 0x7FA0_0000; } });
            ins.exports.run();
            assertSame(get(resultmem, 0, len), r);
        }
        if (bitsForF64 != null && a.some(isNaN)) {
            a.forEach((x, i) => { if (isNaN(x)) { bitsForF64[len+i] = 0x7FF4_0000_0000_0000n; } });
            ins.exports.run();
            assertSame(get(resultmem, 0, len), r);
        }
    }

    function testConstIt(i,r) {
        set(resultmem, 0, zero);
        ins.exports["run_const" + i]();
        assertSame(get(resultmem, 0, len), r);
    }

    let i = 0;
    for (let a of memtype.inputs) {
        let r = xs.map((i) => rop(a[i]));
        testIt(a, r);
        testConstIt(i, r);
        i++;
    }
}