load(libdir + "wasm.js");

// ----------------------------------------------------------------------------
// exports

var o = wasmEvalText('(module)').exports;
assertEq(Object.getOwnPropertyNames(o).length, 0);

var o = wasmEvalText('(module (func))').exports;
assertEq(Object.getOwnPropertyNames(o).length, 0);

var o = wasmEvalText('(module (func) (export "a" 0))').exports;
var names = Object.getOwnPropertyNames(o);
assertEq(names.length, 1);
assertEq(names[0], 'a');
var desc = Object.getOwnPropertyDescriptor(o, 'a');
assertEq(typeof desc.value, "function");
assertEq(desc.value.name, "0");
assertEq(desc.value.length, 0);
assertEq(desc.value(), undefined);
assertEq(desc.writable, false);
assertEq(desc.enumerable, true);
assertEq(desc.configurable, false);
assertEq(desc.value(), undefined);

wasmValidateText('(module (func) (func) (export "a" 0))');
wasmValidateText('(module (func) (func) (export "a" 1))');
wasmValidateText('(module (func $a) (func $b) (export "a" $a) (export "b" $b))');
wasmValidateText('(module (func $a) (func $b) (export "a" $a) (export "b" $b))');

wasmFailValidateText('(module (func) (export "a" 1))', /exported function index out of bounds/);
wasmFailValidateText('(module (func) (func) (export "a" 2))', /exported function index out of bounds/);

var o = wasmEvalText('(module (func) (export "a" 0) (export "b" 0))').exports;
assertEq(Object.getOwnPropertyNames(o).sort().toString(), "a,b");
assertEq(o.a.name, "0");
assertEq(o.b.name, "0");
assertEq(o.a === o.b, true);

var o = wasmEvalText('(module (func) (func) (export "a" 0) (export "b" 1))').exports;
assertEq(Object.getOwnPropertyNames(o).sort().toString(), "a,b");
assertEq(o.a.name, "0");
assertEq(o.b.name, "1");
assertEq(o.a === o.b, false);

var o = wasmEvalText('(module (func (result i32) (i32.const 1)) (func (result i32) (i32.const 2)) (export "a" 0) (export "b" 1))').exports;
assertEq(o.a(), 1);
assertEq(o.b(), 2);
var o = wasmEvalText('(module (func (result i32) (i32.const 1)) (func (result i32) (i32.const 2)) (export "a" 1) (export "b" 0))').exports;
assertEq(o.a(), 2);
assertEq(o.b(), 1);

wasmFailValidateText('(module (func) (export "a" 0) (export "a" 0))', /duplicate export/);
wasmFailValidateText('(module (func) (func) (export "a" 0) (export "a" 1))', /duplicate export/);

// ----------------------------------------------------------------------------
// signatures

wasmFailValidateText('(module (func (result i32)))', mismatchError("void", "i32"));
wasmFailValidateText('(module (func (result i32) (nop)))', mismatchError("void", "i32"));

wasmValidateText('(module (func (nop)))');
wasmValidateText('(module (func (result i32) (i32.const 42)))');
wasmValidateText('(module (func (param i32)))');
wasmValidateText('(module (func (param i32) (result i32) (i32.const 42)))');
wasmValidateText('(module (func (result i32) (param i32) (i32.const 42)))');
wasmValidateText('(module (func (param f32)))');
wasmValidateText('(module (func (param f64)))');

var f = wasmEvalText('(module (func (param i64) (result i32) (i32.const 123)) (export "" 0))').exports[""];
assertErrorMessage(f, TypeError, /i64/);
var f = wasmEvalText('(module (func (param i32) (result i64) (i64.const 123)) (export "" 0))').exports[""];
assertErrorMessage(f, TypeError, /i64/);

var f = wasmEvalText('(module (import $imp "a" "b" (param i64) (result i32)) (func $f (result i32) (call $imp (i64.const 0))) (export "" $f))', {a:{b:()=>{}}}).exports[""];
assertErrorMessage(f, TypeError, /i64/);
var f = wasmEvalText('(module (import $imp "a" "b" (result i64)) (func $f (result i64) (call $imp)) (export "" $f))', {a:{b:()=>{}}}).exports[""];
assertErrorMessage(f, TypeError, /i64/);

setJitCompilerOption('wasm.test-mode', 1);
wasmFullPassI64('(module (func (result i64) (i64.const 123)) (export "run" 0))', {low: 123, high: 0});
wasmFullPassI64('(module (func (param i64) (result i64) (get_local 0)) (export "run" 0))',
                { low: 0x7fffffff, high: 0x12340000},
                {},
                {low: 0x7fffffff, high: 0x12340000});
wasmFullPassI64('(module (func (param i64) (result i64) (i64.add (get_local 0) (i64.const 1))) (export "run" 0))',
                {low: 0x0, high: 0x12340001},
                {},
                { low: 0xffffffff, high: 0x12340000});
setJitCompilerOption('wasm.test-mode', 0);

// ----------------------------------------------------------------------------
// imports

const noImportObj = "second argument must be an object";

assertErrorMessage(() => wasmEvalText('(module (import "a" "b"))', 1), TypeError, noImportObj);
assertErrorMessage(() => wasmEvalText('(module (import "a" "b"))', null), TypeError, noImportObj);

const notObject = /import object field '\w*' is not an Object/;
const notFunction = /import object field '\w*' is not a Function/;

var code = '(module (import "a" "b"))';
assertErrorMessage(() => wasmEvalText(code), TypeError, noImportObj);
assertErrorMessage(() => wasmEvalText(code, {}), TypeError, notObject);
assertErrorMessage(() => wasmEvalText(code, {a:1}), TypeError, notObject);
assertErrorMessage(() => wasmEvalText(code, {a:{}}), TypeError, notFunction);
assertErrorMessage(() => wasmEvalText(code, {a:{b:1}}), TypeError, notFunction);
wasmEvalText(code, {a:{b:()=>{}}});

var code = '(module (import "" "b"))';
wasmEvalText(code, {"":{b:()=>{}}});

var code = '(module (import "a" ""))';
assertErrorMessage(() => wasmEvalText(code), TypeError, noImportObj);
assertErrorMessage(() => wasmEvalText(code, {}), TypeError, notObject);
assertErrorMessage(() => wasmEvalText(code, {a:1}), TypeError, notObject);
wasmEvalText(code, {a:{"":()=>{}}});

var code = '(module (import "a" "") (import "b" "c") (import "c" ""))';
assertErrorMessage(() => wasmEvalText(code, {a:()=>{}, b:{c:()=>{}}, c:{}}), TypeError, notFunction);
wasmEvalText(code, {a:{"":()=>{}}, b:{c:()=>{}}, c:{"":()=>{}}});

wasmEvalText('(module (import "a" "" (result i32)))', {a:{"":()=>{}}});
wasmEvalText('(module (import "a" "" (result f32)))', {a:{"":()=>{}}});
wasmEvalText('(module (import "a" "" (result f64)))', {a:{"":()=>{}}});
wasmEvalText('(module (import $foo "a" "" (result f64)))', {a:{"":()=>{}}});

// ----------------------------------------------------------------------------
// memory

wasmValidateText('(module (memory 0))');
wasmValidateText('(module (memory 1))');
wasmFailValidateText('(module (memory 65536))', /initial memory size too big/);

// May OOM, but must not crash:
try {
    wasmEvalText('(module (memory 65535))');
} catch (e) {
    assertEq(String(e).indexOf("out of memory") != -1 ||
             String(e).indexOf("memory size too big") != -1, true);
}

var buf = wasmEvalText('(module (memory 1) (export "memory" memory))').exports.memory.buffer;
assertEq(buf instanceof ArrayBuffer, true);
assertEq(buf.byteLength, 65536);

var obj = wasmEvalText('(module (memory 1) (func (result i32) (i32.const 42)) (func (nop)) (export "memory" memory) (export "b" 0) (export "c" 1))').exports;
assertEq(obj.memory.buffer instanceof ArrayBuffer, true);
assertEq(obj.b instanceof Function, true);
assertEq(obj.c instanceof Function, true);
assertEq(obj.memory.buffer.byteLength, 65536);
assertEq(obj.b(), 42);
assertEq(obj.c(), undefined);

var buf = wasmEvalText('(module (memory 1) (data (i32.const 0) "") (export "memory" memory))').exports.memory.buffer;
assertEq(new Uint8Array(buf)[0], 0);

var buf = wasmEvalText('(module (memory 1) (data (i32.const 65536) "") (export "memory" memory))').exports.memory.buffer;
assertEq(new Uint8Array(buf)[0], 0);

var buf = wasmEvalText('(module (memory 1) (data (i32.const 0) "a") (export "memory" memory))').exports.memory.buffer;
assertEq(new Uint8Array(buf)[0], 'a'.charCodeAt(0));

var buf = wasmEvalText('(module (memory 1) (data (i32.const 0) "a") (data (i32.const 2) "b") (export "memory" memory))').exports.memory.buffer;
assertEq(new Uint8Array(buf)[0], 'a'.charCodeAt(0));
assertEq(new Uint8Array(buf)[1], 0);
assertEq(new Uint8Array(buf)[2], 'b'.charCodeAt(0));

var buf = wasmEvalText('(module (memory 1) (data (i32.const 65535) "c") (export "memory" memory))').exports.memory.buffer;
assertEq(new Uint8Array(buf)[0], 0);
assertEq(new Uint8Array(buf)[65535], 'c'.charCodeAt(0));

// ----------------------------------------------------------------------------
// locals

assertEq(wasmEvalText('(module (func (param i32) (result i32) (get_local 0)) (export "" 0))').exports[""](), 0);
assertEq(wasmEvalText('(module (func (param i32) (result i32) (get_local 0)) (export "" 0))').exports[""](42), 42);
assertEq(wasmEvalText('(module (func (param i32) (param i32) (result i32) (get_local 0)) (export "" 0))').exports[""](42, 43), 42);
assertEq(wasmEvalText('(module (func (param i32) (param i32) (result i32) (get_local 1)) (export "" 0))').exports[""](42, 43), 43);

wasmFailValidateText('(module (func (get_local 0)))', /get_local index out of range/);
wasmFailValidateText('(module (func (result f32) (local i32) (get_local 0)))', mismatchError("i32", "f32"));
wasmFailValidateText('(module (func (result i32) (local f32) (get_local 0)))', mismatchError("f32", "i32"));
wasmFailValidateText('(module (func (result f32) (param i32) (local f32) (get_local 0)))', mismatchError("i32", "f32"));
wasmFailValidateText('(module (func (result i32) (param i32) (local f32) (get_local 1)))', mismatchError("f32", "i32"));

wasmValidateText('(module (func (local i32)))');
wasmValidateText('(module (func (local i32) (local f32)))');

wasmFullPass('(module (func (result i32) (local i32) (get_local 0)) (export "run" 0))', 0);
wasmFullPass('(module (func (result i32) (param i32) (local f32) (get_local 0)) (export "run" 0))', 0);
wasmFullPass('(module (func (result f32) (param i32) (local f32) (get_local 1)) (export "run" 0))', 0);

wasmFailValidateText('(module (func (set_local 0 (i32.const 0))))', /set_local index out of range/);
wasmFailValidateText('(module (func (local f32) (set_local 0 (i32.const 0))))', mismatchError("i32", "f32"));
wasmFailValidateText('(module (func (local f32) (set_local 0 (nop))))', /popping value from empty stack/);
wasmFailValidateText('(module (func (local i32) (local f32) (set_local 0 (get_local 1))))', mismatchError("f32", "i32"));
wasmFailValidateText('(module (func (local i32) (local f32) (set_local 1 (get_local 0))))', mismatchError("i32", "f32"));

wasmValidateText('(module (func (local i32) (set_local 0 (i32.const 0))))');
wasmValidateText('(module (func (local i32) (local f32) (set_local 0 (get_local 0))))');
wasmValidateText('(module (func (local i32) (local f32) (set_local 1 (get_local 1))))');

wasmFullPass('(module (func (result i32) (local i32) (tee_local 0 (i32.const 42))) (export "run" 0))', 42);
wasmFullPass('(module (func (result i32) (local i32) (tee_local 0 (get_local 0))) (export "run" 0))', 0);

wasmFullPass('(module (func (param $a i32) (result i32) (get_local $a)) (export "run" 0))', 0);
wasmFullPass('(module (func (param $a i32) (local $b i32) (result i32) (block i32 (set_local $b (get_local $a)) (get_local $b))) (export "run" 0))', 42, {}, 42);

wasmValidateText('(module (func (local i32) (local $a f32) (set_local 0 (i32.const 1)) (set_local $a (f32.const nan))))');

// ----------------------------------------------------------------------------
// blocks

wasmFullPass('(module (func (block )) (export "run" 0))', undefined);

wasmFailValidateText('(module (func (result i32) (block )))', mismatchError("void", "i32"));
wasmFailValidateText('(module (func (result i32) (block (block ))))', mismatchError("void", "i32"));
wasmFailValidateText('(module (func (local i32) (set_local 0 (block ))))', /popping value from empty stack/);

wasmFullPass('(module (func (block (block ))) (export "run" 0))', undefined);
wasmFullPass('(module (func (result i32) (block i32 (i32.const 42))) (export "run" 0))', 42);
wasmFullPass('(module (func (result i32) (block i32 (block i32 (i32.const 42)))) (export "run" 0))', 42);
wasmFailValidateText('(module (func (result f32) (block i32 (i32.const 0))))', mismatchError("i32", "f32"));

wasmFullPass('(module (func (result i32) (block i32 (drop (i32.const 13)) (block i32 (i32.const 42)))) (export "run" 0))', 42);
wasmFailValidateText('(module (func (result f32) (param f32) (block i32 (drop (get_local 0)) (i32.const 0))))', mismatchError("i32", "f32"));

wasmFullPass('(module (func (result i32) (local i32) (set_local 0 (i32.const 42)) (get_local 0)) (export "run" 0))', 42);

// ----------------------------------------------------------------------------
// calls

wasmFailValidateText('(module (func (nop)) (func (call 0 (i32.const 0))))', /unused values not explicitly dropped by end of block/);

wasmFailValidateText('(module (func (param i32) (nop)) (func (call 0)))', /peeking at value from outside block/);
wasmFailValidateText('(module (func (param f32) (nop)) (func (call 0 (i32.const 0))))', mismatchError("i32", "f32"));
wasmFailValidateText('(module (func (nop)) (func (call 3)))', /callee index out of range/);

wasmValidateText('(module (func (nop)) (func (call 0)))');
wasmValidateText('(module (func (param i32) (nop)) (func (call 0 (i32.const 0))))');

wasmFullPass('(module (func (result i32) (i32.const 42)) (func (result i32) (call 0)) (export "run" 1))', 42);
assertThrowsInstanceOf(() => wasmEvalText('(module (func (call 0)) (export "" 0))').exports[""](), InternalError);
assertThrowsInstanceOf(() => wasmEvalText('(module (func (call 1)) (func (call 0)) (export "" 0))').exports[""](), InternalError);

wasmValidateText('(module (func (param i32 f32)) (func (call 0 (i32.const 0) (f32.const nan))))');
wasmFailValidateText('(module (func (param i32 f32)) (func (call 0 (i32.const 0) (i32.const 0))))', mismatchError("i32", "f32"));

wasmFailValidateText('(module (import "a" "") (func (call 0 (i32.const 0))))', /unused values not explicitly dropped by end of block/);
wasmFailValidateText('(module (import "a" "" (param i32)) (func (call 0)))', /peeking at value from outside block/);
wasmFailValidateText('(module (import "a" "" (param f32)) (func (call 0 (i32.const 0))))', mismatchError("i32", "f32"));

assertErrorMessage(() => wasmEvalText('(module (import "a" "") (func (call 1)))'), TypeError, noImportObj);
wasmEvalText('(module (import "" "a") (func (call 0)))', {"":{a:()=>{}}});
wasmEvalText('(module (import "" "a" (param i32)) (func (call 0 (i32.const 0))))', {"":{a:()=>{}}});

function checkF32CallImport(v) {
    wasmFullPass('(module (import "" "a" (result f32)) (func (result f32) (call 0)) (export "run" 1))',
                 Math.fround(v),
                 {"":{a:()=>{ return v; }}});
    wasmFullPass('(module (import "" "a" (param f32)) (func (param f32) (call 0 (get_local 0))) (export "run" 1))',
                 undefined,
                 {"":{a:x=>{ assertEq(Math.fround(v), x); }}},
                 v);
}
checkF32CallImport(13.37);
checkF32CallImport(NaN);
checkF32CallImport(-Infinity);
checkF32CallImport(-0);
checkF32CallImport(Math.pow(2, 32) - 1);

var counter = 0;
var f = wasmEvalText('(module (import "" "inc") (func (call 0)) (export "" 1))', {"":{inc:()=>counter++}}).exports[""];
var g = wasmEvalText('(module (import "" "f") (func (block (call 0) (call 0))) (export "" 1))', {"":{f}}).exports[""];
f();
assertEq(counter, 1);
g();
assertEq(counter, 3);

var f = wasmEvalText('(module (import "" "callf") (func (call 0)) (export "" 1))', {"":{callf:()=>f()}}).exports[""];
assertThrowsInstanceOf(() => f(), InternalError);

var f = wasmEvalText('(module (import "" "callg") (func (call 0)) (export "" 1))', {"":{callg:()=>g()}}).exports[""];
var g = wasmEvalText('(module (import "" "callf") (func (call 0)) (export "" 1))', {"":{callf:()=>f()}}).exports[""];
assertThrowsInstanceOf(() => f(), InternalError);

var code = '(module (import "" "one" (result i32)) (import "" "two" (result i32)) (func (result i32) (i32.const 3)) (func (result i32) (i32.const 4)) (func (result i32) BODY) (export "run" 4))';
var imports = {"":{one:()=>1, two:()=>2}};
wasmFullPass(code.replace('BODY', '(call 0)'), 1, imports);
wasmFullPass(code.replace('BODY', '(call 1)'), 2, imports);
wasmFullPass(code.replace('BODY', '(call 2)'), 3, imports);
wasmFullPass(code.replace('BODY', '(call 3)'), 4, imports);

wasmFullPass(`(module (import "" "evalcx" (param i32) (result i32)) (func (result i32) (call 0 (i32.const 0))) (export "run" 1))`, 0, {"":{evalcx}});

if (typeof evaluate === 'function')
    evaluate(`new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary('(module)'))) `, { fileName: null });

{
    setJitCompilerOption('wasm.test-mode', 1);

    let imp = {"":{
        param(i64) {
            assertEqI64(i64, {
                low: 0x9abcdef0,
                high: 0x12345678
            });
            return 42;
        },
        result(i32) {
            return {
                low: 0xabcdef01,
                high: 0x12345678 + i32
            }
        },
        paramAndResult(i64) {
            assertEqI64(i64, {
                low: 0x9abcdef0,
                high: 0x12345678
            });
            i64.low = 1337;
            return i64;
        }
    }}

    wasmFullPass(`(module
        (import "" "param" (param i64) (result i32))
        (func (result i32) (call 0 (i64.const 0x123456789abcdef0)))
        (export "run" 1))`, 42, imp);

    wasmFullPass(`(module
        (import "" "param" (param i64)(param i64)(param i64)(param i64)(param i64)(param i64)(param i64) (param i64) (result i32))
        (func (result i32) (call 0 (i64.const 0x123456789abcdef0)(i64.const 0x123456789abcdef0)(i64.const 0x123456789abcdef0)(i64.const 0x123456789abcdef0)(i64.const 0x123456789abcdef0)(i64.const 0x123456789abcdef0)(i64.const 0x123456789abcdef0)(i64.const 0x123456789abcdef0)))
        (export "run" 1))`, 42, imp);

    wasmFullPassI64(`(module
        (import "" "result" (param i32) (result i64))
        (func (result i64) (call 0 (i32.const 3)))
        (export "run" 1))`, { low: 0xabcdef01, high: 0x1234567b }, imp);

    // Ensure the ion exit is never taken.
    let ionThreshold = 2 * getJitCompilerOptions()['ion.warmup.trigger'];
    wasmFullPassI64(`(module
        (import "" "paramAndResult" (param i64) (result i64))
        (func (result i64) (local i32) (local i64)
         (set_local 0 (i32.const 0))
         (loop $out $in
             (set_local 1 (call 0 (i64.const 0x123456789abcdef0)))
             (set_local 0 (i32.add (get_local 0) (i32.const 1)))
             (if (i32.le_s (get_local 0) (i32.const ${ionThreshold})) (br $in))
         )
         (get_local 1)
        )
    (export "run" 1))`, { low: 1337, high: 0x12345678 }, imp);

    wasmFullPassI64(`(module
        (import "" "paramAndResult" (param i64) (result i64))
        (func (result i64) (local i32) (local i64)
         (set_local 0 (i32.const 0))
         (block $out
             (loop $in
                 (set_local 1 (call 0 (i64.const 0x123456789abcdef0)))
                 (set_local 0 (i32.add (get_local 0) (i32.const 1)))
                 (if (i32.le_s (get_local 0) (i32.const ${ionThreshold})) (br $in))
             )
         )
         (get_local 1)
        )
    (export "run" 1))`, { low: 1337, high: 0x12345678 }, imp);

    setJitCompilerOption('wasm.test-mode', 0);
}

wasmFailValidateText(`(module (type $t (func)) (func (call_indirect $t (i32.const 0))))`, /can't call_indirect without a table/);

var {v2i, i2i, i2v} = wasmEvalText(`(module
    (type (func (result i32)))
    (type (func (param i32) (result i32)))
    (type (func (param i32)))
    (func (type 0) (i32.const 13))
    (func (type 0) (i32.const 42))
    (func (type 1) (i32.add (get_local 0) (i32.const 1)))
    (func (type 1) (i32.add (get_local 0) (i32.const 2)))
    (func (type 1) (i32.add (get_local 0) (i32.const 3)))
    (func (type 1) (i32.add (get_local 0) (i32.const 4)))
    (table anyfunc (elem 0 1 2 3 4 5))
    (func (param i32) (result i32) (call_indirect 0 (get_local 0)))
    (func (param i32) (param i32) (result i32) (call_indirect 1 (get_local 1) (get_local 0)))
    (func (param i32) (call_indirect 2 (i32.const 0) (get_local 0)))
    (export "v2i" 6)
    (export "i2i" 7)
    (export "i2v" 8)
)`).exports;

const signatureMismatch = /indirect call signature mismatch/;

assertEq(v2i(0), 13);
assertEq(v2i(1), 42);
assertErrorMessage(() => v2i(2), Error, signatureMismatch);
assertErrorMessage(() => v2i(3), Error, signatureMismatch);
assertErrorMessage(() => v2i(4), Error, signatureMismatch);
assertErrorMessage(() => v2i(5), Error, signatureMismatch);

assertErrorMessage(() => i2i(0), Error, signatureMismatch);
assertErrorMessage(() => i2i(1), Error, signatureMismatch);
assertEq(i2i(2, 100), 101);
assertEq(i2i(3, 100), 102);
assertEq(i2i(4, 100), 103);
assertEq(i2i(5, 100), 104);

assertErrorMessage(() => i2v(0), Error, signatureMismatch);
assertErrorMessage(() => i2v(1), Error, signatureMismatch);
assertErrorMessage(() => i2v(2), Error, signatureMismatch);
assertErrorMessage(() => i2v(3), Error, signatureMismatch);
assertErrorMessage(() => i2v(4), Error, signatureMismatch);
assertErrorMessage(() => i2v(5), Error, signatureMismatch);

{
    enableSPSProfiling();

    var stack;
    wasmFullPass(
        `(module
            (type $v2v (func))
            (import $foo "" "f")
            (func $a (call $foo))
            (func $b (result i32) (i32.const 0))
            (table anyfunc (elem $a $b))
            (func $bar (call_indirect $v2v (i32.const 0)))
            (export "run" $bar)
        )`,
        undefined,
        {"":{f:() => { stack = new Error().stack }}}
    );

    disableSPSProfiling();

    var inner = stack.indexOf("wasm-function[1]");
    var outer = stack.indexOf("wasm-function[3]");
    assertEq(inner === -1, false);
    assertEq(outer === -1, false);
    assertEq(inner < outer, true);
}

for (bad of [6, 7, 100, Math.pow(2,31)-1, Math.pow(2,31), Math.pow(2,31)+1, Math.pow(2,32)-2, Math.pow(2,32)-1]) {
    assertThrowsInstanceOf(() => v2i(bad), WebAssembly.RuntimeError);
    assertThrowsInstanceOf(() => i2i(bad, 0), WebAssembly.RuntimeError);
    assertThrowsInstanceOf(() => i2v(bad, 0), WebAssembly.RuntimeError);
}

wasmValidateText('(module (func $foo (nop)) (func (call $foo)))');
wasmValidateText('(module (func (call $foo)) (func $foo (nop)))');
wasmValidateText('(module (import $bar "" "a") (func (call $bar)) (func $foo (nop)))');

// ----------------------------------------------------------------------------
// select

wasmFailValidateText('(module (func (select (i32.const 0) (i32.const 0) (f32.const 0))))', mismatchError("f32", "i32"));

wasmFailValidateText('(module (func (select (i32.const 0) (f32.const 0) (i32.const 0))) (export "" 0))', /select operand types must match/);
wasmFailValidateText('(module (func (select (block ) (i32.const 0) (i32.const 0))) (export "" 0))', /popping value from empty stack/);
assertEq(wasmEvalText('(module (func (select (return) (i32.const 0) (i32.const 0))) (export "" 0))').exports[""](), undefined);
assertEq(wasmEvalText('(module (func (i32.add (i32.const 0) (select (return) (i32.const 0) (i32.const 0)))) (export "" 0))').exports[""](), undefined);
wasmFailValidateText('(module (func (select (if i32 (i32.const 1) (i32.const 0) (f32.const 0)) (i32.const 0) (i32.const 0))) (export "" 0))', mismatchError("f32", "i32"));
wasmFailValidateText('(module (func) (func (select (call 0) (call 0) (i32.const 0))) (export "" 0))', /popping value from empty stack/);

(function testSideEffects() {

var numT = 0;
var numF = 0;

var imports = {"": {
    ifTrue: () => 1 + numT++,
    ifFalse: () => -1 + numF++,
}}

// Test that side-effects are applied on both branches.
var f = wasmEvalText(`
(module
 (import "" "ifTrue" (result i32))
 (import "" "ifFalse" (result i32))
 (func (result i32) (param i32)
  (select
   (call 0)
   (call 1)
   (get_local 0)
  )
 )
 (export "" 2)
)
`, imports).exports[""];

assertEq(f(-1), numT);
assertEq(numT, 1);
assertEq(numF, 1);

assertEq(f(0), numF - 2);
assertEq(numT, 2);
assertEq(numF, 2);

assertEq(f(1), numT);
assertEq(numT, 3);
assertEq(numF, 3);

assertEq(f(0), numF - 2);
assertEq(numT, 4);
assertEq(numF, 4);

assertEq(f(0), numF - 2);
assertEq(numT, 5);
assertEq(numF, 5);

assertEq(f(1), numT);
assertEq(numT, 6);
assertEq(numF, 6);

})();

function testSelect(type, trueVal, falseVal) {

    var trueJS = jsify(trueVal);
    var falseJS = jsify(falseVal);

    // Always true condition
    var alwaysTrue = wasmEvalText(`
    (module
     (func (result ${type}) (param i32)
      (select
       (${type}.const ${trueVal})
       (${type}.const ${falseVal})
       (i32.const 1)
      )
     )
     (export "" 0)
    )
    `, imports).exports[""];

    assertEq(alwaysTrue(0), trueJS);
    assertEq(alwaysTrue(1), trueJS);
    assertEq(alwaysTrue(-1), trueJS);

    // Always false condition
    var alwaysFalse = wasmEvalText(`
    (module
     (func (result ${type}) (param i32)
      (select
       (${type}.const ${trueVal})
       (${type}.const ${falseVal})
       (i32.const 0)
      )
     )
     (export "" 0)
    )
    `, imports).exports[""];

    assertEq(alwaysFalse(0), falseJS);
    assertEq(alwaysFalse(1), falseJS);
    assertEq(alwaysFalse(-1), falseJS);

    // Variable condition
    var f = wasmEvalText(`
    (module
     (func (result ${type}) (param i32)
      (select
       (${type}.const ${trueVal})
       (${type}.const ${falseVal})
       (get_local 0)
      )
     )
     (export "" 0)
    )
    `, imports).exports[""];

    assertEq(f(0), falseJS);
    assertEq(f(1), trueJS);
    assertEq(f(-1), trueJS);

    wasmFullPass(`
    (module
     (func (result ${type}) (param i32)
      (select
       (${type}.const ${trueVal})
       (${type}.const ${falseVal})
       (get_local 0)
      )
     )
     (export "run" 0)
    )`,
    trueJS,
    imports,
    1);
}

testSelect('i32', 13, 37);
testSelect('i32', Math.pow(2, 31) - 1, -Math.pow(2, 31));

testSelect('f32', Math.fround(13.37), Math.fround(19.89));
testSelect('f32', 'infinity', '-0');
testSelect('f32', 'nan', Math.pow(2, -31));

testSelect('f64', 13.37, 19.89);
testSelect('f64', 'infinity', '-0');
testSelect('f64', 'nan', Math.pow(2, -31));

{
    setJitCompilerOption('wasm.test-mode', 1);

    var f = wasmEvalText(`
    (module
     (func (result i64) (param i32)
      (select
       (i64.const 0xc0010ff08badf00d)
       (i64.const 0x12345678deadc0de)
       (get_local 0)
      )
     )
     (export "" 0)
    )`, imports).exports[""];

    assertEqI64(f(0),  { low: 0xdeadc0de, high: 0x12345678});
    assertEqI64(f(1),  { low: 0x8badf00d, high: 0xc0010ff0});
    assertEqI64(f(-1), { low: 0x8badf00d, high: 0xc0010ff0});

    setJitCompilerOption('wasm.test-mode', 0);
}
