Bug 1412238 - WebAssembly.Global object identity + mutable export. r=luke
authorLars T Hansen <lhansen@mozilla.com>
Wed, 07 Mar 2018 14:51:58 +0100
changeset 463228 5c82560d19d9cd19b5ae800eca4f0e7b1ab5235b
parent 463227 ccdf91217b3c6e386a357e0db863b59a952b12ac
child 463229 bca28a3a066affab25f49b49767f650a42772675
push id9165
push userasasaki@mozilla.com
push dateThu, 26 Apr 2018 21:04:54 +0000
treeherdermozilla-beta@064c3804de2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs1412238
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1412238 - WebAssembly.Global object identity + mutable export. r=luke A WebAssembly.Global object (WGO) that is imported and then re-exported appears on the exports array as an object that is === to the one that was imported. A global that is exported multiple times is exported as WGOs that are ===. To implement object identity we introduce a GlobalMap that is attached to the instance; it is a hash table because it is expected to be sparse, and it is keyed by globalIndex. (In contrast the spec has a single map for all instances, keyed by storage location, but we don't need the complexity of that.) The GlobalMap is populated with imported and exported WGOs prior to creating an instance. Mutable exportable globals are implemented through a level of indirection: for imported and exported mutables, the instance's global area contains not the global's value but a pointer to a location holding the value. This location is in a non-moveable object owned by the WGO. Mutable globals that are not imported or exported, and immutable globals whether private or not, still have their values stored directly in the instance's global area.
js/src/gc/Policy.h
js/src/jit-test/tests/wasm/globals.js
js/src/jit-test/tests/wasm/spec/globals.wast.js
js/src/jit/CodeGenerator.cpp
js/src/jit/Lowering.cpp
js/src/jit/MIR.h
js/src/jit/Registers.h
js/src/jit/shared/LIR-shared.h
js/src/wasm/AsmJS.cpp
js/src/wasm/WasmBaselineCompile.cpp
js/src/wasm/WasmDebug.cpp
js/src/wasm/WasmGenerator.cpp
js/src/wasm/WasmInstance.cpp
js/src/wasm/WasmInstance.h
js/src/wasm/WasmIonCompile.cpp
js/src/wasm/WasmJS.cpp
js/src/wasm/WasmJS.h
js/src/wasm/WasmModule.cpp
js/src/wasm/WasmModule.h
js/src/wasm/WasmTypes.h
js/src/wasm/WasmValidate.cpp
--- a/js/src/gc/Policy.h
+++ b/js/src/gc/Policy.h
@@ -48,16 +48,17 @@ class SavedFrame;
 class Scope;
 class EnvironmentObject;
 class RequestedModuleObject;
 class ScriptSourceObject;
 class Shape;
 class SharedArrayBufferObject;
 class StructTypeDescr;
 class UnownedBaseShape;
+class WasmGlobalObject;
 class WasmFunctionScope;
 class WasmMemoryObject;
 namespace jit {
 class JitCode;
 } // namespace jit
 } // namespace js
 
 // Expand the given macro D for each valid GC reference type.
@@ -95,16 +96,17 @@ class JitCode;
     D(js::Shape*) \
     D(js::SharedArrayBufferObject*) \
     D(js::StructTypeDescr*) \
     D(js::UnownedBaseShape*) \
     D(js::WasmFunctionScope*) \
     D(js::WasmInstanceObject*) \
     D(js::WasmMemoryObject*) \
     D(js::WasmTableObject*) \
+    D(js::WasmGlobalObject*) \
     D(js::jit::JitCode*)
 
 // Expand the given macro D for each internal tagged GC pointer type.
 #define FOR_EACH_INTERNAL_TAGGED_GC_POINTER_TYPE(D) \
     D(js::TaggedProto)
 
 // Expand the macro D for every GC reference type that we know about.
 #define FOR_EACH_GC_POINTER_TYPE(D) \
--- a/js/src/jit-test/tests/wasm/globals.js
+++ b/js/src/jit-test/tests/wasm/globals.js
@@ -102,18 +102,22 @@ module = wasmEvalText(`(module
 )`, {
     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/);
+if (typeof WebAssembly.Global === "undefined") {
+    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;
@@ -241,23 +245,64 @@ function testInitExpr(type, 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.
-module = new WebAssembly.Module(wasmTextToBinary(`(module (import "globals" "x" (global i64)))`));
-assertErrorMessage(() => new WebAssembly.Instance(module, {globals: {x:42}}),
-                   WebAssembly.LinkError,
-                   /cannot pass i64 to or from JS/);
+
+// Import and export
+if (typeof WebAssembly.Global === "undefined") {
+
+    // Without WebAssembly.Global, i64 cannot be imported or exported
+
+    module = new WebAssembly.Module(wasmTextToBinary(`(module (import "globals" "x" (global i64)))`));
+    assertErrorMessage(() => new WebAssembly.Instance(module, {globals: {x:42}}),
+                       WebAssembly.LinkError,
+                       /cannot pass i64 to or from JS/);
+
+    module = new WebAssembly.Module(wasmTextToBinary(`(module (global i64 (i64.const 42)) (export "" global 0))`));
+    assertErrorMessage(() => new WebAssembly.Instance(module), WebAssembly.LinkError, /cannot pass i64 to or from JS/);
+
+}
+else {
+
+    // We can import and export i64 globals as cells.  They cannot be created
+    // from JS because there's no way to specify a non-zero initial value; that
+    // restriction is tested later.  But we can export one from a module and
+    // import it into another.
 
-module = new WebAssembly.Module(wasmTextToBinary(`(module (global i64 (i64.const 42)) (export "" global 0))`));
-assertErrorMessage(() => new WebAssembly.Instance(module), WebAssembly.LinkError, /cannot pass i64 to or from JS/);
+    let i = new WebAssembly.Instance(
+	new WebAssembly.Module(
+	    wasmTextToBinary(`(module
+			       (global (export "g") i64 (i64.const 37))
+			       (global (export "h") (mut i64) (i64.const 37)))`)));
+
+    let j = new WebAssembly.Instance(
+	new WebAssembly.Module(
+	    wasmTextToBinary(`(module
+			       (import "globals" "g" (global i64))
+			       (func (export "f") (result i32)
+				(i64.eq (get_global 0) (i64.const 37))))`)),
+	{globals: {g: i.exports.g}});
+
+    assertEq(j.exports.f(), 1);
+
+    // We cannot read or write i64 global values from JS.
+
+    let g = i.exports.g;
+
+    assertErrorMessage(() => i.exports.g.value, WebAssembly.LinkError, /cannot pass i64 to or from JS/);
+
+    // Mutability check comes before i64 check.
+    assertErrorMessage(() => i.exports.g.value = 12, TypeError, /can't set value of immutable global/);
+    assertErrorMessage(() => i.exports.h.value = 12, WebAssembly.LinkError, /cannot pass i64 to or from JS/);
+}
 
 // Test inner
 var initialValue = '0x123456789abcdef0';
 var nextValue = '0x531642753864975F';
 wasmAssert(`(module
     (global (mut i64) (i64.const ${initialValue}))
     (global i64 (i64.const ${initialValue}))
     (func $get (result i64) (get_global 0))
@@ -333,16 +378,23 @@ if (typeof WebAssembly.Global === "funct
     assertEq("value" in WebAssembly.Global.prototype, true);
 
     // Can't set the value of an immutable global
     assertErrorMessage(() => (new WebAssembly.Global({type: "i32"})).value = 10,
 		       TypeError,
 		       /can't set value of immutable global/);
 
     {
+	// Can set the value of a mutable global
+	let g = new WebAssembly.Global({type: "i32", mutable: true, value: 37});
+	g.value = 10;
+	assertEq(g.value, 10);
+    }
+
+    {
 	// Misc internal conversions
 	let g = new WebAssembly.Global({type: "i32", value: 42});
 
 	// valueOf
 	assertEq(g - 5, 37);
 
 	// @@toStringTag
 	assertEq(g.toString(), "[object WebAssembly.Global]");
@@ -368,16 +420,108 @@ if (typeof WebAssembly.Global === "funct
 				       (func (export "f") (result i32)
 					(get_global 0)))`)),
 		{ "": { "g": i.exports.g }});
 
 	// And when it is then accessed it has the right value:
 	assertEq(j.exports.f(), 42);
     }
 
+    // Identity of WebAssembly.Global objects (independent of mutablity).
+    {
+	// When a global is exported twice, the two objects are the same.
+	let i =
+	    new WebAssembly.Instance(
+		new WebAssembly.Module(
+		    wasmTextToBinary(`(module
+				       (global i32 (i32.const 0))
+				       (export "a" global 0)
+				       (export "b" global 0))`)));
+	assertEq(i.exports.a, i.exports.b);
+
+	// When a global is imported and then exported, the exported object is
+	// the same as the imported object.
+	let j =
+	    new WebAssembly.Instance(
+		new WebAssembly.Module(
+		    wasmTextToBinary(`(module
+				       (import "" "a" (global i32))
+				       (export "x" global 0))`)),
+		{ "": {a: i.exports.a}});
+
+	assertEq(i.exports.a, j.exports.x);
+
+	// When a global is imported twice (ie aliased) and then exported twice,
+	// the exported objects are the same, and are also the same as the
+	// imported object.
+	let k =
+	    new WebAssembly.Instance(
+		new WebAssembly.Module(
+		    wasmTextToBinary(`(module
+				       (import "" "a" (global i32))
+				       (import "" "b" (global i32))
+				       (export "x" global 0)
+				       (export "y" global 1))`)),
+		{ "": {a: i.exports.a,
+		       b: i.exports.a}});
+
+	assertEq(i.exports.a, k.exports.x);
+	assertEq(k.exports.x, k.exports.y);
+    }
+
+    // Mutability
+    {
+	let i =
+	    new WebAssembly.Instance(
+		new WebAssembly.Module(
+		    wasmTextToBinary(`(module
+				       (global (export "g") (mut i32) (i32.const 37))
+				       (func (export "getter") (result i32)
+					(get_global 0))
+				       (func (export "setter") (param i32)
+					(set_global 0 (get_local 0))))`)));
+
+	let j =
+	    new WebAssembly.Instance(
+		new WebAssembly.Module(
+		    wasmTextToBinary(`(module
+				       (import "" "g" (global (mut i32)))
+				       (func (export "getter") (result i32)
+					(get_global 0))
+				       (func (export "setter") (param i32)
+					(set_global 0 (get_local 0))))`)),
+		{"": {g: i.exports.g}});
+
+	// Initial values
+	assertEq(i.exports.g.value, 37);
+	assertEq(i.exports.getter(), 37);
+	assertEq(j.exports.getter(), 37);
+
+	// Set in i, observe everywhere
+	i.exports.setter(42);
+
+	assertEq(i.exports.g.value, 42);
+	assertEq(i.exports.getter(), 42);
+	assertEq(j.exports.getter(), 42);
+
+	// Set in j, observe everywhere
+	j.exports.setter(78);
+
+    	assertEq(i.exports.g.value, 78);
+	assertEq(i.exports.getter(), 78);
+	assertEq(j.exports.getter(), 78);
+
+	// Set on global object, observe everywhere
+	i.exports.g.value = 197;
+
+    	assertEq(i.exports.g.value, 197);
+	assertEq(i.exports.getter(), 197);
+	assertEq(j.exports.getter(), 197);
+    }
+
     // TEST THIS LAST
 
     // "value" is deletable
     assertEq(delete WebAssembly.Global.prototype.value, true);
     assertEq("value" in WebAssembly.Global.prototype, false);
 
     // ADD NO MORE TESTS HERE!
 }
--- a/js/src/jit-test/tests/wasm/spec/globals.wast.js
+++ b/js/src/jit-test/tests/wasm/spec/globals.wast.js
@@ -48,27 +48,29 @@ run(() => call(instance("\x00\x61\x73\x6
 run(() => call(instance("\x00\x61\x73\x6d\x01\x00\x00\x00\x01\x88\x80\x80\x80\x00\x02\x60\x00\x00\x60\x00\x01\x7d\x02\x8c\x80\x80\x80\x00\x01\x02\x24\x31\x05\x67\x65\x74\x2d\x35\x00\x01\x03\x82\x80\x80\x80\x00\x01\x00\x07\x87\x80\x80\x80\x00\x01\x03\x72\x75\x6e\x00\x01\x0a\x9a\x80\x80\x80\x00\x01\x94\x80\x80\x80\x00\x00\x02\x40\x10\x00\xbc\x43\x00\x00\x00\x41\xbc\x46\x45\x0d\x00\x0f\x0b\x00\x0b", exports("$1", $1)),  "run", []));  // assert_return(() => call($1, "get-5", []), 8.)
 
 // globals.wast:47
 run(() => call(instance("\x00\x61\x73\x6d\x01\x00\x00\x00\x01\x88\x80\x80\x80\x00\x02\x60\x00\x00\x60\x00\x01\x7c\x02\x8c\x80\x80\x80\x00\x01\x02\x24\x31\x05\x67\x65\x74\x2d\x36\x00\x01\x03\x82\x80\x80\x80\x00\x01\x00\x07\x87\x80\x80\x80\x00\x01\x03\x72\x75\x6e\x00\x01\x0a\x9e\x80\x80\x80\x00\x01\x98\x80\x80\x80\x00\x00\x02\x40\x10\x00\xbd\x44\x00\x00\x00\x00\x00\x00\x22\x40\xbd\x51\x45\x0d\x00\x0f\x0b\x00\x0b", exports("$1", $1)),  "run", []));  // assert_return(() => call($1, "get-6", []), 9.)
 
 // globals.wast:49
 assert_invalid("\x00\x61\x73\x6d\x01\x00\x00\x00\x01\x84\x80\x80\x80\x00\x01\x60\x00\x00\x03\x82\x80\x80\x80\x00\x01\x00\x06\x89\x80\x80\x80\x00\x01\x7d\x00\x43\x00\x00\x00\x00\x0b\x0a\x8c\x80\x80\x80\x00\x01\x86\x80\x80\x80\x00\x00\x41\x01\x24\x00\x0b");
 
-// globals.wast:54
-assert_invalid("\x00\x61\x73\x6d\x01\x00\x00\x00\x02\x88\x80\x80\x80\x00\x01\x01\x6d\x01\x61\x03\x7f\x01");
+if (typeof WebAssembly.Global === "undefined") {
+    // globals.wast:54
+    assert_invalid("\x00\x61\x73\x6d\x01\x00\x00\x00\x02\x88\x80\x80\x80\x00\x01\x01\x6d\x01\x61\x03\x7f\x01");
 
-// globals.wast:59
-assert_invalid("\x00\x61\x73\x6d\x01\x00\x00\x00\x02\x88\x80\x80\x80\x00\x01\x01\x6d\x01\x61\x03\x7f\x01");
+    // globals.wast:59
+    assert_invalid("\x00\x61\x73\x6d\x01\x00\x00\x00\x02\x88\x80\x80\x80\x00\x01\x01\x6d\x01\x61\x03\x7f\x01");
 
-// globals.wast:64
-assert_invalid("\x00\x61\x73\x6d\x01\x00\x00\x00\x06\x89\x80\x80\x80\x00\x01\x7d\x01\x43\x00\x00\x00\x00\x0b\x07\x85\x80\x80\x80\x00\x01\x01\x61\x03\x00");
+    // globals.wast:64
+    assert_invalid("\x00\x61\x73\x6d\x01\x00\x00\x00\x06\x89\x80\x80\x80\x00\x01\x7d\x01\x43\x00\x00\x00\x00\x0b\x07\x85\x80\x80\x80\x00\x01\x01\x61\x03\x00");
 
-// globals.wast:69
-assert_invalid("\x00\x61\x73\x6d\x01\x00\x00\x00\x06\x89\x80\x80\x80\x00\x01\x7d\x01\x43\x00\x00\x00\x00\x0b\x07\x85\x80\x80\x80\x00\x01\x01\x61\x03\x00");
+    // globals.wast:69
+    assert_invalid("\x00\x61\x73\x6d\x01\x00\x00\x00\x06\x89\x80\x80\x80\x00\x01\x7d\x01\x43\x00\x00\x00\x00\x0b\x07\x85\x80\x80\x80\x00\x01\x01\x61\x03\x00");
+}
 
 // globals.wast:74
 assert_invalid("\x00\x61\x73\x6d\x01\x00\x00\x00\x06\x8a\x80\x80\x80\x00\x01\x7d\x00\x43\x00\x00\x00\x00\x8c\x0b");
 
 // globals.wast:79
 assert_invalid("\x00\x61\x73\x6d\x01\x00\x00\x00\x06\x86\x80\x80\x80\x00\x01\x7d\x00\x20\x00\x0b");
 
 // globals.wast:84
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -7147,16 +7147,21 @@ CodeGenerator::visitWasmLoadGlobalVar(LW
 {
     MWasmLoadGlobalVar* mir = ins->mir();
 
     MIRType type = mir->type();
     MOZ_ASSERT(IsNumberType(type) || IsSimdType(type));
 
     Register tls = ToRegister(ins->tlsPtr());
     Address addr(tls, offsetof(wasm::TlsData, globalArea) + mir->globalDataOffset());
+    if (mir->isIndirect()) {
+        Register tmp = ToRegister(ins->addrTemp());
+        masm.loadPtr(addr, tmp);
+        addr = Address(tmp, 0);
+    }
     switch (type) {
       case MIRType::Int32:
         masm.load32(addr, ToRegister(ins->output()));
         break;
       case MIRType::Float32:
         masm.loadFloat32(addr, ToFloatRegister(ins->output()));
         break;
       case MIRType::Double:
@@ -7185,16 +7190,21 @@ CodeGenerator::visitWasmStoreGlobalVar(L
 {
     MWasmStoreGlobalVar* mir = ins->mir();
 
     MIRType type = mir->value()->type();
     MOZ_ASSERT(IsNumberType(type) || IsSimdType(type));
 
     Register tls = ToRegister(ins->tlsPtr());
     Address addr(tls, offsetof(wasm::TlsData, globalArea) + mir->globalDataOffset());
+    if (mir->isIndirect()) {
+        Register tmp = ToRegister(ins->addrTemp());
+        masm.loadPtr(addr, tmp);
+        addr = Address(tmp, 0);
+    }
     switch (type) {
       case MIRType::Int32:
         masm.store32(ToRegister(ins->value()), addr);
         break;
       case MIRType::Float32:
         masm.storeFloat32(ToFloatRegister(ins->value()), addr);
         break;
       case MIRType::Double:
@@ -7223,29 +7233,39 @@ CodeGenerator::visitWasmLoadGlobalVarI64
 {
     MWasmLoadGlobalVar* mir = ins->mir();
     MOZ_ASSERT(mir->type() == MIRType::Int64);
 
     Register tls = ToRegister(ins->tlsPtr());
     Address addr(tls, offsetof(wasm::TlsData, globalArea) + mir->globalDataOffset());
 
     Register64 output = ToOutRegister64(ins);
+    if (mir->isIndirect()) {
+        Register tmp = ToRegister(ins->addrTemp());
+        masm.loadPtr(addr, tmp);
+        addr = Address(tmp, 0);
+    }
     masm.load64(addr, output);
 }
 
 void
 CodeGenerator::visitWasmStoreGlobalVarI64(LWasmStoreGlobalVarI64* ins)
 {
     MWasmStoreGlobalVar* mir = ins->mir();
     MOZ_ASSERT(mir->value()->type() == MIRType::Int64);
 
     Register tls = ToRegister(ins->tlsPtr());
     Address addr(tls, offsetof(wasm::TlsData, globalArea) + mir->globalDataOffset());
 
     Register64 value = ToRegister64(ins->value());
+    if (mir->isIndirect()) {
+        Register tmp = ToRegister(ins->addrTemp());
+        masm.loadPtr(addr, tmp);
+        addr = Address(tmp, 0);
+    }
     masm.store64(value, addr);
 }
 
 void
 CodeGenerator::visitTypedArrayLength(LTypedArrayLength* lir)
 {
     Register obj = ToRegister(lir->object());
     Register out = ToRegister(lir->output());
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -4607,46 +4607,48 @@ LIRGenerator::visitWasmAlignmentCheck(MW
 
     auto* lir = new(alloc()) LWasmAlignmentCheck(useRegisterAtStart(index));
     add(lir, ins);
 }
 
 void
 LIRGenerator::visitWasmLoadGlobalVar(MWasmLoadGlobalVar* ins)
 {
+    LDefinition addrTemp = ins->isIndirect() ? temp() : LDefinition::BogusTemp();
     if (ins->type() == MIRType::Int64) {
 #ifdef JS_PUNBOX64
         LAllocation tlsPtr = useRegisterAtStart(ins->tlsPtr());
 #else
         LAllocation tlsPtr = useRegister(ins->tlsPtr());
 #endif
-        defineInt64(new(alloc()) LWasmLoadGlobalVarI64(tlsPtr), ins);
+        defineInt64(new(alloc()) LWasmLoadGlobalVarI64(tlsPtr, addrTemp), ins);
     } else {
         LAllocation tlsPtr = useRegisterAtStart(ins->tlsPtr());
-        define(new(alloc()) LWasmLoadGlobalVar(tlsPtr), ins);
+        define(new(alloc()) LWasmLoadGlobalVar(tlsPtr, addrTemp), ins);
     }
 }
 
 void
 LIRGenerator::visitWasmStoreGlobalVar(MWasmStoreGlobalVar* ins)
 {
     MDefinition* value = ins->value();
+    LDefinition addrTemp = ins->isIndirect() ? temp() : LDefinition::BogusTemp();
     if (value->type() == MIRType::Int64) {
 #ifdef JS_PUNBOX64
         LAllocation tlsPtr = useRegisterAtStart(ins->tlsPtr());
         LInt64Allocation valueAlloc = useInt64RegisterAtStart(value);
 #else
         LAllocation tlsPtr = useRegister(ins->tlsPtr());
         LInt64Allocation valueAlloc = useInt64Register(value);
 #endif
-        add(new(alloc()) LWasmStoreGlobalVarI64(valueAlloc, tlsPtr), ins);
+        add(new(alloc()) LWasmStoreGlobalVarI64(valueAlloc, tlsPtr, addrTemp), ins);
     } else {
         LAllocation tlsPtr = useRegisterAtStart(ins->tlsPtr());
         LAllocation valueAlloc = useRegisterAtStart(value);
-        add(new(alloc()) LWasmStoreGlobalVar(valueAlloc, tlsPtr), ins);
+        add(new(alloc()) LWasmStoreGlobalVar(valueAlloc, tlsPtr, addrTemp), ins);
     }
 }
 
 void
 LIRGenerator::visitWasmParameter(MWasmParameter* ins)
 {
     ABIArg abi = ins->abi();
     if (abi.argInRegister()) {
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -14824,63 +14824,72 @@ class MWasmAtomicBinopHeap
         return AliasSet::Store(AliasSet::WasmHeap);
     }
 };
 
 class MWasmLoadGlobalVar
   : public MUnaryInstruction,
     public NoTypePolicy::Data
 {
-    MWasmLoadGlobalVar(MIRType type, unsigned globalDataOffset, bool isConstant, MDefinition* tlsPtr)
+    MWasmLoadGlobalVar(MIRType type, unsigned globalDataOffset, bool isConstant, bool isIndirect,
+                       MDefinition* tlsPtr)
       : MUnaryInstruction(classOpcode, tlsPtr),
-        globalDataOffset_(globalDataOffset), isConstant_(isConstant)
+        globalDataOffset_(globalDataOffset),
+        isConstant_(isConstant),
+        isIndirect_(isIndirect)
     {
         MOZ_ASSERT(IsNumberType(type) || IsSimdType(type));
         setResultType(type);
         setMovable();
     }
 
     unsigned globalDataOffset_;
     bool isConstant_;
+    bool isIndirect_;
 
   public:
     INSTRUCTION_HEADER(WasmLoadGlobalVar)
     TRIVIAL_NEW_WRAPPERS
     NAMED_OPERANDS((0, tlsPtr))
 
     unsigned globalDataOffset() const { return globalDataOffset_; }
+    bool isIndirect() const { return isIndirect_; }
 
     HashNumber valueHash() const override;
     bool congruentTo(const MDefinition* ins) const override;
     MDefinition* foldsTo(TempAllocator& alloc) override;
 
     AliasSet getAliasSet() const override {
         return isConstant_ ? AliasSet::None() : AliasSet::Load(AliasSet::WasmGlobalVar);
     }
 
     AliasType mightAlias(const MDefinition* def) const override;
 };
 
 class MWasmStoreGlobalVar
   : public MBinaryInstruction,
     public NoTypePolicy::Data
 {
-    MWasmStoreGlobalVar(unsigned globalDataOffset, MDefinition* value, MDefinition* tlsPtr)
+    MWasmStoreGlobalVar(unsigned globalDataOffset, bool isIndirect, MDefinition* value,
+                        MDefinition* tlsPtr)
       : MBinaryInstruction(classOpcode, value, tlsPtr),
-        globalDataOffset_(globalDataOffset)
+        globalDataOffset_(globalDataOffset),
+        isIndirect_(isIndirect)
     { }
 
     unsigned globalDataOffset_;
+    bool isIndirect_;
 
   public:
     INSTRUCTION_HEADER(WasmStoreGlobalVar)
     TRIVIAL_NEW_WRAPPERS
     NAMED_OPERANDS((0, value), (1, tlsPtr))
 
     unsigned globalDataOffset() const { return globalDataOffset_; }
+    bool isIndirect() const { return isIndirect_; }
 
     AliasSet getAliasSet() const override {
         return AliasSet::Store(AliasSet::WasmGlobalVar);
     }
 };
 
 class MWasmParameter : public MNullaryInstruction
 {
--- a/js/src/jit/Registers.h
+++ b/js/src/jit/Registers.h
@@ -120,17 +120,17 @@ struct Register {
 // Architectures where the stack pointer is not a plain register with a standard
 // register encoding must define JS_HAS_HIDDEN_SP and HiddenSPEncoding.
 
 #ifdef JS_HAS_HIDDEN_SP
 struct RegisterOrSP
 {
     // The register code -- but possibly one that cannot be represented as a bit
     // position in a 32-bit vector.
-    const uint32_t code;
+    uint32_t code;
 
     explicit RegisterOrSP(uint32_t code) : code(code) {}
     explicit RegisterOrSP(Register r) : code(r.code()) {}
 };
 
 static inline bool
 IsHiddenSP(RegisterOrSP r)
 {
--- a/js/src/jit/shared/LIR-shared.h
+++ b/js/src/jit/shared/LIR-shared.h
@@ -9725,90 +9725,108 @@ class LWasmAtomicBinopHeapForEffect : pu
         return getTemp(4);
     }
 
     MWasmAtomicBinopHeap* mir() const {
         return mir_->toWasmAtomicBinopHeap();
     }
 };
 
-class LWasmLoadGlobalVar : public LInstructionHelper<1, 1, 0>
+class LWasmLoadGlobalVar : public LInstructionHelper<1, 1, 1>
 {
   public:
     LIR_HEADER(WasmLoadGlobalVar);
-    explicit LWasmLoadGlobalVar(const LAllocation& tlsPtr)
+    explicit LWasmLoadGlobalVar(const LAllocation& tlsPtr, const LDefinition& addrTemp)
       : LInstructionHelper(classOpcode)
     {
         setOperand(0, tlsPtr);
+        setTemp(0, addrTemp);
     }
     MWasmLoadGlobalVar* mir() const {
         return mir_->toWasmLoadGlobalVar();
     }
     const LAllocation* tlsPtr() {
         return getOperand(0);
     }
-};
-
-class LWasmLoadGlobalVarI64 : public LInstructionHelper<INT64_PIECES, 1, 0>
+    const LDefinition* addrTemp() {
+        return getTemp(0);
+    }
+};
+
+class LWasmLoadGlobalVarI64 : public LInstructionHelper<INT64_PIECES, 1, 1>
 {
   public:
     LIR_HEADER(WasmLoadGlobalVarI64);
-    explicit LWasmLoadGlobalVarI64(const LAllocation& tlsPtr)
+    explicit LWasmLoadGlobalVarI64(const LAllocation& tlsPtr, const LDefinition& addrTemp)
       : LInstructionHelper(classOpcode)
     {
         setOperand(0, tlsPtr);
+        setTemp(0, addrTemp);
     }
     MWasmLoadGlobalVar* mir() const {
         return mir_->toWasmLoadGlobalVar();
     }
     const LAllocation* tlsPtr() {
         return getOperand(0);
     }
-};
-
-class LWasmStoreGlobalVar : public LInstructionHelper<0, 2, 0>
+    const LDefinition* addrTemp() {
+        return getTemp(0);
+    }
+};
+
+class LWasmStoreGlobalVar : public LInstructionHelper<0, 2, 1>
 {
   public:
     LIR_HEADER(WasmStoreGlobalVar);
-    LWasmStoreGlobalVar(const LAllocation& value, const LAllocation& tlsPtr)
+    LWasmStoreGlobalVar(const LAllocation& value, const LAllocation& tlsPtr,
+                        const LDefinition& addrTemp)
       : LInstructionHelper(classOpcode)
     {
         setOperand(0, value);
         setOperand(1, tlsPtr);
+        setTemp(0, addrTemp);
     }
     MWasmStoreGlobalVar* mir() const {
         return mir_->toWasmStoreGlobalVar();
     }
     const LAllocation* value() {
         return getOperand(0);
     }
     const LAllocation* tlsPtr() {
         return getOperand(1);
     }
-};
-
-class LWasmStoreGlobalVarI64 : public LInstructionHelper<0, INT64_PIECES + 1, 0>
+    const LDefinition* addrTemp() {
+        return getTemp(0);
+    }
+};
+
+class LWasmStoreGlobalVarI64 : public LInstructionHelper<0, INT64_PIECES + 1, 1>
 {
   public:
     LIR_HEADER(WasmStoreGlobalVarI64);
-    LWasmStoreGlobalVarI64(const LInt64Allocation& value, const LAllocation& tlsPtr)
+    LWasmStoreGlobalVarI64(const LInt64Allocation& value, const LAllocation& tlsPtr,
+                           const LDefinition& addrTemp)
       : LInstructionHelper(classOpcode)
     {
         setInt64Operand(0, value);
         setOperand(INT64_PIECES, tlsPtr);
+        setTemp(0, addrTemp);
     }
     MWasmStoreGlobalVar* mir() const {
         return mir_->toWasmStoreGlobalVar();
     }
     const LInt64Allocation value() {
         return getInt64Operand(0);
     }
     const LAllocation* tlsPtr() {
         return getOperand(INT64_PIECES);
     }
+    const LDefinition* addrTemp() {
+        return getTemp(0);
+    }
 };
 
 class LWasmParameter : public LInstructionHelper<1, 0, 0>
 {
   public:
     LIR_HEADER(WasmParameter);
 
     LWasmParameter()
--- a/js/src/wasm/AsmJS.cpp
+++ b/js/src/wasm/AsmJS.cpp
@@ -1943,18 +1943,21 @@ class MOZ_STACK_CLASS ModuleValidator
         }
         return true;
     }
     bool addGlobalVarInit(PropertyName* var, const NumLit& lit, Type type, bool isConst) {
         MOZ_ASSERT(type.isGlobalVarType());
         MOZ_ASSERT(type == Type::canonicalize(Type::lit(lit)));
 
         uint32_t index = env_.globals.length();
-        if (!env_.globals.emplaceBack(type.canonicalToValType(), !isConst, index))
-            return false;
+        if (!env_.globals.emplaceBack(type.canonicalToValType(), !isConst, index,
+                                      ModuleKind::AsmJS))
+        {
+            return false;
+        }
 
         Global::Which which = isConst ? Global::ConstantLiteral : Global::Variable;
         Global* global = validationLifo_.new_<Global>(which);
         if (!global)
             return false;
         global->u.varOrConst.index_ = index;
         global->u.varOrConst.type_ = (isConst ? Type::lit(lit) : type).which();
         if (isConst)
@@ -1971,17 +1974,17 @@ class MOZ_STACK_CLASS ModuleValidator
         MOZ_ASSERT(type.isGlobalVarType());
 
         UniqueChars fieldChars = StringToNewUTF8CharsZ(cx_, *field);
         if (!fieldChars)
             return false;
 
         uint32_t index = env_.globals.length();
         ValType valType = type.canonicalToValType();
-        if (!env_.globals.emplaceBack(valType, !isConst, index))
+        if (!env_.globals.emplaceBack(valType, !isConst, index, ModuleKind::AsmJS))
             return false;
 
         Global::Which which = isConst ? Global::ConstantImport : Global::Variable;
         Global* global = validationLifo_.new_<Global>(which);
         if (!global)
             return false;
         global->u.varOrConst.index_ = index;
         global->u.varOrConst.type_ = type.which();
@@ -8161,18 +8164,20 @@ TryInstantiate(JSContext* cx, CallArgs a
             return false;
     }
 
     ValVector valImports;
     Rooted<FunctionVector> funcs(cx, FunctionVector(cx));
     if (!GetImports(cx, metadata, globalVal, importVal, &funcs, &valImports))
         return false;
 
+    Rooted<WasmGlobalObjectVector> globalObjs(cx);
+
     RootedWasmTableObject table(cx);
-    if (!module.instantiate(cx, funcs, table, memory, valImports, nullptr, instanceObj))
+    if (!module.instantiate(cx, funcs, table, memory, valImports, globalObjs.get(), nullptr, instanceObj))
         return false;
 
     exportObj.set(&instanceObj->exportsObj());
     return true;
 }
 
 static bool
 HandleInstantiationFailure(JSContext* cx, CallArgs args, const AsmJSMetadata& metadata)
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -4124,74 +4124,26 @@ class BaseCompiler final : public BaseCo
         MOZ_CRASH("NYI");
 #endif
     }
 
     //////////////////////////////////////////////////////////////////////
     //
     // Global variable access.
 
-    uint32_t globalToTlsOffset(uint32_t globalOffset) {
-        return offsetof(TlsData, globalArea) + globalOffset;
-    }
-
-    void loadGlobalVarI32(unsigned globalDataOffset, RegI32 r)
-    {
-        ScratchI32 tmp(*this);
-        masm.loadWasmTlsRegFromFrame(tmp);
-        masm.load32(Address(tmp, globalToTlsOffset(globalDataOffset)), r);
-    }
-
-    void loadGlobalVarI64(unsigned globalDataOffset, RegI64 r)
+    Address addressOfGlobalVar(const GlobalDesc& global, RegI32 tmp)
     {
-        ScratchI32 tmp(*this);
-        masm.loadWasmTlsRegFromFrame(tmp);
-        masm.load64(Address(tmp, globalToTlsOffset(globalDataOffset)), r);
-    }
-
-    void loadGlobalVarF32(unsigned globalDataOffset, RegF32 r)
-    {
-        ScratchI32 tmp(*this);
-        masm.loadWasmTlsRegFromFrame(tmp);
-        masm.loadFloat32(Address(tmp, globalToTlsOffset(globalDataOffset)), r);
-    }
-
-    void loadGlobalVarF64(unsigned globalDataOffset, RegF64 r)
-    {
-        ScratchI32 tmp(*this);
+        uint32_t globalToTlsOffset = offsetof(TlsData, globalArea) + global.offset();
+
         masm.loadWasmTlsRegFromFrame(tmp);
-        masm.loadDouble(Address(tmp, globalToTlsOffset(globalDataOffset)), r);
-    }
-
-    void storeGlobalVarI32(unsigned globalDataOffset, RegI32 r)
-    {
-        ScratchI32 tmp(*this);
-        masm.loadWasmTlsRegFromFrame(tmp);
-        masm.store32(r, Address(tmp, globalToTlsOffset(globalDataOffset)));
-    }
-
-    void storeGlobalVarI64(unsigned globalDataOffset, RegI64 r)
-    {
-        ScratchI32 tmp(*this);
-        masm.loadWasmTlsRegFromFrame(tmp);
-        masm.store64(r, Address(tmp, globalToTlsOffset(globalDataOffset)));
-    }
-
-    void storeGlobalVarF32(unsigned globalDataOffset, RegF32 r)
-    {
-        ScratchI32 tmp(*this);
-        masm.loadWasmTlsRegFromFrame(tmp);
-        masm.storeFloat32(r, Address(tmp, globalToTlsOffset(globalDataOffset)));
-    }
-
-    void storeGlobalVarF64(unsigned globalDataOffset, RegF64 r)
-    {
-        ScratchI32 tmp(*this);
-        masm.loadWasmTlsRegFromFrame(tmp);
-        masm.storeDouble(r, Address(tmp, globalToTlsOffset(globalDataOffset)));
+        if (global.isIndirect()) {
+            masm.loadPtr(Address(tmp, globalToTlsOffset), tmp);
+            return Address(tmp, 0);
+        }
+        return Address(tmp, globalToTlsOffset);
     }
 
     //////////////////////////////////////////////////////////////////////
     //
     // Heap access.
 
     void bceCheckLocal(MemoryAccessDesc* access, AccessCheck* check, uint32_t local) {
         if (local >= sizeof(BCESet)*8)
@@ -8025,38 +7977,39 @@ BaseCompiler::emitGetGlobal()
             pushF64(value.f64());
             break;
           default:
             MOZ_CRASH("Global constant type");
         }
         return true;
     }
 
+    ScratchI32 tmp(*this);
     switch (global.type()) {
       case ValType::I32: {
         RegI32 rv = needI32();
-        loadGlobalVarI32(global.offset(), rv);
+        masm.load32(addressOfGlobalVar(global, tmp), rv);
         pushI32(rv);
         break;
       }
       case ValType::I64: {
         RegI64 rv = needI64();
-        loadGlobalVarI64(global.offset(), rv);
+        masm.load64(addressOfGlobalVar(global, tmp), rv);
         pushI64(rv);
         break;
       }
       case ValType::F32: {
         RegF32 rv = needF32();
-        loadGlobalVarF32(global.offset(), rv);
+        masm.loadFloat32(addressOfGlobalVar(global, tmp), rv);
         pushF32(rv);
         break;
       }
       case ValType::F64: {
         RegF64 rv = needF64();
-        loadGlobalVarF64(global.offset(), rv);
+        masm.loadDouble(addressOfGlobalVar(global, tmp), rv);
         pushF64(rv);
         break;
       }
       default:
         MOZ_CRASH("Global variable type");
         break;
     }
     return true;
@@ -8070,38 +8023,39 @@ BaseCompiler::emitSetGlobal()
     if (!iter_.readSetGlobal(&id, &unused_value))
         return false;
 
     if (deadCode_)
         return true;
 
     const GlobalDesc& global = env_.globals[id];
 
+    ScratchI32 tmp(*this);
     switch (global.type()) {
       case ValType::I32: {
         RegI32 rv = popI32();
-        storeGlobalVarI32(global.offset(), rv);
+        masm.store32(rv, addressOfGlobalVar(global, tmp));
         freeI32(rv);
         break;
       }
       case ValType::I64: {
         RegI64 rv = popI64();
-        storeGlobalVarI64(global.offset(), rv);
+        masm.store64(rv, addressOfGlobalVar(global, tmp));
         freeI64(rv);
         break;
       }
       case ValType::F32: {
         RegF32 rv = popF32();
-        storeGlobalVarF32(global.offset(), rv);
+        masm.storeFloat32(rv, addressOfGlobalVar(global, tmp));
         freeF32(rv);
         break;
       }
       case ValType::F64: {
         RegF64 rv = popF64();
-        storeGlobalVarF64(global.offset(), rv);
+        masm.storeDouble(rv, addressOfGlobalVar(global, tmp));
         freeF64(rv);
         break;
       }
       default:
         MOZ_CRASH("Global variable type");
         break;
     }
     return true;
--- a/js/src/wasm/WasmDebug.cpp
+++ b/js/src/wasm/WasmDebug.cpp
@@ -576,16 +576,18 @@ DebugState::getGlobal(Instance& instance
           default:
             MOZ_CRASH("Global constant type");
         }
         return true;
     }
 
     uint8_t* globalData = instance.globalData();
     void* dataPtr = globalData + global.offset();
+    if (global.isIndirect())
+        dataPtr = *static_cast<void**>(dataPtr);
     switch (global.type()) {
       case ValType::I32: {
         vp.set(Int32Value(*static_cast<int32_t*>(dataPtr)));
         break;
       }
       case ValType::I64: {
         // Just display as a Number; it's ok if we lose some precision
         vp.set(NumberValue((double)*static_cast<int64_t*>(dataPtr)));
--- a/js/src/wasm/WasmGenerator.cpp
+++ b/js/src/wasm/WasmGenerator.cpp
@@ -269,17 +269,17 @@ ModuleGenerator::init(Metadata* maybeAsm
             }
         }
     }
 
     for (GlobalDesc& global : env_->globals) {
         if (global.isConstant())
             continue;
 
-        uint32_t width = SizeOf(global.type());
+        uint32_t width = global.isIndirect() ? sizeof(void*) : SizeOf(global.type());
 
         uint32_t globalDataOffset;
         if (!allocateGlobalBytes(width, width, &globalDataOffset))
             return false;
 
         global.setOffset(globalDataOffset);
     }
 
--- a/js/src/wasm/WasmInstance.cpp
+++ b/js/src/wasm/WasmInstance.cpp
@@ -379,17 +379,18 @@ Instance::wake(Instance* instance, uint3
 Instance::Instance(JSContext* cx,
                    Handle<WasmInstanceObject*> object,
                    SharedCode code,
                    UniqueDebugState debug,
                    UniqueTlsData tlsDataIn,
                    HandleWasmMemoryObject memory,
                    SharedTableVector&& tables,
                    Handle<FunctionVector> funcImports,
-                   const ValVector& globalImports)
+                   const ValVector& globalImportValues,
+                   const WasmGlobalObjectVector& globalObjs)
   : compartment_(cx->compartment()),
     object_(object),
     code_(code),
     debug_(Move(debug)),
     tlsData_(Move(tlsDataIn)),
     memory_(memory),
     tables_(Move(tables)),
     enterFrameTrapsEnabled_(false)
@@ -441,35 +442,54 @@ Instance::Instance(JSContext* cx,
         const TableDesc& td = metadata().tables[i];
         TableTls& table = tableTls(td);
         table.length = tables_[i]->length();
         table.base = tables_[i]->base();
     }
 
     for (size_t i = 0; i < metadata().globals.length(); i++) {
         const GlobalDesc& global = metadata().globals[i];
+
+        // Constants are baked into the code, never stored in the global area.
         if (global.isConstant())
             continue;
 
         uint8_t* globalAddr = globalData() + global.offset();
         switch (global.kind()) {
           case GlobalKind::Import: {
-            globalImports[global.importIndex()].writePayload(globalAddr);
+            if (global.isIndirect())
+                *(void**)globalAddr = globalObjs[global.importIndex()]->cell();
+            else
+                globalImportValues[global.importIndex()].writePayload(globalAddr);
             break;
           }
           case GlobalKind::Variable: {
             const InitExpr& init = global.initExpr();
             switch (init.kind()) {
               case InitExpr::Kind::Constant: {
-                init.val().writePayload(globalAddr);
+                if (global.isIndirect())
+                    *(void**)globalAddr = globalObjs[i]->cell();
+                else
+                    init.val().writePayload(globalAddr);
                 break;
               }
               case InitExpr::Kind::GetGlobal: {
                 const GlobalDesc& imported = metadata().globals[init.globalIndex()];
-                globalImports[imported.importIndex()].writePayload(globalAddr);
+
+                // Global-ref initializers cannot reference mutable globals, so
+                // the source global should never be indirect.
+                MOZ_ASSERT(!imported.isIndirect());
+
+                if (global.isIndirect()) {
+                    void* address = globalObjs[i]->cell();
+                    *(void**)globalAddr = address;
+                    globalImportValues[imported.importIndex()].writePayload((uint8_t*)address);
+                } else {
+                    globalImportValues[imported.importIndex()].writePayload(globalAddr);
+                }
                 break;
               }
             }
             break;
           }
           case GlobalKind::Constant: {
             MOZ_CRASH("skipped at the top");
           }
--- a/js/src/wasm/WasmInstance.h
+++ b/js/src/wasm/WasmInstance.h
@@ -70,17 +70,18 @@ class Instance
     Instance(JSContext* cx,
              HandleWasmInstanceObject object,
              SharedCode code,
              UniqueDebugState debug,
              UniqueTlsData tlsData,
              HandleWasmMemoryObject memory,
              SharedTableVector&& tables,
              Handle<FunctionVector> funcImports,
-             const ValVector& globalImports);
+             const ValVector& globalImportValues,
+             const WasmGlobalObjectVector& globalObjs);
     ~Instance();
     bool init(JSContext* cx);
     void trace(JSTracer* trc);
 
     JSCompartment* compartment() const { return compartment_; }
     const Code& code() const { return *code_; }
     const CodeTier& code(Tier t) const { return code_->codeTier(t); }
     DebugState& debug() { return *debug_; }
--- a/js/src/wasm/WasmIonCompile.cpp
+++ b/js/src/wasm/WasmIonCompile.cpp
@@ -1009,31 +1009,33 @@ class FunctionCompiler
         if (isSmallerAccessForI64(result, access)) {
             binop = MExtendInt32ToInt64::New(alloc(), binop, true);
             curBlock_->add(binop);
         }
 
         return binop;
     }
 
-    MDefinition* loadGlobalVar(unsigned globalDataOffset, bool isConst, MIRType type)
+    MDefinition* loadGlobalVar(unsigned globalDataOffset, bool isConst, bool isIndirect, MIRType type)
     {
         if (inDeadCode())
             return nullptr;
 
-        auto* load = MWasmLoadGlobalVar::New(alloc(), type, globalDataOffset, isConst, tlsPointer_);
+        auto* load = MWasmLoadGlobalVar::New(alloc(), type, globalDataOffset, isConst, isIndirect,
+                                             tlsPointer_);
         curBlock_->add(load);
         return load;
     }
 
-    void storeGlobalVar(uint32_t globalDataOffset, MDefinition* v)
+    void storeGlobalVar(uint32_t globalDataOffset, bool isIndirect, MDefinition* v)
     {
         if (inDeadCode())
             return;
-        curBlock_->add(MWasmStoreGlobalVar::New(alloc(), globalDataOffset, v, tlsPointer_));
+        curBlock_->add(MWasmStoreGlobalVar::New(alloc(), globalDataOffset, isIndirect, v,
+                                                tlsPointer_));
     }
 
     void addInterruptCheck()
     {
         if (inDeadCode())
             return;
         curBlock_->add(MWasmInterruptCheck::New(alloc(), tlsPointer_, bytecodeOffset()));
     }
@@ -2288,17 +2290,17 @@ EmitGetGlobal(FunctionCompiler& f)
 {
     uint32_t id;
     if (!f.iter().readGetGlobal(&id))
         return false;
 
     const GlobalDesc& global = f.env().globals[id];
     if (!global.isConstant()) {
         f.iter().setResult(f.loadGlobalVar(global.offset(), !global.isMutable(),
-                                           ToMIRType(global.type())));
+                                           global.isIndirect(), ToMIRType(global.type())));
         return true;
     }
 
     Val value = global.constantValue();
     MIRType mirType = ToMIRType(value.type());
 
     MDefinition* result;
     switch (value.type()) {
@@ -2339,33 +2341,32 @@ EmitSetGlobal(FunctionCompiler& f)
 {
     uint32_t id;
     MDefinition* value;
     if (!f.iter().readSetGlobal(&id, &value))
         return false;
 
     const GlobalDesc& global = f.env().globals[id];
     MOZ_ASSERT(global.isMutable());
-
-    f.storeGlobalVar(global.offset(), value);
+    f.storeGlobalVar(global.offset(), global.isIndirect(), value);
     return true;
 }
 
 static bool
 EmitTeeGlobal(FunctionCompiler& f)
 {
     uint32_t id;
     MDefinition* value;
     if (!f.iter().readTeeGlobal(&id, &value))
         return false;
 
     const GlobalDesc& global = f.env().globals[id];
     MOZ_ASSERT(global.isMutable());
 
-    f.storeGlobalVar(global.offset(), value);
+    f.storeGlobalVar(global.offset(), global.isIndirect(), value);
     return true;
 }
 
 template <typename MIRClass>
 static bool
 EmitUnary(FunctionCompiler& f, ValType operandType)
 {
     MDefinition* input;
--- a/js/src/wasm/WasmJS.cpp
+++ b/js/src/wasm/WasmJS.cpp
@@ -135,37 +135,28 @@ wasm::ToWebAssemblyValue(JSContext* cx, 
         return true;
       }
       default: {
         MOZ_CRASH("unexpected import value type, caller must guard");
       }
     }
 }
 
-void
-wasm::ToJSValue(const Val& val, MutableHandleValue value)
+Value
+wasm::ToJSValue(const Val& val)
 {
     switch (val.type()) {
-      case ValType::I32: {
-        value.set(Int32Value(val.i32()));
-        return;
-      }
-      case ValType::F32: {
-        float f = val.f32();
-        value.set(DoubleValue(JS::CanonicalizeNaN(double(f))));
-        return;
-      }
-      case ValType::F64: {
-        double d = val.f64();
-        value.set(DoubleValue(JS::CanonicalizeNaN(d)));
-        return;
-      }
-      default: {
+      case ValType::I32:
+        return Int32Value(val.i32());
+      case ValType::F32:
+        return DoubleValue(JS::CanonicalizeNaN(double(val.f32())));
+      case ValType::F64:
+        return DoubleValue(JS::CanonicalizeNaN(val.f64()));
+      default:
         MOZ_CRASH("unexpected type when translating to a JS value");
-      }
     }
 }
 
 // ============================================================================
 // Imports
 
 static bool
 ThrowBadImportArg(JSContext* cx)
@@ -194,17 +185,18 @@ GetProperty(JSContext* cx, HandleObject 
 
 static bool
 GetImports(JSContext* cx,
            const Module& module,
            HandleObject importObj,
            MutableHandle<FunctionVector> funcImports,
            MutableHandleWasmTableObject tableImport,
            MutableHandleWasmMemoryObject memoryImport,
-           ValVector* globalImports)
+           WasmGlobalObjectVector& globalObjs,
+           ValVector* globalImportValues)
 {
     const ImportVector& imports = module.imports();
     if (!imports.empty() && !importObj)
         return ThrowBadImportArg(cx);
 
     const Metadata& metadata = module.metadata();
 
     uint32_t globalIndex = 0;
@@ -245,36 +237,45 @@ GetImports(JSContext* cx,
                 return ThrowBadImportType(cx, import.field.get(), "Memory");
 
             MOZ_ASSERT(!memoryImport);
             memoryImport.set(&v.toObject().as<WasmMemoryObject>());
             break;
 
           case DefinitionKind::Global:
             Val val;
-            const GlobalDesc& global = globals[globalIndex++];
-            MOZ_ASSERT(global.importIndex() == globalIndex - 1);
-            MOZ_ASSERT(!global.isMutable());
+            const uint32_t index = globalIndex++;
+            const GlobalDesc& global = globals[index];
+            MOZ_ASSERT(global.importIndex() == index);
 
 #if defined(ENABLE_WASM_GLOBAL) && defined(EARLY_BETA_OR_EARLIER)
-            if (v.isObject() && v.toObject().is<WasmGlobalObject>())
-                v.set(v.toObject().as<WasmGlobalObject>().value());
+            if (v.isObject() && v.toObject().is<WasmGlobalObject>()) {
+                RootedWasmGlobalObject obj(cx, &v.toObject().as<WasmGlobalObject>());
+                if (globalObjs.length() <= index && !globalObjs.resize(index+1)) {
+                    ReportOutOfMemory(cx);
+                    return false;
+                }
+                globalObjs[index] = obj;
+                val = obj->val();
+            } else
 #endif
-
-            if (global.type() == ValType::I64) {
-                JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_I64_LINK);
-                return false;
+            {
+                if (global.type() == ValType::I64) {
+                    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_I64_LINK);
+                    return false;
+                }
+
+                if (!v.isNumber())
+                    return ThrowBadImportType(cx, import.field.get(), "Number");
+
+                if (!ToWebAssemblyValue(cx, global.type(), v, &val))
+                    return false;
             }
-            if (!v.isNumber())
-                return ThrowBadImportType(cx, import.field.get(), "Number");
-
-            if (!ToWebAssemblyValue(cx, global.type(), v, &val))
-                return false;
-
-            if (!globalImports->append(val))
+
+            if (!globalImportValues->append(val))
                 return false;
         }
     }
 
     MOZ_ASSERT(globalIndex == globals.length() || !globals[globalIndex].isImport());
 
     return true;
 }
@@ -333,21 +334,23 @@ wasm::Eval(JSContext* cx, Handle<TypedAr
         }
         ReportOutOfMemory(cx);
         return false;
     }
 
     Rooted<FunctionVector> funcs(cx, FunctionVector(cx));
     RootedWasmTableObject table(cx);
     RootedWasmMemoryObject memory(cx);
+    Rooted<WasmGlobalObjectVector> globalObjs(cx);
+
     ValVector globals;
-    if (!GetImports(cx, *module, importObj, &funcs, &table, &memory, &globals))
+    if (!GetImports(cx, *module, importObj, &funcs, &table, &memory, globalObjs.get(), &globals))
         return false;
 
-    return module->instantiate(cx, funcs, table, memory, globals, nullptr, instanceObj);
+    return module->instantiate(cx, funcs, table, memory, globals, globalObjs.get(), nullptr, instanceObj);
 }
 
 // ============================================================================
 // Common functions
 
 // '[EnforceRange] unsigned long' types are coerced with
 //    ConvertToInt(v, 32, 'unsigned')
 // defined in Web IDL Section 3.2.4.9.
@@ -974,38 +977,42 @@ WasmInstanceObject::isNewborn() const
     return getReservedSlot(INSTANCE_SLOT).isUndefined();
 }
 
 /* static */ void
 WasmInstanceObject::finalize(FreeOp* fop, JSObject* obj)
 {
     fop->delete_(&obj->as<WasmInstanceObject>().exports());
     fop->delete_(&obj->as<WasmInstanceObject>().scopes());
+    fop->delete_(&obj->as<WasmInstanceObject>().indirectGlobals());
     if (!obj->as<WasmInstanceObject>().isNewborn())
         fop->delete_(&obj->as<WasmInstanceObject>().instance());
 }
 
 /* static */ void
 WasmInstanceObject::trace(JSTracer* trc, JSObject* obj)
 {
     WasmInstanceObject& instanceObj = obj->as<WasmInstanceObject>();
     instanceObj.exports().trace(trc);
+    instanceObj.indirectGlobals().trace(trc);
     if (!instanceObj.isNewborn())
         instanceObj.instance().tracePrivate(trc);
 }
 
 /* static */ WasmInstanceObject*
 WasmInstanceObject::create(JSContext* cx,
                            SharedCode code,
                            UniqueDebugState debug,
                            UniqueTlsData tlsData,
                            HandleWasmMemoryObject memory,
                            SharedTableVector&& tables,
                            Handle<FunctionVector> funcImports,
-                           const ValVector& globalImports,
+                           const GlobalDescVector& globals,
+                           const ValVector& globalImportValues,
+                           const WasmGlobalObjectVector& globalObjs,
                            HandleObject proto)
 {
     UniquePtr<ExportMap> exports = js::MakeUnique<ExportMap>();
     if (!exports || !exports->init()) {
         ReportOutOfMemory(cx);
         return nullptr;
     }
 
@@ -1015,33 +1022,58 @@ WasmInstanceObject::create(JSContext* cx
         return nullptr;
     }
 
     AutoSetNewObjectMetadata metadata(cx);
     RootedWasmInstanceObject obj(cx, NewObjectWithGivenProto<WasmInstanceObject>(cx, proto));
     if (!obj)
         return nullptr;
 
+    uint32_t indirectGlobals = 0;
+
+#if defined(ENABLE_WASM_GLOBAL) && defined(EARLY_BETA_OR_EARLIER)
+    for (uint32_t i = 0; i < globalObjs.length(); i++) {
+        if (globalObjs[i] && globals[i].isIndirect())
+            indirectGlobals++;
+    }
+#endif
+
+    Rooted<UniquePtr<WasmGlobalObjectVector>> indirectGlobalObjs(cx, js::MakeUnique<WasmGlobalObjectVector>());
+    if (!indirectGlobalObjs || !indirectGlobalObjs->resize(indirectGlobals))
+        return nullptr;
+
+#if defined(ENABLE_WASM_GLOBAL) && defined(EARLY_BETA_OR_EARLIER)
+    {
+        uint32_t next = 0;
+        for (uint32_t i = 0; i < globalObjs.length(); i++) {
+            if (globalObjs[i] && globals[i].isIndirect())
+                (*indirectGlobalObjs)[next++] = globalObjs[i];
+        }
+    }
+#endif
+
     obj->setReservedSlot(EXPORTS_SLOT, PrivateValue(exports.release()));
     obj->setReservedSlot(SCOPES_SLOT, PrivateValue(scopes.release()));
+    obj->setReservedSlot(GLOBALS_SLOT, PrivateValue(indirectGlobalObjs.release()));
     obj->setReservedSlot(INSTANCE_SCOPE_SLOT, UndefinedValue());
     MOZ_ASSERT(obj->isNewborn());
 
     MOZ_ASSERT(obj->isTenured(), "assumed by WasmTableObject write barriers");
 
     // Root the Instance via WasmInstanceObject before any possible GC.
     auto* instance = cx->new_<Instance>(cx,
                                         obj,
                                         code,
                                         Move(debug),
                                         Move(tlsData),
                                         memory,
                                         Move(tables),
                                         funcImports,
-                                        globalImports);
+                                        globalImportValues,
+                                        globalObjs);
     if (!instance)
         return nullptr;
 
     obj->initReservedSlot(INSTANCE_SLOT, PrivateValue(instance));
     MOZ_ASSERT(!obj->isNewborn());
 
     if (!instance->init(cx))
         return nullptr;
@@ -1071,21 +1103,23 @@ static bool
 Instantiate(JSContext* cx, const Module& module, HandleObject importObj,
             MutableHandleWasmInstanceObject instanceObj)
 {
     RootedObject instanceProto(cx, &cx->global()->getPrototype(JSProto_WasmInstance).toObject());
 
     Rooted<FunctionVector> funcs(cx, FunctionVector(cx));
     RootedWasmTableObject table(cx);
     RootedWasmMemoryObject memory(cx);
+    Rooted<WasmGlobalObjectVector> globalObjs(cx);
+
     ValVector globals;
-    if (!GetImports(cx, module, importObj, &funcs, &table, &memory, &globals))
+    if (!GetImports(cx, module, importObj, &funcs, &table, &memory, globalObjs.get(), &globals))
         return false;
 
-    return module.instantiate(cx, funcs, table, memory, globals, instanceProto, instanceObj);
+    return module.instantiate(cx, funcs, table, memory, globals, globalObjs.get(), instanceProto, instanceObj);
 }
 
 /* static */ bool
 WasmInstanceObject::construct(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (!ThrowIfNotConstructing(cx, args, "Instance"))
@@ -1132,16 +1166,22 @@ WasmInstanceObject::exports() const
 }
 
 WasmInstanceObject::ScopeMap&
 WasmInstanceObject::scopes() const
 {
     return *(ScopeMap*)getReservedSlot(SCOPES_SLOT).toPrivate();
 }
 
+WasmGlobalObjectVector&
+WasmInstanceObject::indirectGlobals() const
+{
+    return *(WasmGlobalObjectVector*)getReservedSlot(GLOBALS_SLOT).toPrivate();
+}
+
 static bool
 WasmCall(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     RootedFunction callee(cx, &args.callee().as<JSFunction>());
 
     Instance& instance = ExportedFunctionToInstance(callee);
     uint32_t funcIndex = ExportedFunctionToFuncIndex(callee);
@@ -1987,53 +2027,71 @@ Table&
 WasmTableObject::table() const
 {
     return *(Table*)getReservedSlot(TABLE_SLOT).toPrivate();
 }
 
 // ============================================================================
 // WebAssembly.global class and methods
 
-#if defined(ENABLE_WASM_GLOBAL) && defined(EARLY_BETA_OR_EARLIER)
-
 const ClassOps WasmGlobalObject::classOps_ =
 {
     nullptr, /* addProperty */
     nullptr, /* delProperty */
     nullptr, /* enumerate */
     nullptr, /* newEnumerate */
     nullptr, /* resolve */
     nullptr, /* mayResolve */
-    nullptr, /* finalize */
+    WasmGlobalObject::finalize,
     nullptr, /* call */
     nullptr, /* hasInstance */
     nullptr, /* construct */
     nullptr  /* trace */
 };
 
 const Class WasmGlobalObject::class_ =
 {
     "WebAssembly.Global",
-    JSCLASS_HAS_RESERVED_SLOTS(WasmGlobalObject::RESERVED_SLOTS),
+    JSCLASS_HAS_RESERVED_SLOTS(WasmGlobalObject::RESERVED_SLOTS) |
+    JSCLASS_BACKGROUND_FINALIZE,
     &WasmGlobalObject::classOps_
 };
 
+/* static */ void
+WasmGlobalObject::finalize(FreeOp*, JSObject* obj)
+{
+    WasmGlobalObject* global = reinterpret_cast<WasmGlobalObject*>(obj);
+    js_delete(global->cell());
+}
+
 /* static */ WasmGlobalObject*
-WasmGlobalObject::create(JSContext* cx, wasm::ValType type, bool isMutable, HandleValue val)
+WasmGlobalObject::create(JSContext* cx, const Val& val, bool isMutable)
 {
+    UniquePtr<Cell> cell = js::MakeUnique<Cell>();
+    if (!cell)
+        return nullptr;
+
+    switch (val.type()) {
+      case ValType::I32: cell->i32 = val.i32(); break;
+      case ValType::I64: cell->i64 = val.i64(); break;
+      case ValType::F32: cell->f32 = val.f32(); break;
+      case ValType::F64: cell->f64 = val.f64(); break;
+      default:           MOZ_CRASH();
+    }
+
     RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmGlobal).toObject());
 
     AutoSetNewObjectMetadata metadata(cx);
-    Rooted<WasmGlobalObject*> obj(cx, NewObjectWithGivenProto<WasmGlobalObject>(cx, proto));
+    RootedWasmGlobalObject obj(cx, NewObjectWithGivenProto<WasmGlobalObject>(cx, proto));
     if (!obj)
         return nullptr;
 
-    obj->initReservedSlot(TYPE_SLOT, Int32Value(int32_t(type)));
+    obj->initReservedSlot(TYPE_SLOT, Int32Value(int32_t(val.type())));
     obj->initReservedSlot(MUTABLE_SLOT, JS::BooleanValue(isMutable));
-    obj->initReservedSlot(VALUE_SLOT, val);
+    obj->initReservedSlot(CELL_SLOT, PrivateValue(cell.release()));
 
     return obj;
 }
 
 /* static */ bool
 WasmGlobalObject::construct(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
@@ -2084,21 +2142,17 @@ WasmGlobalObject::construct(JSContext* c
     RootedValue valueVal(cx);
     if (!JS_GetProperty(cx, obj, "value", &valueVal))
         return false;
 
     Val globalVal;
     if (!ToWebAssemblyValue(cx, globalType, valueVal, &globalVal))
         return false;
 
-    RootedValue globalValue(cx);
-    ToJSValue(globalVal, &globalValue);
-
-    Rooted<WasmGlobalObject*> global(cx, WasmGlobalObject::create(cx, globalType, isMutable,
-                                                                  globalValue));
+    WasmGlobalObject* global = WasmGlobalObject::create(cx, globalVal, isMutable);
     if (!global)
         return false;
 
     args.rval().setObject(*global);
     return true;
 }
 
 static bool
@@ -2112,42 +2166,58 @@ WasmGlobalObject::valueGetterImpl(JSCont
 {
     switch (args.thisv().toObject().as<WasmGlobalObject>().type()) {
       case ValType::I32:
       case ValType::F32:
       case ValType::F64:
         args.rval().set(args.thisv().toObject().as<WasmGlobalObject>().value());
         return true;
       case ValType::I64:
-        JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_I64_TYPE);
+        JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_I64_LINK);
         return false;
       default:
         MOZ_CRASH();
     }
 }
 
 /* static */ bool
 WasmGlobalObject::valueGetter(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return CallNonGenericMethod<IsGlobal, valueGetterImpl>(cx, args);
 }
 
 /* static */ bool
 WasmGlobalObject::valueSetterImpl(JSContext* cx, const CallArgs& args)
 {
-    if (!args.thisv().toObject().as<WasmGlobalObject>().isMutable()) {
+    RootedWasmGlobalObject global(cx, &args.thisv().toObject().as<WasmGlobalObject>());
+    if (!global->isMutable()) {
         JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_GLOBAL_IMMUTABLE);
         return false;
     }
 
-    // TODO - implement this, we probably need a different representation for
-    // mutable globals.
-    JS_ReportErrorASCII(cx, "Value setter not yet implemented");
-    return false;
+    if (global->type() == ValType::I64) {
+        JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_I64_LINK);
+        return false;
+    }
+
+    Val val;
+    if (!ToWebAssemblyValue(cx, global->type(), args.get(0), &val))
+        return false;
+
+    Cell* cell = global->cell();
+    switch (global->type()) {
+      case ValType::I32: cell->i32 = val.i32(); break;
+      case ValType::F32: cell->f32 = val.f32(); break;
+      case ValType::F64: cell->f64 = val.f64(); break;
+      default:           MOZ_CRASH();
+    }
+
+    args.rval().setUndefined();
+    return true;
 }
 
 /* static */ bool
 WasmGlobalObject::valueSetter(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return CallNonGenericMethod<IsGlobal, valueSetterImpl>(cx, args);
 }
@@ -2175,23 +2245,43 @@ WasmGlobalObject::type() const
 }
 
 bool
 WasmGlobalObject::isMutable() const
 {
     return getReservedSlot(MUTABLE_SLOT).toBoolean();
 }
 
+Val
+WasmGlobalObject::val() const
+{
+    Cell* cell = this->cell();
+    Val val;
+    switch (type()) {
+      case ValType::I32: val = Val(uint32_t(cell->i32)); break;
+      case ValType::I64: val = Val(uint64_t(cell->i64)); break;
+      case ValType::F32: val = Val(cell->f32); break;
+      case ValType::F64: val = Val(cell->f64); break;
+      default:           MOZ_CRASH();
+    }
+    return val;
+}
+
 Value
 WasmGlobalObject::value() const
 {
-    return getReservedSlot(VALUE_SLOT);
+    // ToJSValue crashes on I64; this is desirable.
+    return ToJSValue(val());
 }
 
-#endif // ENABLE_WASM_GLOBAL && EARLY_BETA_OR_EARLIER
+WasmGlobalObject::Cell*
+WasmGlobalObject::cell() const
+{
+    return reinterpret_cast<Cell*>(getReservedSlot(CELL_SLOT).toPrivate());
+}
 
 // ============================================================================
 // WebAssembly class and static methods
 
 static bool
 WebAssembly_toSource(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
--- a/js/src/wasm/WasmJS.h
+++ b/js/src/wasm/WasmJS.h
@@ -44,18 +44,18 @@ bool
 HasSupport(JSContext* cx);
 
 // ToWebAssemblyValue and ToJSValue are conversion functions defined in
 // the Wasm JS API spec.
 
 bool
 ToWebAssemblyValue(JSContext* cx, ValType targetType, HandleValue v, Val* val);
 
-void
-ToJSValue(const Val& val, MutableHandleValue v);
+Value
+ToJSValue(const Val& val);
 
 // Compiles the given binary wasm module given the ArrayBufferObject
 // and links the module's imports with the given import object.
 
 MOZ_MUST_USE bool
 Eval(JSContext* cx, Handle<TypedArrayObject*> code, HandleObject importObj,
      MutableHandleWasmInstanceObject instanceObj);
 
@@ -114,27 +114,76 @@ class WasmModuleObject : public NativeOb
     static bool construct(JSContext*, unsigned, Value*);
 
     static WasmModuleObject* create(JSContext* cx,
                                     wasm::Module& module,
                                     HandleObject proto = nullptr);
     wasm::Module& module() const;
 };
 
+// The class of WebAssembly.Global.  This wraps a storage location, and there is
+// a per-agent one-to-one relationship between the WasmGlobalObject and the
+// storage location (the Cell) it wraps: if a module re-exports an imported
+// global, the imported and exported WasmGlobalObjects are the same, and if a
+// module exports a global twice, the two exported WasmGlobalObjects are the
+// same.
+
+class WasmGlobalObject : public NativeObject
+{
+    static const unsigned TYPE_SLOT = 0;
+    static const unsigned MUTABLE_SLOT = 1;
+    static const unsigned CELL_SLOT = 2;
+
+    static const ClassOps classOps_;
+    static void finalize(FreeOp*, JSObject* obj);
+
+    static bool valueGetterImpl(JSContext* cx, const CallArgs& args);
+    static bool valueGetter(JSContext* cx, unsigned argc, Value* vp);
+    static bool valueSetterImpl(JSContext* cx, const CallArgs& args);
+    static bool valueSetter(JSContext* cx, unsigned argc, Value* vp);
+
+  public:
+    // For exposed globals the Cell holds the value of the global; the
+    // instance's global area holds a pointer to the Cell.
+    union Cell {
+        int32_t i32;
+        int64_t i64;
+        float   f32;
+        double  f64;
+    };
+
+    static const unsigned RESERVED_SLOTS = 3;
+    static const Class class_;
+    static const JSPropertySpec properties[];
+    static const JSFunctionSpec methods[];
+    static const JSFunctionSpec static_methods[];
+    static bool construct(JSContext*, unsigned, Value*);
+
+    static WasmGlobalObject* create(JSContext* cx, const wasm::Val& value, bool isMutable);
+
+    wasm::ValType type() const;
+    wasm::Val val() const;
+    bool isMutable() const;
+    // value() will MOZ_CRASH if the type is int64
+    Value value() const;
+    Cell* cell() const;
+};
+
 // The class of WebAssembly.Instance. Each WasmInstanceObject owns a
 // wasm::Instance. These objects are used both as content-facing JS objects and
 // as internal implementation details of asm.js.
 
 class WasmInstanceObject : public NativeObject
 {
     static const unsigned INSTANCE_SLOT = 0;
     static const unsigned EXPORTS_OBJ_SLOT = 1;
     static const unsigned EXPORTS_SLOT = 2;
     static const unsigned SCOPES_SLOT = 3;
     static const unsigned INSTANCE_SCOPE_SLOT = 4;
+    static const unsigned GLOBALS_SLOT = 5;
 
     static const ClassOps classOps_;
     static bool exportsGetterImpl(JSContext* cx, const CallArgs& args);
     static bool exportsGetter(JSContext* cx, unsigned argc, Value* vp);
     bool isNewborn() const;
     static void finalize(FreeOp* fop, JSObject* obj);
     static void trace(JSTracer* trc, JSObject* obj);
 
@@ -153,31 +202,33 @@ class WasmInstanceObject : public Native
     // during debugging.
     using ScopeMap = JS::WeakCache<GCHashMap<uint32_t,
                                              ReadBarriered<WasmFunctionScope*>,
                                              DefaultHasher<uint32_t>,
                                              SystemAllocPolicy>>;
     ScopeMap& scopes() const;
 
   public:
-    static const unsigned RESERVED_SLOTS = 5;
+    static const unsigned RESERVED_SLOTS = 6;
     static const Class class_;
     static const JSPropertySpec properties[];
     static const JSFunctionSpec methods[];
     static const JSFunctionSpec static_methods[];
     static bool construct(JSContext*, unsigned, Value*);
 
     static WasmInstanceObject* create(JSContext* cx,
                                       RefPtr<const wasm::Code> code,
                                       UniquePtr<wasm::DebugState> debug,
                                       wasm::UniqueTlsData tlsData,
                                       HandleWasmMemoryObject memory,
                                       Vector<RefPtr<wasm::Table>, 0, SystemAllocPolicy>&& tables,
                                       Handle<FunctionVector> funcImports,
-                                      const wasm::ValVector& globalImports,
+                                      const wasm::GlobalDescVector& globals,
+                                      const wasm::ValVector& globalImportValues,
+                                      const WasmGlobalObjectVector& globalObjs,
                                       HandleObject proto);
     void initExportsObj(JSObject& exportsObj);
 
     wasm::Instance& instance() const;
     JSObject& exportsObj() const;
 
     static bool getExportedFunction(JSContext* cx,
                                     HandleWasmInstanceObject instanceObj,
@@ -185,16 +236,18 @@ class WasmInstanceObject : public Native
                                     MutableHandleFunction fun);
 
     const wasm::CodeRange& getExportedFunctionCodeRange(HandleFunction fun, wasm::Tier tier);
 
     static WasmInstanceScope* getScope(JSContext* cx, HandleWasmInstanceObject instanceObj);
     static WasmFunctionScope* getFunctionScope(JSContext* cx,
                                                HandleWasmInstanceObject instanceObj,
                                                uint32_t funcIndex);
+
+    WasmGlobalObjectVector& indirectGlobals() const;
 };
 
 // The class of WebAssembly.Memory. A WasmMemoryObject references an ArrayBuffer
 // or SharedArrayBuffer object which owns the actual memory.
 
 class WasmMemoryObject : public NativeObject
 {
     static const unsigned BUFFER_SLOT = 0;
@@ -280,47 +333,11 @@ class WasmTableObject : public NativeObj
 
     // Note that, after creation, a WasmTableObject's table() is not initialized
     // and must be initialized before use.
 
     static WasmTableObject* create(JSContext* cx, const wasm::Limits& limits);
     wasm::Table& table() const;
 };
 
-#if defined(ENABLE_WASM_GLOBAL) && defined(EARLY_BETA_OR_EARLIER)
-
-// The class of WebAssembly.Global.  A WasmGlobalObject holds either the value
-// of an immutable wasm global or the cell of a mutable wasm global.
-
-class WasmGlobalObject : public NativeObject
-{
-    static const unsigned TYPE_SLOT = 0;
-    static const unsigned MUTABLE_SLOT = 1;
-    static const unsigned VALUE_SLOT = 2;
-
-    static const ClassOps classOps_;
-
-    static bool valueGetterImpl(JSContext* cx, const CallArgs& args);
-    static bool valueGetter(JSContext* cx, unsigned argc, Value* vp);
-    static bool valueSetterImpl(JSContext* cx, const CallArgs& args);
-    static bool valueSetter(JSContext* cx, unsigned argc, Value* vp);
-
-  public:
-    static const unsigned RESERVED_SLOTS = 3;
-    static const Class class_;
-    static const JSPropertySpec properties[];
-    static const JSFunctionSpec methods[];
-    static const JSFunctionSpec static_methods[];
-    static bool construct(JSContext*, unsigned, Value*);
-
-    static WasmGlobalObject* create(JSContext* cx, wasm::ValType type, bool isMutable,
-                                    HandleValue value);
-
-    wasm::ValType type() const;
-    bool isMutable() const;
-    Value value() const;
-};
-
-#endif // ENABLE_WASM_GLOBAL && EARLY_BETA_OR_EARLIER
-
 } // namespace js
 
 #endif // wasm_js_h
--- a/js/src/wasm/WasmModule.cpp
+++ b/js/src/wasm/WasmModule.cpp
@@ -700,77 +700,77 @@ Module::extractCode(JSContext* cx, Tier 
     if (!JS_DefineProperty(cx, result, "segments", value, JSPROP_ENUMERATE))
         return false;
 
     vp.setObject(*result);
     return true;
 }
 
 static uint32_t
-EvaluateInitExpr(const ValVector& globalImports, InitExpr initExpr)
+EvaluateInitExpr(const ValVector& globalImportValues, InitExpr initExpr)
 {
     switch (initExpr.kind()) {
       case InitExpr::Kind::Constant:
         return initExpr.val().i32();
       case InitExpr::Kind::GetGlobal:
-        return globalImports[initExpr.globalIndex()].i32();
+        return globalImportValues[initExpr.globalIndex()].i32();
     }
 
     MOZ_CRASH("bad initializer expression");
 }
 
 bool
 Module::initSegments(JSContext* cx,
                      HandleWasmInstanceObject instanceObj,
                      Handle<FunctionVector> funcImports,
                      HandleWasmMemoryObject memoryObj,
-                     const ValVector& globalImports) const
+                     const ValVector& globalImportValues) const
 {
     Instance& instance = instanceObj->instance();
     const SharedTableVector& tables = instance.tables();
 
     Tier tier = code().bestTier();
 
     // Perform all error checks up front so that this function does not perform
     // partial initialization if an error is reported.
 
     for (const ElemSegment& seg : elemSegments_) {
         uint32_t numElems = seg.elemCodeRangeIndices(tier).length();
 
         uint32_t tableLength = tables[seg.tableIndex]->length();
-        uint32_t offset = EvaluateInitExpr(globalImports, seg.offset);
+        uint32_t offset = EvaluateInitExpr(globalImportValues, seg.offset);
 
         if (offset > tableLength || tableLength - offset < numElems) {
             JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_FIT,
                                      "elem", "table");
             return false;
         }
     }
 
     if (memoryObj) {
         uint32_t memoryLength = memoryObj->volatileMemoryLength();
         for (const DataSegment& seg : dataSegments_) {
-            uint32_t offset = EvaluateInitExpr(globalImports, seg.offset);
+            uint32_t offset = EvaluateInitExpr(globalImportValues, seg.offset);
 
             if (offset > memoryLength || memoryLength - offset < seg.length) {
                 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_FIT,
                                          "data", "memory");
                 return false;
             }
         }
     } else {
         MOZ_ASSERT(dataSegments_.empty());
     }
 
     // Now that initialization can't fail partway through, write data/elem
     // segments into memories/tables.
 
     for (const ElemSegment& seg : elemSegments_) {
         Table& table = *tables[seg.tableIndex];
-        uint32_t offset = EvaluateInitExpr(globalImports, seg.offset);
+        uint32_t offset = EvaluateInitExpr(globalImportValues, seg.offset);
         const CodeRangeVector& codeRanges = metadata(tier).codeRanges;
         uint8_t* codeBase = instance.codeBase(tier);
 
         for (uint32_t i = 0; i < seg.elemCodeRangeIndices(tier).length(); i++) {
             uint32_t funcIndex = seg.elemFuncIndices[i];
             if (funcIndex < funcImports.length() && IsExportedWasmFunction(funcImports[funcIndex])) {
                 MOZ_ASSERT(!metadata().isAsmJS());
                 MOZ_ASSERT(!table.isTypedFunction());
@@ -792,17 +792,17 @@ Module::initSegments(JSContext* cx,
     }
 
     if (memoryObj) {
         uint8_t* memoryBase = memoryObj->buffer().dataPointerEither().unwrap(/* memcpy */);
 
         for (const DataSegment& seg : dataSegments_) {
             MOZ_ASSERT(seg.bytecodeOffset <= bytecode_->length());
             MOZ_ASSERT(seg.length <= bytecode_->length() - seg.bytecodeOffset);
-            uint32_t offset = EvaluateInitExpr(globalImports, seg.offset);
+            uint32_t offset = EvaluateInitExpr(globalImportValues, seg.offset);
             memcpy(memoryBase + offset, bytecode_->begin() + seg.bytecodeOffset, seg.length);
         }
     }
 
     return true;
 }
 
 static const Import&
@@ -989,16 +989,78 @@ Module::instantiateTable(JSContext* cx, 
                 return false;
             }
         }
     }
 
     return true;
 }
 
+static Val
+ExtractGlobalValue(const ValVector& globalImportValues, uint32_t globalIndex, const GlobalDesc& global)
+{
+    switch (global.kind()) {
+      case GlobalKind::Import: {
+        return globalImportValues[globalIndex];
+      }
+      case GlobalKind::Variable: {
+        const InitExpr& init = global.initExpr();
+        switch (init.kind()) {
+          case InitExpr::Kind::Constant:
+            return init.val();
+          case InitExpr::Kind::GetGlobal:
+            return globalImportValues[init.globalIndex()];
+        }
+        break;
+      }
+      case GlobalKind::Constant: {
+        return global.constantValue();
+      }
+    }
+    MOZ_CRASH("Not a global value");
+}
+
+bool
+Module::instantiateGlobalExports(JSContext* cx,
+                                 const ValVector& globalImportValues,
+                                 WasmGlobalObjectVector& globalObjs) const
+{
+#if defined(ENABLE_WASM_GLOBAL) && defined(EARLY_BETA_OR_EARLIER)
+    // If there are exported globals that aren't in the globalObjs because they
+    // originate in this module or because they were immutable imports that came
+    // in as values (not cells) then we must create cells in the globalObjs for
+    // them here, as WasmInstanceObject::create() and CreateExportObject() will
+    // need the cells to exist.
+
+    const GlobalDescVector& globals = metadata().globals;
+
+    for (const Export& exp : exports_) {
+        if (exp.kind() == DefinitionKind::Global) {
+            unsigned globalIndex = exp.globalIndex();
+
+            if (globalIndex >= globalObjs.length() || !globalObjs[globalIndex]) {
+                const GlobalDesc& global = globals[globalIndex];
+
+                Val val = ExtractGlobalValue(globalImportValues, globalIndex, global);
+                RootedWasmGlobalObject go(cx, WasmGlobalObject::create(cx, val,
+                                                                       global.isMutable()));
+                if (!go)
+                    return false;
+                if (globalObjs.length() <= globalIndex && !globalObjs.resize(globalIndex+1)) {
+                    ReportOutOfMemory(cx);
+                    return false;
+                }
+                globalObjs[globalIndex] = go;
+            }
+        }
+    }
+#endif
+    return true;
+}
+
 static bool
 GetFunctionExport(JSContext* cx,
                   HandleWasmInstanceObject instanceObj,
                   Handle<FunctionVector> funcImports,
                   const Export& exp,
                   MutableHandleValue val)
 {
     if (exp.funcIndex() < funcImports.length() &&
@@ -1012,73 +1074,50 @@ GetFunctionExport(JSContext* cx,
     if (!instanceObj->getExportedFunction(cx, instanceObj, exp.funcIndex(), &fun))
         return false;
 
     val.setObject(*fun);
     return true;
 }
 
 static bool
-GetGlobalExport(JSContext* cx, const GlobalDescVector& globals, uint32_t globalIndex,
-                const ValVector& globalImports, MutableHandleValue jsval)
+GetGlobalExport(JSContext* cx,
+                const GlobalDescVector& globals,
+                uint32_t globalIndex,
+                const ValVector& globalImportValues,
+                const WasmGlobalObjectVector& globalObjs,
+                MutableHandleValue jsval)
 {
+#if defined(ENABLE_WASM_GLOBAL) && defined(EARLY_BETA_OR_EARLIER)
+    jsval.setObject(*globalObjs[globalIndex]);
+#else
     const GlobalDesc& global = globals[globalIndex];
 
-    // Imports are located upfront in the globals array.
-    Val val;
-    switch (global.kind()) {
-      case GlobalKind::Import: {
-        val = globalImports[globalIndex];
-        break;
-      }
-      case GlobalKind::Variable: {
-        MOZ_ASSERT(!global.isMutable(), "mutable variables can't be exported");
-        const InitExpr& init = global.initExpr();
-        switch (init.kind()) {
-          case InitExpr::Kind::Constant: {
-            val = init.val();
-            break;
-          }
-          case InitExpr::Kind::GetGlobal: {
-            val = globalImports[init.globalIndex()];
-            break;
-          }
-        }
-        break;
-      }
-      case GlobalKind::Constant: {
-        val = global.constantValue();
-        break;
-      }
-    }
+    MOZ_ASSERT(!global.isMutable(), "Mutable variables can't be exported.");
 
+    Val val = ExtractGlobalValue(globalImportValues, globalIndex, global);
     if (val.type() == ValType::I64) {
         JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_I64_LINK);
         return false;
     }
 
-    ToJSValue(val, jsval);
-
-#if defined(ENABLE_WASM_GLOBAL) && defined(EARLY_BETA_OR_EARLIER)
-    Rooted<WasmGlobalObject*> go(cx, WasmGlobalObject::create(cx, ValType::I32, false, jsval));
-    if (!go)
-        return false;
-    jsval.setObject(*go);
+    jsval.set(ToJSValue(val));
 #endif
 
     return true;
 }
 
 static bool
 CreateExportObject(JSContext* cx,
                    HandleWasmInstanceObject instanceObj,
                    Handle<FunctionVector> funcImports,
                    HandleWasmTableObject tableObj,
                    HandleWasmMemoryObject memoryObj,
-                   const ValVector& globalImports,
+                   const ValVector& globalImportValues,
+                   const WasmGlobalObjectVector& globalObjs,
                    const ExportVector& exports)
 {
     const Instance& instance = instanceObj->instance();
     const Metadata& metadata = instance.metadata();
 
     if (metadata.isAsmJS() && exports.length() == 1 && strlen(exports[0].fieldName()) == 0) {
         RootedValue val(cx);
         if (!GetFunctionExport(cx, instanceObj, funcImports, exports[0], &val))
@@ -1109,18 +1148,21 @@ CreateExportObject(JSContext* cx,
             break;
           case DefinitionKind::Table:
             val = ObjectValue(*tableObj);
             break;
           case DefinitionKind::Memory:
             val = ObjectValue(*memoryObj);
             break;
           case DefinitionKind::Global:
-            if (!GetGlobalExport(cx, metadata.globals, exp.globalIndex(), globalImports, &val))
+            if (!GetGlobalExport(cx, metadata.globals, exp.globalIndex(), globalImportValues,
+                                 globalObjs, &val))
+            {
                 return false;
+            }
             break;
         }
 
         if (!JS_DefinePropertyById(cx, exportObj, id, val, JSPROP_ENUMERATE))
             return false;
     }
 
     if (!metadata.isAsmJS()) {
@@ -1132,32 +1174,36 @@ CreateExportObject(JSContext* cx,
     return true;
 }
 
 bool
 Module::instantiate(JSContext* cx,
                     Handle<FunctionVector> funcImports,
                     HandleWasmTableObject tableImport,
                     HandleWasmMemoryObject memoryImport,
-                    const ValVector& globalImports,
+                    const ValVector& globalImportValues,
+                    WasmGlobalObjectVector& globalObjs,
                     HandleObject instanceProto,
                     MutableHandleWasmInstanceObject instance) const
 {
     if (!instantiateFunctions(cx, funcImports))
         return false;
 
     RootedWasmMemoryObject memory(cx, memoryImport);
     if (!instantiateMemory(cx, &memory))
         return false;
 
     RootedWasmTableObject table(cx, tableImport);
     SharedTableVector tables;
     if (!instantiateTable(cx, &table, &tables))
         return false;
 
+    if (!instantiateGlobalExports(cx, globalImportValues, globalObjs))
+        return false;
+
     UniqueTlsData tlsData = CreateTlsData(metadata().globalDataLength);
     if (!tlsData) {
         ReportOutOfMemory(cx);
         return false;
     }
 
     SharedCode code(code_);
 
@@ -1225,37 +1271,42 @@ Module::instantiate(JSContext* cx,
 
     instance.set(WasmInstanceObject::create(cx,
                                             code,
                                             Move(debug),
                                             Move(tlsData),
                                             memory,
                                             Move(tables),
                                             funcImports,
-                                            globalImports,
+                                            metadata().globals,
+                                            globalImportValues,
+                                            globalObjs,
                                             instanceProto));
     if (!instance)
         return false;
 
-    if (!CreateExportObject(cx, instance, funcImports, table, memory, globalImports, exports_))
+    if (!CreateExportObject(cx, instance, funcImports, table, memory, globalImportValues,
+                            globalObjs, exports_))
+    {
         return false;
+    }
 
     // Register the instance with the JSCompartment so that it can find out
     // about global events like profiling being enabled in the compartment.
     // Registration does not require a fully-initialized instance and must
     // precede initSegments as the final pre-requisite for a live instance.
 
     if (!cx->compartment()->wasm.registerInstance(cx, instance))
         return false;
 
     // Perform initialization as the final step after the instance is fully
     // constructed since this can make the instance live to content (even if the
     // start function fails).
 
-    if (!initSegments(cx, instance, funcImports, memory, globalImports))
+    if (!initSegments(cx, instance, funcImports, memory, globalImportValues))
         return false;
 
     // Now that the instance is fully live and initialized, the start function.
     // Note that failure may cause instantiation to throw, but the instance may
     // still be live via edges created by initSegments or the start function.
 
     if (metadata().startFuncIndex) {
         FixedInvokeArgs<0> args(cx);
--- a/js/src/wasm/WasmModule.h
+++ b/js/src/wasm/WasmModule.h
@@ -144,21 +144,24 @@ class Module : public JS::WasmModule
 
     mutable Atomic<bool>    codeIsBusy_;
 
     bool instantiateFunctions(JSContext* cx, Handle<FunctionVector> funcImports) const;
     bool instantiateMemory(JSContext* cx, MutableHandleWasmMemoryObject memory) const;
     bool instantiateTable(JSContext* cx,
                           MutableHandleWasmTableObject table,
                           SharedTableVector* tables) const;
+    bool instantiateGlobalExports(JSContext* cx,
+                                  const ValVector& globalImportValues,
+                                  WasmGlobalObjectVector& globalObjs) const;
     bool initSegments(JSContext* cx,
                       HandleWasmInstanceObject instance,
                       Handle<FunctionVector> funcImports,
                       HandleWasmMemoryObject memory,
-                      const ValVector& globalImports) const;
+                      const ValVector& globalImportValues) const;
 
     class Tier2GeneratorTaskImpl;
     void notifyCompilationListeners();
 
   public:
     Module(Assumptions&& assumptions,
            const Code& code,
            UniqueConstBytes unlinkedCodeForDebugging,
@@ -196,17 +199,18 @@ class Module : public JS::WasmModule
     uint32_t codeLength(Tier t) const { return code_->segment(t).length(); }
 
     // Instantiate this module with the given imports:
 
     bool instantiate(JSContext* cx,
                      Handle<FunctionVector> funcImports,
                      HandleWasmTableObject tableImport,
                      HandleWasmMemoryObject memoryImport,
-                     const ValVector& globalImports,
+                     const ValVector& globalImportValues,
+                     WasmGlobalObjectVector& globalObjs,
                      HandleObject instanceProto,
                      MutableHandleWasmInstanceObject instanceObj) const;
 
     // Tier-2 compilation may be initiated after the Module is constructed at
     // most once, ideally before any client can attempt to serialize the Module.
     // When tier-2 compilation completes, ModuleGenerator calls finishTier2()
     // from a helper thread, passing tier-variant data which will be installed
     // and made visible.
--- a/js/src/wasm/WasmTypes.h
+++ b/js/src/wasm/WasmTypes.h
@@ -65,16 +65,20 @@ typedef Rooted<WasmInstanceObject*> Root
 typedef Handle<WasmInstanceObject*> HandleWasmInstanceObject;
 typedef MutableHandle<WasmInstanceObject*> MutableHandleWasmInstanceObject;
 
 class WasmTableObject;
 typedef Rooted<WasmTableObject*> RootedWasmTableObject;
 typedef Handle<WasmTableObject*> HandleWasmTableObject;
 typedef MutableHandle<WasmTableObject*> MutableHandleWasmTableObject;
 
+class WasmGlobalObject;
+typedef GCVector<HeapPtr<WasmGlobalObject*>, 8, SystemAllocPolicy> WasmGlobalObjectVector;
+typedef Rooted<WasmGlobalObject*> RootedWasmGlobalObject;
+
 namespace wasm {
 
 using mozilla::Atomic;
 using mozilla::DebugOnly;
 using mozilla::EnumeratedArray;
 using mozilla::Maybe;
 using mozilla::Move;
 using mozilla::MallocSizeOf;
@@ -427,16 +431,30 @@ class Tiers
     Tier* begin() {
         return t_;
     }
     Tier* end() {
         return t_ + n_;
     }
 };
 
+// A Module can either be asm.js or wasm.
+
+enum ModuleKind
+{
+    Wasm,
+    AsmJS
+};
+
+enum class Shareable
+{
+    False,
+    True
+};
+
 // The Val class represents a single WebAssembly value of a given value type,
 // mostly for the purpose of numeric literals and initializers. A Val does not
 // directly map to a JS value since there is not (currently) a precise
 // representation of i64 values. A Val may contain non-canonical NaNs since,
 // within WebAssembly, floats are not canonicalized. Canonicalization must
 // happen at the JS boundary.
 
 class Val
@@ -681,19 +699,23 @@ class Export
     uint32_t funcIndex() const;
     uint32_t globalIndex() const;
 
     WASM_DECLARE_SERIALIZABLE(Export)
 };
 
 typedef Vector<Export, 0, SystemAllocPolicy> ExportVector;
 
-// A GlobalDesc describes a single global variable. Currently, asm.js and wasm
-// exposes mutable and immutable private globals, but can't import nor export
-// mutable globals.
+// A GlobalDesc describes a single global variable.
+//
+// wasm can import and export mutable and immutable globals.
+//
+// asm.js can import mutable and immutable globals, but a mutable global has a
+// location that is private to the module, and its initial value is copied into
+// that cell from the environment.  asm.js cannot export globals.
 
 enum class GlobalKind
 {
     Import,
     Constant,
     Variable
 };
 
@@ -706,67 +728,92 @@ class GlobalDesc
                 struct {
                     ValType type_;
                     uint32_t index_;
                 } import;
                 U() {}
             } val;
             unsigned offset_;
             bool isMutable_;
+            bool isWasm_;
+            bool isExport_;
         } var;
         Val cst_;
         V() {}
     } u;
     GlobalKind kind_;
 
+    // Private, as they have unusual semantics.
+
+    bool isExport() const { return !isConstant() && u.var.isExport_; }
+    bool isWasm() const { return !isConstant() && u.var.isWasm_; }
+
   public:
     GlobalDesc() = default;
 
-    explicit GlobalDesc(InitExpr initial, bool isMutable)
+    explicit GlobalDesc(InitExpr initial, bool isMutable, ModuleKind kind = ModuleKind::Wasm)
       : kind_((isMutable || !initial.isVal()) ? GlobalKind::Variable : GlobalKind::Constant)
     {
         if (isVariable()) {
             u.var.val.initial_ = initial;
             u.var.isMutable_ = isMutable;
+            u.var.isWasm_ = kind == Wasm;
+            u.var.isExport_ = false;
             u.var.offset_ = UINT32_MAX;
         } else {
             u.cst_ = initial.val();
         }
     }
 
-    explicit GlobalDesc(ValType type, bool isMutable, uint32_t importIndex)
+    explicit GlobalDesc(ValType type, bool isMutable, uint32_t importIndex, ModuleKind kind = ModuleKind::Wasm)
       : kind_(GlobalKind::Import)
     {
         u.var.val.import.type_ = type;
         u.var.val.import.index_ = importIndex;
         u.var.isMutable_ = isMutable;
+        u.var.isWasm_ = kind == Wasm;
+        u.var.isExport_ = false;
         u.var.offset_ = UINT32_MAX;
     }
 
     void setOffset(unsigned offset) {
         MOZ_ASSERT(!isConstant());
         MOZ_ASSERT(u.var.offset_ == UINT32_MAX);
         u.var.offset_ = offset;
     }
     unsigned offset() const {
         MOZ_ASSERT(!isConstant());
         MOZ_ASSERT(u.var.offset_ != UINT32_MAX);
         return u.var.offset_;
     }
 
+    void setIsExport() {
+        if (!isConstant())
+            u.var.isExport_ = true;
+    }
+
     GlobalKind kind() const { return kind_; }
     bool isVariable() const { return kind_ == GlobalKind::Variable; }
     bool isConstant() const { return kind_ == GlobalKind::Constant; }
     bool isImport() const { return kind_ == GlobalKind::Import; }
 
     bool isMutable() const { return !isConstant() && u.var.isMutable_; }
     Val constantValue() const { MOZ_ASSERT(isConstant()); return u.cst_; }
     const InitExpr& initExpr() const { MOZ_ASSERT(isVariable()); return u.var.val.initial_; }
     uint32_t importIndex() const { MOZ_ASSERT(isImport()); return u.var.val.import.index_; }
 
+    // If isIndirect() is true then storage for the value is not in the
+    // instance's global area, but in a WasmGlobalObject::Cell hanging off a
+    // WasmGlobalObject; the global area contains a pointer to the Cell.
+    //
+    // We don't want to indirect unless we must, so only mutable, exposed
+    // globals are indirected - in all other cases we copy values into and out
+    // of them module.
+    bool isIndirect() const { return isMutable() && isWasm() && (isImport() || isExport()); }
+
     ValType type() const {
         switch (kind_) {
           case GlobalKind::Import:   return u.var.val.import.type_;
           case GlobalKind::Variable: return u.var.val.initial_.type();
           case GlobalKind::Constant: return u.cst_.type();
         }
         MOZ_CRASH("unexpected global kind");
     }
@@ -1439,30 +1486,16 @@ struct Assumptions
     bool operator!=(const Assumptions& rhs) const { return !(*this == rhs); }
 
     size_t serializedSize() const;
     uint8_t* serialize(uint8_t* cursor) const;
     const uint8_t* deserialize(const uint8_t* cursor, size_t remain);
     size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
 };
 
-// A Module can either be asm.js or wasm.
-
-enum ModuleKind
-{
-    Wasm,
-    AsmJS
-};
-
-enum class Shareable
-{
-    False,
-    True
-};
-
 // Represents the resizable limits of memories and tables.
 
 struct Limits
 {
     uint32_t initial;
     Maybe<uint32_t> maximum;
 
     // `shared` is Shareable::False for tables but may be Shareable::True for
--- a/js/src/wasm/WasmValidate.cpp
+++ b/js/src/wasm/WasmValidate.cpp
@@ -1145,18 +1145,20 @@ GlobalIsJSCompatible(Decoder& d, ValType
       case ValType::F32:
       case ValType::F64:
       case ValType::I64:
         break;
       default:
         return d.fail("unexpected variable type in global import/export");
     }
 
+#if !(defined(ENABLE_WASM_GLOBAL) && defined(EARLY_BETA_OR_EARLIER))
     if (isMutable)
         return d.fail("can't import/export mutable globals in the MVP");
+#endif
 
     return true;
 }
 
 static bool
 DecodeGlobalType(Decoder& d, ValType* type, bool* isMutable)
 {
     if (!DecodeValType(d, ModuleKind::Wasm, type))
@@ -1554,18 +1556,19 @@ DecodeExport(Decoder& d, ModuleEnvironme
       case DefinitionKind::Global: {
         uint32_t globalIndex;
         if (!d.readVarU32(&globalIndex))
             return d.fail("expected global index");
 
         if (globalIndex >= env->globals.length())
             return d.fail("exported global index out of bounds");
 
-        const GlobalDesc& global = env->globals[globalIndex];
-        if (!GlobalIsJSCompatible(d, global.type(), global.isMutable()))
+        GlobalDesc* global = &env->globals[globalIndex];
+        global->setIsExport();
+        if (!GlobalIsJSCompatible(d, global->type(), global->isMutable()))
             return false;
 
         return env->exports.emplaceBack(Move(fieldName), globalIndex, DefinitionKind::Global);
       }
       default:
         return d.fail("unexpected export kind");
     }