WebAssembly JS API: implement Global
authorkeith_miller@apple.com <keith_miller@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 14 Dec 2016 21:29:14 +0000 (21:29 +0000)
committerkeith_miller@apple.com <keith_miller@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 14 Dec 2016 21:29:14 +0000 (21:29 +0000)
https://bugs.webkit.org/show_bug.cgi?id=164133

Reviewed by Saam Barati.

JSTests:

* wasm/Builder.js:
(export.default.Builder.prototype._registerSectionBuilders.switch.case.string_appeared_here.this.section):
* wasm/Builder_WebAssemblyBinary.js:
(const.valueType.WASM.description.type.i32.type.const.putGlobalType):
(const.putOp):
(const.putInitExpr):
(const.emitters.Import):
(const.emitters.Global):
(const.emitters.Export):
(const.emitters.Code):
* wasm/LowLevelBinary.js:
(export.default.LowLevelBinary.prototype.varuint32):
(export.default.LowLevelBinary.prototype.varint32):
* wasm/js-api/global-error.js: Added.
(catch):
(assert.truthy):
* wasm/js-api/global-external-init-from-import.js: Added.
* wasm/js-api/global-internal-init-from-import.js: Added.
* wasm/js-api/global-mutate.js: Added.
(createInternalGlobalModule):
* wasm/js-api/globals-export.js: Added.
* wasm/js-api/globals-import.js: Added.
* wasm/wasm.json:

Source/JavaScriptCore:

This patch adds support for globals. It handles imports, exports
and internal globals. In the MVP only internal globals are allowed
to be mutable. This means we can store a C-array of 64-bit slots
off the instance holding them. When globals are exported to JS
they are done so as numbers. This means that i64 globals cannot be
imported or exported.

* wasm/WasmB3IRGenerator.cpp:
(JSC::Wasm::B3IRGenerator::B3IRGenerator):
(JSC::Wasm::B3IRGenerator::getGlobal):
(JSC::Wasm::B3IRGenerator::setGlobal):
(JSC::Wasm::B3IRGenerator::addCallIndirect):
(JSC::Wasm::parseAndCompile):
* wasm/WasmFormat.h:
* wasm/WasmFunctionParser.h:
(JSC::Wasm::FunctionParser<Context>::parseExpression):
* wasm/WasmModuleParser.cpp:
(JSC::Wasm::ModuleParser::parseImport):
(JSC::Wasm::ModuleParser::parseGlobal):
(JSC::Wasm::ModuleParser::parseExport):
(JSC::Wasm::ModuleParser::parseElement):
(JSC::Wasm::ModuleParser::parseInitExpr):
(JSC::Wasm::ModuleParser::parseGlobalType):
(JSC::Wasm::ModuleParser::parseData):
* wasm/WasmModuleParser.h:
* wasm/WasmParser.h:
(JSC::Wasm::Parser::parseVarInt32):
(JSC::Wasm::Parser::parseVarInt64):
(JSC::Wasm::Parser::parseUInt64):
* wasm/WasmValidate.cpp:
(JSC::Wasm::Validate::hasMemory):
(JSC::Wasm::Validate::Validate):
(JSC::Wasm::Validate::getGlobal):
(JSC::Wasm::Validate::setGlobal):
(JSC::Wasm::validateFunction):
* wasm/generateWasmOpsHeader.py:
* wasm/js/JSWebAssemblyInstance.cpp:
(JSC::JSWebAssemblyInstance::create):
(JSC::JSWebAssemblyInstance::finishCreation):
(JSC::JSWebAssemblyInstance::visitChildren):
* wasm/js/JSWebAssemblyInstance.h:
(JSC::JSWebAssemblyInstance::loadI32Global):
(JSC::JSWebAssemblyInstance::loadI64Global):
(JSC::JSWebAssemblyInstance::loadF32Global):
(JSC::JSWebAssemblyInstance::loadF64Global):
(JSC::JSWebAssemblyInstance::setGlobal):
(JSC::JSWebAssemblyInstance::offsetOfGlobals):
* wasm/js/WebAssemblyInstanceConstructor.cpp:
(JSC::constructJSWebAssemblyInstance):
* wasm/js/WebAssemblyModuleRecord.cpp:
(JSC::WebAssemblyModuleRecord::finishCreation):
(JSC::WebAssemblyModuleRecord::link):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@209830 268f45cc-cd09-0410-ab3c-d52691b4dbfc

24 files changed:
JSTests/ChangeLog
JSTests/wasm/Builder.js
JSTests/wasm/Builder_WebAssemblyBinary.js
JSTests/wasm/LowLevelBinary.js
JSTests/wasm/js-api/global-error.js [new file with mode: 0644]
JSTests/wasm/js-api/global-external-init-from-import.js [new file with mode: 0644]
JSTests/wasm/js-api/global-internal-init-from-import.js [new file with mode: 0644]
JSTests/wasm/js-api/global-mutate.js [new file with mode: 0644]
JSTests/wasm/js-api/globals-export.js [new file with mode: 0644]
JSTests/wasm/js-api/globals-import.js [new file with mode: 0644]
JSTests/wasm/wasm.json
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/wasm/WasmB3IRGenerator.cpp
Source/JavaScriptCore/wasm/WasmFormat.h
Source/JavaScriptCore/wasm/WasmFunctionParser.h
Source/JavaScriptCore/wasm/WasmModuleParser.cpp
Source/JavaScriptCore/wasm/WasmModuleParser.h
Source/JavaScriptCore/wasm/WasmParser.h
Source/JavaScriptCore/wasm/WasmValidate.cpp
Source/JavaScriptCore/wasm/generateWasmOpsHeader.py
Source/JavaScriptCore/wasm/js/JSWebAssemblyInstance.cpp
Source/JavaScriptCore/wasm/js/JSWebAssemblyInstance.h
Source/JavaScriptCore/wasm/js/WebAssemblyInstanceConstructor.cpp
Source/JavaScriptCore/wasm/js/WebAssemblyModuleRecord.cpp

index 7947658..bf17037 100644 (file)
@@ -1,3 +1,34 @@
+2016-12-14  Keith Miller  <keith_miller@apple.com>
+
+        WebAssembly JS API: implement Global
+        https://bugs.webkit.org/show_bug.cgi?id=164133
+
+        Reviewed by Saam Barati.
+
+        * wasm/Builder.js:
+        (export.default.Builder.prototype._registerSectionBuilders.switch.case.string_appeared_here.this.section):
+        * wasm/Builder_WebAssemblyBinary.js:
+        (const.valueType.WASM.description.type.i32.type.const.putGlobalType):
+        (const.putOp):
+        (const.putInitExpr):
+        (const.emitters.Import):
+        (const.emitters.Global):
+        (const.emitters.Export):
+        (const.emitters.Code):
+        * wasm/LowLevelBinary.js:
+        (export.default.LowLevelBinary.prototype.varuint32):
+        (export.default.LowLevelBinary.prototype.varint32):
+        * wasm/js-api/global-error.js: Added.
+        (catch):
+        (assert.truthy):
+        * wasm/js-api/global-external-init-from-import.js: Added.
+        * wasm/js-api/global-internal-init-from-import.js: Added.
+        * wasm/js-api/global-mutate.js: Added.
+        (createInternalGlobalModule):
+        * wasm/js-api/globals-export.js: Added.
+        * wasm/js-api/globals-import.js: Added.
+        * wasm/wasm.json:
+
 2016-12-13  Saam Barati  <sbarati@apple.com>
 
         WebAssembly: implement the elements section
index 322da03..11572da 100644 (file)
@@ -174,6 +174,41 @@ const _exportFunctionContinuation = (builder, section, nextBuilder) => {
     };
 };
 
+const _normalizeMutability = (mutability) => {
+    if (mutability === "mutable")
+        return 1;
+    else if (mutability === "immutable")
+        return 0;
+    else
+        throw new Error(`mutability should be either "mutable" or "immutable", but got ${global.mutablity}`);
+};
+
+const _exportGlobalContinuation = (builder, section, nextBuilder) => {
+    return (field, index) => {
+        assert.isNumber(index, `Global exports only support number indices right now`);
+        section.data.push({ field, kind: "Global", index });
+        return nextBuilder;
+    }
+};
+
+const _importGlobalContinuation = (builder, section, nextBuilder) => {
+    return () => {
+        const globalBuilder = {
+            End: () => nextBuilder
+        };
+        for (let op of WASM.description.value_type) {
+            globalBuilder[_toJavaScriptName(op)] = (module, field, mutability) => {
+                assert.isString(module, `Import global module should be a string, got "${module}"`);
+                assert.isString(field, `Import global field should be a string, got "${field}"`);
+                assert.isString(mutability, `Import global mutability should be a string, got "${mutability}"`);
+                section.data.push({ globalDescription: { type: op, mutability: _normalizeMutability(mutability) }, module, field, kind: "Global" });
+                return globalBuilder;
+            };
+        }
+        return globalBuilder;
+    };
+};
+
 const _checkStackArgs = (op, param) => {
     for (let expect of param) {
         if (WASM.isValidType(expect)) {
@@ -202,6 +237,7 @@ const _checkStackReturn = (op, ret) => {
         } else {
             // Handle our own meta-types.
             switch (expect) {
+            case "any": break;
             case "bool": break; // FIXME implement bool. https://bugs.webkit.org/show_bug.cgi?id=163421
             case "call": break; // FIXME implement call stack return check based on function signature. https://bugs.webkit.org/show_bug.cgi?id=163421
             case "control": break; // FIXME implement control. https://bugs.webkit.org/show_bug.cgi?id=163421
@@ -407,8 +443,8 @@ export default class Builder {
                     const s = this._addSection(section);
                     const importBuilder = {
                         End: () => this,
-                        Global: () => { throw new Error(`Unimplemented: import global`); },
                     };
+                    importBuilder.Global = _importGlobalContinuation(this, s, importBuilder);
                     importBuilder.Function = _importFunctionContinuation(this, s, importBuilder);
                     importBuilder.Memory = _importMemoryContinuation(this, s, importBuilder);
                     importBuilder.Table = _importTableContinuation(this, s, importBuilder);
@@ -456,8 +492,23 @@ export default class Builder {
                 break;
 
             case "Global":
-                // FIXME implement global https://bugs.webkit.org/show_bug.cgi?id=164133
-                this[section] = () => { throw new Error(`Unimplemented: section type "${section}"`); };
+                this[section] = function() {
+                    const s = this._addSection(section);
+                    const globalBuilder = {
+                        End: () => this,
+                        GetGlobal: (type, initValue, mutability) => {
+                            s.data.push({ type, op: "get_global", mutability: _normalizeMutability(mutability), initValue });
+                            return globalBuilder;
+                        }
+                    };
+                    for (let op of WASM.description.value_type) {
+                        globalBuilder[_toJavaScriptName(op)] = (initValue, mutability) => {
+                            s.data.push({ type: op, op: op + ".const", mutability: _normalizeMutability(mutability), initValue });
+                            return globalBuilder;
+                        };
+                    }
+                    return globalBuilder;
+                };
                 break;
 
             case "Export":
@@ -467,8 +518,8 @@ export default class Builder {
                         End: () => this,
                         Table: () => { throw new Error(`Unimplemented: export table`); },
                         Memory: () => { throw new Error(`Unimplemented: export memory`); },
-                        Global: () => { throw new Error(`Unimplemented: export global`); },
                     };
+                    exportBuilder.Global = _exportGlobalContinuation(this, s, exportBuilder);
                     exportBuilder.Function = _exportFunctionContinuation(this, s, exportBuilder);
                     return exportBuilder;
                 };
@@ -508,7 +559,7 @@ export default class Builder {
                     const builder = this;
                     const codeBuilder =  {
                         End: () => {
-                            // We now have enough information to remap the export section's "type" and "index" according to the Code section we're currently ending.
+                            // We now have enough information to remap the export section's "type" and "index" according to the Code section we are currently ending.
                             const typeSection = builder._getSection("Type");
                             const importSection = builder._getSection("Import");
                             const exportSection = builder._getSection("Export");
@@ -516,6 +567,8 @@ export default class Builder {
                             const codeSection = s;
                             if (exportSection) {
                                 for (const e of exportSection.data) {
+                                    if (e.kind !== "Function" || typeof(e.type) !== "undefined")
+                                        continue;
                                     switch (typeof(e.index)) {
                                     default: throw new Error(`Unexpected export index "${e.index}"`);
                                     case "string": {
index 349ca52..cacebe3 100644 (file)
@@ -51,6 +51,40 @@ const putTable = (bin, {initial, maximum, element}) => {
     putResizableLimits(bin, initial, maximum);
 };
 
+const valueType = WASM.description.type.i32.type
+
+const putGlobalType = (bin, global) => {
+    put(bin, valueType, WASM.typeValue[global.type]);
+    put(bin, "varuint1", global.mutability);
+};
+
+const putOp = (bin, op) => {
+    put(bin, "uint8", op.value);
+    if (op.arguments.length !== 0)
+        throw new Error(`Unimplemented: arguments`); // FIXME https://bugs.webkit.org/show_bug.cgi?id=162706
+
+    switch (op.name) {
+    default:
+        for (let i = 0; i < op.immediates.length; ++i) {
+            const type = WASM.description.opcode[op.name].immediate[i].type
+            if (!bin[type])
+                throw new TypeError(`Unknown type: ${type} in op: ${op.name}`);
+            put(bin, type, op.immediates[i]);
+        }
+        break;
+    case "br_table":
+        put(bin, "varuint32", op.immediates.length - 1);
+        for (let imm of op.immediates)
+            put(bin, "varuint32", imm);
+        break;
+    }
+};
+
+const putInitExpr = (bin, expr) => {
+    putOp(bin, { value: WASM.description.opcode[expr.op].value, name: expr.op, immediates: [expr.initValue], arguments: [] });
+    putOp(bin, { value: WASM.description.opcode.end.value, name: "end", immediates: [], arguments: [] });
+};
+
 const emitters = {
     Type: (section, bin) => {
         put(bin, "varuint32", section.data.length);
@@ -88,7 +122,9 @@ const emitters = {
                 putResizableLimits(bin, initial, maximum);
                 break;
             };
-            case "Global": throw new Error(`Not yet implemented`);
+            case "Global":
+                putGlobalType(bin, entry.globalDescription);
+                break;
             }
         }
     },
@@ -117,7 +153,14 @@ const emitters = {
         }
     },
 
-    Global: (section, bin) => { throw new Error(`Not yet implemented`); },
+    Global: (section, bin) => {
+        put(bin, "varuint32", section.data.length);
+        for (const global of section.data) {
+            putGlobalType(bin, global);
+            putInitExpr(bin, global)
+        }
+    },
+
     Export: (section, bin) => {
         put(bin, "varuint32", section.data.length);
         for (const entry of section.data) {
@@ -125,10 +168,13 @@ const emitters = {
             put(bin, "uint8", WASM.externalKindValue[entry.kind]);
             switch (entry.kind) {
             default: throw new Error(`Implementation problem: unexpected kind ${entry.kind}`);
-            case "Function": put(bin, "varuint32", entry.index); break;
+            case "Global":
+            case "Function":
+                put(bin, "varuint32", entry.index);
+                break;
             case "Table": throw new Error(`Not yet implemented`);
             case "Memory": throw new Error(`Not yet implemented`);
-            case "Global": throw new Error(`Not yet implemented`);
+
             }
         }
     },
@@ -164,27 +210,9 @@ const emitters = {
                 put(funcBin, "varint7", WASM.typeValue[func.locals[i]]);
             }
 
-            for (const op of func.code) {
-                put(funcBin, "uint8", op.value);
-                if (op.arguments.length !== 0)
-                    throw new Error(`Unimplemented: arguments`); // FIXME https://bugs.webkit.org/show_bug.cgi?id=162706
-
-                switch (op.name) {
-                default:
-                    for (let i = 0; i < op.immediates.length; ++i) {
-                        const type = WASM.description.opcode[op.name].immediate[i].type
-                        if (!funcBin[type])
-                            throw new TypeError(`Unknown type: ${type} in op: ${op.name}`);
-                        put(funcBin, type, op.immediates[i]);
-                    }
-                    break;
-                case "br_table":
-                    put(funcBin, "varuint32", op.immediates.length - 1);
-                    for (let imm of op.immediates)
-                        put(funcBin, "varuint32", imm);
-                    break;
-                }
-            }
+            for (const op of func.code)
+                putOp(funcBin, op);
+
             funcBin.apply();
         }
     },
index 814cb91..ee4fbbc 100644 (file)
@@ -123,6 +123,7 @@ export default class LowLevelBinary {
         this._push8(v >>> 24);
     }
     varuint32(v) {
+        assert.isNumber(v);
         if (v < varuint32Min || varuint32Max < v)
             throw new RangeError(`Invalid varuint32 ${v} range is [${varuint32Min}, ${varuint32Max}]`);
         while (v >= 0x80) {
@@ -132,6 +133,7 @@ export default class LowLevelBinary {
         this.uint8(v);
     }
     varint32(v) {
+        assert.isNumber(v);
         if (v < varint32Min || varint32Max < v)
             throw new RangeError(`Invalid varint32 ${v} range is [${varint32Min}, ${varint32Max}]`);
         do {
diff --git a/JSTests/wasm/js-api/global-error.js b/JSTests/wasm/js-api/global-error.js
new file mode 100644 (file)
index 0000000..6fa35ec
--- /dev/null
@@ -0,0 +1,238 @@
+import * as assert from '../assert.js';
+import Builder from '../Builder.js';
+
+{
+    // Test init from non-import.
+    const builder = new Builder();
+
+    builder.Type().End()
+        .Function().End()
+        .Global().GetGlobal("i32", 0, "immutable").End()
+        .Export()
+            .Function("getGlobal")
+            .Global("global", 1)
+        .End()
+        .Code()
+
+        .Function("getGlobal", { params: [], ret: "i32" })
+        .GetGlobal(1)
+        .End()
+
+        .End()
+
+    const bin = builder.WebAssembly();
+    bin.trim();
+
+    let passed = false;
+    try {
+        const module = new WebAssembly.Module(bin.get());
+    } catch (e) {
+        if (e.message === "couldn't parse section Global: Global declarations (evaluating 'new WebAssembly.Module(bin.get())')")
+            passed = true;
+    }
+    assert.truthy(passed);
+}
+
+
+{
+    // Test import mutable.
+    const builder = new Builder();
+
+    builder.Type().End()
+        .Import()
+            .Global().I32("imp", "global", "mutable").End()
+        .End()
+        .Function().End()
+        .Global().GetGlobal("i32", 0, "immutable").End()
+        .Export()
+            .Function("getGlobal")
+            .Global("global", 1)
+        .End()
+        .Code()
+
+        .Function("getGlobal", { params: [], ret: "i32" })
+        .GetGlobal(1)
+        .End()
+
+        .End()
+
+    const bin = builder.WebAssembly();
+    bin.trim();
+
+    let passed = false;
+    try {
+        const module = new WebAssembly.Module(bin.get());
+    } catch (e) {
+        if (e.message === "couldn't parse section Import: Import declarations (evaluating 'new WebAssembly.Module(bin.get())')")
+            passed = true;
+    }
+    assert.truthy(passed);
+}
+
+{
+    // Test export mutable.
+    const builder = new Builder();
+
+    builder.Type().End()
+        .Function().End()
+        .Global().I32(0, "mutable").End()
+        .Export()
+            .Function("setGlobal")
+            .Global("global", 0)
+        .End()
+        .Code()
+
+        .Function("setGlobal", { params: [], ret: "i32" })
+        .GetGlobal(1)
+        .End()
+
+        .End()
+
+    const bin = builder.WebAssembly();
+    bin.trim();
+
+    let passed = false;
+    try {
+        const module = new WebAssembly.Module(bin.get());
+    } catch (e) {
+        if (e.message === "couldn't parse section Export: Exports (evaluating 'new WebAssembly.Module(bin.get())')")
+            passed = true;
+    }
+    assert.truthy(passed);
+}
+
+{
+    // Test set immutable.
+    const builder = new Builder();
+
+    builder.Type().End()
+        .Function().End()
+        .Global().I32(0, "immutable").End()
+        .Export()
+            .Function("setGlobal")
+            .Global("global", 0)
+        .End()
+        .Code()
+
+        .Function("setGlobal", { params: [], ret: "i32" })
+        .I32Const(0)
+        .SetGlobal(0)
+        .End()
+
+        .End()
+
+    const bin = builder.WebAssembly();
+    bin.trim();
+
+    let passed = false;
+    try {
+        const module = new WebAssembly.Module(bin.get());
+    } catch (e) {
+        if (e.message === "Attempt to store to immutable global. (evaluating 'new WebAssembly.Module(bin.get())')")
+            passed = true;
+    }
+    assert.truthy(passed);
+}
+
+
+{
+    // Test set non-existant global.
+    const builder = new Builder();
+
+    builder.Type().End()
+        .Function().End()
+        .Global().I32(0, "immutable").End()
+        .Export()
+            .Function("setGlobal")
+            .Global("global", 0)
+        .End()
+        .Code()
+
+        .Function("setGlobal", { params: [], ret: "i32" })
+        .I32Const(0)
+        .SetGlobal(1)
+        .End()
+
+        .End()
+
+    const bin = builder.WebAssembly();
+    bin.trim();
+
+    let passed = false;
+    try {
+        const module = new WebAssembly.Module(bin.get());
+    } catch (e) {
+        if (e.message === "Attempt to use unknown global. (evaluating 'new WebAssembly.Module(bin.get())')")
+            passed = true;
+    }
+    assert.truthy(passed);
+}
+
+
+{
+    // Test set to incorrect type
+    const builder = new Builder();
+
+    builder.Type().End()
+        .Function().End()
+        .Global().F32(0, "mutable").End()
+        .Export()
+            .Function("setGlobal")
+        .End()
+        .Code()
+
+        .Function("setGlobal", { params: [], ret: "i32" })
+        .I32Const(0)
+        .SetGlobal(0)
+        .End()
+
+        .End()
+
+    const bin = builder.WebAssembly();
+    bin.trim();
+
+    let passed = false;
+    try {
+        const module = new WebAssembly.Module(bin.get());
+    } catch (e) {
+        if (e.message === "Attempt to set global with type: F32 with a variable of type: I32 (evaluating 'new WebAssembly.Module(bin.get())')")
+            passed = true;
+    }
+    assert.truthy(passed);
+}
+
+for ( let imp of [undefined, null, {}, () => {}, "number", new Number(4)]) {
+    // Test import non-number.
+    const builder = new Builder();
+
+    builder.Type().End()
+        .Import()
+            .Global().I32("imp", "global", "immutable").End()
+        .End()
+        .Function().End()
+        .Global().GetGlobal("i32", 0, "immutable").End()
+        .Export()
+            .Function("getGlobal")
+            .Global("global", 1)
+        .End()
+        .Code()
+
+        .Function("getGlobal", { params: [], ret: "i32" })
+        .GetGlobal(1)
+        .End()
+
+        .End()
+
+    const bin = builder.WebAssembly();
+    bin.trim();
+
+    let passed = false;
+    const module = new WebAssembly.Module(bin.get());
+    try {
+        new WebAssembly.Instance(module, { imp: { global: imp } });
+    } catch (e) {
+        if (e.message === "imported global must be a number (evaluating 'new WebAssembly.Instance(module, { imp: { global: imp } })')")
+            passed = true;
+    }
+    assert.truthy(passed);
+}
diff --git a/JSTests/wasm/js-api/global-external-init-from-import.js b/JSTests/wasm/js-api/global-external-init-from-import.js
new file mode 100644 (file)
index 0000000..41fc6fa
--- /dev/null
@@ -0,0 +1,30 @@
+import * as assert from '../assert.js';
+import Builder from '../Builder.js';
+
+const builder = new Builder();
+
+builder.Type().End()
+    .Import()
+        .Global().I32("imp", "global", "immutable").End()
+    .End()
+    .Function().End()
+    .Global().GetGlobal("i32", 0, "immutable").End()
+    .Export()
+        .Function("getGlobal")
+    .Global("global", 1)
+    .End()
+    .Code()
+
+    .Function("getGlobal", { params: [], ret: "i32" })
+        .GetGlobal(1)
+    .End()
+
+    .End()
+
+const bin = builder.WebAssembly();
+bin.trim();
+
+const module = new WebAssembly.Module(bin.get());
+const instance = new WebAssembly.Instance(module, { imp: { global: 5 } });
+assert.eq(instance.exports.getGlobal(), 5);
+assert.eq(instance.exports.global, 5);
diff --git a/JSTests/wasm/js-api/global-internal-init-from-import.js b/JSTests/wasm/js-api/global-internal-init-from-import.js
new file mode 100644 (file)
index 0000000..91bf35b
--- /dev/null
@@ -0,0 +1,26 @@
+import * as assert from '../assert.js';
+import Builder from '../Builder.js';
+
+const builder = new Builder();
+
+builder.Type().End()
+    .Import()
+        .Global().I32("imp", "global", "immutable").End()
+    .End()
+    .Function().End()
+    .Global().GetGlobal("i32", 0, "mutable").End()
+    .Export().Function("getGlobal").End()
+    .Code()
+
+    .Function("getGlobal", { params: [], ret: "i32" })
+        .GetGlobal(1)
+    .End()
+
+    .End()
+
+const bin = builder.WebAssembly();
+bin.trim();
+
+const module = new WebAssembly.Module(bin.get());
+const instance = new WebAssembly.Instance(module, { imp: { global: 5 } });
+assert.eq(instance.exports.getGlobal(), 5);
diff --git a/JSTests/wasm/js-api/global-mutate.js b/JSTests/wasm/js-api/global-mutate.js
new file mode 100644 (file)
index 0000000..06f7f22
--- /dev/null
@@ -0,0 +1,39 @@
+import * as assert from '../assert.js';
+import Builder from '../Builder.js';
+
+function createInternalGlobalModule() {
+    const builder = new Builder();
+
+    builder.Type().End()
+        .Function().End()
+        .Global().I32(5, "mutable").End()
+        .Export()
+            .Function("getGlobal")
+            .Function("setGlobal")
+        .End()
+        .Code()
+
+        // GetGlobal
+        .Function("getGlobal", { params: [], ret: "i32" })
+            .GetGlobal(0)
+        .End()
+
+        // SetGlobal
+        .Function("setGlobal", { params: ["i32"] })
+            .GetLocal(0)
+            .SetGlobal(0)
+        .End()
+
+        .End()
+
+    const bin = builder.WebAssembly();
+    bin.trim();
+
+    const module = new WebAssembly.Module(bin.get());
+    const instance = new WebAssembly.Instance(module);
+    assert.eq(instance.exports.getGlobal(), 5);
+    instance.exports.setGlobal(3);
+    assert.eq(instance.exports.getGlobal(), 3);
+}
+
+createInternalGlobalModule();
diff --git a/JSTests/wasm/js-api/globals-export.js b/JSTests/wasm/js-api/globals-export.js
new file mode 100644 (file)
index 0000000..6668269
--- /dev/null
@@ -0,0 +1,28 @@
+import * as assert from '../assert.js';
+import Builder from '../Builder.js';
+
+const builder = new Builder();
+
+builder.Type().End()
+    .Import()
+        .Global().I32("imp", "global", "immutable").End()
+    .End()
+    .Function().End()
+    .Export()
+        .Function("getGlobal")
+        .Global("global", 0)
+    .End()
+    .Code()
+
+    .Function("getGlobal", { params: [], ret: "i32" })
+        .GetGlobal(0)
+    .End()
+
+    .End()
+
+const bin = builder.WebAssembly();
+bin.trim();
+
+const module = new WebAssembly.Module(bin.get());
+const instance = new WebAssembly.Instance(module, { imp: { global: 5 } });
+assert.eq(instance.exports.global, 5);
diff --git a/JSTests/wasm/js-api/globals-import.js b/JSTests/wasm/js-api/globals-import.js
new file mode 100644 (file)
index 0000000..2922323
--- /dev/null
@@ -0,0 +1,29 @@
+import * as assert from '../assert.js';
+import Builder from '../Builder.js';
+
+const builder = new Builder();
+
+builder.Type().End()
+    .Import()
+        .Global().I32("imp", "global", "immutable").End()
+    .End()
+    .Function().End()
+    .Global().I32(5, "mutable").End()
+    .Export()
+        .Function("getGlobal")
+    .End()
+    .Code()
+
+    // GetGlobal
+    .Function("getGlobal", { params: [], ret: "i32" })
+        .GetGlobal(0)
+    .End()
+
+    .End()
+
+const bin = builder.WebAssembly();
+bin.trim();
+
+const module = new WebAssembly.Module(bin.get());
+const instance = new WebAssembly.Instance(module, { imp: { global: 5 } });
+assert.eq(instance.exports.getGlobal(), 5);
index ec0efc5..cf7e6c6 100644 (file)
         "i64.const":           { "category": "special",    "value":  66, "return": ["i64"],      "parameter": [],                       "immediate": [{"name": "value",          "type": "varint64"}],                                           "description": "a constant value interpreted as i64" },
         "f64.const":           { "category": "special",    "value":  68, "return": ["f64"],      "parameter": [],                       "immediate": [{"name": "value",          "type": "uint64"}],                                             "description": "a constant value interpreted as f64" },
         "f32.const":           { "category": "special",    "value":  67, "return": ["f32"],      "parameter": [],                       "immediate": [{"name": "value",          "type": "uint32"}],                                             "description": "a constant value interpreted as f32" },
-        "get_local":           { "category": "special",    "value":  32, "return": ["local"],    "parameter": [],                       "immediate": [{"name": "local_index",    "type": "varuint32"}],                                          "description": "read a local variable or parameter" },
-        "set_local":           { "category": "special",    "value":  33, "return": [],           "parameter": ["local"],                "immediate": [{"name": "local_index",    "type": "varuint32"}],                                          "description": "write a local variable or parameter" },
-        "tee_local":           { "category": "special",    "value":  34, "return": ["prev"],     "parameter": ["any"],                  "immediate": [{"name": "local_index",    "type": "varuint32"}],                                          "description": "write a local variable or parameter and return the same value" },
-        "get_global":          { "category": "special",    "value":  35, "return": ["global"],   "parameter": [],                       "immediate": [{"name": "global_index",   "type": "varuint32"}],                                          "description": "read a global variable" },
-        "set_global":          { "category": "special",    "value":  36, "return": [""],         "parameter": ["global"],               "immediate": [{"name": "global_index",   "type": "varuint32"}],                                          "description": "write a global variable" },
+        "get_local":           { "category": "special",    "value":  32, "return": ["any"],    "parameter": [],                       "immediate": [{"name": "local_index",    "type": "varuint32"}],                                          "description": "read a local variable or parameter" },
+        "set_local":           { "category": "special",    "value":  33, "return": [],           "parameter": ["any"],                "immediate": [{"name": "local_index",    "type": "varuint32"}],                                          "description": "write a local variable or parameter" },
+        "tee_local":           { "category": "special",    "value":  34, "return": ["any"],     "parameter": ["any"],                  "immediate": [{"name": "local_index",    "type": "varuint32"}],                                          "description": "write a local variable or parameter and return the same value" },
+        "get_global":          { "category": "special",    "value":  35, "return": ["any"],   "parameter": [],                       "immediate": [{"name": "global_index",   "type": "varuint32"}],                                          "description": "read a global variable" },
+        "set_global":          { "category": "special",    "value":  36, "return": [],         "parameter": ["any"],               "immediate": [{"name": "global_index",   "type": "varuint32"}],                                          "description": "write a global variable" },
         "call":                { "category": "call",       "value":  16, "return": ["call"],     "parameter": ["call"],                 "immediate": [{"name": "function_index", "type": "varuint32"}],                                          "description": "call a function by its index" },
         "call_indirect":       { "category": "call",       "value":  17, "return": ["call"],     "parameter": ["call"],                 "immediate": [{"name": "type_index",     "type": "varuint32"}, {"name": "reserved",     "type": "varuint1"}], "description": "call a function indirect with an expected signature" },
         "i32.load8_s":         { "category": "memory",     "value":  44, "return": ["i32"],      "parameter": ["addr"],                 "immediate": [{"name": "flags",          "type": "varuint32"}, {"name": "offset", "type": "varuint32"}], "description": "load from memory" },
index 6dee888..24ebf47 100644 (file)
@@ -1,3 +1,63 @@
+2016-12-14  Keith Miller  <keith_miller@apple.com>
+
+        WebAssembly JS API: implement Global
+        https://bugs.webkit.org/show_bug.cgi?id=164133
+
+        Reviewed by Saam Barati.
+
+        This patch adds support for globals. It handles imports, exports
+        and internal globals. In the MVP only internal globals are allowed
+        to be mutable. This means we can store a C-array of 64-bit slots
+        off the instance holding them. When globals are exported to JS
+        they are done so as numbers. This means that i64 globals cannot be
+        imported or exported.
+
+        * wasm/WasmB3IRGenerator.cpp:
+        (JSC::Wasm::B3IRGenerator::B3IRGenerator):
+        (JSC::Wasm::B3IRGenerator::getGlobal):
+        (JSC::Wasm::B3IRGenerator::setGlobal):
+        (JSC::Wasm::B3IRGenerator::addCallIndirect):
+        (JSC::Wasm::parseAndCompile):
+        * wasm/WasmFormat.h:
+        * wasm/WasmFunctionParser.h:
+        (JSC::Wasm::FunctionParser<Context>::parseExpression):
+        * wasm/WasmModuleParser.cpp:
+        (JSC::Wasm::ModuleParser::parseImport):
+        (JSC::Wasm::ModuleParser::parseGlobal):
+        (JSC::Wasm::ModuleParser::parseExport):
+        (JSC::Wasm::ModuleParser::parseElement):
+        (JSC::Wasm::ModuleParser::parseInitExpr):
+        (JSC::Wasm::ModuleParser::parseGlobalType):
+        (JSC::Wasm::ModuleParser::parseData):
+        * wasm/WasmModuleParser.h:
+        * wasm/WasmParser.h:
+        (JSC::Wasm::Parser::parseVarInt32):
+        (JSC::Wasm::Parser::parseVarInt64):
+        (JSC::Wasm::Parser::parseUInt64):
+        * wasm/WasmValidate.cpp:
+        (JSC::Wasm::Validate::hasMemory):
+        (JSC::Wasm::Validate::Validate):
+        (JSC::Wasm::Validate::getGlobal):
+        (JSC::Wasm::Validate::setGlobal):
+        (JSC::Wasm::validateFunction):
+        * wasm/generateWasmOpsHeader.py:
+        * wasm/js/JSWebAssemblyInstance.cpp:
+        (JSC::JSWebAssemblyInstance::create):
+        (JSC::JSWebAssemblyInstance::finishCreation):
+        (JSC::JSWebAssemblyInstance::visitChildren):
+        * wasm/js/JSWebAssemblyInstance.h:
+        (JSC::JSWebAssemblyInstance::loadI32Global):
+        (JSC::JSWebAssemblyInstance::loadI64Global):
+        (JSC::JSWebAssemblyInstance::loadF32Global):
+        (JSC::JSWebAssemblyInstance::loadF64Global):
+        (JSC::JSWebAssemblyInstance::setGlobal):
+        (JSC::JSWebAssemblyInstance::offsetOfGlobals):
+        * wasm/js/WebAssemblyInstanceConstructor.cpp:
+        (JSC::constructJSWebAssemblyInstance):
+        * wasm/js/WebAssemblyModuleRecord.cpp:
+        (JSC::WebAssemblyModuleRecord::finishCreation):
+        (JSC::WebAssemblyModuleRecord::link):
+
 2016-12-14  Filip Pizlo  <fpizlo@apple.com>
 
         Unreviewed, re-enable concurrent GC on ARM64 now that the most likely culprit of the memory
index 790e0b1..6008cdc 100644 (file)
@@ -137,7 +137,7 @@ public:
 
     static constexpr ExpressionType emptyExpression = nullptr;
 
-    B3IRGenerator(VM&, const MemoryInformation&, Procedure&, WasmInternalFunction*, Vector<UnlinkedWasmToWasmCall>&, const ImmutableFunctionIndexSpace&);
+    B3IRGenerator(VM&, const ModuleInformation&, Procedure&, WasmInternalFunction*, Vector<UnlinkedWasmToWasmCall>&, const ImmutableFunctionIndexSpace&);
 
     bool WARN_UNUSED_RETURN addArguments(const Vector<Type>&);
     bool WARN_UNUSED_RETURN addLocal(Type, uint32_t);
@@ -147,6 +147,10 @@ public:
     bool WARN_UNUSED_RETURN getLocal(uint32_t index, ExpressionType& result);
     bool WARN_UNUSED_RETURN setLocal(uint32_t index, ExpressionType value);
 
+    // Globals
+    bool WARN_UNUSED_RETURN getGlobal(uint32_t index, ExpressionType& result);
+    bool WARN_UNUSED_RETURN setGlobal(uint32_t index, ExpressionType value);
+
     // Memory
     bool WARN_UNUSED_RETURN load(LoadOpType, ExpressionType pointer, ExpressionType& result, uint32_t offset);
     bool WARN_UNUSED_RETURN store(StoreOpType, ExpressionType pointer, ExpressionType value, uint32_t offset);
@@ -190,6 +194,7 @@ private:
 
     VM& m_vm;
     const ImmutableFunctionIndexSpace& m_functionIndexSpace;
+    const ModuleInformation& m_info;
     Procedure& m_proc;
     BasicBlock* m_currentBlock;
     Vector<Variable*> m_locals;
@@ -197,12 +202,14 @@ private:
     GPRReg m_memoryBaseGPR;
     GPRReg m_memorySizeGPR;
     Value* m_zeroValues[numTypes];
-    Value* m_functionIndexSpaceValue;
+    Value* m_instanceValue;
+
 };
 
-B3IRGenerator::B3IRGenerator(VM& vm, const MemoryInformation& memory, Procedure& procedure, WasmInternalFunction* compilation, Vector<UnlinkedWasmToWasmCall>& unlinkedWasmToWasmCalls, const ImmutableFunctionIndexSpace& functionIndexSpace)
+B3IRGenerator::B3IRGenerator(VM& vm, const ModuleInformation& info, Procedure& procedure, WasmInternalFunction* compilation, Vector<UnlinkedWasmToWasmCall>& unlinkedWasmToWasmCalls, const ImmutableFunctionIndexSpace& functionIndexSpace)
     : m_vm(vm)
     , m_functionIndexSpace(functionIndexSpace)
+    , m_info(info)
     , m_proc(procedure)
     , m_unlinkedWasmToWasmCalls(unlinkedWasmToWasmCalls)
 {
@@ -222,13 +229,13 @@ B3IRGenerator::B3IRGenerator(VM& vm, const MemoryInformation& memory, Procedure&
         }
     }
 
-    if (!!memory) {
-        m_memoryBaseGPR = memory.pinnedRegisters().baseMemoryPointer;
+    if (!!info.memory) {
+        m_memoryBaseGPR = info.memory.pinnedRegisters().baseMemoryPointer;
         m_proc.pinRegister(m_memoryBaseGPR);
-        ASSERT(!memory.pinnedRegisters().sizeRegisters[0].sizeOffset);
-        m_memorySizeGPR = memory.pinnedRegisters().sizeRegisters[0].sizeRegister;
-        for (const PinnedSizeRegisterInfo& info : memory.pinnedRegisters().sizeRegisters)
-            m_proc.pinRegister(info.sizeRegister);
+        ASSERT(!info.memory.pinnedRegisters().sizeRegisters[0].sizeOffset);
+        m_memorySizeGPR = info.memory.pinnedRegisters().sizeRegisters[0].sizeRegister;
+        for (const PinnedSizeRegisterInfo& regInfo : info.memory.pinnedRegisters().sizeRegisters)
+            m_proc.pinRegister(regInfo.sizeRegister);
 
         m_proc.setWasmBoundsCheckGenerator([=] (CCallHelpers& jit, GPRReg pinnedGPR, unsigned) {
             AllowMacroScratchRegisterUsage allowScratch(jit);
@@ -271,7 +278,8 @@ B3IRGenerator::B3IRGenerator(VM& vm, const MemoryInformation& memory, Procedure&
 
     wasmCallingConvention().setupFrameInPrologue(&compilation->wasmCalleeMoveLocation, m_proc, Origin(), m_currentBlock);
 
-    m_functionIndexSpaceValue = m_currentBlock->appendNew<ConstPtrValue>(m_proc, Origin(), functionIndexSpace.buffer.get());
+    m_instanceValue = m_currentBlock->appendNew<MemoryValue>(m_proc, Load, pointerType(), Origin(),
+        m_currentBlock->appendNew<ConstPtrValue>(m_proc, Origin(), &m_vm.topJSWebAssemblyInstance));
 }
 
 Value* B3IRGenerator::zeroForType(Type type)
@@ -325,6 +333,21 @@ bool B3IRGenerator::setLocal(uint32_t index, ExpressionType value)
     return true;
 }
 
+bool B3IRGenerator::getGlobal(uint32_t index, ExpressionType& result)
+{
+    Value* globalsArray = m_currentBlock->appendNew<MemoryValue>(m_proc, Load, pointerType(), Origin(), m_instanceValue, JSWebAssemblyInstance::offsetOfGlobals());
+    result = m_currentBlock->appendNew<MemoryValue>(m_proc, Load, toB3Type(m_info.globals[index].type), Origin(), globalsArray, index * sizeof(Register));
+    return true;
+}
+
+bool B3IRGenerator::setGlobal(uint32_t index, ExpressionType value)
+{
+    ASSERT(toB3Type(m_info.globals[index].type) == value->type());
+    Value* globalsArray = m_currentBlock->appendNew<MemoryValue>(m_proc, Load, pointerType(), Origin(), m_instanceValue, JSWebAssemblyInstance::offsetOfGlobals());
+    m_currentBlock->appendNew<MemoryValue>(m_proc, Store, Origin(), value, globalsArray, index * sizeof(Register));
+    return true;
+}
+
 inline Value* B3IRGenerator::emitCheckAndPreparePointer(ExpressionType pointer, uint32_t offset, uint32_t sizeOfOperation)
 {
     ASSERT(m_memoryBaseGPR && m_memorySizeGPR);
@@ -674,10 +697,8 @@ bool B3IRGenerator::addCallIndirect(const Signature* signature, Vector<Expressio
     ExpressionType callableFunctionBuffer;
     ExpressionType callableFunctionBufferSize;
     {
-        ExpressionType topInstance = m_currentBlock->appendNew<MemoryValue>(m_proc, Load, pointerType(), Origin(),
-            m_currentBlock->appendNew<ConstPtrValue>(m_proc, Origin(), &m_vm.topJSWebAssemblyInstance));
         ExpressionType table = m_currentBlock->appendNew<MemoryValue>(m_proc, Load, pointerType(), Origin(),
-            topInstance, JSWebAssemblyInstance::offsetOfTable());
+            m_instanceValue, JSWebAssemblyInstance::offsetOfTable());
         callableFunctionBuffer = m_currentBlock->appendNew<MemoryValue>(m_proc, Load, pointerType(), Origin(),
             table, JSWebAssemblyTable::offsetOfFunctions());
         callableFunctionBufferSize = m_currentBlock->appendNew<MemoryValue>(m_proc, Load, Int32, Origin(),
@@ -872,7 +893,7 @@ std::unique_ptr<WasmInternalFunction> parseAndCompile(VM& vm, const uint8_t* fun
     auto result = std::make_unique<WasmInternalFunction>();
 
     Procedure procedure;
-    B3IRGenerator context(vm, info.memory, procedure, result.get(), unlinkedWasmToWasmCalls, functionIndexSpace);
+    B3IRGenerator context(vm, info, procedure, result.get(), unlinkedWasmToWasmCalls, functionIndexSpace);
     FunctionParser<B3IRGenerator> parser(context, functionStart, functionLength, signature, functionIndexSpace, info);
     if (!parser.parse())
         RELEASE_ASSERT_NOT_REACHED();
index 3910608..4e0eafd 100644 (file)
@@ -93,7 +93,7 @@ struct Signature {
     Type returnType;
     Vector<Type> arguments;
 };
-    
+
 struct Import {
     Identifier module;
     Identifier field;
@@ -104,12 +104,26 @@ struct Import {
 struct Export {
     Identifier field;
     External::Kind kind;
-    union {
-        uint32_t functionIndex;
-        // FIXME implement Table https://bugs.webkit.org/show_bug.cgi?id=165782
-        // FIXME implement Memory https://bugs.webkit.org/show_bug.cgi?id=165671
-        // FIXME implement Global https://bugs.webkit.org/show_bug.cgi?id=164133
+    unsigned kindIndex; // Index in the vector of the corresponding kind.
+};
+
+struct Global {
+    enum Mutability : uint8_t {
+        // FIXME auto-generate this. https://bugs.webkit.org/show_bug.cgi?id=165231
+        Mutable = 1,
+        Immutable = 0
+    };
+
+    enum InitializationType {
+        IsImport,
+        FromGlobalImport,
+        FromExpression
     };
+
+    Mutability mutability;
+    Type type;
+    InitializationType initializationType { IsImport };
+    uint64_t initialBitsOrImportNumber { 0 };
 };
 
 struct FunctionLocationInBinary {
@@ -184,7 +198,6 @@ struct ModuleInformation {
     Vector<Signature> signatures;
     Vector<Import> imports;
     Vector<Signature*> importFunctions;
-    // FIXME implement import Global https://bugs.webkit.org/show_bug.cgi?id=164133
     Vector<Signature*> internalFunctionSignatures;
     MemoryInformation memory;
     Vector<Export> exports;
@@ -192,6 +205,8 @@ struct ModuleInformation {
     Vector<Segment::Ptr> data;
     Vector<Element> elements;
     TableInformation tableInformation;
+    Vector<Global> globals;
+    unsigned firstInternalGlobal { 0 };
 
     ~ModuleInformation();
 };
index 88283ba..231c0a1 100644 (file)
@@ -351,6 +351,28 @@ bool FunctionParser<Context>::parseExpression(OpType op)
         return m_context.setLocal(index, m_expressionStack.last());
     }
 
+    case OpType::GetGlobal: {
+        uint32_t index;
+        if (!parseVarUInt32(index))
+            return false;
+        ExpressionType result;
+        if (!m_context.getGlobal(index, result))
+            return false;
+
+        m_expressionStack.append(result);
+        return true;
+    }
+
+    case OpType::SetGlobal: {
+        uint32_t index;
+        if (!parseVarUInt32(index))
+            return false;
+        ExpressionType value;
+        if (!popExpressionStack(value))
+            return false;
+        return m_context.setGlobal(index, value);
+    }
+
     case OpType::Call: {
         uint32_t functionIndex;
         if (!parseVarUInt32(functionIndex))
@@ -563,12 +585,9 @@ bool FunctionParser<Context>::parseExpression(OpType op)
 
     case OpType::GrowMemory:
     case OpType::CurrentMemory:
-    case OpType::GetGlobal:
-    case OpType::SetGlobal: {
         // FIXME: Not yet implemented.
         return false;
     }
-    }
 
     ASSERT_NOT_REACHED();
 }
index 212a9a3..00ef829 100644 (file)
@@ -217,6 +217,7 @@ bool ModuleParser::parseImport()
     uint32_t importCount;
     if (!parseVarUInt32(importCount)
         || importCount == std::numeric_limits<uint32_t>::max()
+        || !m_module->globals.tryReserveCapacity(importCount) // FIXME this over-allocates when we fix the FIXMEs below.
         || !m_module->imports.tryReserveCapacity(importCount) // FIXME this over-allocates when we fix the FIXMEs below.
         || !m_module->importFunctions.tryReserveCapacity(importCount) // FIXME this over-allocates when we fix the FIXMEs below.
         || !m_functionIndexSpace.tryReserveCapacity(importCount)) // FIXME this over-allocates when we fix the FIXMEs below. We'll allocate some more here when we know how many functions to expect.
@@ -263,8 +264,15 @@ bool ModuleParser::parseImport()
             break;
         }
         case External::Global: {
-            // FIXME https://bugs.webkit.org/show_bug.cgi?id=164133
-            // In the MVP, only immutable global variables can be imported.
+            Global global;
+            if (!parseGlobalType(global))
+                return false;
+
+            if (global.mutability == Global::Mutable)
+                return false;
+
+            imp.kindIndex = m_module->globals.size();
+            m_module->globals.uncheckedAppend(WTFMove(global));
             break;
         }
         }
@@ -272,6 +280,7 @@ bool ModuleParser::parseImport()
         m_module->imports.uncheckedAppend(imp);
     }
 
+    m_module->firstInternalGlobal = m_module->globals.size();
     return true;
 }
 
@@ -423,8 +432,52 @@ bool ModuleParser::parseMemory()
 
 bool ModuleParser::parseGlobal()
 {
-    // FIXME https://bugs.webkit.org/show_bug.cgi?id=164133
-    RELEASE_ASSERT_NOT_REACHED();
+    uint32_t globalCount;
+    if (!parseVarUInt32(globalCount))
+        return false;
+    if (!m_module->globals.tryReserveCapacity(globalCount + m_module->firstInternalGlobal))
+        return false;
+
+    for (uint32_t globalIndex = 0; globalIndex < globalCount; ++globalIndex) {
+        Global global;
+        if (!parseGlobalType(global))
+            return false;
+
+        uint8_t initOpcode;
+        if (!parseInitExpr(initOpcode, global.initialBitsOrImportNumber))
+            return false;
+
+        global.initializationType = Global::FromExpression;
+        Type typeForInitOpcode;
+        switch (initOpcode) {
+        case I32Const:
+            typeForInitOpcode = I32;
+            break;
+        case I64Const:
+            typeForInitOpcode = I64;
+            break;
+        case F32Const:
+            typeForInitOpcode = F32;
+            break;
+        case F64Const:
+            typeForInitOpcode = F64;
+            break;
+        case GetGlobal:
+            if (global.initialBitsOrImportNumber >= m_module->firstInternalGlobal)
+                return false;
+            typeForInitOpcode = m_module->globals[global.initialBitsOrImportNumber].type;
+            global.initializationType = Global::FromGlobalImport;
+            break;
+        default:
+            RELEASE_ASSERT_NOT_REACHED();
+        }
+
+        if (typeForInitOpcode != global.type)
+            return false;
+
+        m_module->globals.uncheckedAppend(WTFMove(global));
+    }
+
     return true;
 }
 
@@ -448,10 +501,12 @@ bool ModuleParser::parseExport()
         if (!parseExternalKind(exp.kind))
             return false;
 
+        if (!parseVarUInt32(exp.kindIndex))
+            return false;
+
         switch (exp.kind) {
         case External::Function: {
-            if (!parseVarUInt32(exp.functionIndex)
-                || exp.functionIndex >= m_functionIndexSpace.size())
+            if (exp.kindIndex >= m_functionIndexSpace.size())
                 return false;
             break;
         }
@@ -464,8 +519,11 @@ bool ModuleParser::parseExport()
             break;
         }
         case External::Global: {
-            // FIXME https://bugs.webkit.org/show_bug.cgi?id=164133
-            // In the MVP, only immutable global variables can be exported.
+            if (exp.kindIndex >= m_module->globals.size())
+                return false;
+
+            if (m_module->globals[exp.kindIndex].mutability != Global::Immutable)
+                return false;
             break;
         }
         }
@@ -509,8 +567,12 @@ bool ModuleParser::parseElement()
         if (tableIndex != 0)
             return false;
 
-        uint32_t offset;
-        if (!parseInitExpr(offset))
+        uint64_t offset;
+        uint8_t initOpcode;
+        if (!parseInitExpr(initOpcode, offset))
+            return false;
+
+        if (initOpcode != OpType::I32Const)
             return false;
 
         uint32_t indexCount;
@@ -577,19 +639,79 @@ bool ModuleParser::parseCode()
     return true;
 }
 
-bool ModuleParser::parseInitExpr(uint32_t& value)
+bool ModuleParser::parseInitExpr(uint8_t& opcode, uint64_t& bitsOrImportNumber)
 {
-    // FIXME allow complex init_expr here. https://bugs.webkit.org/show_bug.cgi?id=165700
-    // For now we only handle i32.const as offset.
+    if (!parseUInt8(opcode))
+        return false;
+
+    switch (opcode) {
+    case I32Const: {
+        int32_t constant;
+        if (!parseVarInt32(constant))
+            return false;
+        bitsOrImportNumber = static_cast<uint64_t>(constant);
+        break;
+    }
+
+    case I64Const: {
+        int64_t constant;
+        if (!parseVarInt64(constant))
+            return false;
+        bitsOrImportNumber = constant;
+        break;
+    }
+
+    case F32Const: {
+        uint32_t constant;
+        if (!parseUInt32(constant))
+            return false;
+        bitsOrImportNumber = constant;
+        break;
+    }
+
+    case F64Const: {
+        uint64_t constant;
+        if (!parseUInt64(constant))
+            return false;
+        bitsOrImportNumber = constant;
+        break;
+    }
+
+    case GetGlobal: {
+        uint32_t index;
+        if (!parseVarUInt32(index))
+            return false;
+
+        if (index >= m_module->imports.size())
+            return false;
+        const Import& import = m_module->imports[index];
+        if (m_module->imports[index].kind != External::Global
+            || import.kindIndex >= m_module->firstInternalGlobal)
+            return false;
+
+        ASSERT(m_module->globals[import.kindIndex].mutability == Global::Immutable);
+
+        bitsOrImportNumber = index;
+        break;
+    }
+
+    default:
+        return false;
+    }
 
-    uint8_t opcode;
     uint8_t endOpcode;
-    if (!parseUInt8(opcode)
-        || opcode != Wasm::I32Const
-        || !parseVarUInt32(value)
-        || !parseUInt8(endOpcode)
-        || endOpcode != Wasm::End)
+    if (!parseUInt8(endOpcode) || endOpcode != OpType::End)
+        return false;
+
+    return true;
+}
+
+bool ModuleParser::parseGlobalType(Global& global)
+{
+    uint8_t mutability;
+    if (!parseValueType(global.type) || !parseVarUInt1(mutability))
         return false;
+    global.mutability = static_cast<Global::Mutability>(mutability);
     return true;
 }
 
@@ -607,14 +729,19 @@ bool ModuleParser::parseData()
         if (verbose)
             dataLogLn("  segment #", segmentNumber);
         uint32_t index;
-        uint32_t offset;
+        uint64_t offset;
+        uint8_t initOpcode;
         uint32_t dataByteLength;
         if (!parseVarUInt32(index)
             || index)
             return false;
 
-        if (!parseInitExpr(offset))
+        if (!parseInitExpr(initOpcode, offset))
+            return false;
+
+        if (initOpcode != OpType::I32Const)
             return false;
+
         if (verbose)
             dataLogLn("    offset: ", offset);
 
index 3d8a861..b718743 100644 (file)
@@ -74,6 +74,8 @@ public:
     }
 
 private:
+    bool parseGlobalType(Global&);
+
 #define WASM_SECTION_DECLARE_PARSER(NAME, ID, DESCRIPTION) bool WARN_UNUSED_RETURN parse ## NAME();
     FOR_EACH_WASM_SECTION(WASM_SECTION_DECLARE_PARSER)
 #undef WASM_SECTION_DECLARE_PARSER
@@ -81,7 +83,7 @@ private:
     bool WARN_UNUSED_RETURN parseMemoryHelper(bool isImport);
     bool WARN_UNUSED_RETURN parseTableHelper(bool isImport);
     bool WARN_UNUSED_RETURN parseResizableLimits(uint32_t& initial, std::optional<uint32_t>& maximum);
-    bool WARN_UNUSED_RETURN parseInitExpr(uint32_t&);
+    bool WARN_UNUSED_RETURN parseInitExpr(uint8_t&, uint64_t&);
 
     VM* m_vm;
     std::unique_ptr<ModuleInformation> m_module;
index eee39b4..08fabbf 100644 (file)
@@ -51,9 +51,13 @@ protected:
     bool WARN_UNUSED_RETURN parseUInt7(uint8_t&);
     bool WARN_UNUSED_RETURN parseUInt8(uint8_t&);
     bool WARN_UNUSED_RETURN parseUInt32(uint32_t&);
+    bool WARN_UNUSED_RETURN parseUInt64(uint64_t&);
     bool WARN_UNUSED_RETURN parseVarUInt32(uint32_t&);
     bool WARN_UNUSED_RETURN parseVarUInt64(uint64_t&);
 
+    bool WARN_UNUSED_RETURN parseVarInt32(int32_t&);
+    bool WARN_UNUSED_RETURN parseVarInt64(int64_t&);
+
     bool WARN_UNUSED_RETURN parseResultType(Type&);
     bool WARN_UNUSED_RETURN parseValueType(Type&);
     bool WARN_UNUSED_RETURN parseExternalKind(External::Kind&);
@@ -124,6 +128,16 @@ ALWAYS_INLINE bool Parser::parseVarUInt64(uint64_t& result)
     return WTF::LEBDecoder::decodeUInt64(m_source, m_sourceLength, m_offset, result);
 }
 
+ALWAYS_INLINE bool Parser::parseVarInt32(int32_t& result)
+{
+    return WTF::LEBDecoder::decodeInt32(m_source, m_sourceLength, m_offset, result);
+}
+
+ALWAYS_INLINE bool Parser::parseVarInt64(int64_t& result)
+{
+    return WTF::LEBDecoder::decodeInt64(m_source, m_sourceLength, m_offset, result);
+}
+
 ALWAYS_INLINE bool Parser::parseUInt32(uint32_t& result)
 {
     if (length() < 4 || m_offset > length() - 4)
@@ -133,6 +147,15 @@ ALWAYS_INLINE bool Parser::parseUInt32(uint32_t& result)
     return true;
 }
 
+ALWAYS_INLINE bool Parser::parseUInt64(uint64_t& result)
+{
+    if (length() < 8 || m_offset > length() - 8)
+        return false;
+    result = *reinterpret_cast<const uint64_t*>(source() + m_offset);
+    m_offset += 8;
+    return true;
+}
+
 ALWAYS_INLINE bool Parser::parseUInt8(uint8_t& result)
 {
     if (m_offset >= length())
index 22f95bb..3bb5219 100644 (file)
@@ -86,6 +86,10 @@ public:
     bool WARN_UNUSED_RETURN getLocal(uint32_t index, ExpressionType& result);
     bool WARN_UNUSED_RETURN setLocal(uint32_t index, ExpressionType value);
 
+    // Globals
+    bool WARN_UNUSED_RETURN getGlobal(uint32_t index, ExpressionType& result);
+    bool WARN_UNUSED_RETURN setGlobal(uint32_t index, ExpressionType value);
+
     // Memory
     bool WARN_UNUSED_RETURN load(LoadOpType, ExpressionType pointer, ExpressionType& result, uint32_t offset);
     bool WARN_UNUSED_RETURN store(StoreOpType, ExpressionType pointer, ExpressionType value, uint32_t offset);
@@ -116,13 +120,13 @@ public:
 
     void dump(const Vector<ControlEntry>& controlStack, const ExpressionList& expressionStack);
 
-    bool hasMemory() const { return !!m_memory; }
+    bool hasMemory() const { return !!m_module.memory; }
 
     void setErrorMessage(String&& message) { ASSERT(m_errorMessage.isNull()); m_errorMessage = WTFMove(message); }
     String errorMessage() const { return m_errorMessage; }
-    Validate(ExpressionType returnType, const MemoryInformation& memory)
+    Validate(ExpressionType returnType, const ModuleInformation& module)
         : m_returnType(returnType)
-        , m_memory(memory)
+        , m_module(module)
     {
     }
 
@@ -135,7 +139,7 @@ private:
     ExpressionType m_returnType;
     Vector<Type> m_locals;
     String m_errorMessage;
-    const MemoryInformation& m_memory;
+    const ModuleInformation& m_module;
 };
 
 bool Validate::addArguments(const Vector<Type>& args)
@@ -180,6 +184,38 @@ bool Validate::setLocal(uint32_t index, ExpressionType value)
     return false;
 }
 
+bool Validate::getGlobal(uint32_t index, ExpressionType& result)
+{
+    if (index < m_module.globals.size()) {
+        result = m_module.globals[index].type;
+        ASSERT(isValueType(result));
+        return true;
+    }
+    m_errorMessage = ASCIILiteral("Attempt to use unknown global.");
+    return false;
+}
+
+bool Validate::setGlobal(uint32_t index, ExpressionType value)
+{
+    if (index >= m_module.globals.size()) {
+        m_errorMessage = ASCIILiteral("Attempt to use unknown global.");
+        return false;
+    }
+
+    if (m_module.globals[index].mutability == Global::Immutable) {
+        m_errorMessage = ASCIILiteral("Attempt to store to immutable global.");
+        return false;
+    }
+
+    ExpressionType globalType = m_module.globals[index].type;
+    ASSERT(isValueType(globalType));
+    if (globalType == value)
+        return true;
+
+    m_errorMessage = makeString("Attempt to set global with type: ", toString(globalType), " with a variable of type: ", toString(value));
+    return false;
+}
+
 Validate::ControlType Validate::addBlock(Type signature)
 {
     return ControlData(BlockType::Block, signature);
@@ -401,10 +437,10 @@ void Validate::dump(const Vector<ControlEntry>&, const ExpressionList&)
     // Think of this as penance for the sin of bad error messages.
 }
 
-String validateFunction(const uint8_t* source, size_t length, const Signature* signature, const ImmutableFunctionIndexSpace& functionIndexSpace, const ModuleInformation& info)
+String validateFunction(const uint8_t* source, size_t length, const Signature* signature, const ImmutableFunctionIndexSpace& functionIndexSpace, const ModuleInformation& module)
 {
-    Validate context(signature->returnType, info.memory);
-    FunctionParser<Validate> validator(context, source, length, signature, functionIndexSpace, info);
+    Validate context(signature->returnType, module);
+    FunctionParser<Validate> validator(context, source, length, signature, functionIndexSpace, module);
 
     if (!validator.parse()) {
         // FIXME: add better location information here. see: https://bugs.webkit.org/show_bug.cgi?id=164288
index a553fb9..00d2838 100755 (executable)
@@ -150,7 +150,7 @@ inline B3::Type toB3Type(Type type)
 }
 #undef CREATE_CASE
 
-#define CREATE_CASE(name, id, b3type, inc) case name: return "name";
+#define CREATE_CASE(name, id, b3type, inc) case name: return #name;
 inline const char* toString(Type type)
 {
     switch (type) {
index 8e22b07..960e248 100644 (file)
 
 namespace JSC {
 
-JSWebAssemblyInstance* JSWebAssemblyInstance::create(VM& vm, Structure* structure, JSWebAssemblyModule* module, JSModuleNamespaceObject* moduleNamespaceObject, unsigned numImportFunctions)
+JSWebAssemblyInstance* JSWebAssemblyInstance::create(VM& vm, Structure* structure, JSWebAssemblyModule* module, JSModuleNamespaceObject* moduleNamespaceObject)
 {
-    auto* instance = new (NotNull, allocateCell<JSWebAssemblyInstance>(vm.heap, allocationSize(numImportFunctions))) JSWebAssemblyInstance(vm, structure, numImportFunctions);
+    // FIXME: These objects could be pretty big we should try to throw OOM here.
+    auto* instance = new (NotNull, allocateCell<JSWebAssemblyInstance>(vm.heap, allocationSize(module->moduleInformation().importFunctions.size()))) JSWebAssemblyInstance(vm, structure, module->moduleInformation().importFunctions.size());
     instance->finishCreation(vm, module, moduleNamespaceObject);
     return instance;
 }
@@ -61,6 +62,11 @@ void JSWebAssemblyInstance::finishCreation(VM& vm, JSWebAssemblyModule* module,
 {
     Base::finishCreation(vm);
     ASSERT(inherits(info()));
+
+    const size_t extraMemorySize = module->moduleInformation().globals.size() * sizeof(Register);
+    m_globals = MallocPtr<uint64_t>::malloc(extraMemorySize);
+    heap()->reportExtraMemoryAllocated(extraMemorySize);
+
     m_module.set(vm, this, module);
     m_moduleNamespaceObject.set(vm, this, moduleNamespaceObject);
     putDirect(vm, Identifier::fromString(&vm, "exports"), moduleNamespaceObject, None);
@@ -81,6 +87,7 @@ void JSWebAssemblyInstance::visitChildren(JSCell* cell, SlotVisitor& visitor)
     visitor.append(&thisObject->m_moduleNamespaceObject);
     visitor.append(&thisObject->m_memory);
     visitor.append(&thisObject->m_table);
+    visitor.reportExtraMemoryVisited(thisObject->module()->moduleInformation().globals.size());
     for (unsigned i = 0; i < thisObject->m_numImportFunctions; ++i)
         visitor.append(thisObject->importFunction(i));
 }
index 30cf350..f34b052 100644 (file)
@@ -42,7 +42,7 @@ public:
     typedef JSDestructibleObject Base;
 
 
-    static JSWebAssemblyInstance* create(VM&, Structure*, JSWebAssemblyModule*, JSModuleNamespaceObject*, unsigned);
+    static JSWebAssemblyInstance* create(VM&, Structure*, JSWebAssemblyModule*, JSModuleNamespaceObject*);
     static Structure* createStructure(VM&, JSGlobalObject*, JSValue);
 
     DECLARE_INFO;
@@ -75,15 +75,22 @@ public:
     JSWebAssemblyTable* table() { return m_table.get(); }
     void setTable(VM& vm, JSWebAssemblyTable* table) { m_table.set(vm, this, table); }
 
+    int32_t loadI32Global(unsigned i) const { return m_globals.get()[i]; }
+    int64_t loadI64Global(unsigned i) const { return m_globals.get()[i]; }
+    float loadF32Global(unsigned i) const { return bitwise_cast<float>(loadI32Global(i)); }
+    double loadF64Global(unsigned i) const { return bitwise_cast<double>(loadI64Global(i)); }
+    void setGlobal(unsigned i, int64_t bits) { m_globals.get()[i] = bits; }
+
     static size_t offsetOfImportFunction(unsigned idx)
     {
         return offsetOfImportFunctions() + sizeof(WriteBarrier<JSCell>) * idx;
     }
 
     static ptrdiff_t offsetOfTable() { return OBJECT_OFFSETOF(JSWebAssemblyInstance, m_table); }
+    static ptrdiff_t offsetOfGlobals() { return OBJECT_OFFSETOF(JSWebAssemblyInstance, m_globals); }
 
 protected:
-    JSWebAssemblyInstance(VM&, Structure*, unsigned);
+    JSWebAssemblyInstance(VM&, Structure*, unsigned numImportFunctions);
     void finishCreation(VM&, JSWebAssemblyModule*, JSModuleNamespaceObject*);
     static void destroy(JSCell*);
     static void visitChildren(JSCell*, SlotVisitor&);
@@ -103,6 +110,7 @@ private:
     WriteBarrier<JSModuleNamespaceObject> m_moduleNamespaceObject;
     WriteBarrier<JSWebAssemblyMemory> m_memory;
     WriteBarrier<JSWebAssemblyTable> m_table;
+    MallocPtr<uint64_t> m_globals;
     unsigned m_numImportFunctions;
 };
 
index b5c3b61..51dd98f 100644 (file)
@@ -81,14 +81,13 @@ static EncodedJSValue JSC_HOST_CALL constructJSWebAssemblyInstance(ExecState* ex
     Structure* instanceStructure = InternalFunction::createSubclassStructure(exec, exec->newTarget(), globalObject->WebAssemblyInstanceStructure());
     RETURN_IF_EXCEPTION(throwScope, { });
 
-    JSWebAssemblyInstance* instance = JSWebAssemblyInstance::create(vm, instanceStructure, jsModule, moduleRecord->getModuleNamespace(exec), moduleInformation.imports.size());
+    JSWebAssemblyInstance* instance = JSWebAssemblyInstance::create(vm, instanceStructure, jsModule, moduleRecord->getModuleNamespace(exec));
     RETURN_IF_EXCEPTION(throwScope, { });
 
     // Let funcs, memories and tables be initially-empty lists of callable JavaScript objects, WebAssembly.Memory objects and WebAssembly.Table objects, respectively.
     // Let imports be an initially-empty list of external values.
     unsigned numImportFunctions = 0;
-
-    // FIXME implement Global https://bugs.webkit.org/show_bug.cgi?id=164133
+    unsigned numImportGlobals = 0;
 
     bool hasMemoryImport = false;
     bool hasTableImport = false;
@@ -195,11 +194,26 @@ static EncodedJSValue JSC_HOST_CALL constructJSWebAssemblyInstance(ExecState* ex
         }
         case Wasm::External::Global: {
             // 5. If i is a global import:
-            // FIXME implement Global https://bugs.webkit.org/show_bug.cgi?id=164133
             // i. If i is not an immutable global, throw a TypeError.
+            ASSERT(moduleInformation.globals[import.kindIndex].mutability == Wasm::Global::Immutable);
             // ii. If Type(v) is not Number, throw a TypeError.
+            if (!value.isNumber())
+                return JSValue::encode(throwException(exec, throwScope, createTypeError(exec, ASCIILiteral("imported global must be a number"), defaultSourceAppender, runtimeTypeForValue(value))));
             // iii. Append ToWebAssemblyValue(v) to imports.
-            RELEASE_ASSERT_NOT_REACHED();
+            switch (moduleInformation.globals[import.kindIndex].type) {
+            case Wasm::I32:
+                instance->setGlobal(numImportGlobals++, value.toInt32(exec));
+                break;
+            case Wasm::F32:
+                instance->setGlobal(numImportGlobals++, bitwise_cast<uint32_t>(value.toFloat(exec)));
+                break;
+            case Wasm::F64:
+                instance->setGlobal(numImportGlobals++, bitwise_cast<uint64_t>(value.asNumber()));
+                break;
+            default:
+                RELEASE_ASSERT_NOT_REACHED();
+            }
+            ASSERT(!throwScope.exception());
             break;
         }
         }
@@ -241,8 +255,23 @@ static EncodedJSValue JSC_HOST_CALL constructJSWebAssemblyInstance(ExecState* ex
         }
     }
 
+    // Globals
+    {
+        ASSERT(numImportGlobals == moduleInformation.firstInternalGlobal);
+        for (size_t globalIndex = numImportGlobals; globalIndex < moduleInformation.globals.size(); ++globalIndex) {
+            const auto& global = moduleInformation.globals[globalIndex];
+            ASSERT(global.initializationType != Wasm::Global::IsImport);
+            if (global.initializationType == Wasm::Global::FromGlobalImport) {
+                ASSERT(global.initialBitsOrImportNumber < numImportGlobals);
+                instance->setGlobal(globalIndex, instance->loadI64Global(global.initialBitsOrImportNumber));
+            } else
+                instance->setGlobal(globalIndex, global.initialBitsOrImportNumber);
+        }
+    }
+
     moduleRecord->link(exec, instance);
     RETURN_IF_EXCEPTION(throwScope, { });
+
     if (verbose)
         moduleRecord->dump();
     JSValue startResult = moduleRecord->evaluate(exec);
index 7ee44d9..f0c3c36 100644 (file)
@@ -85,8 +85,8 @@ void WebAssemblyModuleRecord::finishCreation(ExecState* exec, VM& vm, const Wasm
             break;
         }
         case Wasm::External::Global: {
-            // FIXME https://bugs.webkit.org/show_bug.cgi?id=164133
             // In the MVP, only immutable global variables can be exported.
+            addExportEntry(ExportEntry::createLocal(exp.field, exp.field));
             break;
         }
         }
@@ -128,7 +128,7 @@ void WebAssemblyModuleRecord::link(ExecState* state, JSWebAssemblyInstance* inst
             // 1. If e is a closure c:
             //   i. If there is an Exported Function Exotic Object func in funcs whose func.[[Closure]] equals c, then return func.
             //   ii. (Note: At most one wrapper is created for any closure, so func is unique, even if there are multiple occurrances in the list. Moreover, if the item was an import that is already an Exported Function Exotic Object, then the original function object will be found. For imports that are regular JS functions, a new wrapper will be created.)
-            if (exp.functionIndex < importCount) {
+            if (exp.kindIndex < importCount) {
                 // FIXME Implement re-exporting an import. https://bugs.webkit.org/show_bug.cgi?id=165510
                 RELEASE_ASSERT_NOT_REACHED();
             }
@@ -136,12 +136,12 @@ void WebAssemblyModuleRecord::link(ExecState* state, JSWebAssemblyInstance* inst
             //     a. Let func be an Exported Function Exotic Object created from c.
             //     b. Append func to funcs.
             //     c. Return func.
-            JSWebAssemblyCallee* jsEntrypointCallee = module->jsEntrypointCalleeFromFunctionIndexSpace(exp.functionIndex);
-            JSWebAssemblyCallee* wasmEntrypointCallee = module->wasmEntrypointCalleeFromFunctionIndexSpace(exp.functionIndex);
-            Wasm::Signature* signature = module->signatureForFunctionIndexSpace(exp.functionIndex);
+            JSWebAssemblyCallee* jsEntrypointCallee = module->jsEntrypointCalleeFromFunctionIndexSpace(exp.kindIndex);
+            JSWebAssemblyCallee* wasmEntrypointCallee = module->wasmEntrypointCalleeFromFunctionIndexSpace(exp.kindIndex);
+            Wasm::Signature* signature = module->signatureForFunctionIndexSpace(exp.kindIndex);
             WebAssemblyFunction* function = WebAssemblyFunction::create(vm, globalObject, signature->arguments.size(), exp.field.string(), instance, jsEntrypointCallee, wasmEntrypointCallee, signature);
             exportedValue = function;
-            if (hasStart && startFunctionIndexSpace == exp.functionIndex)
+            if (hasStart && startFunctionIndexSpace == exp.kindIndex)
                 m_startFunction.set(vm, this, function);
             break;
         }
@@ -153,9 +153,28 @@ void WebAssemblyModuleRecord::link(ExecState* state, JSWebAssemblyInstance* inst
             // FIXME: https://bugs.webkit.org/show_bug.cgi?id=165671
             break;
         }
+
         case Wasm::External::Global: {
-            // FIXME https://bugs.webkit.org/show_bug.cgi?id=164133
-            // In the MVP, only immutable global variables can be exported.
+            // Assert: the global is immutable by MVP validation constraint.
+            const Wasm::Global& global = moduleInformation.globals[exp.kindIndex];
+            ASSERT(global.mutability == Wasm::Global::Immutable);
+            // Return ToJSValue(v).
+            switch (global.type) {
+            case Wasm::I32:
+                exportedValue = JSValue(instance->loadI32Global(exp.kindIndex));
+                break;
+
+            case Wasm::F32:
+                exportedValue = JSValue(instance->loadF32Global(exp.kindIndex));
+                break;
+
+            case Wasm::F64:
+                exportedValue = JSValue(instance->loadF64Global(exp.kindIndex));
+                break;
+
+            default:
+                RELEASE_ASSERT_NOT_REACHED();
+            }
             break;
         }
         }