load(libdir + "wasm.js");

const { Instance, Module } = WebAssembly;

// Locally-defined globals
assertErrorMessage(() => wasmEvalText(`(module (global))`), SyntaxError, /parsing/);
assertErrorMessage(() => wasmEvalText(`(module (global i32))`), SyntaxError, /parsing/);
assertErrorMessage(() => wasmEvalText(`(module (global (mut i32)))`), SyntaxError, /parsing/);

// Initializer expressions.
wasmFailValidateText(`(module (global i32 (f32.const 13.37)))`, /type mismatch/);
wasmFailValidateText(`(module (global f64 (f32.const 13.37)))`, /type mismatch/);
wasmFailValidateText(`(module (global i32 (i32.add (i32.const 13) (i32.const 37))))`, /failed to read end/);

wasmFailValidateText(`(module (global i32 (get_global 0)))`, /out of range/);
wasmFailValidateText(`(module (global i32 (get_global 1)) (global i32 (i32.const 1)))`, /out of range/);

// Test a well-defined global section.
function testInner(type, initialValue, nextValue, coercion, assertFunc = assertEq)
{
    var module = wasmEvalText(`(module
        (global (mut ${type}) (${type}.const ${initialValue}))
        (global ${type} (${type}.const ${initialValue}))

        (func $get (result ${type}) (get_global 0))
        (func $set (param ${type}) (set_global 0 (get_local 0)))

        (func $get_cst (result ${type}) (get_global 1))

        (export "get" $get)
        (export "get_cst" $get_cst)

        (export "set" $set)
    )`).exports;

    assertFunc(module.get(), coercion(initialValue));
    assertEq(module.set(coercion(nextValue)), undefined);
    assertFunc(module.get(), coercion(nextValue));

    assertFunc(module.get_cst(), coercion(initialValue));
}

testInner('i32', 13, 37, x => x|0);
testInner('f32', 13.37, 0.1989, Math.fround);
testInner('f64', 13.37, 0.1989, x => +x);

// Semantic errors.
wasmFailValidateText(`(module (global (mut i32) (i32.const 1337)) (func (set_global 1 (i32.const 0))))`, /out of range/);
wasmFailValidateText(`(module (global i32 (i32.const 1337)) (func (set_global 0 (i32.const 0))))`, /can't write an immutable global/);

// Big module with many variables: test that setting one doesn't overwrite the
// other ones.
function get_set(i, type) {
    return `
        (func $get_${i} (result ${type}) (get_global ${i}))
        (func $set_${i} (param ${type}) (set_global ${i} (get_local 0)))
    `;
}

var module = wasmEvalText(`(module
    (global (mut i32) (i32.const 42))
    (global (mut i32) (i32.const 10))
    (global (mut f32) (f32.const 13.37))
    (global (mut f64) (f64.const 13.37))
    (global (mut i32) (i32.const -18))

    ${get_set(0, 'i32')}
    ${get_set(1, 'i32')}
    ${get_set(2, 'f32')}
    ${get_set(3, 'f64')}
    ${get_set(4, 'i32')}

    (export "get0" $get_0) (export "set0" $set_0)
    (export "get1" $get_1) (export "set1" $set_1)
    (export "get2" $get_2) (export "set2" $set_2)
    (export "get3" $get_3) (export "set3" $set_3)
    (export "get4" $get_4) (export "set4" $set_4)
)`).exports;

let values = [42, 10, Math.fround(13.37), 13.37, -18];
let nextValues = [13, 37, Math.fround(-17.89), 9.3, -13];
for (let i = 0; i < 5; i++) {
    assertEq(module[`get${i}`](), values[i]);
    assertEq(module[`set${i}`](nextValues[i]), undefined);
    assertEq(module[`get${i}`](), nextValues[i]);
    for (let j = 0; j < 5; j++) {
        if (i === j)
            continue;
        assertEq(module[`get${j}`](), values[j]);
    }
    assertEq(module[`set${i}`](values[i]), undefined);
    assertEq(module[`get${i}`](), values[i]);
}

// Initializer expressions can also be used in elem section initializers.
wasmFailValidateText(`(module (import "globals" "a" (global f32)) (table 4 anyfunc) (elem (get_global 0) $f) (func $f))`, /type mismatch/);

module = wasmEvalText(`(module
    (import "globals" "a" (global i32))
    (table (export "tbl") 4 anyfunc)
    (elem (get_global 0) $f)
    (func $f)
    (export "f" $f)
)`, {
    globals: {
        a: 1
    }
}).exports;
assertEq(module.f, module.tbl.get(1));

// Import/export rules.
wasmFailValidateText(`(module (import "globals" "x" (global (mut i32))))`, /can't import.* mutable globals in the MVP/);
wasmFailValidateText(`(module (global (mut i32) (i32.const 42)) (export "" global 0))`, /can't .*export mutable globals in the MVP/);

// Import/export semantics.
module = wasmEvalText(`(module
 (import $g "globals" "x" (global i32))
 (func $get (result i32) (get_global $g))
 (export "getter" $get)
 (export "value" global 0)
)`, { globals: {x: 42} }).exports;

assertEq(module.getter(), 42);
assertEq(module.value, 42);

// Can only import numbers (no implicit coercions).
module = new WebAssembly.Module(wasmTextToBinary(`(module
    (global (import "globs" "i32") i32)
    (global (import "globs" "f32") f32)
    (global (import "globs" "f64") f32)
)`));

const assertLinkFails = (m, imp, err) => {
    assertErrorMessage(() => new WebAssembly.Instance(m, imp), TypeError, err);
}

var imp = {
    globs: {
        i32: 0,
        f32: Infinity,
        f64: NaN
    }
};

let i = new WebAssembly.Instance(module, imp);

for (let v of [
    null,
    {},
    "42",
    /not a number/,
    false,
    undefined,
    Symbol(),
    { valueOf() { return 42; } }
]) {
    imp.globs.i32 = v;
    assertLinkFails(module, imp, /not a number/);

    imp.globs.i32 = 0;
    imp.globs.f32 = v;
    assertLinkFails(module, imp, /not a number/);

    imp.globs.f32 = Math.fround(13.37);
    imp.globs.f64 = v;
    assertLinkFails(module, imp, /not a number/);

    imp.globs.f64 = 13.37;
}

// Imported globals and locally defined globals use the same index space.
module = wasmEvalText(`(module
 (import "globals" "x" (global i32))
 (global i32 (i32.const 1337))
 (export "imported" global 0)
 (export "defined" global 1)
)`, { globals: {x: 42} }).exports;

assertEq(module.imported, 42);
assertEq(module.defined, 1337);

// Initializer expressions can reference an imported immutable global.
wasmFailValidateText(`(module (global f32 (f32.const 13.37)) (global i32 (get_global 0)))`, /must reference a global immutable import/);
wasmFailValidateText(`(module (global (mut f32) (f32.const 13.37)) (global i32 (get_global 0)))`, /must reference a global immutable import/);
wasmFailValidateText(`(module (global (mut i32) (i32.const 0)) (global i32 (get_global 0)))`, /must reference a global immutable import/);

wasmFailValidateText(`(module (import "globals" "a" (global f32)) (global i32 (get_global 0)))`, /type mismatch/);

function testInitExpr(type, initialValue, nextValue, coercion, assertFunc = assertEq) {
    var module = wasmEvalText(`(module
        (import "globals" "a" (global ${type}))

        (global (mut ${type}) (get_global 0))
        (global ${type} (get_global 0))

        (func $get0 (result ${type}) (get_global 0))

        (func $get1 (result ${type}) (get_global 1))
        (func $set1 (param ${type}) (set_global 1 (get_local 0)))

        (func $get_cst (result ${type}) (get_global 2))

        (export "get0" $get0)
        (export "get1" $get1)
        (export "get_cst" $get_cst)

        (export "set1" $set1)
    )`, {
        globals: {
            a: coercion(initialValue)
        }
    }).exports;

    assertFunc(module.get0(), coercion(initialValue));
    assertFunc(module.get1(), coercion(initialValue));

    assertEq(module.set1(coercion(nextValue)), undefined);
    assertFunc(module.get1(), coercion(nextValue));
    assertFunc(module.get0(), coercion(initialValue));

    assertFunc(module.get_cst(), coercion(initialValue));
}

testInitExpr('i32', 13, 37, x => x|0);
testInitExpr('f32', 13.37, 0.1989, Math.fround);
testInitExpr('f64', 13.37, 0.1989, x => +x);

// Int64.
{
    wasmFailValidateText(`(module (import "globals" "x" (global i64)))`, /can't import.* an Int64 global/);
    wasmFailValidateText(`(module (global i64 (i64.const 42)) (export "" global 0))`, /can't .*export an Int64 global/);

    setJitCompilerOption('wasm.test-mode', 1);
    testInner('i64', '0x531642753864975F', '0x123456789abcdef0', createI64, assertEqI64);
    testInitExpr('i64', '0x531642753864975F', '0x123456789abcdef0', createI64, assertEqI64);

    module = wasmEvalText(`(module
     (import "globals" "x" (global i64))
     (global i64 (i64.const 0xFAFADADABABA))
     (export "imported" global 0)
     (export "defined" global 1)
    )`, { globals: {x: createI64('0x1234567887654321')} }).exports;

    assertEqI64(module.imported, createI64('0x1234567887654321'));
    assertEqI64(module.defined, createI64('0xFAFADADABABA'));

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