Merge mozilla-central to autoland. a=merge CLOSED TREE
authorGurzau Raul <rgurzau@mozilla.com>
Thu, 25 Jan 2018 19:06:59 +0200
changeset 400793 3d46c42a4abdc13f63d9051cd07cc68c9e7dbbff
parent 400792 89401c18ca7dc930ce5de789d7621776985ee7b1 (current diff)
parent 400779 59960ae69d7e675cfcfbf0ead6125cc8d3719f1f (diff)
child 400794 a39f7d5c004134b3a3140b96351729fab8392535
push id33318
push useraiakab@mozilla.com
push dateFri, 26 Jan 2018 00:17:50 +0000
treeherdermozilla-central@c25d4fc1b17e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone60.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
Merge mozilla-central to autoland. a=merge CLOSED TREE
services/crypto/modules/utils.js
services/sync/modules/record.js
toolkit/components/places/tests/unifiedcomplete/test_search_suggestions.js
--- a/dom/base/ChromeUtils.cpp
+++ b/dom/base/ChromeUtils.cpp
@@ -397,18 +397,18 @@ ChromeUtils::Import(const GlobalObject& 
     targetObj.setObjectOrNull(aTargetObj.Value());
     optionalArgc = 1;
   } else {
     targetObj.setUndefined();
     optionalArgc = 0;
   }
 
   JS::Rooted<JS::Value> retval(cx);
-  nsresult rv = moduleloader->Import(registryLocation, targetObj, cx,
-                                     optionalArgc, &retval);
+  nsresult rv = moduleloader->ImportInto(registryLocation, targetObj, cx,
+                                         optionalArgc, &retval);
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return;
   }
 
   // Import() on the component loader can return NS_OK while leaving an
   // exception on the JSContext.  Check for that case.
   if (JS_IsExceptionPending(cx)) {
@@ -416,16 +416,159 @@ ChromeUtils::Import(const GlobalObject& 
     return;
   }
 
   // Now we better have an object.
   MOZ_ASSERT(retval.isObject());
   aRetval.set(&retval.toObject());
 }
 
+namespace module_getter {
+  static const size_t SLOT_ID = 0;
+  static const size_t SLOT_URI = 1;
+
+  static bool
+  ExtractArgs(JSContext* aCx, JS::CallArgs& aArgs,
+              JS::MutableHandle<JSObject*> aCallee,
+              JS::MutableHandle<JSObject*> aThisObj,
+              JS::MutableHandle<jsid> aId)
+  {
+    aCallee.set(&aArgs.callee());
+
+    JS::Handle<JS::Value> thisv = aArgs.thisv();
+    if (!thisv.isObject()) {
+      JS_ReportErrorASCII(aCx, "Invalid target object");
+      return false;
+    }
+
+    aThisObj.set(&thisv.toObject());
+
+    JS::Rooted<JS::Value> id(aCx, js::GetFunctionNativeReserved(aCallee, SLOT_ID));
+    MOZ_ALWAYS_TRUE(JS_ValueToId(aCx, id, aId));
+    return true;
+  }
+
+  static bool
+  ModuleGetter(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
+  {
+    JS::CallArgs args = JS::CallArgsFromVp(aArgc, aVp);
+
+    JS::Rooted<JSObject*> callee(aCx);
+    JS::Rooted<JSObject*> thisObj(aCx);
+    JS::Rooted<jsid> id(aCx);
+    if (!ExtractArgs(aCx, args, &callee, &thisObj, &id)) {
+      return false;
+    }
+
+    JS::Rooted<JSString*> moduleURI(
+      aCx, js::GetFunctionNativeReserved(callee, SLOT_URI).toString());
+    JSAutoByteString bytes;
+    if (!bytes.encodeUtf8(aCx, moduleURI)) {
+      return false;
+    }
+    nsDependentCString uri(bytes.ptr());
+
+    RefPtr<mozJSComponentLoader> moduleloader = mozJSComponentLoader::Get();
+    MOZ_ASSERT(moduleloader);
+
+    JS::Rooted<JSObject*> moduleGlobal(aCx);
+    JS::Rooted<JSObject*> moduleExports(aCx);
+    nsresult rv = moduleloader->Import(aCx, uri, &moduleGlobal, &moduleExports);
+    if (NS_FAILED(rv)) {
+      Throw(aCx, rv);
+      return false;
+    }
+
+    JS::RootedValue value(aCx);
+    {
+      JSAutoCompartment ac(aCx, moduleExports);
+
+      if (!JS_GetPropertyById(aCx, moduleExports, id, &value)) {
+        return false;
+      }
+    }
+
+    if (!JS_WrapValue(aCx, &value) ||
+        !JS_DefinePropertyById(aCx, thisObj, id, value,
+                               JSPROP_ENUMERATE)) {
+      return false;
+    }
+
+    args.rval().set(value);
+    return true;
+  }
+
+  static bool
+  ModuleSetter(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
+  {
+    JS::CallArgs args = JS::CallArgsFromVp(aArgc, aVp);
+
+    JS::Rooted<JSObject*> callee(aCx);
+    JS::Rooted<JSObject*> thisObj(aCx);
+    JS::Rooted<jsid> id(aCx);
+    if (!ExtractArgs(aCx, args, &callee, &thisObj, &id)) {
+      return false;
+    }
+
+    return JS_DefinePropertyById(aCx, thisObj, id, args.get(0),
+                                 JSPROP_ENUMERATE);
+  }
+
+  static bool
+  DefineGetter(JSContext* aCx,
+               JS::Handle<JSObject*> aTarget,
+               const nsAString& aId,
+               const nsAString& aResourceURI)
+  {
+    JS::RootedValue uri(aCx);
+    JS::RootedValue idValue(aCx);
+    JS::Rooted<jsid> id(aCx);
+    if (!xpc::NonVoidStringToJsval(aCx, aResourceURI, &uri) ||
+        !xpc::NonVoidStringToJsval(aCx, aId, &idValue) ||
+        !JS_ValueToId(aCx, idValue, &id)) {
+      return false;
+    }
+    idValue = js::IdToValue(id);
+
+
+    JS::Rooted<JSObject*> getter(aCx, JS_GetFunctionObject(
+      js::NewFunctionByIdWithReserved(aCx, ModuleGetter, 0, 0, id)));
+
+    JS::Rooted<JSObject*> setter(aCx, JS_GetFunctionObject(
+      js::NewFunctionByIdWithReserved(aCx, ModuleSetter, 0, 0, id)));
+
+    if (!getter || !setter) {
+      JS_ReportOutOfMemory(aCx);
+      return false;
+    }
+
+    js::SetFunctionNativeReserved(getter, SLOT_ID, idValue);
+    js::SetFunctionNativeReserved(setter, SLOT_ID, idValue);
+
+    js::SetFunctionNativeReserved(getter, SLOT_URI, uri);
+
+    return JS_DefinePropertyById(aCx, aTarget, id,
+                                 JS_DATA_TO_FUNC_PTR(JSNative, getter.get()),
+                                 JS_DATA_TO_FUNC_PTR(JSNative, setter.get()),
+                                 JSPROP_GETTER | JSPROP_SETTER | JSPROP_ENUMERATE);
+  }
+} // namespace module_getter
+
+/* static */ void
+ChromeUtils::DefineModuleGetter(const GlobalObject& global,
+                                JS::Handle<JSObject*> target,
+                                const nsAString& id,
+                                const nsAString& resourceURI,
+                                ErrorResult& aRv)
+{
+  if (!module_getter::DefineGetter(global.Context(), target, id, resourceURI)) {
+    aRv.NoteJSContextException(global.Context());
+  }
+}
+
 /* static */ void
 ChromeUtils::OriginAttributesToSuffix(dom::GlobalObject& aGlobal,
                                       const dom::OriginAttributesDictionary& aAttrs,
                                       nsCString& aSuffix)
 
 {
   OriginAttributes attrs(aAttrs);
   attrs.CreateSuffix(aSuffix);
--- a/dom/base/ChromeUtils.h
+++ b/dom/base/ChromeUtils.h
@@ -155,14 +155,20 @@ public:
 
   static void ClearRecentJSDevError(GlobalObject& aGlobal);
 
   static void Import(const GlobalObject& aGlobal,
                      const nsAString& aResourceURI,
                      const Optional<JS::Handle<JSObject*>>& aTargetObj,
                      JS::MutableHandle<JSObject*> aRetval,
                      ErrorResult& aRv);
+
+  static void DefineModuleGetter(const GlobalObject& global,
+                                 JS::Handle<JSObject*> target,
+                                 const nsAString& id,
+                                 const nsAString& resourceURI,
+                                 ErrorResult& aRv);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_ChromeUtils__
--- a/dom/plugins/ipc/PluginModuleChild.cpp
+++ b/dom/plugins/ipc/PluginModuleChild.cpp
@@ -2370,17 +2370,17 @@ PluginModuleChild::NPN_RetainObject(NPOb
 
 void
 PluginModuleChild::NPN_ReleaseObject(NPObject* aNPObj)
 {
     AssertPluginThread();
 
     PluginInstanceChild* instance = PluginScriptableObjectChild::GetInstanceForNPObject(aNPObj);
     if (!instance) {
-        NS_ERROR("Releasing object not in mObjectMap?");
+        // The PluginInstanceChild was destroyed
         return;
     }
 
     DeletingObjectEntry* doe = nullptr;
     if (instance->mDeletingHash) {
         doe = instance->mDeletingHash->GetEntry(aNPObj);
         if (!doe) {
             NS_ERROR("An object for a destroyed instance isn't in the instance deletion hash");
--- a/dom/plugins/ipc/PluginScriptableObjectChild.cpp
+++ b/dom/plugins/ipc/PluginScriptableObjectChild.cpp
@@ -1268,16 +1268,21 @@ PluginScriptableObjectChild::UnregisterO
     sObjectMap = nullptr;
   }
 }
 
 /* static */ PluginInstanceChild*
 PluginScriptableObjectChild::GetInstanceForNPObject(NPObject* aObject)
 {
   AssertPluginThread();
+  if (!sObjectMap) {
+    // All PluginInstanceChilds have been destroyed
+    return nullptr;
+  }
+
   NPObjectData* d = sObjectMap->GetEntry(aObject);
   if (!d) {
     return nullptr;
   }
   return d->instance;
 }
 
 /* static */ void
--- a/dom/webidl/ChromeUtils.webidl
+++ b/dom/webidl/ChromeUtils.webidl
@@ -256,16 +256,49 @@ partial namespace ChromeUtils {
    * The implementation maintains a hash of aResourceURI->global obj.
    * Subsequent invocations of import with 'aResourceURI' pointing to
    * the same file will not cause the module to be re-evaluated, but
    * the symbols in EXPORTED_SYMBOLS will be exported into the
    * specified target object and the global object returned as above.
    */
   [Throws]
   object import(DOMString aResourceURI, optional object? aTargetObj);
+
+  /**
+   * Defines a property on the given target which lazily imports a JavaScript
+   * module when accessed.
+   *
+   * The first time the property is accessed, the module at the given URL is
+   * imported, and the property is replaced with the module's exported symbol
+   * of the same name.
+   *
+   * Some points to note when using this utility:
+   *
+   * - The cached module export is always stored on the `this` object that was
+   *   used to access the getter. This means that if you define the lazy
+   *   getter on a prototype, the module will be re-imported every time the
+   *   property is accessed on a new instance.
+   *
+   * - The getter property may be overwritten by simple assignment, but as
+   *   with imports, the new property value is always defined on the `this`
+   *   object that the setter is called with.
+   *
+   * - If the module import fails, the getter will throw an error, and the
+   *   property will not be replaced. Each subsequent attempt to access the
+   *   getter will attempt to re-import the object, which will likely continue
+   *   to result in errors.
+   *
+   * @param target The target object on which to define the property.
+   * @param id The name of the property to define, and of the symbol to
+   *           import.
+   * @param resourceURI The resource URI of the module, as passed to
+   *                    ChromeUtils.import.
+   */
+  [Throws]
+  void defineModuleGetter(object target, DOMString id, DOMString resourceURI);
 };
 
 /**
  * Used by principals and the script security manager to represent origin
  * attributes. The first dictionary is designed to contain the full set of
  * OriginAttributes, the second is used for pattern-matching (i.e. does this
  * OriginAttributesDictionary match the non-empty attributes in this pattern).
  *
--- a/js/moz.configure
+++ b/js/moz.configure
@@ -179,26 +179,41 @@ set_define('JS_CODEGEN_MIPS64', jit_code
 set_define('JS_CODEGEN_X86', jit_codegen.x86)
 set_define('JS_CODEGEN_X64', jit_codegen.x64)
 
 @depends('--enable-ion', simulator, target, moz_debug)
 def jit_disasm_arm(ion_enabled, simulator, target, debug):
     if not ion_enabled:
         return
 
-    if simulator:
+    if simulator and debug:
         if getattr(simulator, 'arm', None):
             return True
 
     if target.cpu == 'arm' and debug:
         return True
 
 set_config('JS_DISASM_ARM', jit_disasm_arm)
 set_define('JS_DISASM_ARM', jit_disasm_arm)
 
+@depends('--enable-ion', simulator, target, moz_debug)
+def jit_disasm_arm64(ion_enabled, simulator, target, debug):
+    if not ion_enabled:
+        return
+
+    if simulator and debug:
+        if getattr(simulator, 'arm64', None):
+            return True
+
+    if target.cpu == 'aarch64' and debug:
+        return True
+
+set_config('JS_DISASM_ARM64', jit_disasm_arm64)
+set_define('JS_DISASM_ARM64', jit_disasm_arm64)
+
 # Profiling
 # =======================================================
 js_option('--enable-instruments', env='MOZ_INSTRUMENTS',
           help='Enable instruments remote profiling')
 
 @depends('--enable-instruments', target)
 def instruments(value, target):
     if value and target.os != 'OSX':
--- a/js/public/StructuredClone.h
+++ b/js/public/StructuredClone.h
@@ -260,16 +260,21 @@ public:
 
 /** Note: if the *data contains transferable objects, it can be read only once. */
 JS_PUBLIC_API(bool)
 JS_ReadStructuredClone(JSContext* cx, JSStructuredCloneData& data, uint32_t version,
                        JS::StructuredCloneScope scope,
                        JS::MutableHandleValue vp,
                        const JSStructuredCloneCallbacks* optionalCallbacks, void* closure);
 
+/**
+ * Note: If the scope is DifferentProcess then the cloneDataPolicy must deny
+ * shared-memory objects, or an error will be signaled if a shared memory object
+ * is seen.
+ */
 JS_PUBLIC_API(bool)
 JS_WriteStructuredClone(JSContext* cx, JS::HandleValue v, JSStructuredCloneData* data,
                         JS::StructuredCloneScope scope,
                         JS::CloneDataPolicy cloneDataPolicy,
                         const JSStructuredCloneCallbacks* optionalCallbacks,
                         void* closure, JS::HandleValue transferable);
 
 JS_PUBLIC_API(bool)
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -5358,17 +5358,19 @@ gc::ZealModeHelpText),
     JS_FN_HELP("serialize", Serialize, 1, 0,
 "serialize(data, [transferables, [policy]])",
 "  Serialize 'data' using JS_WriteStructuredClone. Returns a structured\n"
 "  clone buffer object. 'policy' may be an options hash. Valid keys:\n"
 "    'SharedArrayBuffer' - either 'allow' (the default) or 'deny'\n"
 "      to specify whether SharedArrayBuffers may be serialized.\n"
 "    'scope' - SameProcessSameThread, SameProcessDifferentThread, or\n"
 "      DifferentProcess. Determines how some values will be serialized.\n"
-"      Clone buffers may only be deserialized with a compatible scope."),
+"      Clone buffers may only be deserialized with a compatible scope.\n"
+"      NOTE - For DifferentProcess, must also set SharedArrayBuffer:'deny'\n"
+"      if data contains any shared memory object."),
 
     JS_FN_HELP("deserialize", Deserialize, 1, 0,
 "deserialize(clonebuffer[, opts])",
 "  Deserialize data generated by serialize. 'opts' is an options hash with one\n"
 "  recognized key 'scope', which limits the clone buffers that are considered\n"
 "  valid. Allowed values: 'SameProcessSameThread', 'SameProcessDifferentThread',\n"
 "  and 'DifferentProcess'. So for example, a DifferentProcess clone buffer\n"
 "  may be deserialized in any scope, but a SameProcessSameThread clone buffer\n"
--- a/js/src/jit-test/tests/wasm/globals.js
+++ b/js/src/jit-test/tests/wasm/globals.js
@@ -114,17 +114,22 @@ wasmFailValidateText(`(module (global (m
 module = wasmEvalText(`(module
  (import $g "globals" "x" (global i32))
  (func $get (result i32) (get_global $g))
  (export "getter" $get)
  (export "value" global 0)
 )`, { globals: {x: 42} }).exports;
 
 assertEq(module.getter(), 42);
-assertEq(module.value, 42);
+// Adapt to ongoing experiment with WebAssembly.Global.
+// assertEq() will not trigger @@toPrimitive, so we must have a cast here.
+if (typeof WebAssembly.Global === "function")
+    assertEq(Number(module.value), 42);
+else
+    assertEq(module.value, 42);
 
 // Can only import numbers (no implicit coercions).
 module = new WebAssembly.Module(wasmTextToBinary(`(module
     (global (import "globs" "i32") i32)
     (global (import "globs" "f32") f32)
     (global (import "globs" "f64") f32)
 )`));
 
@@ -169,18 +174,24 @@ for (let v of [
 // Imported globals and locally defined globals use the same index space.
 module = wasmEvalText(`(module
  (import "globals" "x" (global i32))
  (global i32 (i32.const 1337))
  (export "imported" global 0)
  (export "defined" global 1)
 )`, { globals: {x: 42} }).exports;
 
-assertEq(module.imported, 42);
-assertEq(module.defined, 1337);
+// See comment earlier about WebAssembly.Global
+if (typeof WebAssembly.Global === "function") {
+    assertEq(Number(module.imported), 42);
+    assertEq(Number(module.defined), 1337);
+} else {
+    assertEq(module.imported, 42);
+    assertEq(module.defined, 1337);
+}
 
 // Initializer expressions can reference an imported immutable global.
 wasmFailValidateText(`(module (global f32 (f32.const 13.37)) (global i32 (get_global 0)))`, /must reference a global immutable import/);
 wasmFailValidateText(`(module (global (mut f32) (f32.const 13.37)) (global i32 (get_global 0)))`, /must reference a global immutable import/);
 wasmFailValidateText(`(module (global (mut i32) (i32.const 0)) (global i32 (get_global 0)))`, /must reference a global immutable import/);
 
 wasmFailValidateText(`(module (import "globals" "a" (global f32)) (global i32 (get_global 0)))`, /type mismatch/);
 
@@ -207,22 +218,30 @@ function testInitExpr(type, initialValue
     )`, {
         globals: {
             a: coercion(initialValue)
         }
     }).exports;
 
     assertFunc(module.get0(), coercion(initialValue));
     assertFunc(module.get1(), coercion(initialValue));
-    assertFunc(module.global_imm, coercion(initialValue));
+    // See comment earlier about WebAssembly.Global
+    if (typeof WebAssembly.Global === "function")
+	assertFunc(Number(module.global_imm), coercion(initialValue));
+    else
+	assertFunc(module.global_imm, coercion(initialValue));
 
     assertEq(module.set1(coercion(nextValue)), undefined);
     assertFunc(module.get1(), coercion(nextValue));
     assertFunc(module.get0(), coercion(initialValue));
-    assertFunc(module.global_imm, coercion(initialValue));
+    // See comment earlier about WebAssembly.Global
+    if (typeof WebAssembly.Global === "function")
+	assertFunc(Number(module.global_imm), coercion(initialValue));
+    else
+	assertFunc(module.global_imm, coercion(initialValue));
 
     assertFunc(module.get_cst(), coercion(initialValue));
 }
 
 testInitExpr('i32', 13, 37, x => x|0);
 testInitExpr('f32', 13.37, 0.1989, Math.fround);
 testInitExpr('f64', 13.37, 0.1989, x => +x);
 
@@ -265,8 +284,69 @@ wasmAssert(`(module
 
     dv.setFloat64(0, module.nan64, true);
     assertEq(dv.getUint32(4, true), 0x7ff80000);
     assertEq(dv.getUint32(0, true), 0x00000000);
 
     dv.setFloat32(0, module.nan32, true);
     assertEq(dv.getUint32(0, true), 0x7fc00000);
 }
+
+// WebAssembly.Global experiment
+
+if (typeof WebAssembly.Global === "function") {
+
+    // These types should work:
+    assertEq(new WebAssembly.Global({type: "i32"}) instanceof WebAssembly.Global, true);
+    assertEq(new WebAssembly.Global({type: "f32"}) instanceof WebAssembly.Global, true);
+    assertEq(new WebAssembly.Global({type: "f64"}) instanceof WebAssembly.Global, true);
+
+    // These types should not work:
+    assertErrorMessage(() => new WebAssembly.Global({type: "i64"}),
+		       TypeError,
+		       /bad type for a WebAssembly.Global/);
+    assertErrorMessage(() => new WebAssembly.Global({}),
+		       TypeError,
+		       /bad type for a WebAssembly.Global/);
+    assertErrorMessage(() => new WebAssembly.Global({type: "fnord"}),
+		       TypeError,
+		       /bad type for a WebAssembly.Global/);
+    assertErrorMessage(() => new WebAssembly.Global(),
+		       TypeError,
+		       /WebAssembly.Global requires more than 0 arguments/);
+
+    // Coercion of init value; ".value" accessor
+    assertEq((new WebAssembly.Global({type: "i32", value: 3.14})).value, 3);
+    assertEq((new WebAssembly.Global({type: "f32", value: { valueOf: () => 33.5 }})).value, 33.5);
+
+    // Misc internal conversions
+    let g = new WebAssembly.Global({type: "i32", value: 42});
+
+    // @@toPrimitive
+    assertEq(g - 5, 37);
+    assertEq(String(g), "42");
+
+    // @@toStringTag
+    assertEq(g.toString(), "[object WebAssembly.Global]");
+
+    // An exported global should appear as a WebAssembly.Global instance:
+    let i =
+	new WebAssembly.Instance(
+	    new WebAssembly.Module(
+		wasmTextToBinary(`(module (global (export "g") i32 (i32.const 42)))`)));
+
+    assertEq(typeof i.exports.g, "object");
+    assertEq(i.exports.g instanceof WebAssembly.Global, true);
+
+    // An exported global can be imported into another instance even if
+    // it is an object:
+    let j =
+	new WebAssembly.Instance(
+	    new WebAssembly.Module(
+		wasmTextToBinary(`(module
+				   (global (import "" "g") i32)
+				   (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);
+}
--- a/js/src/jit-test/tests/wasm/spec/harness/index.js
+++ b/js/src/jit-test/tests/wasm/spec/harness/index.js
@@ -217,16 +217,25 @@ function call(instance, name, args) {
 };
 
 function get(instance, name) {
     _assert(instance instanceof Result);
 
     if (instance.isError())
         return instance;
 
+    // Experimental API change.  We try to export WebAssembly.Global instances,
+    // not primitive values.  In that case the Number() cast is necessary here
+    // to convert the Global to a value: the harness examines types carefully
+    // and will not trigger the @@toPrimitive hook on Global, unlike most user
+    // code.
+
+    if (typeof WebAssembly.Global === "function")
+	return ValueResult(Number(instance.value.exports[name]));
+
     return ValueResult(instance.value.exports[name]);
 }
 
 function exports(name, instance) {
     _assert(instance instanceof Result);
 
     if (instance.isError())
         return instance;
--- a/js/src/jit/MacroAssembler.h
+++ b/js/src/jit/MacroAssembler.h
@@ -1412,16 +1412,35 @@ class MacroAssembler : public MacroAssem
     inline void truncateDoubleToInt64(Address src, Address dest, Register temp)
         DEFINED_ON(x86_shared);
     inline void truncateDoubleToUInt64(Address src, Address dest, Register temp,
                                        FloatRegister floatTemp)
         DEFINED_ON(x86, x64);
 
   public:
     // ========================================================================
+    // Convert floating point.
+
+    // temp required on x86 and x64; must be undefined on mips64.
+    void convertUInt64ToFloat32(Register64 src, FloatRegister dest, Register temp)
+        DEFINED_ON(arm64, mips64, x64, x86);
+
+    void convertInt64ToFloat32(Register64 src, FloatRegister dest)
+        DEFINED_ON(arm64, mips64, x64, x86);
+
+    bool convertUInt64ToDoubleNeedsTemp() PER_ARCH;
+
+    // temp required when convertUInt64ToDoubleNeedsTemp() returns true.
+    void convertUInt64ToDouble(Register64 src, FloatRegister dest, Register temp) PER_ARCH;
+
+    void convertInt64ToDouble(Register64 src, FloatRegister dest)
+        DEFINED_ON(arm64, mips64, x64, x86);
+
+  public:
+    // ========================================================================
     // wasm support
 
     CodeOffset illegalInstruction() PER_SHARED_ARCH;
     void wasmTrap(wasm::Trap trap, wasm::BytecodeOffset bytecodeOffset);
 
     // Emit a bounds check against the wasm heap limit, jumping to 'label' if 'cond' holds.
     // Required when WASM_HUGE_MEMORY is not defined.
     template <class L>
@@ -1501,19 +1520,32 @@ class MacroAssembler : public MacroAssem
         DEFINED_ON(x86_shared);
 
     void wasmTruncateFloat32ToUInt32(FloatRegister input, Register output, Label* oolEntry) PER_ARCH;
     void wasmTruncateFloat32ToInt32(FloatRegister input, Register output, Label* oolEntry) PER_SHARED_ARCH;
     void outOfLineWasmTruncateFloat32ToInt32(FloatRegister input, bool isUnsigned,
                                              wasm::BytecodeOffset off, Label* rejoin)
         DEFINED_ON(x86_shared);
 
+    void wasmTruncateDoubleToInt64(FloatRegister input, Register64 output, Label* oolEntry,
+                                   Label* oolRejoin, FloatRegister tempDouble)
+        DEFINED_ON(arm64, x86, x64);
+    void wasmTruncateDoubleToUInt64(FloatRegister input, Register64 output, Label* oolEntry,
+                                    Label* oolRejoin, FloatRegister tempDouble)
+        DEFINED_ON(arm64, x86, x64);
     void outOfLineWasmTruncateDoubleToInt64(FloatRegister input, bool isUnsigned,
                                             wasm::BytecodeOffset off, Label* rejoin)
         DEFINED_ON(x86_shared);
+
+    void wasmTruncateFloat32ToInt64(FloatRegister input, Register64 output, Label* oolEntry,
+                                    Label* oolRejoin, FloatRegister tempDouble)
+        DEFINED_ON(arm64, x86, x64);
+    void wasmTruncateFloat32ToUInt64(FloatRegister input, Register64 output, Label* oolEntry,
+                                     Label* oolRejoin, FloatRegister tempDouble)
+        DEFINED_ON(arm64, x86, x64);
     void outOfLineWasmTruncateFloat32ToInt64(FloatRegister input, bool isUnsigned,
                                              wasm::BytecodeOffset off, Label* rejoin)
         DEFINED_ON(x86_shared);
 
     // This function takes care of loading the callee's TLS and pinned regs but
     // it is the caller's responsibility to save/restore TLS or pinned regs.
     void wasmCallImport(const wasm::CallSiteDesc& desc, const wasm::CalleeDesc& callee);
 
--- a/js/src/jit/arm/Assembler-arm.cpp
+++ b/js/src/jit/arm/Assembler-arm.cpp
@@ -5,33 +5,33 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "jit/arm/Assembler-arm.h"
 
 #include "mozilla/DebugOnly.h"
 #include "mozilla/MathAlgorithms.h"
 
 #include "jscompartment.h"
-#ifdef JS_DISASM_ARM
-#include "jsprf.h"
-#endif
 #include "jsutil.h"
 
 #include "gc/Marking.h"
 #include "jit/arm/disasm/Disasm-arm.h"
 #include "jit/arm/MacroAssembler-arm.h"
 #include "jit/ExecutableAllocator.h"
 #include "jit/JitCompartment.h"
 #include "jit/MacroAssembler.h"
 
 using namespace js;
 using namespace js::jit;
 
 using mozilla::CountLeadingZeroes32;
 
+using LabelDoc = DisassemblerSpew::LabelDoc;
+using LiteralDoc = DisassemblerSpew::LiteralDoc;
+
 void dbg_break() {}
 
 // The ABIArgGenerator is used for making system ABI calls and for inter-wasm
 // calls. The system ABI can either be SoftFp or HardFp, and inter-wasm calls
 // are always HardFp calls. The initialization defaults to HardFp, and the ABI
 // choice is made before any system ABI calls with the method "setUseHardFp".
 ABIArgGenerator::ABIArgGenerator()
   : intRegIndex_(0),
@@ -1435,251 +1435,16 @@ Assembler::dataRelocationTableBytes() co
 size_t
 Assembler::bytesNeeded() const
 {
     return size() +
         jumpRelocationTableBytes() +
         dataRelocationTableBytes();
 }
 
-#ifdef JS_DISASM_ARM
-
-void
-Assembler::spewInst(Instruction* i)
-{
-    disasm::NameConverter converter;
-    disasm::Disassembler dasm(converter);
-    disasm::EmbeddedVector<char, disasm::ReasonableBufferSize> buffer;
-    uint8_t* loc = reinterpret_cast<uint8_t*>(const_cast<uint32_t*>(i->raw()));
-    dasm.InstructionDecode(buffer, loc);
-    printf("   %08x  %s\n", reinterpret_cast<uint32_t>(loc), buffer.start());
-}
-
-// Labels are named as they are encountered by adding names to a
-// table, using the Label address as the key.  This is made tricky by
-// the (memory for) Label objects being reused, but reused label
-// objects are recognizable from being marked as not used or not
-// bound.  See spewResolve().
-//
-// In a number of cases there is no information about the target, and
-// we just end up printing "patchable constant load to PC".  This is
-// true especially for jumps to bailout handlers (which have no
-// names).  See spewData() and its callers.  In some cases (loop back
-// edges) some information about the intended target may be propagated
-// from higher levels, and if so it's printed here.
-
-void
-Assembler::spew(Instruction* i)
-{
-    if (spewDisabled() || !i)
-        return;
-    disasm::NameConverter converter;
-    disasm::Disassembler dasm(converter);
-    disasm::EmbeddedVector<char, disasm::ReasonableBufferSize> buffer;
-    uint8_t* loc = reinterpret_cast<uint8_t*>(const_cast<uint32_t*>(i->raw()));
-    dasm.InstructionDecode(buffer, loc);
-    spew("   %08x  %s", reinterpret_cast<uint32_t>(loc), buffer.start());
-}
-
-void
-Assembler::spewTarget(Label* target)
-{
-    if (spewDisabled())
-        return;
-    spew("                        -> %d%s", spewResolve(target), !target->bound() ? "f" : "");
-}
-
-// If a target label is known, always print that and do not attempt to
-// disassemble the branch operands, as they will often be encoding
-// metainformation (pointers for a chain of jump instructions), and
-// not actual branch targets.
-
-void
-Assembler::spewBranch(Instruction* i, Label* target /* may be nullptr */)
-{
-    if (spewDisabled() || !i)
-        return;
-    disasm::NameConverter converter;
-    disasm::Disassembler dasm(converter);
-    disasm::EmbeddedVector<char, disasm::ReasonableBufferSize> buffer;
-    uint8_t* loc = reinterpret_cast<uint8_t*>(const_cast<uint32_t*>(i->raw()));
-    dasm.InstructionDecode(buffer, loc);
-    char labelBuf[128];
-    labelBuf[0] = 0;
-    if (!target)
-        snprintf(labelBuf, sizeof(labelBuf), "  -> (link-time target)");
-    if (InstBranchImm::IsTHIS(*i)) {
-        InstBranchImm* bimm = InstBranchImm::AsTHIS(*i);
-        BOffImm destOff;
-        bimm->extractImm(&destOff);
-        if (destOff.isInvalid() || target) {
-            // The target information in the instruction is likely garbage, so remove it.
-            // The target label will in any case be printed if we have it.
-            //
-            // The format of the instruction disassembly is [0-9a-f]{8}\s+\S+\s+.*,
-            // where the \S+ string is the opcode.  Strip everything after the opcode,
-            // and attach the label if we have it.
-            int i;
-            for ( i=8 ; i < buffer.length() && buffer[i] == ' ' ; i++ )
-                ;
-            for ( ; i < buffer.length() && buffer[i] != ' ' ; i++ )
-                ;
-            buffer[i] = 0;
-            if (target) {
-                snprintf(labelBuf, sizeof(labelBuf), "  -> %d%s", spewResolve(target),
-                         !target->bound() ? "f" : "");
-                target = nullptr;
-            }
-        }
-    }
-    spew("   %08x  %s%s", reinterpret_cast<uint32_t>(loc), buffer.start(), labelBuf);
-    if (target)
-        spewTarget(target);
-}
-
-void
-Assembler::spewLabel(Label* l)
-{
-    if (spewDisabled())
-        return;
-    spew("                        %d:", spewResolve(l));
-}
-
-void
-Assembler::spewRetarget(Label* label, Label* target)
-{
-    if (spewDisabled())
-        return;
-    spew("                        %d: .retarget -> %d%s",
-         spewResolve(label), spewResolve(target), !target->bound() ? "f" : "");
-}
-
-void
-Assembler::spewData(BufferOffset addr, size_t numInstr, bool loadToPC)
-{
-    if (spewDisabled())
-        return;
-    Instruction* inst = m_buffer.getInstOrNull(addr);
-    if (!inst)
-        return;
-    uint32_t *instr = reinterpret_cast<uint32_t*>(inst);
-    for ( size_t k=0 ; k < numInstr ; k++ ) {
-        spew("   %08x  %08x       (patchable constant load%s)",
-             reinterpret_cast<uint32_t>(instr+k), *(instr+k), loadToPC ? " to PC" : "");
-    }
-}
-
-bool
-Assembler::spewDisabled()
-{
-    return !(JitSpewEnabled(JitSpew_Codegen) || printer_);
-}
-
-void
-Assembler::spew(const char* fmt, ...)
-{
-    va_list args;
-    va_start(args, fmt);
-    spew(fmt, args);
-    va_end(args);
-}
-
-void
-Assembler::spew(const char* fmt, va_list va)
-{
-    if (printer_) {
-        printer_->vprintf(fmt, va);
-        printer_->put("\n");
-    }
-    js::jit::JitSpewVA(js::jit::JitSpew_Codegen, fmt, va);
-}
-
-uint32_t
-Assembler::spewResolve(Label* l)
-{
-    // Note, spewResolve will sometimes return 0 when it is triggered
-    // by the profiler and not by a full disassembly, since in that
-    // case a label can be used or bound but not previously have been
-    // defined.
-    return l->used() || l->bound() ? spewProbe(l) : spewDefine(l);
-}
-
-uint32_t
-Assembler::spewProbe(Label* l)
-{
-    uint32_t key = reinterpret_cast<uint32_t>(l);
-    uint32_t value = 0;
-    spewNodes_.lookup(key, &value);
-    return value;
-}
-
-uint32_t
-Assembler::spewDefine(Label* l)
-{
-    uint32_t key = reinterpret_cast<uint32_t>(l);
-    spewNodes_.remove(key);
-    uint32_t value = spewNext_++;
-    if (!spewNodes_.add(key, value))
-        return 0;
-    return value;
-}
-
-Assembler::SpewNodes::~SpewNodes()
-{
-    Node* p = nodes;
-    while (p) {
-        Node* victim = p;
-        p = p->next;
-        js_free(victim);
-    }
-}
-
-bool
-Assembler::SpewNodes::lookup(uint32_t key, uint32_t* value)
-{
-    for ( Node* p = nodes ; p ; p = p->next ) {
-        if (p->key == key) {
-            *value = p->value;
-            return true;
-        }
-    }
-    return false;
-}
-
-bool
-Assembler::SpewNodes::add(uint32_t key, uint32_t value)
-{
-    Node* node = (Node*)js_malloc(sizeof(Node));
-    if (!node)
-        return false;
-    node->key = key;
-    node->value = value;
-    node->next = nodes;
-    nodes = node;
-    return true;
-}
-
-bool
-Assembler::SpewNodes::remove(uint32_t key)
-{
-    for ( Node* p = nodes, *pp = nullptr ; p ; pp = p, p = p->next ) {
-        if (p->key == key) {
-            if (pp)
-                pp->next = p->next;
-            else
-                nodes = p->next;
-            js_free(p);
-            return true;
-        }
-    }
-    return false;
-}
-
-#endif // JS_DISASM_ARM
-
 // Allocate memory for a branch instruction, it will be overwritten
 // subsequently and should not be disassembled.
 
 BufferOffset
 Assembler::allocBranchInst()
 {
     return m_buffer.putInt(Always | InstNOP::NopInst);
 }
@@ -2131,95 +1896,109 @@ Assembler::as_extdtr(LoadStore ls, int s
 BufferOffset
 Assembler::as_dtm(LoadStore ls, Register rn, uint32_t mask,
                 DTMMode mode, DTMWriteBack wb, Condition c)
 {
     return writeInst(0x08000000 | RN(rn) | ls | mode | mask | c | wb);
 }
 
 BufferOffset
-Assembler::allocEntry(size_t numInst, unsigned numPoolEntries,
-                      uint8_t* inst, uint8_t* data, ARMBuffer::PoolEntry* pe,
-                      bool loadToPC)
+Assembler::allocLiteralLoadEntry(size_t numInst, unsigned numPoolEntries,
+                                 PoolHintPun& php, uint8_t* data,
+                                 const LiteralDoc& doc,
+                                 ARMBuffer::PoolEntry* pe, bool loadToPC)
 {
+    uint8_t* inst = (uint8_t*)&php.raw;
+
+    MOZ_ASSERT(inst);
+    MOZ_ASSERT(numInst == 1);   // Or fix the disassembly
+
     BufferOffset offs = m_buffer.allocEntry(numInst, numPoolEntries, inst, data, pe);
     propagateOOM(offs.assigned());
 #ifdef JS_DISASM_ARM
-    spewData(offs, numInst, loadToPC);
+    Instruction* instruction = m_buffer.getInstOrNull(offs);
+    if (instruction)
+        spewLiteralLoad(php, loadToPC, instruction, doc);
 #endif
     return offs;
 }
 
 // This is also used for instructions that might be resolved into branches,
 // or might not.  If dest==pc then it is effectively a branch.
 
 BufferOffset
 Assembler::as_Imm32Pool(Register dest, uint32_t value, Condition c)
 {
     PoolHintPun php;
     php.phd.init(0, c, PoolHintData::PoolDTR, dest);
-    BufferOffset offs = allocEntry(1, 1, (uint8_t*)&php.raw, (uint8_t*)&value, nullptr, dest == pc);
+    BufferOffset offs = allocLiteralLoadEntry(1, 1, php,
+                                              (uint8_t*)&value,
+                                              LiteralDoc(value),
+                                              nullptr,
+                                              dest == pc);
     return offs;
 }
 
 /* static */ void
 Assembler::WritePoolEntry(Instruction* addr, Condition c, uint32_t data)
 {
     MOZ_ASSERT(addr->is<InstLDR>());
     *addr->as<InstLDR>()->dest() = data;
     MOZ_ASSERT(addr->extractCond() == c);
 }
 
 BufferOffset
-Assembler::as_BranchPool(uint32_t value, RepatchLabel* label, ARMBuffer::PoolEntry* pe, Condition c,
-                         Label* documentation)
+Assembler::as_BranchPool(uint32_t value, RepatchLabel* label,
+                         const LabelDoc& documentation,
+                         ARMBuffer::PoolEntry* pe, Condition c)
 {
     PoolHintPun php;
     php.phd.init(0, c, PoolHintData::PoolBranch, pc);
-    BufferOffset ret = allocEntry(1, 1, (uint8_t*)&php.raw, (uint8_t*)&value, pe,
-                                  /* loadToPC = */ true);
+    BufferOffset ret = allocLiteralLoadEntry(1, 1, php, (uint8_t*)&value,
+                                             LiteralDoc(),
+                                             pe,
+                                             /* loadToPC = */ true);
     // If this label is already bound, then immediately replace the stub load
     // with a correct branch.
     if (label->bound()) {
         BufferOffset dest(label);
         BOffImm offset = dest.diffB<BOffImm>(ret);
         if (offset.isInvalid()) {
             m_buffer.fail_bail();
             return ret;
         }
         as_b(offset, c, ret);
     } else if (!oom()) {
         label->use(ret.getOffset());
     }
 #ifdef JS_DISASM_ARM
-    if (documentation)
-        spewTarget(documentation);
+    spew_.spewRef(documentation);
 #endif
     return ret;
 }
 
 BufferOffset
 Assembler::as_FImm64Pool(VFPRegister dest, double d, Condition c)
 {
     MOZ_ASSERT(dest.isDouble());
     PoolHintPun php;
     php.phd.init(0, c, PoolHintData::PoolVDTR, dest);
-    return allocEntry(1, 2, (uint8_t*)&php.raw, (uint8_t*)&d);
+    return allocLiteralLoadEntry(1, 2, php, (uint8_t*)&d, LiteralDoc(d));
 }
 
 BufferOffset
 Assembler::as_FImm32Pool(VFPRegister dest, float f, Condition c)
 {
     // Insert floats into the double pool as they have the same limitations on
     // immediate offset. This wastes 4 bytes padding per float. An alternative
     // would be to have a separate pool for floats.
     MOZ_ASSERT(dest.isSingle());
     PoolHintPun php;
     php.phd.init(0, c, PoolHintData::PoolVDTR, dest);
-    return allocEntry(1, 1, (uint8_t*)&php.raw, (uint8_t*)&f);
+    return allocLiteralLoadEntry(1, 1, php, (uint8_t*)&f, LiteralDoc(f));
 }
 
 // Pool callbacks stuff:
 void
 Assembler::InsertIndexIntoTag(uint8_t* load_, uint32_t index)
 {
     uint32_t* load = (uint32_t*)load_;
     PoolHintPun php;
@@ -2402,32 +2181,31 @@ Assembler::WritePoolGuard(BufferOffset b
     *dest = InstBImm(off, Always);
 }
 
 // Branch can branch to an immediate *or* to a register.
 // Branches to immediates are pc relative, branches to registers are absolute.
 BufferOffset
 Assembler::as_b(BOffImm off, Condition c, Label* documentation)
 {
-    BufferOffset ret = writeBranchInst(((int)c) | OpB | off.encode(), documentation);
-    return ret;
+    return writeBranchInst(((int)c) | OpB | off.encode(), refLabel(documentation));
 }
 
 BufferOffset
 Assembler::as_b(Label* l, Condition c)
 {
     if (l->bound()) {
         // Note only one instruction is emitted here, the NOP is overwritten.
         BufferOffset ret = allocBranchInst();
         if (oom())
             return BufferOffset();
 
         as_b(BufferOffset(l).diffB<BOffImm>(ret), c, ret);
 #ifdef JS_DISASM_ARM
-        spewBranch(m_buffer.getInstOrNull(ret), l);
+        spewBranch(m_buffer.getInstOrNull(ret), refLabel(l));
 #endif
         return ret;
     }
 
     if (oom())
         return BufferOffset();
 
     BufferOffset ret;
@@ -2481,17 +2259,17 @@ Assembler::as_blx(Register r, Condition 
     return writeInst(((int) c) | OpBlx | r.code());
 }
 
 // bl can only branch to an pc-relative immediate offset
 // It cannot change the processor state.
 BufferOffset
 Assembler::as_bl(BOffImm off, Condition c, Label* documentation)
 {
-    return writeBranchInst(((int)c) | OpBl | off.encode(), documentation);
+    return writeBranchInst(((int)c) | OpBl | off.encode(), refLabel(documentation));
 }
 
 BufferOffset
 Assembler::as_bl(Label* l, Condition c)
 {
     if (l->bound()) {
         // Note only one instruction is emitted here, the NOP is overwritten.
         BufferOffset ret = allocBranchInst();
@@ -2501,17 +2279,17 @@ Assembler::as_bl(Label* l, Condition c)
         BOffImm offset = BufferOffset(l).diffB<BOffImm>(ret);
         if (offset.isInvalid()) {
             m_buffer.fail_bail();
             return BufferOffset();
         }
 
         as_bl(offset, c, ret);
 #ifdef JS_DISASM_ARM
-        spewBranch(m_buffer.getInstOrNull(ret), l);
+        spewBranch(m_buffer.getInstOrNull(ret), refLabel(l));
 #endif
         return ret;
     }
 
     if (oom())
         return BufferOffset();
 
     BufferOffset ret;
@@ -2850,17 +2628,17 @@ Assembler::nextLink(BufferOffset b, Buff
     new (next) BufferOffset(destOff.decode());
     return true;
 }
 
 void
 Assembler::bind(Label* label, BufferOffset boff)
 {
 #ifdef JS_DISASM_ARM
-    spewLabel(label);
+    spew_.spewBind(label);
 #endif
     if (oom()) {
         // Ensure we always bind the label. This matches what we do on
         // x86/x64 and silences the assert in ~Label.
         label->bind(0);
         return;
     }
 
@@ -2936,17 +2714,17 @@ Assembler::bind(RepatchLabel* label)
     }
     label->bind(dest.getOffset());
 }
 
 void
 Assembler::retarget(Label* label, Label* target)
 {
 #ifdef JS_DISASM_ARM
-    spewRetarget(label, target);
+    spew_.spewRetarget(label, target);
 #endif
     if (label->used() && !oom()) {
         if (target->bound()) {
             bind(label, BufferOffset(target));
         } else if (target->used()) {
             // The target is not bound but used. Prepend label's branch list
             // onto target's.
             BufferOffset labelBranchOffset(label);
@@ -3486,8 +3264,171 @@ Assembler::GetPoolMaxOffset()
     }
     return AsmPoolMaxOffset;
 }
 
 SecondScratchRegisterScope::SecondScratchRegisterScope(MacroAssembler &masm)
   : AutoRegisterScope(masm, masm.getSecondScratchReg())
 {
 }
+
+#ifdef JS_DISASM_ARM
+
+/* static */ void
+Assembler::disassembleInstruction(const Instruction* i, DisasmBuffer& buffer)
+{
+    disasm::NameConverter converter;
+    disasm::Disassembler dasm(converter);
+    uint8_t* loc = reinterpret_cast<uint8_t*>(const_cast<uint32_t*>(i->raw()));
+    dasm.InstructionDecode(buffer, loc);
+}
+
+void
+Assembler::initDisassembler()
+{
+    // The line is normally laid out like this:
+    //
+    // xxxxxxxx        ldr r, op   ; comment
+    //
+    // where xx...x is the instruction bit pattern.
+    //
+    // Labels are laid out by themselves to line up with the instructions above
+    // and below:
+    //
+    //            nnnn:
+    //
+    // Branch targets are normally on the same line as the branch instruction,
+    // but when they cannot be they will be on a line by themselves, indented
+    // significantly:
+    //
+    //                     -> label
+
+    spew_.setLabelIndent("          ");             // 10
+    spew_.setTargetIndent("                    ");  // 20
+}
+
+void
+Assembler::finishDisassembler()
+{
+    spew_.spewOrphans();
+}
+
+// Labels are named as they are encountered by adding names to a
+// table, using the Label address as the key.  This is made tricky by
+// the (memory for) Label objects being reused, but reused label
+// objects are recognizable from being marked as not used or not
+// bound.  See spew_.refLabel().
+//
+// In a number of cases there is no information about the target, and
+// we just end up printing "patchable constant load to PC".  This is
+// true especially for jumps to bailout handlers (which have no
+// names).  See allocLiteralLoadEntry() and its callers.  In some cases
+// (loop back edges) some information about the intended target may be
+// propagated from higher levels, and if so it's printed here.
+
+void
+Assembler::spew(Instruction* i)
+{
+    if (spew_.isDisabled() || !i)
+        return;
+
+    DisasmBuffer buffer;
+    disassembleInstruction(i, buffer);
+    spew_.spew("%s", buffer.start());
+}
+
+// If a target label is known, always print that and do not attempt to
+// disassemble the branch operands, as they will often be encoding
+// metainformation (pointers for a chain of jump instructions), and
+// not actual branch targets.
+
+void
+Assembler::spewBranch(Instruction* i, const LabelDoc& target)
+{
+    if (spew_.isDisabled() || !i)
+        return;
+
+    DisasmBuffer buffer;
+    disassembleInstruction(i, buffer);
+
+    char labelBuf[128];
+    labelBuf[0] = 0;
+
+    bool haveTarget = target.valid;
+    if (!haveTarget)
+        snprintf(labelBuf, sizeof(labelBuf), "  -> (link-time target)");
+
+    if (InstBranchImm::IsTHIS(*i)) {
+        InstBranchImm* bimm = InstBranchImm::AsTHIS(*i);
+        BOffImm destOff;
+        bimm->extractImm(&destOff);
+        if (destOff.isInvalid() || haveTarget) {
+            // The target information in the instruction is likely garbage, so remove it.
+            // The target label will in any case be printed if we have it.
+            //
+            // The format of the instruction disassembly is [0-9a-f]{8}\s+\S+\s+.*,
+            // where the \S+ string is the opcode.  Strip everything after the opcode,
+            // and attach the label if we have it.
+            int i;
+            for ( i=8 ; i < buffer.length() && buffer[i] == ' ' ; i++ )
+                ;
+            for ( ; i < buffer.length() && buffer[i] != ' ' ; i++ )
+                ;
+            buffer[i] = 0;
+            if (haveTarget) {
+                snprintf(labelBuf, sizeof(labelBuf), "  -> %d%s", target.doc,
+                         !target.bound ? "f" : "");
+                haveTarget = false;
+            }
+        }
+    }
+    spew_.spew("%s%s", buffer.start(), labelBuf);
+
+    if (haveTarget)
+        spew_.spewRef(target);
+}
+
+void
+Assembler::spewLiteralLoad(PoolHintPun& php, bool loadToPC, const Instruction* i,
+                           const LiteralDoc& doc)
+{
+    if (spew_.isDisabled())
+        return;
+
+    char litbuf[2048];
+    spew_.formatLiteral(doc, litbuf, sizeof(litbuf));
+
+    // See patchConstantPoolLoad, above.  We assemble the instruction into a
+    // buffer with a zero offset, as documentation, but the offset will be
+    // patched later.
+
+    uint32_t inst;
+    PoolHintData& data = php.phd;
+    switch (php.phd.getLoadType()) {
+      case PoolHintData::PoolDTR:
+        Assembler::as_dtr_patch(IsLoad, 32, Offset, data.getReg(),
+                                DTRAddr(pc, DtrOffImm(0)),
+                                data.getCond(), &inst);
+        break;
+      case PoolHintData::PoolBranch:
+        if (data.isValidPoolHint()) {
+            Assembler::as_dtr_patch(IsLoad, 32, Offset, pc,
+                                    DTRAddr(pc, DtrOffImm(0)),
+                                    data.getCond(), &inst);
+        }
+        break;
+      case PoolHintData::PoolVDTR:
+        Assembler::as_vdtr_patch(IsLoad, data.getVFPReg(), VFPAddr(pc, VFPOffImm(0)),
+                                 data.getCond(), &inst);
+        break;
+
+      default:
+        MOZ_CRASH();
+    }
+
+    DisasmBuffer buffer;
+    disasm::NameConverter converter;
+    disasm::Disassembler dasm(converter);
+    dasm.InstructionDecode(buffer, reinterpret_cast<uint8_t*>(&inst));
+    spew_.spew("%s    ; .const %s", buffer.start(), litbuf);
+}
+
+#endif // JS_DISASM_ARM
--- a/js/src/jit/arm/Assembler-arm.h
+++ b/js/src/jit/arm/Assembler-arm.h
@@ -7,25 +7,32 @@
 #ifndef jit_arm_Assembler_arm_h
 #define jit_arm_Assembler_arm_h
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/MathAlgorithms.h"
 
 #include "jit/arm/Architecture-arm.h"
+#include "jit/arm/disasm/Disasm-arm.h"
 #include "jit/CompactBuffer.h"
 #include "jit/IonCode.h"
 #include "jit/JitCompartment.h"
 #include "jit/shared/Assembler-shared.h"
+#include "jit/shared/Disassembler-shared.h"
 #include "jit/shared/IonAssemblerBufferWithConstantPools.h"
 
+union PoolHintPun;
+
 namespace js {
 namespace jit {
 
+using LiteralDoc = DisassemblerSpew::LiteralDoc;
+using LabelDoc = DisassemblerSpew::LabelDoc;
+
 // NOTE: there are duplicates in this list! Sometimes we want to specifically
 // refer to the link register as a link register (bl lr is much clearer than bl
 // r14). HOWEVER, this register can easily be a gpr when it is not busy holding
 // the return address.
 static constexpr Register r0  { Registers::r0 };
 static constexpr Register r1  { Registers::r1 };
 static constexpr Register r2  { Registers::r2 };
 static constexpr Register r3  { Registers::r3 };
@@ -1238,32 +1245,37 @@ class Assembler : public AssemblerShared
     // needs to go out here :(
 
     BufferOffset nextOffset() {
         return m_buffer.nextOffset();
     }
 
   protected:
     // Shim around AssemblerBufferWithConstantPools::allocEntry.
-    BufferOffset allocEntry(size_t numInst, unsigned numPoolEntries,
-                            uint8_t* inst, uint8_t* data, ARMBuffer::PoolEntry* pe = nullptr,
-                            bool loadToPC = false);
+    BufferOffset allocLiteralLoadEntry(size_t numInst, unsigned numPoolEntries,
+                                       PoolHintPun& php, uint8_t* data,
+                                       const LiteralDoc& doc = LiteralDoc(),
+                                       ARMBuffer::PoolEntry* pe = nullptr,
+                                       bool loadToPC = false);
 
     Instruction* editSrc(BufferOffset bo) {
         return m_buffer.getInst(bo);
     }
 
 #ifdef JS_DISASM_ARM
-    static void spewInst(Instruction* i);
+    typedef disasm::EmbeddedVector<char, disasm::ReasonableBufferSize> DisasmBuffer;
+
+    static void disassembleInstruction(const Instruction* i, DisasmBuffer& buffer);
+
+    void initDisassembler();
+    void finishDisassembler();
     void spew(Instruction* i);
-    void spewBranch(Instruction* i, Label* target);
-    void spewData(BufferOffset addr, size_t numInstr, bool loadToPC);
-    void spewLabel(Label* label);
-    void spewRetarget(Label* label, Label* target);
-    void spewTarget(Label* l);
+    void spewBranch(Instruction* i, const LabelDoc& target);
+    void spewLiteralLoad(PoolHintPun& php, bool loadToPC, const Instruction* offs,
+                         const LiteralDoc& doc);
 #endif
 
   public:
     void resetCounter();
     uint32_t actualIndex(uint32_t) const;
     static uint8_t* PatchableJumpAddress(JitCode* code, uint32_t index);
     static uint32_t NopFill;
     static uint32_t GetNopFill();
@@ -1291,60 +1303,38 @@ class Assembler : public AssemblerShared
     js::Vector<RelativePatch, 8, SystemAllocPolicy> jumps_;
 
     CompactBufferWriter jumpRelocations_;
     CompactBufferWriter dataRelocations_;
 
     ARMBuffer m_buffer;
 
 #ifdef JS_DISASM_ARM
-  private:
-    class SpewNodes {
-        struct Node {
-            uint32_t key;
-            uint32_t value;
-            Node* next;
-        };
-
-        Node* nodes;
-
-    public:
-        SpewNodes() : nodes(nullptr) {}
-        ~SpewNodes();
-
-        bool lookup(uint32_t key, uint32_t* value);
-        bool add(uint32_t key, uint32_t value);
-        bool remove(uint32_t key);
-    };
-
-    SpewNodes spewNodes_;
-    uint32_t spewNext_;
-    Sprinter* printer_;
-
-    bool spewDisabled();
-    uint32_t spewResolve(Label* l);
-    uint32_t spewProbe(Label* l);
-    uint32_t spewDefine(Label* l);
-    void spew(const char* fmt, ...) MOZ_FORMAT_PRINTF(2, 3);
-    void spew(const char* fmt, va_list args) MOZ_FORMAT_PRINTF(2, 0);
+    DisassemblerSpew spew_;
 #endif
 
   public:
     // For the alignment fill use NOP: 0x0320f000 or (Always | InstNOP::NopInst).
     // For the nopFill use a branch to the next instruction: 0xeaffffff.
     Assembler()
       : m_buffer(1, 1, 8, GetPoolMaxOffset(), 8, 0xe320f000, 0xeaffffff, GetNopFill()),
-#ifdef JS_DISASM_ARM
-        spewNext_(1000),
-        printer_(nullptr),
-#endif
         isFinished(false),
         dtmActive(false),
         dtmCond(Always)
-    { }
+    {
+#ifdef JS_DISASM_ARM
+        initDisassembler();
+#endif
+    }
+
+    ~Assembler() {
+#ifdef JS_DISASM_ARM
+        finishDisassembler();
+#endif
+    }
 
     // We need to wait until an AutoJitContextAlloc is created by the
     // MacroAssembler, before allocating any space.
     void initWithAllocator() {
         m_buffer.initWithAllocator();
     }
 
     static Condition InvertCondition(Condition cond);
@@ -1392,26 +1382,36 @@ class Assembler : public AssemblerShared
     static uintptr_t GetPointer(uint8_t*);
     template <class Iter>
     static const uint32_t* GetPtr32Target(Iter iter, Register* dest = nullptr, RelocStyle* rs = nullptr);
 
     bool oom() const;
 
     void setPrinter(Sprinter* sp) {
 #ifdef JS_DISASM_ARM
-        printer_ = sp;
+        spew_.setPrinter(sp);
 #endif
     }
 
     static const Register getStackPointer() {
         return StackPointer;
     }
 
   private:
     bool isFinished;
+
+  protected:
+    LabelDoc refLabel(const Label* label) {
+#ifdef JS_DISASM_ARM
+        return spew_.refLabel(label);
+#else
+        return LabelDoc();
+#endif
+    }
+
   public:
     void finish();
     bool appendRawCode(const uint8_t* code, size_t numBytes);
     bool reserve(size_t size);
     bool swapBuffer(wasm::Bytes& bytes);
     void copyJumpRelocationTable(uint8_t* dest);
     void copyDataRelocationTable(uint8_t* dest);
 
@@ -1431,17 +1431,18 @@ class Assembler : public AssemblerShared
 #ifdef JS_DISASM_ARM
         spew(m_buffer.getInstOrNull(offs));
 #endif
         return offs;
     }
 
     // As above, but also mark the instruction as a branch.  Very hot, inlined
     // for performance
-    MOZ_ALWAYS_INLINE BufferOffset writeBranchInst(uint32_t x, Label* documentation = nullptr) {
+    MOZ_ALWAYS_INLINE BufferOffset
+    writeBranchInst(uint32_t x, const LabelDoc& documentation) {
         BufferOffset offs = m_buffer.putInt(x);
 #ifdef JS_DISASM_ARM
         spewBranch(m_buffer.getInstOrNull(offs), documentation);
 #endif
         return offs;
     }
 
     // Write a placeholder NOP for a branch into the instruction stream
@@ -1554,18 +1555,18 @@ class Assembler : public AssemblerShared
 
     // Overwrite a pool entry with new data.
     static void WritePoolEntry(Instruction* addr, Condition c, uint32_t data);
 
     // Load a 32 bit immediate from a pool into a register.
     BufferOffset as_Imm32Pool(Register dest, uint32_t value, Condition c = Always);
     // Make a patchable jump that can target the entire 32 bit address space.
     BufferOffset as_BranchPool(uint32_t value, RepatchLabel* label,
-                               ARMBuffer::PoolEntry* pe = nullptr, Condition c = Always,
-                               Label* documentation = nullptr);
+                               const LabelDoc& documentation,
+                               ARMBuffer::PoolEntry* pe = nullptr, Condition c = Always);
 
     // Load a 64 bit floating point immediate from a pool into a register.
     BufferOffset as_FImm64Pool(VFPRegister dest, double value, Condition c = Always);
     // Load a 32 bit floating point immediate from a pool into a register.
     BufferOffset as_FImm32Pool(VFPRegister dest, float value, Condition c = Always);
 
     // Atomic instructions: ldrexd, ldrex, ldrexh, ldrexb, strexd, strex, strexh, strexb.
     //
@@ -1764,17 +1765,17 @@ class Assembler : public AssemblerShared
     void flush() {
         MOZ_ASSERT(!isFinished);
         m_buffer.flushPool();
         return;
     }
 
     void comment(const char* msg) {
 #ifdef JS_DISASM_ARM
-        spew("; %s", msg);
+        spew_.spew("; %s", msg);
 #endif
     }
 
     // Copy the assembly code to the given buffer, and perform any pending
     // relocations relying on the target address.
     void executableCopy(uint8_t* buffer, bool flushICache = true);
 
     // Actual assembly emitting functions.
--- a/js/src/jit/arm/MacroAssembler-arm.cpp
+++ b/js/src/jit/arm/MacroAssembler-arm.cpp
@@ -91,39 +91,16 @@ MacroAssemblerARM::convertUInt32ToDouble
     // Direct conversions aren't possible.
     VFPRegister dest = VFPRegister(dest_);
     as_vxfer(src, InvalidReg, dest.uintOverlay(), CoreToFloat);
     as_vcvt(dest, dest.uintOverlay());
 }
 
 static const double TO_DOUBLE_HIGH_SCALE = 0x100000000;
 
-bool
-MacroAssemblerARMCompat::convertUInt64ToDoubleNeedsTemp()
-{
-    return false;
-}
-
-void
-MacroAssemblerARMCompat::convertUInt64ToDouble(Register64 src, FloatRegister dest, Register temp)
-{
-    MOZ_ASSERT(temp == Register::Invalid());
-    ScratchDoubleScope scratchDouble(asMasm());
-
-    convertUInt32ToDouble(src.high, dest);
-    {
-        ScratchRegisterScope scratch(asMasm());
-        movePtr(ImmPtr(&TO_DOUBLE_HIGH_SCALE), scratch);
-        ma_vldr(Operand(Address(scratch, 0)).toVFPAddr(), scratchDouble);
-    }
-    asMasm().mulDouble(scratchDouble, dest);
-    convertUInt32ToDouble(src.low, scratchDouble);
-    asMasm().addDouble(scratchDouble, dest);
-}
-
 void
 MacroAssemblerARM::convertUInt32ToFloat32(Register src, FloatRegister dest_)
 {
     // Direct conversions aren't possible.
     VFPRegister dest = VFPRegister(dest_);
     as_vxfer(src, InvalidReg, dest.uintOverlay(), CoreToFloat);
     as_vcvt(VFPRegister(dest).singleOverlay(), dest.uintOverlay());
 }
@@ -4147,17 +4124,17 @@ MacroAssemblerARMCompat::roundf(FloatReg
 
     bind(&fin);
 }
 
 CodeOffsetJump
 MacroAssemblerARMCompat::jumpWithPatch(RepatchLabel* label, Condition cond, Label* documentation)
 {
     ARMBuffer::PoolEntry pe;
-    BufferOffset bo = as_BranchPool(0xdeadbeef, label, &pe, cond, documentation);
+    BufferOffset bo = as_BranchPool(0xdeadbeef, label, refLabel(documentation), &pe, cond);
     // Fill in a new CodeOffset with both the load and the pool entry that the
     // instruction loads from.
     CodeOffsetJump ret(bo.getOffset(), pe.index());
     return ret;
 }
 
 void
 MacroAssemblerARMCompat::profilerEnterFrame(Register framePtr, Register scratch)
@@ -5615,16 +5592,41 @@ MacroAssembler::atomicFetchOp64(const Sy
 
 void
 MacroAssembler::atomicFetchOp64(const Synchronization& sync, AtomicOp op, Register64 value,
                                 const BaseIndex& mem, Register64 temp, Register64 output)
 {
     AtomicFetchOp64(*this, sync, op, value, mem, temp, output);
 }
 
+// ========================================================================
+// Convert floating point.
+
+bool
+MacroAssembler::convertUInt64ToDoubleNeedsTemp()
+{
+    return false;
+}
+
+void
+MacroAssembler::convertUInt64ToDouble(Register64 src, FloatRegister dest, Register temp)
+{
+    MOZ_ASSERT(temp == Register::Invalid());
+    ScratchDoubleScope scratchDouble(*this);
+
+    convertUInt32ToDouble(src.high, dest);
+    {
+        ScratchRegisterScope scratch(*this);
+        movePtr(ImmPtr(&TO_DOUBLE_HIGH_SCALE), scratch);
+        ma_vldr(Operand(Address(scratch, 0)).toVFPAddr(), scratchDouble);
+    }
+    mulDouble(scratchDouble, dest);
+    convertUInt32ToDouble(src.low, scratchDouble);
+    addDouble(scratchDouble, dest);
+}
 
 //}}} check_macroassembler_style
 
 void
 MacroAssemblerARM::wasmTruncateToInt32(FloatRegister input, Register output, MIRType fromType,
                                        bool isUnsigned, Label* oolEntry)
 {
     // vcvt* converts NaN into 0, so check for NaNs here.
--- a/js/src/jit/arm/MacroAssembler-arm.h
+++ b/js/src/jit/arm/MacroAssembler-arm.h
@@ -1156,19 +1156,16 @@ class MacroAssemblerARMCompat : public M
     void cmpPtr(Register lhs, ImmGCPtr rhs);
     void cmpPtr(Register lhs, Imm32 rhs);
     void cmpPtr(const Address& lhs, Register rhs);
     void cmpPtr(const Address& lhs, ImmWord rhs);
     void cmpPtr(const Address& lhs, ImmPtr rhs);
     void cmpPtr(const Address& lhs, ImmGCPtr rhs);
     void cmpPtr(const Address& lhs, Imm32 rhs);
 
-    static bool convertUInt64ToDoubleNeedsTemp();
-    void convertUInt64ToDouble(Register64 src, FloatRegister dest, Register temp);
-
     void setStackArg(Register reg, uint32_t arg);
 
     void breakpoint();
     // Conditional breakpoint.
     void breakpoint(Condition cc);
 
     // Trigger the simulator's interactive read-eval-print loop.
     // The message will be printed at the stopping point.
--- a/js/src/jit/arm/Simulator-arm.cpp
+++ b/js/src/jit/arm/Simulator-arm.cpp
@@ -60,16 +60,43 @@ int64_t
     uint32_t hi = uint32_t(x) % uint32_t(y);
     return (int64_t(hi) << 32) | lo;
 }
 }
 
 namespace js {
 namespace jit {
 
+// For decoding load-exclusive and store-exclusive instructions.
+namespace excl {
+
+// Bit positions.
+enum {
+    ExclusiveOpHi = 24,         // Hi bit of opcode field
+    ExclusiveOpLo = 23,         // Lo bit of opcode field
+    ExclusiveSizeHi = 22,       // Hi bit of operand size field
+    ExclusiveSizeLo = 21,       // Lo bit of operand size field
+    ExclusiveLoad = 20          // Bit indicating load
+};
+
+// Opcode bits for exclusive instructions.
+enum {
+    ExclusiveOpcode = 3
+};
+
+// Operand size, Bits(ExclusiveSizeHi,ExclusiveSizeLo).
+enum {
+    ExclusiveWord = 0,
+    ExclusiveDouble = 1,
+    ExclusiveByte = 2,
+    ExclusiveHalf = 3
+};
+
+}
+
 // Load/store multiple addressing mode.
 enum BlockAddrMode {
     // Alias modes for comparison when writeback does not matter.
     da_x         = (0|0|0) << 21,  // Decrement after.
     ia_x         = (0|4|0) << 21,  // Increment after.
     db_x         = (8|0|0) << 21,  // Decrement before.
     ib_x         = (8|4|0) << 21,  // Increment before.
 };
@@ -408,25 +435,27 @@ void
 Simulator::Destroy(Simulator* sim)
 {
     js_delete(sim);
 }
 
 void
 Simulator::disassemble(SimInstruction* instr, size_t n)
 {
+#ifdef JS_DISASM_ARM
     disasm::NameConverter converter;
     disasm::Disassembler dasm(converter);
     disasm::EmbeddedVector<char, disasm::ReasonableBufferSize> buffer;
     while (n-- > 0) {
         dasm.InstructionDecode(buffer,
                                reinterpret_cast<uint8_t*>(instr));
         fprintf(stderr, "  0x%08x  %s\n", uint32_t(instr), buffer.start());
         instr = reinterpret_cast<SimInstruction*>(reinterpret_cast<uint8_t*>(instr) + 4);
     }
+#endif
 }
 
 void
 Simulator::disasm(SimInstruction* instr)
 {
     disassemble(instr, 1);
 }
 
@@ -3099,62 +3128,62 @@ Simulator::decodeType01(SimInstruction* 
                         lo_res = static_cast<int32_t>(result & 0xffffffff);
                     }
                     set_register(rd_lo, lo_res);
                     set_register(rd_hi, hi_res);
                     if (instr->hasS())
                         MOZ_CRASH();
                 }
             } else {
-                if (instr->bits(disasm::ExclusiveOpHi, disasm::ExclusiveOpLo) == disasm::ExclusiveOpcode) {
+                if (instr->bits(excl::ExclusiveOpHi, excl::ExclusiveOpLo) == excl::ExclusiveOpcode) {
                     // Load-exclusive / store-exclusive.
-                    if (instr->bit(disasm::ExclusiveLoad)) {
+                    if (instr->bit(excl::ExclusiveLoad)) {
                         int rn = instr->rnValue();
                         int rt = instr->rtValue();
                         int32_t address = get_register(rn);
-                        switch (instr->bits(disasm::ExclusiveSizeHi, disasm::ExclusiveSizeLo)) {
-                          case disasm::ExclusiveWord:
+                        switch (instr->bits(excl::ExclusiveSizeHi, excl::ExclusiveSizeLo)) {
+                          case excl::ExclusiveWord:
                             set_register(rt, readExW(address, instr));
                             break;
-                          case disasm::ExclusiveDouble: {
+                          case excl::ExclusiveDouble: {
                             MOZ_ASSERT((rt % 2) == 0);
                             int32_t hibits;
                             int32_t lobits = readExDW(address, &hibits);
                             set_register(rt, lobits);
                             set_register(rt+1, hibits);
                             break;
                           }
-                          case disasm::ExclusiveByte:
+                          case excl::ExclusiveByte:
                             set_register(rt, readExBU(address));
                             break;
-                          case disasm::ExclusiveHalf:
+                          case excl::ExclusiveHalf:
                             set_register(rt, readExHU(address, instr));
                             break;
                         }
                     } else {
                         int rn = instr->rnValue();
                         int rd = instr->rdValue();
                         int rt = instr->bits(3,0);
                         int32_t address = get_register(rn);
                         int32_t value = get_register(rt);
                         int32_t result = 0;
-                        switch (instr->bits(disasm::ExclusiveSizeHi, disasm::ExclusiveSizeLo)) {
-                          case disasm::ExclusiveWord:
+                        switch (instr->bits(excl::ExclusiveSizeHi, excl::ExclusiveSizeLo)) {
+                          case excl::ExclusiveWord:
                             result = writeExW(address, value, instr);
                             break;
-                          case disasm::ExclusiveDouble: {
+                          case excl::ExclusiveDouble: {
                             MOZ_ASSERT((rt % 2) == 0);
                             int32_t value2 = get_register(rt+1);
                             result = writeExDW(address, value, value2);
                             break;
                           }
-                          case disasm::ExclusiveByte:
+                          case excl::ExclusiveByte:
                             result = writeExB(address, (uint8_t)value);
                             break;
-                          case disasm::ExclusiveHalf:
+                          case excl::ExclusiveHalf:
                             result = writeExH(address, (uint16_t)value, instr);
                             break;
                         }
                         set_register(rd, result);
                     }
                 } else {
                     MOZ_CRASH(); // Not used atm
                 }
--- a/js/src/jit/arm64/Assembler-arm64.cpp
+++ b/js/src/jit/arm64/Assembler-arm64.cpp
@@ -11,16 +11,17 @@
 
 #include "jscompartment.h"
 #include "jsutil.h"
 
 #include "gc/Marking.h"
 
 #include "jit/arm64/Architecture-arm64.h"
 #include "jit/arm64/MacroAssembler-arm64.h"
+#include "jit/arm64/vixl/Disasm-vixl.h"
 #include "jit/ExecutableAllocator.h"
 #include "jit/JitCompartment.h"
 
 #include "gc/StoreBuffer-inl.h"
 
 using namespace js;
 using namespace js::jit;
 
@@ -162,61 +163,67 @@ Assembler::executableCopy(uint8_t* buffe
         }
     }
 
     if (flushICache)
         AutoFlushICache::setRange(uintptr_t(buffer), armbuffer_.size());
 }
 
 BufferOffset
-Assembler::immPool(ARMRegister dest, uint8_t* value, vixl::LoadLiteralOp op, ARMBuffer::PoolEntry* pe)
+Assembler::immPool(ARMRegister dest, uint8_t* value, vixl::LoadLiteralOp op,
+                   const LiteralDoc& doc, ARMBuffer::PoolEntry* pe)
 {
     uint32_t inst = op | Rt(dest);
     const size_t numInst = 1;
     const unsigned sizeOfPoolEntryInBytes = 4;
     const unsigned numPoolEntries = sizeof(value) / sizeOfPoolEntryInBytes;
-    return allocEntry(numInst, numPoolEntries, (uint8_t*)&inst, value, pe);
+    return allocLiteralLoadEntry(numInst, numPoolEntries, (uint8_t*)&inst, value, doc, pe);
 }
 
 BufferOffset
 Assembler::immPool64(ARMRegister dest, uint64_t value, ARMBuffer::PoolEntry* pe)
 {
-    return immPool(dest, (uint8_t*)&value, vixl::LDR_x_lit, pe);
+    return immPool(dest, (uint8_t*)&value, vixl::LDR_x_lit, LiteralDoc(value), pe);
 }
 
 BufferOffset
 Assembler::immPool64Branch(RepatchLabel* label, ARMBuffer::PoolEntry* pe, Condition c)
 {
     MOZ_CRASH("immPool64Branch");
 }
 
 BufferOffset
-Assembler::fImmPool(ARMFPRegister dest, uint8_t* value, vixl::LoadLiteralOp op)
+Assembler::fImmPool(ARMFPRegister dest, uint8_t* value, vixl::LoadLiteralOp op,
+                    const LiteralDoc& doc)
 {
     uint32_t inst = op | Rt(dest);
     const size_t numInst = 1;
     const unsigned sizeOfPoolEntryInBits = 32;
     const unsigned numPoolEntries = dest.size() / sizeOfPoolEntryInBits;
-    return allocEntry(numInst, numPoolEntries, (uint8_t*)&inst, value);
+    return allocLiteralLoadEntry(numInst, numPoolEntries, (uint8_t*)&inst, value, doc);
 }
 
 BufferOffset
 Assembler::fImmPool64(ARMFPRegister dest, double value)
 {
-    return fImmPool(dest, (uint8_t*)&value, vixl::LDR_d_lit);
+    return fImmPool(dest, (uint8_t*)&value, vixl::LDR_d_lit, LiteralDoc(value));
 }
+
 BufferOffset
 Assembler::fImmPool32(ARMFPRegister dest, float value)
 {
-    return fImmPool(dest, (uint8_t*)&value, vixl::LDR_s_lit);
+    return fImmPool(dest, (uint8_t*)&value, vixl::LDR_s_lit, LiteralDoc(value));
 }
 
 void
 Assembler::bind(Label* label, BufferOffset targetOffset)
 {
+#ifdef JS_DISASM_ARM64
+    spew_.spewBind(label);
+#endif
     // Nothing has seen the label yet: just mark the location.
     // If we've run out of memory, don't attempt to modify the buffer which may
     // not be there. Just mark the label as bound to the (possibly bogus)
     // targetOffset.
     if (!label->used() || oom()) {
         label->bind(targetOffset.getOffset());
         return;
     }
@@ -629,16 +636,19 @@ Assembler::FixupNurseryObjects(JSContext
 
     if (hasNurseryPointers)
         cx->zone()->group()->storeBuffer().putWholeCell(code);
 }
 
 void
 Assembler::retarget(Label* label, Label* target)
 {
+#ifdef JS_DISASM_ARM64
+    spew_.spewRetarget(label, target);
+#endif
     if (label->used()) {
         if (target->bound()) {
             bind(label, BufferOffset(target));
         } else if (target->used()) {
             // The target is not bound but used. Prepend label's branch list
             // onto target's.
             BufferOffset labelBranchOffset(label);
 
--- a/js/src/jit/arm64/Assembler-arm64.h
+++ b/js/src/jit/arm64/Assembler-arm64.h
@@ -5,26 +5,30 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef A64_ASSEMBLER_A64_H_
 #define A64_ASSEMBLER_A64_H_
 
 #include "jit/arm64/vixl/Assembler-vixl.h"
 
 #include "jit/JitCompartment.h"
+#include "jit/shared/Disassembler-shared.h"
 
 namespace js {
 namespace jit {
 
 // VIXL imports.
 typedef vixl::Register ARMRegister;
 typedef vixl::FPRegister ARMFPRegister;
 using vixl::ARMBuffer;
 using vixl::Instruction;
 
+using LabelDoc = DisassemblerSpew::LabelDoc;
+using LiteralDoc = DisassemblerSpew::LiteralDoc;
+
 static const uint32_t AlignmentAtPrologue = 0;
 static const uint32_t AlignmentMidPrologue = 8;
 static const Scale ScalePointer = TimesEight;
 
 // The MacroAssembler uses scratch registers extensively and unexpectedly.
 // For safety, scratch registers should always be acquired using
 // vixl::UseScratchRegisterScope.
 static constexpr Register ScratchReg { Registers::ip0 };
@@ -200,20 +204,21 @@ class Assembler : public vixl::Assembler
     void trace(JSTracer* trc);
 
     // Emit the jump table, returning the BufferOffset to the first entry in the table.
     BufferOffset emitExtendedJumpTable();
     BufferOffset ExtendedJumpTable_;
     void executableCopy(uint8_t* buffer, bool flushICache = true);
 
     BufferOffset immPool(ARMRegister dest, uint8_t* value, vixl::LoadLiteralOp op,
-                         ARMBuffer::PoolEntry* pe = nullptr);
+                         const LiteralDoc& doc, ARMBuffer::PoolEntry* pe = nullptr);
     BufferOffset immPool64(ARMRegister dest, uint64_t value, ARMBuffer::PoolEntry* pe = nullptr);
     BufferOffset immPool64Branch(RepatchLabel* label, ARMBuffer::PoolEntry* pe, vixl::Condition c);
-    BufferOffset fImmPool(ARMFPRegister dest, uint8_t* value, vixl::LoadLiteralOp op);
+    BufferOffset fImmPool(ARMFPRegister dest, uint8_t* value, vixl::LoadLiteralOp op,
+                          const LiteralDoc& doc);
     BufferOffset fImmPool64(ARMFPRegister dest, double value);
     BufferOffset fImmPool32(ARMFPRegister dest, float value);
 
     void bind(Label* label) { bind(label, nextOffset()); }
     void bind(Label* label, BufferOffset boff);
     void bind(RepatchLabel* label);
     void bindLater(Label* label, wasm::OldTrapDesc target) {
         MOZ_CRASH("NYI");
@@ -262,28 +267,33 @@ class Assembler : public vixl::Assembler
 
     // The buffer is about to be linked. Ensure any constant pools or
     // excess bookkeeping has been flushed to the instruction stream.
     void flush() {
         armbuffer_.flushPool();
     }
 
     void comment(const char* msg) {
-        // This is not implemented because setPrinter() is not implemented.
-        // TODO spew("; %s", msg);
+#ifdef JS_DISASM_ARM64
+        spew_.spew("; %s", msg);
+#endif
     }
 
     int actualIndex(int curOffset) {
         ARMBuffer::PoolEntry pe(curOffset);
         return armbuffer_.poolEntryOffset(pe);
     }
     static uint8_t* PatchableJumpAddress(JitCode* code, uint32_t index) {
         return code->raw() + index;
     }
+
     void setPrinter(Sprinter* sp) {
+#ifdef JS_DISASM_ARM64
+        spew_.setPrinter(sp);
+#endif
     }
 
     static bool SupportsFloatingPoint() { return true; }
     static bool SupportsUnalignedAccesses() { return true; }
     static bool SupportsSimd() { return js::jit::SupportsSimd; }
 
     static bool HasRoundInstruction(RoundingMode mode) { return false; }
 
--- a/js/src/jit/arm64/MacroAssembler-arm64.cpp
+++ b/js/src/jit/arm64/MacroAssembler-arm64.cpp
@@ -76,57 +76,57 @@ BufferOffset
 MacroAssemblerCompat::movePatchablePtr(ImmPtr ptr, Register dest)
 {
     const size_t numInst = 1; // Inserting one load instruction.
     const unsigned numPoolEntries = 2; // Every pool entry is 4 bytes.
     uint8_t* literalAddr = (uint8_t*)(&ptr.value); // TODO: Should be const.
 
     // Scratch space for generating the load instruction.
     //
-    // allocEntry() will use InsertIndexIntoTag() to store a temporary
+    // allocLiteralLoadEntry() will use InsertIndexIntoTag() to store a temporary
     // index to the corresponding PoolEntry in the instruction itself.
     //
     // That index will be fixed up later when finishPool()
     // walks over all marked loads and calls PatchConstantPoolLoad().
     uint32_t instructionScratch = 0;
 
     // Emit the instruction mask in the scratch space.
     // The offset doesn't matter: it will be fixed up later.
     vixl::Assembler::ldr((Instruction*)&instructionScratch, ARMRegister(dest, 64), 0);
 
     // Add the entry to the pool, fix up the LDR imm19 offset,
     // and add the completed instruction to the buffer.
-    return allocEntry(numInst, numPoolEntries, (uint8_t*)&instructionScratch,
-                      literalAddr);
+    return allocLiteralLoadEntry(numInst, numPoolEntries, (uint8_t*)&instructionScratch,
+                                 literalAddr);
 }
 
 BufferOffset
 MacroAssemblerCompat::movePatchablePtr(ImmWord ptr, Register dest)
 {
     const size_t numInst = 1; // Inserting one load instruction.
     const unsigned numPoolEntries = 2; // Every pool entry is 4 bytes.
     uint8_t* literalAddr = (uint8_t*)(&ptr.value);
 
     // Scratch space for generating the load instruction.
     //
-    // allocEntry() will use InsertIndexIntoTag() to store a temporary
+    // allocLiteralLoadEntry() will use InsertIndexIntoTag() to store a temporary
     // index to the corresponding PoolEntry in the instruction itself.
     //
     // That index will be fixed up later when finishPool()
     // walks over all marked loads and calls PatchConstantPoolLoad().
     uint32_t instructionScratch = 0;
 
     // Emit the instruction mask in the scratch space.
     // The offset doesn't matter: it will be fixed up later.
     vixl::Assembler::ldr((Instruction*)&instructionScratch, ARMRegister(dest, 64), 0);
 
     // Add the entry to the pool, fix up the LDR imm19 offset,
     // and add the completed instruction to the buffer.
-    return allocEntry(numInst, numPoolEntries, (uint8_t*)&instructionScratch,
-                      literalAddr);
+    return allocLiteralLoadEntry(numInst, numPoolEntries, (uint8_t*)&instructionScratch,
+                                 literalAddr);
 }
 
 void
 MacroAssemblerCompat::loadPrivate(const Address& src, Register dest)
 {
     loadPtr(src, dest);
     asMasm().lshiftPtr(Imm32(1), dest);
 }
@@ -877,16 +877,79 @@ MacroAssembler::wasmTruncateFloat32ToUIn
 }
 
 void
 MacroAssembler::wasmTruncateFloat32ToInt32(FloatRegister input, Register output, Label* oolEntry)
 {
     MOZ_CRASH("NYI");
 }
 
+void
+MacroAssembler::wasmTruncateDoubleToInt64(FloatRegister input, Register64 output, Label* oolEntry,
+                                          Label* oolRejoin, FloatRegister tempDouble)
+{
+    MOZ_CRASH("NYI");
+}
+
+void
+MacroAssembler::wasmTruncateDoubleToUInt64(FloatRegister input, Register64 output, Label* oolEntry,
+                                           Label* oolRejoin, FloatRegister tempDouble)
+{
+    MOZ_CRASH("NYI");
+}
+
+void
+MacroAssembler::wasmTruncateFloat32ToInt64(FloatRegister input, Register64 output, Label* oolEntry,
+                                           Label* oolRejoin, FloatRegister tempDouble)
+{
+    MOZ_CRASH("NYI");
+}
+
+void
+MacroAssembler::wasmTruncateFloat32ToUInt64(FloatRegister input, Register64 output, Label* oolEntry,
+                                            Label* oolRejoin, FloatRegister tempDouble)
+{
+    MOZ_CRASH("NYI");
+}
+
+// ========================================================================
+// Convert floating point.
+
+bool
+MacroAssembler::convertUInt64ToDoubleNeedsTemp()
+{
+    return false;
+}
+
+void
+MacroAssembler::convertUInt64ToDouble(Register64 src, FloatRegister dest, Register temp)
+{
+    MOZ_ASSERT(temp == Register::Invalid());
+    Ucvtf(ARMFPRegister(dest, 64), ARMRegister(src.reg, 64));
+}
+
+void
+MacroAssembler::convertInt64ToDouble(Register64 src, FloatRegister dest)
+{
+    Scvtf(ARMFPRegister(dest, 64), ARMRegister(src.reg, 64));
+}
+
+void
+MacroAssembler::convertUInt64ToFloat32(Register64 src, FloatRegister dest, Register temp)
+{
+    MOZ_ASSERT(temp == Register::Invalid());
+    Ucvtf(ARMFPRegister(dest, 32), ARMRegister(src.reg, 64));
+}
+
+void
+MacroAssembler::convertInt64ToFloat32(Register64 src, FloatRegister dest)
+{
+    Scvtf(ARMFPRegister(dest, 32), ARMRegister(src.reg, 64));
+}
+
 // ========================================================================
 // Primitive atomic operations.
 
 void
 MacroAssembler::compareExchange(Scalar::Type type, const Synchronization& sync, const Address& mem,
                                 Register oldval, Register newval, Register output)
 {
     MOZ_CRASH("NYI");
--- a/js/src/jit/arm64/MacroAssembler-arm64.h
+++ b/js/src/jit/arm64/MacroAssembler-arm64.h
@@ -666,17 +666,17 @@ class MacroAssemblerCompat : public vixl
     void jump(Label* label) {
         B(label);
     }
     void jump(JitCode* code) {
         branch(code);
     }
     void jump(TrampolinePtr code) {
         syncStackPtr();
-        BufferOffset loc = b(-1); // The jump target will be patched by executableCopy().
+        BufferOffset loc = b(-1, LabelDoc()); // The jump target will be patched by executableCopy().
         addPendingJump(loc, ImmPtr(code.value), Relocation::HARDCODED);
     }
     void jump(RepatchLabel* label) {
         MOZ_CRASH("jump (repatchlabel)");
     }
     void jump(Register reg) {
         Br(ARMRegister(reg, 64));
     }
@@ -1261,48 +1261,52 @@ class MacroAssemblerCompat : public vixl
         B(dest, cond);
     }
 
     void branch(Condition cond, Label* label) {
         B(label, cond);
     }
     void branch(JitCode* target) {
         syncStackPtr();
-        BufferOffset loc = b(-1); // The jump target will be patched by executableCopy().
+        BufferOffset loc = b(-1, LabelDoc()); // The jump target will be patched by executableCopy().
         addPendingJump(loc, ImmPtr(target->raw()), Relocation::JITCODE);
     }
 
-    CodeOffsetJump jumpWithPatch(RepatchLabel* label, Condition cond = Always,
-                                 Label* documentation = nullptr)
+    CodeOffsetJump jumpWithPatch(RepatchLabel* label, Condition cond = Always, Label* documentation = nullptr)
     {
+#ifdef JS_DISASM_ARM64
+        LabelDoc doc = spew_.refLabel(documentation);
+#else
+        LabelDoc doc;
+#endif
         ARMBuffer::PoolEntry pe;
         BufferOffset load_bo;
         BufferOffset branch_bo;
 
         // Does not overwrite condition codes from the caller.
         {
             vixl::UseScratchRegisterScope temps(this);
             const ARMRegister scratch64 = temps.AcquireX();
             load_bo = immPool64(scratch64, (uint64_t)label, &pe);
         }
 
         MOZ_ASSERT(!label->bound());
         if (cond != Always) {
             Label notTaken;
             B(&notTaken, Assembler::InvertCondition(cond));
-            branch_bo = b(-1);
+            branch_bo = b(-1, doc);
             bind(&notTaken);
         } else {
             nop();
-            branch_bo = b(-1);
+            branch_bo = b(-1, doc);
         }
         label->use(branch_bo.getOffset());
         return CodeOffsetJump(load_bo.getOffset(), pe.index());
     }
-    CodeOffsetJump backedgeJump(RepatchLabel* label, Label* documentation = nullptr) {
+    CodeOffsetJump backedgeJump(RepatchLabel* label, Label* documentation) {
         return jumpWithPatch(label, Always, documentation);
     }
 
     void compareDouble(DoubleCondition cond, FloatRegister lhs, FloatRegister rhs) {
         Fcmp(ARMFPRegister(lhs, 64), ARMFPRegister(rhs, 64));
     }
 
     void compareFloat(DoubleCondition cond, FloatRegister lhs, FloatRegister rhs) {
@@ -1981,25 +1985,16 @@ class MacroAssemblerCompat : public vixl
 #endif
     }
 
     void abiret() {
         syncStackPtr(); // SP is always used to transmit the stack between calls.
         vixl::MacroAssembler::Ret(vixl::lr);
     }
 
-    bool convertUInt64ToDoubleNeedsTemp() {
-        return false;
-    }
-
-    void convertUInt64ToDouble(Register64 src, FloatRegister dest, Register temp) {
-        MOZ_ASSERT(temp == Register::Invalid());
-        Ucvtf(ARMFPRegister(dest, 64), ARMRegister(src.reg, 64));
-    }
-
     void clampCheck(Register r, Label* handleNotAnInt) {
         MOZ_CRASH("clampCheck");
     }
 
     void stackCheck(ImmWord limitAddr, Label* label) {
         MOZ_CRASH("stackCheck");
     }
 
--- a/js/src/jit/arm64/vixl/Assembler-vixl.cpp
+++ b/js/src/jit/arm64/vixl/Assembler-vixl.cpp
@@ -1700,18 +1700,18 @@ void Assembler::LoadStoreStruct(const VR
                                 NEONLoadStoreMultiStructOp op) {
   LoadStoreStructVerify(vt, addr, op);
   VIXL_ASSERT(vt.IsVector() || vt.Is1D());
   Emit(op | LoadStoreStructAddrModeField(addr) | LSVFormat(vt) | Rt(vt));
 }
 
 
 void Assembler::LoadStoreStructSingleAllLanes(const VRegister& vt,
-                                      const MemOperand& addr,
-                                      NEONLoadStoreSingleStructOp op) {
+					      const MemOperand& addr,
+					      NEONLoadStoreSingleStructOp op) {
   LoadStoreStructVerify(vt, addr, op);
   Emit(op | LoadStoreStructAddrModeField(addr) | LSVFormat(vt) | Rt(vt));
 }
 
 
 void Assembler::ld1(const VRegister& vt,
                     const MemOperand& src) {
   LoadStoreStruct(vt, src, NEON_LD1_1v);
--- a/js/src/jit/arm64/vixl/Assembler-vixl.h
+++ b/js/src/jit/arm64/vixl/Assembler-vixl.h
@@ -30,24 +30,28 @@
 #include "jit/arm64/vixl/Globals-vixl.h"
 #include "jit/arm64/vixl/Instructions-vixl.h"
 #include "jit/arm64/vixl/MozBaseAssembler-vixl.h"
 #include "jit/arm64/vixl/Utils-vixl.h"
 
 #include "jit/JitSpewer.h"
 
 #include "jit/shared/Assembler-shared.h"
+#include "jit/shared/Disassembler-shared.h"
 #include "jit/shared/IonAssemblerBufferWithConstantPools.h"
 
 namespace vixl {
 
 using js::jit::BufferOffset;
 using js::jit::Label;
 using js::jit::Address;
 using js::jit::BaseIndex;
+using js::jit::DisassemblerSpew;
+
+using LabelDoc = DisassemblerSpew::LabelDoc;
 
 typedef uint64_t RegList;
 static const int kRegListSizeInBits = sizeof(RegList) * 8;
 
 
 // Registers.
 
 // Some CPURegister methods can return Register or VRegister types, so we need
@@ -966,42 +970,42 @@ class Assembler : public MozBaseAssemble
 
   // Unconditional branch to label.
   BufferOffset b(Label* label);
 
   // Conditional branch to label.
   BufferOffset b(Label* label, Condition cond);
 
   // Unconditional branch to PC offset.
-  BufferOffset b(int imm26);
+  BufferOffset b(int imm26, const LabelDoc& doc);
   static void b(Instruction* at, int imm26);
 
   // Conditional branch to PC offset.
-  BufferOffset b(int imm19, Condition cond);
+  BufferOffset b(int imm19, Condition cond, const LabelDoc& doc);
   static void b(Instruction*at, int imm19, Condition cond);
 
   // Branch with link to label.
   void bl(Label* label);
 
   // Branch with link to PC offset.
-  void bl(int imm26);
+  void bl(int imm26, const LabelDoc& doc);
   static void bl(Instruction* at, int imm26);
 
   // Compare and branch to label if zero.
   void cbz(const Register& rt, Label* label);
 
   // Compare and branch to PC offset if zero.
-  void cbz(const Register& rt, int imm19);
+  void cbz(const Register& rt, int imm19, const LabelDoc& doc);
   static void cbz(Instruction* at, const Register& rt, int imm19);
 
   // Compare and branch to label if not zero.
   void cbnz(const Register& rt, Label* label);
 
   // Compare and branch to PC offset if not zero.
-  void cbnz(const Register& rt, int imm19);
+  void cbnz(const Register& rt, int imm19, const LabelDoc& doc);
   static void cbnz(Instruction* at, const Register& rt, int imm19);
 
   // Table lookup from one register.
   void tbl(const VRegister& vd,
            const VRegister& vn,
            const VRegister& vm);
 
   // Table lookup from two registers.
@@ -1050,42 +1054,42 @@ class Assembler : public MozBaseAssemble
            const VRegister& vn3,
            const VRegister& vn4,
            const VRegister& vm);
 
   // Test bit and branch to label if zero.
   void tbz(const Register& rt, unsigned bit_pos, Label* label);
 
   // Test bit and branch to PC offset if zero.
-  void tbz(const Register& rt, unsigned bit_pos, int imm14);
+  void tbz(const Register& rt, unsigned bit_pos, int imm14, const LabelDoc& doc);
   static void tbz(Instruction* at, const Register& rt, unsigned bit_pos, int imm14);
 
   // Test bit and branch to label if not zero.
   void tbnz(const Register& rt, unsigned bit_pos, Label* label);
 
   // Test bit and branch to PC offset if not zero.
-  void tbnz(const Register& rt, unsigned bit_pos, int imm14);
+  void tbnz(const Register& rt, unsigned bit_pos, int imm14, const LabelDoc& doc);
   static void tbnz(Instruction* at, const Register& rt, unsigned bit_pos, int imm14);
 
   // Address calculation instructions.
   // Calculate a PC-relative address. Unlike for branches the offset in adr is
   // unscaled (i.e. the result can be unaligned).
 
   // Calculate the address of a label.
   void adr(const Register& rd, Label* label);
 
   // Calculate the address of a PC offset.
-  void adr(const Register& rd, int imm21);
+  void adr(const Register& rd, int imm21, const LabelDoc& doc);
   static void adr(Instruction* at, const Register& rd, int imm21);
 
   // Calculate the page address of a label.
   void adrp(const Register& rd, Label* label);
 
   // Calculate the page address of a PC offset.
-  void adrp(const Register& rd, int imm21);
+  void adrp(const Register& rd, int imm21, const LabelDoc& doc);
   static void adrp(Instruction* at, const Register& rd, int imm21);
 
   // Data Processing instructions.
   // Add.
   void add(const Register& rd,
            const Register& rn,
            const Operand& operand);
 
--- a/js/src/jit/arm64/vixl/Disasm-vixl.cpp
+++ b/js/src/jit/arm64/vixl/Disasm-vixl.cpp
@@ -2746,20 +2746,24 @@ int64_t Disassembler::CodeRelativeAddres
   return reinterpret_cast<intptr_t>(addr) + code_address_offset();
 }
 
 
 void Disassembler::Format(const Instruction* instr, const char* mnemonic,
                           const char* format) {
   VIXL_ASSERT(mnemonic != NULL);
   ResetOutput();
+  uint32_t pos = buffer_pos_;
   Substitute(instr, mnemonic);
   if (format != NULL) {
-    VIXL_ASSERT(buffer_pos_ < buffer_size_);
-    buffer_[buffer_pos_++] = ' ';
+    uint32_t spaces = buffer_pos_ - pos < 8 ? 8 - (buffer_pos_ - pos) : 1;
+    while (spaces--) {
+      VIXL_ASSERT(buffer_pos_ < buffer_size_);
+      buffer_[buffer_pos_++] = ' ';
+    }
     Substitute(instr, format);
   }
   VIXL_ASSERT(buffer_pos_ < buffer_size_);
   buffer_[buffer_pos_] = 0;
   ProcessOutput(instr);
 }
 
 
@@ -3480,9 +3484,18 @@ void Disassembler::AppendToOutput(const 
 
 void PrintDisassembler::ProcessOutput(const Instruction* instr) {
   fprintf(stream_, "0x%016" PRIx64 "  %08" PRIx32 "\t\t%s\n",
           reinterpret_cast<uint64_t>(instr),
           instr->InstructionBits(),
           GetOutput());
 }
 
+void DisassembleInstruction(char* buffer, size_t bufsize, const Instruction* instr)
+{
+    vixl::Disassembler disasm(buffer, bufsize-1);
+    vixl::Decoder decoder;
+    decoder.AppendVisitor(&disasm);
+    decoder.Decode(instr);
+    buffer[bufsize-1] = 0;      // Just to be safe
+}
+
 }  // namespace vixl
--- a/js/src/jit/arm64/vixl/Disasm-vixl.h
+++ b/js/src/jit/arm64/vixl/Disasm-vixl.h
@@ -167,11 +167,14 @@ class PrintDisassembler: public Disassem
   explicit PrintDisassembler(FILE* stream) : stream_(stream) { }
 
  protected:
   virtual void ProcessOutput(const Instruction* instr) override;
 
  private:
   FILE *stream_;
 };
+
+void DisassembleInstruction(char* buffer, size_t bufsize, const Instruction* instr);
+
 }  // namespace vixl
 
 #endif  // VIXL_A64_DISASM_A64_H
--- a/js/src/jit/arm64/vixl/MozAssembler-vixl.cpp
+++ b/js/src/jit/arm64/vixl/MozAssembler-vixl.cpp
@@ -26,16 +26,17 @@
 
 #include "jsutil.h"
 
 #include "jit/arm64/vixl/Assembler-vixl.h"
 #include "jit/Label.h"
 
 namespace vixl {
 
+using LabelDoc = js::jit::DisassemblerSpew::LabelDoc;
 
 // Assembler
 void Assembler::FinalizeCode() {
 #ifdef DEBUG
   finalized_ = true;
 #endif
 }
 
@@ -191,179 +192,188 @@ ptrdiff_t MozBaseAssembler::LinkAndGetIn
                                                           Label* label) {
   return LinkAndGetOffsetTo(branch, branchRange, kInstructionSizeLog2, label);
 }
 
 ptrdiff_t MozBaseAssembler::LinkAndGetPageOffsetTo(BufferOffset branch, Label* label) {
   return LinkAndGetOffsetTo(branch, UncondBranchRangeType, kPageSizeLog2, label);
 }
 
-BufferOffset Assembler::b(int imm26) {
-  return EmitBranch(B | ImmUncondBranch(imm26));
+BufferOffset Assembler::b(int imm26, const LabelDoc& doc) {
+  return EmitBranch(B | ImmUncondBranch(imm26), doc);
 }
 
 
 void Assembler::b(Instruction* at, int imm26) {
   return EmitBranch(at, B | ImmUncondBranch(imm26));
 }
 
 
-BufferOffset Assembler::b(int imm19, Condition cond) {
-  return EmitBranch(B_cond | ImmCondBranch(imm19) | cond);
+BufferOffset Assembler::b(int imm19, Condition cond, const LabelDoc& doc) {
+  return EmitBranch(B_cond | ImmCondBranch(imm19) | cond, doc);
 }
 
 
 void Assembler::b(Instruction* at, int imm19, Condition cond) {
   EmitBranch(at, B_cond | ImmCondBranch(imm19) | cond);
 }
 
 
 BufferOffset Assembler::b(Label* label) {
   // Encode the relative offset from the inserted branch to the label.
-  return b(LinkAndGetInstructionOffsetTo(nextInstrOffset(), UncondBranchRangeType, label));
+  LabelDoc doc = refLabel(label);
+  return b(LinkAndGetInstructionOffsetTo(nextInstrOffset(), UncondBranchRangeType, label), doc);
 }
 
 
 BufferOffset Assembler::b(Label* label, Condition cond) {
   // Encode the relative offset from the inserted branch to the label.
-  return b(LinkAndGetInstructionOffsetTo(nextInstrOffset(), CondBranchRangeType, label), cond);
+  LabelDoc doc = refLabel(label);
+  return b(LinkAndGetInstructionOffsetTo(nextInstrOffset(), CondBranchRangeType, label), cond, doc);
 }
 
 void Assembler::br(Instruction* at, const Register& xn) {
   VIXL_ASSERT(xn.Is64Bits());
   // No need for EmitBranch(): no immediate offset needs fixing.
   Emit(at, BR | Rn(xn));
 }
 
 
 void Assembler::blr(Instruction* at, const Register& xn) {
   VIXL_ASSERT(xn.Is64Bits());
   // No need for EmitBranch(): no immediate offset needs fixing.
   Emit(at, BLR | Rn(xn));
 }
 
 
-void Assembler::bl(int imm26) {
-  EmitBranch(BL | ImmUncondBranch(imm26));
+void Assembler::bl(int imm26, const LabelDoc& doc) {
+  EmitBranch(BL | ImmUncondBranch(imm26), doc);
 }
 
 
 void Assembler::bl(Instruction* at, int imm26) {
   EmitBranch(at, BL | ImmUncondBranch(imm26));
 }
 
 
 void Assembler::bl(Label* label) {
   // Encode the relative offset from the inserted branch to the label.
-  return bl(LinkAndGetInstructionOffsetTo(nextInstrOffset(), UncondBranchRangeType, label));
+  LabelDoc doc = refLabel(label);
+  return bl(LinkAndGetInstructionOffsetTo(nextInstrOffset(), UncondBranchRangeType, label), doc);
 }
 
 
-void Assembler::cbz(const Register& rt, int imm19) {
-  EmitBranch(SF(rt) | CBZ | ImmCmpBranch(imm19) | Rt(rt));
+void Assembler::cbz(const Register& rt, int imm19, const LabelDoc& doc) {
+  EmitBranch(SF(rt) | CBZ | ImmCmpBranch(imm19) | Rt(rt), doc);
 }
 
 
 void Assembler::cbz(Instruction* at, const Register& rt, int imm19) {
   EmitBranch(at, SF(rt) | CBZ | ImmCmpBranch(imm19) | Rt(rt));
 }
 
 
 void Assembler::cbz(const Register& rt, Label* label) {
   // Encode the relative offset from the inserted branch to the label.
-  return cbz(rt, LinkAndGetInstructionOffsetTo(nextInstrOffset(), CondBranchRangeType, label));
+  LabelDoc doc = refLabel(label);
+  return cbz(rt, LinkAndGetInstructionOffsetTo(nextInstrOffset(), CondBranchRangeType, label), doc);
 }
 
 
-void Assembler::cbnz(const Register& rt, int imm19) {
-  EmitBranch(SF(rt) | CBNZ | ImmCmpBranch(imm19) | Rt(rt));
+void Assembler::cbnz(const Register& rt, int imm19, const LabelDoc& doc) {
+  EmitBranch(SF(rt) | CBNZ | ImmCmpBranch(imm19) | Rt(rt), doc);
 }
 
 
 void Assembler::cbnz(Instruction* at, const Register& rt, int imm19) {
   EmitBranch(at, SF(rt) | CBNZ | ImmCmpBranch(imm19) | Rt(rt));
 }
 
 
 void Assembler::cbnz(const Register& rt, Label* label) {
   // Encode the relative offset from the inserted branch to the label.
-  return cbnz(rt, LinkAndGetInstructionOffsetTo(nextInstrOffset(), CondBranchRangeType, label));
+  LabelDoc doc = refLabel(label);
+  return cbnz(rt, LinkAndGetInstructionOffsetTo(nextInstrOffset(), CondBranchRangeType, label), doc);
 }
 
 
-void Assembler::tbz(const Register& rt, unsigned bit_pos, int imm14) {
+void Assembler::tbz(const Register& rt, unsigned bit_pos, int imm14, const LabelDoc& doc) {
   VIXL_ASSERT(rt.Is64Bits() || (rt.Is32Bits() && (bit_pos < kWRegSize)));
-  EmitBranch(TBZ | ImmTestBranchBit(bit_pos) | ImmTestBranch(imm14) | Rt(rt));
+  EmitBranch(TBZ | ImmTestBranchBit(bit_pos) | ImmTestBranch(imm14) | Rt(rt), doc);
 }
 
 
 void Assembler::tbz(Instruction* at, const Register& rt, unsigned bit_pos, int imm14) {
   VIXL_ASSERT(rt.Is64Bits() || (rt.Is32Bits() && (bit_pos < kWRegSize)));
   EmitBranch(at, TBZ | ImmTestBranchBit(bit_pos) | ImmTestBranch(imm14) | Rt(rt));
 }
 
 
 void Assembler::tbz(const Register& rt, unsigned bit_pos, Label* label) {
   // Encode the relative offset from the inserted branch to the label.
-  return tbz(rt, bit_pos, LinkAndGetInstructionOffsetTo(nextInstrOffset(), TestBranchRangeType, label));
+  LabelDoc doc = refLabel(label);
+  return tbz(rt, bit_pos, LinkAndGetInstructionOffsetTo(nextInstrOffset(), TestBranchRangeType, label), doc);
 }
 
 
-void Assembler::tbnz(const Register& rt, unsigned bit_pos, int imm14) {
+void Assembler::tbnz(const Register& rt, unsigned bit_pos, int imm14, const LabelDoc& doc) {
   VIXL_ASSERT(rt.Is64Bits() || (rt.Is32Bits() && (bit_pos < kWRegSize)));
-  EmitBranch(TBNZ | ImmTestBranchBit(bit_pos) | ImmTestBranch(imm14) | Rt(rt));
+  EmitBranch(TBNZ | ImmTestBranchBit(bit_pos) | ImmTestBranch(imm14) | Rt(rt), doc);
 }
 
 
 void Assembler::tbnz(Instruction* at, const Register& rt, unsigned bit_pos, int imm14) {
   VIXL_ASSERT(rt.Is64Bits() || (rt.Is32Bits() && (bit_pos < kWRegSize)));
   EmitBranch(at, TBNZ | ImmTestBranchBit(bit_pos) | ImmTestBranch(imm14) | Rt(rt));
 }
 
 
 void Assembler::tbnz(const Register& rt, unsigned bit_pos, Label* label) {
   // Encode the relative offset from the inserted branch to the label.
-  return tbnz(rt, bit_pos, LinkAndGetInstructionOffsetTo(nextInstrOffset(), TestBranchRangeType, label));
+  LabelDoc doc = refLabel(label);
+  return tbnz(rt, bit_pos, LinkAndGetInstructionOffsetTo(nextInstrOffset(), TestBranchRangeType, label), doc);
 }
 
 
-void Assembler::adr(const Register& rd, int imm21) {
+void Assembler::adr(const Register& rd, int imm21, const LabelDoc& doc) {
   VIXL_ASSERT(rd.Is64Bits());
-  EmitBranch(ADR | ImmPCRelAddress(imm21) | Rd(rd));
+  EmitBranch(ADR | ImmPCRelAddress(imm21) | Rd(rd), doc);
 }
 
 
 void Assembler::adr(Instruction* at, const Register& rd, int imm21) {
   VIXL_ASSERT(rd.Is64Bits());
   EmitBranch(at, ADR | ImmPCRelAddress(imm21) | Rd(rd));
 }
 
 
 void Assembler::adr(const Register& rd, Label* label) {
   // Encode the relative offset from the inserted adr to the label.
-  return adr(rd, LinkAndGetByteOffsetTo(nextInstrOffset(), label));
+  LabelDoc doc = refLabel(label);
+  return adr(rd, LinkAndGetByteOffsetTo(nextInstrOffset(), label), doc);
 }
 
 
-void Assembler::adrp(const Register& rd, int imm21) {
+void Assembler::adrp(const Register& rd, int imm21, const LabelDoc& doc) {
   VIXL_ASSERT(rd.Is64Bits());
-  EmitBranch(ADRP | ImmPCRelAddress(imm21) | Rd(rd));
+  EmitBranch(ADRP | ImmPCRelAddress(imm21) | Rd(rd), doc);
 }
 
 
 void Assembler::adrp(Instruction* at, const Register& rd, int imm21) {
   VIXL_ASSERT(rd.Is64Bits());
   EmitBranch(at, ADRP | ImmPCRelAddress(imm21) | Rd(rd));
 }
 
 
 void Assembler::adrp(const Register& rd, Label* label) {
   VIXL_ASSERT(AllowPageOffsetDependentCode());
   // Encode the relative offset from the inserted adr to the label.
-  return adrp(rd, LinkAndGetPageOffsetTo(nextInstrOffset(), label));
+  LabelDoc doc = refLabel(label);
+  return adrp(rd, LinkAndGetPageOffsetTo(nextInstrOffset(), label), doc);
 }
 
 
 BufferOffset Assembler::ands(const Register& rd, const Register& rn, const Operand& operand) {
   return Logical(rd, rn, operand, ANDS);
 }
 
 
--- a/js/src/jit/arm64/vixl/MozBaseAssembler-vixl.h
+++ b/js/src/jit/arm64/vixl/MozBaseAssembler-vixl.h
@@ -26,23 +26,31 @@
 
 #ifndef jit_arm64_vixl_MozBaseAssembler_vixl_h
 #define jit_arm64_vixl_MozBaseAssembler_vixl_h
 
 #include "jit/arm64/vixl/Constants-vixl.h"
 #include "jit/arm64/vixl/Instructions-vixl.h"
 
 #include "jit/shared/Assembler-shared.h"
+#include "jit/shared/Disassembler-shared.h"
 #include "jit/shared/IonAssemblerBufferWithConstantPools.h"
 
 namespace vixl {
 
 
 using js::jit::BufferOffset;
+using js::jit::DisassemblerSpew;
 
+using LabelDoc = DisassemblerSpew::LabelDoc;
+using LiteralDoc = DisassemblerSpew::LiteralDoc;
+
+#ifdef JS_DISASM_ARM64
+void DisassembleInstruction(char* buffer, size_t bufsize, const Instruction* instr);
+#endif
 
 class MozBaseAssembler;
 typedef js::jit::AssemblerBufferWithConstantPools<1024, 4, Instruction, MozBaseAssembler,
                                                   NumShortBranchRangeTypes> ARMBuffer;
 
 // Base class for vixl::Assembler, for isolating Moz-specific changes to VIXL.
 class MozBaseAssembler : public js::jit::AssemblerShared {
   // Buffer initialization constants.
@@ -50,27 +58,44 @@ class MozBaseAssembler : public js::jit:
   static const unsigned BufferHeaderSize = 1;
   static const size_t   BufferCodeAlignment = 8;
   static const size_t   BufferMaxPoolOffset = 1024;
   static const unsigned BufferPCBias = 0;
   static const uint32_t BufferAlignmentFillInstruction = BRK | (0xdead << ImmException_offset);
   static const uint32_t BufferNopFillInstruction = HINT | (31 << Rt_offset);
   static const unsigned BufferNumDebugNopsToInsert = 0;
 
+#ifdef JS_DISASM_ARM64
+  static constexpr const char* const InstrIndent = "        ";
+  static constexpr const char* const LabelIndent = "          ";
+  static constexpr const char* const TargetIndent = "                    ";
+#endif
+
  public:
   MozBaseAssembler()
     : armbuffer_(BufferGuardSize,
                  BufferHeaderSize,
                  BufferCodeAlignment,
                  BufferMaxPoolOffset,
                  BufferPCBias,
                  BufferAlignmentFillInstruction,
                  BufferNopFillInstruction,
                  BufferNumDebugNopsToInsert)
-  { }
+  {
+#ifdef JS_DISASM_ARM64
+      spew_.setLabelIndent(LabelIndent);
+      spew_.setTargetIndent(TargetIndent);
+#endif
+}
+  ~MozBaseAssembler()
+  {
+#ifdef JS_DISASM_ARM64
+      spew_.spewOrphans();
+#endif
+  }
 
  public:
   // Helper function for use with the ARMBuffer.
   // The MacroAssembler must create an AutoJitContextAlloc before initializing the buffer.
   void initWithAllocator() {
     armbuffer_.initWithAllocator();
   }
 
@@ -97,36 +122,131 @@ class MozBaseAssembler : public js::jit:
   // Get the next usable buffer offset. Note that a constant pool may be placed
   // here before the next instruction is emitted.
   BufferOffset nextOffset() const {
     return armbuffer_.nextOffset();
   }
 
   // Allocate memory in the buffer by forwarding to armbuffer_.
   // Propagate OOM errors.
-  BufferOffset allocEntry(size_t numInst, unsigned numPoolEntries,
-                          uint8_t* inst, uint8_t* data,
-                          ARMBuffer::PoolEntry* pe = nullptr)
+  BufferOffset allocLiteralLoadEntry(size_t numInst, unsigned numPoolEntries,
+				     uint8_t* inst, uint8_t* data,
+				     const LiteralDoc& doc = LiteralDoc(),
+				     ARMBuffer::PoolEntry* pe = nullptr)
   {
+    MOZ_ASSERT(inst);
+    MOZ_ASSERT(numInst == 1);	/* If not, then fix disassembly */
     BufferOffset offset = armbuffer_.allocEntry(numInst, numPoolEntries, inst,
                                                 data, pe);
     propagateOOM(offset.assigned());
+#ifdef JS_DISASM_ARM64
+    Instruction* instruction = armbuffer_.getInstOrNull(offset);
+    if (instruction)
+        spewLiteralLoad(reinterpret_cast<vixl::Instruction*>(instruction), doc);
+#endif
     return offset;
   }
 
+#ifdef JS_DISASM_ARM64
+  DisassemblerSpew spew_;
+
+  void spew(const vixl::Instruction* instr) {
+    if (spew_.isDisabled() || !instr)
+      return;
+
+    char buffer[2048];
+    DisassembleInstruction(buffer, sizeof(buffer), instr);
+    spew_.spew("%08" PRIx32 "%s%s", instr->InstructionBits(), InstrIndent, buffer);
+  }
+
+  void spewBranch(const vixl::Instruction* instr, const LabelDoc& target) {
+    if (spew_.isDisabled() || !instr)
+      return;
+
+    char buffer[2048];
+    DisassembleInstruction(buffer, sizeof(buffer), instr);
+
+    char labelBuf[128];
+    labelBuf[0] = 0;
+
+    bool hasTarget = target.valid;
+    if (!hasTarget)
+      snprintf(labelBuf, sizeof(labelBuf), "-> (link-time target)");
+
+    if (instr->IsImmBranch() && hasTarget) {
+      // The target information in the instruction is likely garbage, so remove it.
+      // The target label will in any case be printed if we have it.
+      //
+      // The format of the instruction disassembly is /.*#.*/.  Strip the # and later.
+      size_t i;
+      const size_t BUFLEN = sizeof(buffer)-1;
+      for ( i=0 ; i < BUFLEN && buffer[i] && buffer[i] != '#' ; i++ )
+	;
+      buffer[i] = 0;
+
+      snprintf(labelBuf, sizeof(labelBuf), "-> %d%s", target.doc, !target.bound ? "f" : "");
+      hasTarget = false;
+    }
+
+    spew_.spew("%08" PRIx32 "%s%s%s", instr->InstructionBits(), InstrIndent, buffer, labelBuf);
+
+    if (hasTarget)
+      spew_.spewRef(target);
+  }
+
+  void spewLiteralLoad(const vixl::Instruction* instr, const LiteralDoc& doc) {
+    if (spew_.isDisabled() || !instr)
+      return;
+
+    char buffer[2048];
+    DisassembleInstruction(buffer, sizeof(buffer), instr);
+
+    char litbuf[2048];
+    spew_.formatLiteral(doc, litbuf, sizeof(litbuf));
+
+    // The instruction will have the form /^.*pc\+0/ followed by junk that we
+    // don't need; try to strip it.
+
+    char *probe = strstr(buffer, "pc+0");
+    if (probe)
+      *(probe + 4) = 0;
+    spew_.spew("%08" PRIx32 "%s%s    ; .const %s", instr->InstructionBits(), InstrIndent, buffer, litbuf);
+  }
+
+  LabelDoc refLabel(Label* label) {
+    if (spew_.isDisabled())
+      return LabelDoc();
+
+    return spew_.refLabel(label);
+  }
+#else
+  LabelDoc refLabel(js::jit::Label*) {
+      return LabelDoc();
+  }
+#endif
+
   // Emit the instruction, returning its offset.
   BufferOffset Emit(Instr instruction, bool isBranch = false) {
     JS_STATIC_ASSERT(sizeof(instruction) == kInstructionSize);
     // TODO: isBranch is obsolete and should be removed.
     (void)isBranch;
-    return armbuffer_.putInt(*(uint32_t*)(&instruction));
+    BufferOffset offs = armbuffer_.putInt(*(uint32_t*)(&instruction));
+#ifdef JS_DISASM_ARM64
+    if (!isBranch)
+	spew(armbuffer_.getInstOrNull(offs));
+#endif
+    return offs;
   }
 
-  BufferOffset EmitBranch(Instr instruction) {
-    return Emit(instruction, true);
+  BufferOffset EmitBranch(Instr instruction, const LabelDoc& doc) {
+    BufferOffset offs = Emit(instruction, true);
+#ifdef JS_DISASM_ARM64
+    spewBranch(armbuffer_.getInstOrNull(offs), doc);
+#endif
+    return offs;
   }
 
  public:
   // Emit the instruction at |at|.
   static void Emit(Instruction* at, Instr instruction) {
     JS_STATIC_ASSERT(sizeof(instruction) == kInstructionSize);
     memcpy(at, &instruction, sizeof(instruction));
   }
--- a/js/src/jit/mips32/MacroAssembler-mips32.cpp
+++ b/js/src/jit/mips32/MacroAssembler-mips32.cpp
@@ -72,35 +72,16 @@ MacroAssemblerMIPSCompat::convertUInt32T
     as_mtc1(ScratchRegister, dest);
     as_cvtdw(dest, dest);
 
     // Add unsigned value of INT32_MIN
     ma_lid(SecondScratchDoubleReg, 2147483648.0);
     as_addd(dest, dest, SecondScratchDoubleReg);
 }
 
-static const double TO_DOUBLE_HIGH_SCALE = 0x100000000;
-
-bool
-MacroAssemblerMIPSCompat::convertUInt64ToDoubleNeedsTemp()
-{
-    return false;
-}
-
-void
-MacroAssemblerMIPSCompat::convertUInt64ToDouble(Register64 src, FloatRegister dest, Register temp)
-{
-    MOZ_ASSERT(temp == Register::Invalid());
-    convertUInt32ToDouble(src.high, dest);
-    loadConstantDouble(TO_DOUBLE_HIGH_SCALE, ScratchDoubleReg);
-    asMasm().mulDouble(ScratchDoubleReg, dest);
-    convertUInt32ToDouble(src.low, ScratchDoubleReg);
-    asMasm().addDouble(ScratchDoubleReg, dest);
-}
-
 void
 MacroAssemblerMIPSCompat::convertUInt32ToFloat32(Register src, FloatRegister dest)
 {
     Label positive, done;
     ma_b(src, src, &positive, NotSigned, ShortJump);
 
     // We cannot do the same as convertUInt32ToDouble because float32 doesn't
     // have enough precision.
@@ -2501,9 +2482,31 @@ MacroAssembler::wasmTruncateFloat32ToUIn
     ma_or(output, ScratchRegister);
     ma_b(&done);
     bind(&simple);
     as_truncws(ScratchDoubleReg, input);
     moveFromFloat32(ScratchDoubleReg, output);
     bind(&done);
 }
 
+// ========================================================================
+// Convert floating point.
+
+static const double TO_DOUBLE_HIGH_SCALE = 0x100000000;
+
+bool
+MacroAssembler::convertUInt64ToDoubleNeedsTemp()
+{
+    return false;
+}
+
+void
+MacroAssembler::convertUInt64ToDouble(Register64 src, FloatRegister dest, Register temp)
+{
+    MOZ_ASSERT(temp == Register::Invalid());
+    convertUInt32ToDouble(src.high, dest);
+    loadConstantDouble(TO_DOUBLE_HIGH_SCALE, ScratchDoubleReg);
+    mulDouble(ScratchDoubleReg, dest);
+    convertUInt32ToDouble(src.low, ScratchDoubleReg);
+    addDouble(ScratchDoubleReg, dest);
+}
+
 //}}} check_macroassembler_style
--- a/js/src/jit/mips32/MacroAssembler-mips32.h
+++ b/js/src/jit/mips32/MacroAssembler-mips32.h
@@ -995,19 +995,16 @@ class MacroAssemblerMIPSCompat : public 
         as_movd(dest, src);
     }
 
     void zeroDouble(FloatRegister reg) {
         moveToDoubleLo(zero, reg);
         moveToDoubleHi(zero, reg);
     }
 
-    static bool convertUInt64ToDoubleNeedsTemp();
-    void convertUInt64ToDouble(Register64 src, FloatRegister dest, Register temp);
-
     void breakpoint();
 
     void checkStackAlignment();
 
     void alignStackPointer();
     void restoreStackPointer();
     static void calculateAlignedStackPointer(void** stackPointer);
 
--- a/js/src/jit/mips64/CodeGenerator-mips64.cpp
+++ b/js/src/jit/mips64/CodeGenerator-mips64.cpp
@@ -738,22 +738,22 @@ CodeGeneratorMIPS64::visitInt64ToFloatin
     Register input = ToRegister(lir->input());
     FloatRegister output = ToFloatRegister(lir->output());
 
     MIRType outputType = lir->mir()->type();
     MOZ_ASSERT(outputType == MIRType::Double || outputType == MIRType::Float32);
 
     if (outputType == MIRType::Double) {
         if (lir->mir()->isUnsigned())
-            masm.convertUInt64ToDouble(input, output);
+            masm.convertUInt64ToDouble(input, output, Register::Invalid());
         else
             masm.convertInt64ToDouble(input, output);
     } else {
         if (lir->mir()->isUnsigned())
-            masm.convertUInt64ToFloat32(input, output);
+            masm.convertUInt64ToFloat32(input, output, Register::Invalid());
         else
             masm.convertInt64ToFloat32(input, output);
     }
 }
 
 void
 CodeGeneratorMIPS64::visitTestI64AndBranch(LTestI64AndBranch* lir)
 {
--- a/js/src/jit/mips64/MacroAssembler-mips64.cpp
+++ b/js/src/jit/mips64/MacroAssembler-mips64.cpp
@@ -70,30 +70,16 @@ MacroAssemblerMIPS64Compat::convertUInt3
     as_cvtdw(dest, dest);
 
     // Add unsigned value of INT32_MIN
     ma_lid(SecondScratchDoubleReg, 2147483648.0);
     as_addd(dest, dest, SecondScratchDoubleReg);
 }
 
 void
-MacroAssemblerMIPS64Compat::convertInt64ToDouble(Register src, FloatRegister dest)
-{
-    as_dmtc1(src, dest);
-    as_cvtdl(dest, dest);
-}
-
-void
-MacroAssemblerMIPS64Compat::convertInt64ToFloat32(Register src, FloatRegister dest)
-{
-    as_dmtc1(src, dest);
-    as_cvtsl(dest, dest);
-}
-
-void
 MacroAssemblerMIPS64Compat::convertUInt64ToDouble(Register src, FloatRegister dest)
 {
     Label positive, done;
     ma_b(src, src, &positive, NotSigned, ShortJump);
 
     MOZ_ASSERT(src!= ScratchRegister);
     MOZ_ASSERT(src!= SecondScratchReg);
 
@@ -108,52 +94,16 @@ MacroAssemblerMIPS64Compat::convertUInt6
     bind(&positive);
     as_dmtc1(src, dest);
     as_cvtdl(dest, dest);
 
     bind(&done);
 }
 
 void
-MacroAssemblerMIPS64Compat::convertUInt64ToFloat32(Register src, FloatRegister dest)
-{
-    Label positive, done;
-    ma_b(src, src, &positive, NotSigned, ShortJump);
-
-    MOZ_ASSERT(src!= ScratchRegister);
-    MOZ_ASSERT(src!= SecondScratchReg);
-
-    ma_and(ScratchRegister, src, Imm32(1));
-    ma_dsrl(SecondScratchReg, src, Imm32(1));
-    ma_or(ScratchRegister, SecondScratchReg);
-    as_dmtc1(ScratchRegister, dest);
-    as_cvtsl(dest, dest);
-    asMasm().addFloat32(dest, dest);
-    ma_b(&done, ShortJump);
-
-    bind(&positive);
-    as_dmtc1(src, dest);
-    as_cvtsl(dest, dest);
-
-    bind(&done);
-}
-
-bool
-MacroAssemblerMIPS64Compat::convertUInt64ToDoubleNeedsTemp()
-{
-    return false;
-}
-
-void
-MacroAssemblerMIPS64Compat::convertUInt64ToDouble(Register64 src, FloatRegister dest, Register temp)
-{
-    convertUInt64ToDouble(src.reg, dest);
-}
-
-void
 MacroAssemblerMIPS64Compat::convertUInt32ToFloat32(Register src, FloatRegister dest)
 {
     Label positive, done;
     ma_b(src, src, &positive, NotSigned, ShortJump);
 
     // We cannot do the same as convertUInt32ToDouble because float32 doesn't
     // have enough precision.
     convertUInt32ToDouble(src, dest);
@@ -2599,9 +2549,66 @@ MacroAssembler::wasmTruncateFloat32ToUIn
     as_cfc1(ScratchRegister, Assembler::FCSR);
     ma_ext(ScratchRegister, ScratchRegister, 6, 1);
     ma_or(ScratchRegister, output);
     moveFromFloat32(ScratchDoubleReg, output);
     ma_b(ScratchRegister, Imm32(0), oolEntry, Assembler::NotEqual);
 
 }
 
+// ========================================================================
+// Convert floating point.
+
+void
+MacroAssembler::convertInt64ToDouble(Register64 src, FloatRegister dest)
+{
+    as_dmtc1(src.reg, dest);
+    as_cvtdl(dest, dest);
+}
+
+void
+MacroAssembler::convertInt64ToFloat32(Register64 src, FloatRegister dest)
+{
+    as_dmtc1(src.reg, dest);
+    as_cvtsl(dest, dest);
+}
+
+bool
+MacroAssembler::convertUInt64ToDoubleNeedsTemp()
+{
+    return false;
+}
+
+void
+MacroAssembler::convertUInt64ToDouble(Register64 src, FloatRegister dest, Register temp)
+{
+    MOZ_ASSERT(temp == Register::Invalid());
+    MacroAssemblerSpecific::convertUInt64ToDouble(src.reg, dest);
+}
+
+void
+MacroAssembler::convertUInt64ToFloat32(Register64 src_, FloatRegister dest, Register temp)
+{
+    MOZ_ASSERT(temp == Register::Invalid());
+
+    Register src = src_.reg;
+    Label positive, done;
+    ma_b(src, src, &positive, NotSigned, ShortJump);
+
+    MOZ_ASSERT(src!= ScratchRegister);
+    MOZ_ASSERT(src!= SecondScratchReg);
+
+    ma_and(ScratchRegister, src, Imm32(1));
+    ma_dsrl(SecondScratchReg, src, Imm32(1));
+    ma_or(ScratchRegister, SecondScratchReg);
+    as_dmtc1(ScratchRegister, dest);
+    as_cvtsl(dest, dest);
+    addFloat32(dest, dest);
+    ma_b(&done, ShortJump);
+
+    bind(&positive);
+    as_dmtc1(src, dest);
+    as_cvtsl(dest, dest);
+
+    bind(&done);
+}
+
 //}}} check_macroassembler_style
--- a/js/src/jit/mips64/MacroAssembler-mips64.h
+++ b/js/src/jit/mips64/MacroAssembler-mips64.h
@@ -977,24 +977,17 @@ class MacroAssemblerMIPS64Compat : publi
     void moveDouble(FloatRegister src, FloatRegister dest) {
         as_movd(dest, src);
     }
 
     void zeroDouble(FloatRegister reg) {
         moveToDouble(zero, reg);
     }
 
-    void convertInt64ToDouble(Register src, FloatRegister dest);
-    void convertInt64ToFloat32(Register src, FloatRegister dest);
-
     void convertUInt64ToDouble(Register src, FloatRegister dest);
-    void convertUInt64ToFloat32(Register src, FloatRegister dest);
-
-    static bool convertUInt64ToDoubleNeedsTemp();
-    void convertUInt64ToDouble(Register64 src, FloatRegister dest, Register temp);
 
     void breakpoint();
 
     void checkStackAlignment();
 
     static void calculateAlignedStackPointer(void** stackPointer);
 
     // If source is a double, load it into dest. If source is int32,
--- a/js/src/jit/none/MacroAssembler-none.h
+++ b/js/src/jit/none/MacroAssembler-none.h
@@ -337,18 +337,16 @@ class MacroAssemblerNone : public Assemb
     void convertDoubleToInt32(FloatRegister, Register, Label*, bool v = true) { MOZ_CRASH(); }
     void convertBoolToInt32(Register, Register) { MOZ_CRASH(); }
 
     void convertDoubleToFloat32(FloatRegister, FloatRegister) { MOZ_CRASH(); }
     void convertInt32ToFloat32(Register, FloatRegister) { MOZ_CRASH(); }
 
     template <typename T> void convertInt32ToDouble(T, FloatRegister) { MOZ_CRASH(); }
     void convertFloat32ToDouble(FloatRegister, FloatRegister) { MOZ_CRASH(); }
-    static bool convertUInt64ToDoubleNeedsTemp() { MOZ_CRASH(); }
-    void convertUInt64ToDouble(Register64, FloatRegister, Register) { MOZ_CRASH(); }
 
     void boolValueToDouble(ValueOperand, FloatRegister) { MOZ_CRASH(); }
     void boolValueToFloat32(ValueOperand, FloatRegister) { MOZ_CRASH(); }
     void int32ValueToDouble(ValueOperand, FloatRegister) { MOZ_CRASH(); }
     void int32ValueToFloat32(ValueOperand, FloatRegister) { MOZ_CRASH(); }
 
     void loadConstantDouble(double, FloatRegister) { MOZ_CRASH(); }
     void loadConstantFloat32(float, FloatRegister) { MOZ_CRASH(); }
new file mode 100644
--- /dev/null
+++ b/js/src/jit/shared/Disassembler-shared.cpp
@@ -0,0 +1,275 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "jit/shared/Disassembler-shared.h"
+
+#include "jsprf.h"
+
+#include "jit/JitSpewer.h"
+
+using namespace js::jit;
+
+#ifdef JS_DISASM_SUPPORTED
+// See comments in spew(), below.
+mozilla::Atomic<uint32_t> DisassemblerSpew::live_(0);
+#endif
+
+DisassemblerSpew::DisassemblerSpew()
+  : printer_(nullptr)
+#ifdef JS_DISASM_SUPPORTED
+    ,
+    labelIndent_(""),
+    targetIndent_(""),
+    spewNext_(1000),
+    nodes_(nullptr)
+#endif
+{
+#ifdef JS_DISASM_SUPPORTED
+    live_++;
+#endif
+}
+
+DisassemblerSpew::~DisassemblerSpew()
+{
+#ifdef JS_DISASM_SUPPORTED
+    Node* p = nodes_;
+    while (p) {
+        Node* victim = p;
+        p = p->next;
+        js_free(victim);
+    }
+    live_--;
+#endif
+}
+
+void
+DisassemblerSpew::setPrinter(Sprinter* printer)
+{
+    printer_ = printer;
+}
+
+bool
+DisassemblerSpew::isDisabled()
+{
+    return !(JitSpewEnabled(JitSpew_Codegen) || printer_);
+}
+
+void
+DisassemblerSpew::spew(const char* fmt, ...)
+{
+    // Nested assemblers are handled by prefixing the output with '>..> ' where
+    // the number of '>' is the nesting level, and the outermost assembler is
+    // taken as being at nesting level zero (and does not require the trailing
+    // space character).  This markup disambiguates eg the output of an IC
+    // compilation that happens as a subtask of a normal compilation from the
+    // output of the normal compilation.
+    //
+    // We track the nesting level globally, on the assumption that anyone
+    // wanting to look at disassembly is running with --no-threads.  If this
+    // turns out to be wrong then live_ can be made thread-local.
+
+#ifdef JS_DISASM_SUPPORTED
+    char fmt2[1024];
+    MOZ_RELEASE_ASSERT(sizeof(fmt2) >= strlen(fmt) + live_ + 1);
+    uint32_t i;
+    for (i = 0; i < live_-1; i++ )
+        fmt2[i] = '>';
+    if (live_ > 1)
+        fmt2[i++] = ' ';
+    strcpy(fmt2 + i, fmt);
+#else
+    const char* fmt2 = fmt;
+#endif
+
+    va_list args;
+    va_start(args, fmt);
+    spewVA(fmt2, args);
+    va_end(args);
+}
+
+void
+DisassemblerSpew::spewVA(const char* fmt, va_list va)
+{
+    if (printer_) {
+        printer_->vprintf(fmt, va);
+        printer_->put("\n");
+    }
+    js::jit::JitSpewVA(js::jit::JitSpew_Codegen, fmt, va);
+}
+
+#ifdef JS_DISASM_SUPPORTED
+
+void
+DisassemblerSpew::setLabelIndent(const char* s)
+{
+    labelIndent_ = s;
+}
+
+void
+DisassemblerSpew::setTargetIndent(const char* s)
+{
+    targetIndent_ = s;
+}
+
+DisassemblerSpew::LabelDoc
+DisassemblerSpew::refLabel(const Label* l)
+{
+    return l ? LabelDoc(internalResolve(l), l->bound()) : LabelDoc();
+}
+
+void
+DisassemblerSpew::spewRef(const LabelDoc& target)
+{
+    if (isDisabled())
+        return;
+    if (!target.valid)
+        return;
+    spew("%s-> %d%s", targetIndent_, target.doc, !target.bound ? "f" : "");
+}
+
+void
+DisassemblerSpew::spewBind(const Label* label)
+{
+    if (isDisabled())
+        return;
+    uint32_t v = internalResolve(label);
+    Node* probe = lookup(label);
+    if (probe)
+        probe->bound = true;
+    spew("%s%d:", labelIndent_, v);
+}
+
+void
+DisassemblerSpew::spewRetarget(const Label* label, const Label* target)
+{
+    if (isDisabled())
+        return;
+    LabelDoc labelDoc = LabelDoc(internalResolve(label), label->bound());
+    LabelDoc targetDoc = LabelDoc(internalResolve(target), target->bound());
+    Node* probe = lookup(label);
+    if (probe)
+        probe->bound = true;
+    spew("%s%d: .retarget -> %d%s",
+         labelIndent_, labelDoc.doc, targetDoc.doc, !targetDoc.bound ? "f" : "");
+}
+
+void
+DisassemblerSpew::formatLiteral(const LiteralDoc& doc, char* buffer, size_t bufsize)
+{
+    switch (doc.type) {
+      case LiteralDoc::Type::Patchable:
+        snprintf(buffer, bufsize, "patchable");
+        break;
+      case LiteralDoc::Type::I32:
+        snprintf(buffer, bufsize, "%d", doc.value.i32);
+        break;
+      case LiteralDoc::Type::U32:
+        snprintf(buffer, bufsize, "%u", doc.value.u32);
+        break;
+      case LiteralDoc::Type::I64:
+        snprintf(buffer, bufsize, "%" PRIi64, doc.value.i64);
+        break;
+      case LiteralDoc::Type::U64:
+        snprintf(buffer, bufsize, "%" PRIu64, doc.value.u64);
+        break;
+      case LiteralDoc::Type::F32:
+        snprintf(buffer, bufsize, "%g", doc.value.f32);
+        break;
+      case LiteralDoc::Type::F64:
+        snprintf(buffer, bufsize, "%g", doc.value.f64);
+        break;
+      default:
+        MOZ_CRASH();
+    }
+}
+
+void
+DisassemblerSpew::spewOrphans()
+{
+    for (Node* p = nodes_; p; p = p->next) {
+        if (!p->bound)
+            spew("%s%d:    ; .orphan", labelIndent_, p->value);
+    }
+}
+
+uint32_t
+DisassemblerSpew::internalResolve(const Label* l)
+{
+    // Note, internalResolve will sometimes return 0 when it is triggered by the
+    // profiler and not by a full disassembly, since in that case a label can be
+    // used or bound but not previously have been defined.  In that case,
+    // internalResolve(l) will not necessarily create a binding for l!
+    // Consequently a subsequent lookup(l) may still return null.
+    return l->used() || l->bound() ? probe(l) : define(l);
+}
+
+uint32_t
+DisassemblerSpew::probe(const Label* l)
+{
+    Node* n = lookup(l);
+    return n ? n->value : 0;
+}
+
+uint32_t
+DisassemblerSpew::define(const Label* l)
+{
+    remove(l);
+    uint32_t value = spewNext_++;
+    if (!add(l, value))
+        return 0;
+    return value;
+}
+
+DisassemblerSpew::Node*
+DisassemblerSpew::lookup(const Label* key)
+{
+    Node* p;
+    for (p = nodes_; p && p->key != key; p = p->next)
+        ;
+    return p;
+}
+
+DisassemblerSpew::Node*
+DisassemblerSpew::add(const Label* key, uint32_t value)
+{
+    MOZ_ASSERT(!lookup(key));
+    Node* node = (Node*)js_malloc(sizeof(Node));
+    if (node) {
+        node->key = key;
+        node->value = value;
+        node->bound = false;
+        node->next = nodes_;
+        nodes_ = node;
+    }
+    return node;
+}
+
+bool
+DisassemblerSpew::remove(const Label* key)
+{
+    // We do not require that there is a node matching the key.
+    for (Node* p = nodes_, *pp = nullptr; p; pp = p, p = p->next) {
+        if (p->key == key) {
+            if (pp)
+                pp->next = p->next;
+            else
+                nodes_ = p->next;
+            js_free(p);
+            return true;
+        }
+    }
+    return false;
+}
+
+#else
+
+DisassemblerSpew::LabelDoc
+DisassemblerSpew::refLabel(const Label* l)
+{
+    return LabelDoc();
+}
+
+#endif
new file mode 100644
--- /dev/null
+++ b/js/src/jit/shared/Disassembler-shared.h
@@ -0,0 +1,183 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef jit_shared_Disassembler_shared_h
+#define jit_shared_Disassembler_shared_h
+
+#include "mozilla/Atomics.h"
+
+#include "jit/Label.h"
+#ifdef JS_DISASM_SUPPORTED
+# include "jit/shared/IonAssemblerBuffer.h"
+#endif
+
+using js::jit::Label;
+using js::Sprinter;
+
+#if defined(JS_DISASM_ARM) || defined(JS_DISASM_ARM64)
+#  define JS_DISASM_SUPPORTED
+#endif
+
+namespace js {
+namespace jit {
+
+// A wrapper around spew/disassembly functionality.  The disassembler is built
+// on a per-instruction disassembler (as in our ARM, ARM64 back-ends) and
+// formats labels with meaningful names and literals with meaningful values, if
+// the assembler creates documentation (with provided helpers) at appropriate
+// points.
+
+class DisassemblerSpew
+{
+#ifdef JS_DISASM_SUPPORTED
+    struct Node
+    {
+        const Label* key;       // Never dereferenced, only used for its value
+        uint32_t value;         // The printable label value
+        bool bound;             // If the label has been seen by spewBind()
+        Node* next;
+    };
+
+    Node* lookup(const Label* key);
+    Node* add(const Label* key, uint32_t value);
+    bool remove(const Label* key);
+
+    uint32_t probe(const Label* l);
+    uint32_t define(const Label* l);
+    uint32_t internalResolve(const Label* l);
+#endif
+
+    void spewVA(const char* fmt, va_list args) MOZ_FORMAT_PRINTF(2, 0);
+
+  public:
+    DisassemblerSpew();
+    ~DisassemblerSpew();
+
+#ifdef JS_DISASM_SUPPORTED
+    // Set indentation strings.  The spewer retains a reference to s.
+    void setLabelIndent(const char* s);
+    void setTargetIndent(const char* s);
+#endif
+
+    // Set the spew printer, which will always be used if it is set, regardless
+    // of whether the system spew channel is enabled or not.  The spewer retains
+    // a reference to sp.
+    void setPrinter(Sprinter* sp);
+
+    // Return true if disassembly spew is disabled and no additional printer is
+    // set.
+    bool isDisabled();
+
+    // Format and print text on the spew channel; output is suppressed if spew
+    // is disabled.  The output is not indented, and is terminated by a newline.
+    void spew(const char* fmt, ...) MOZ_FORMAT_PRINTF(2, 3);
+
+    // Documentation for a label reference.
+    struct LabelDoc
+    {
+#ifdef JS_DISASM_SUPPORTED
+	LabelDoc() : doc(0), bound(false), valid(false) {}
+	LabelDoc(uint32_t doc, bool bound) : doc(doc), bound(bound), valid(true) {}
+	const uint32_t doc;
+	const bool bound;
+	const bool valid;
+#else
+	LabelDoc() {}
+	LabelDoc(uint32_t, bool) {}
+#endif
+    };
+
+    // Documentation for a literal load.
+    struct LiteralDoc
+    {
+#ifdef JS_DISASM_SUPPORTED
+        enum class Type { Patchable, I32, U32, I64, U64, F32, F64 };
+        const Type type;
+        union {
+            int32_t  i32;
+            uint32_t u32;
+            int64_t  i64;
+            uint64_t u64;
+            float    f32;
+            double   f64;
+        } value;
+        LiteralDoc() : type(Type::Patchable) {}
+        explicit LiteralDoc(int32_t v) : type(Type::I32) { value.i32 = v; }
+        explicit LiteralDoc(uint32_t v) : type(Type::U32) { value.u32 = v; }
+        explicit LiteralDoc(int64_t v) : type(Type::I64) { value.i64 = v; }
+        explicit LiteralDoc(uint64_t v) : type(Type::U64) { value.u64 = v; }
+        explicit LiteralDoc(float v) : type(Type::F32) { value.f32 = v; }
+        explicit LiteralDoc(double v) : type(Type::F64) { value.f64 = v; }
+#else
+        LiteralDoc() {}
+        explicit LiteralDoc(int32_t) {}
+        explicit LiteralDoc(uint32_t) {}
+        explicit LiteralDoc(int64_t) {}
+        explicit LiteralDoc(uint64_t) {}
+        explicit LiteralDoc(float) {}
+        explicit LiteralDoc(double) {}
+#endif
+    };
+
+    // Reference a label, resolving it to a printable representation.
+    //
+    // NOTE: The printable representation depends on the state of the label, so
+    // if we call resolve() when emitting & disassembling a branch instruction
+    // then it should be called before the label becomes Used, if emitting the
+    // branch can change the label's state.
+    //
+    // If the disassembler is not defined this returns a structure that is
+    // marked not valid.
+    LabelDoc refLabel(const Label* l);
+
+#ifdef JS_DISASM_SUPPORTED
+    // Spew the label information previously gathered by refLabel(), at a point
+    // where the label is referenced.  The output is indented by targetIndent_
+    // and terminated by a newline.
+    void spewRef(const LabelDoc& target);
+
+    // Spew the label at the point where the label is bound.  The output is
+    // indented by labelIndent_ and terminated by a newline.
+    void spewBind(const Label* label);
+
+    // Spew a retarget directive at the point where the retarget is recorded.
+    // The output is indented by labelIndent_ and terminated by a newline.
+    void spewRetarget(const Label* label, const Label* target);
+
+    // Format a literal value into the buffer.  The buffer is always
+    // NUL-terminated even if this chops the formatted value.
+    void formatLiteral(const LiteralDoc& doc, char* buffer, size_t bufsize);
+
+    // Print any unbound labels, one per line, with normal label indent and with
+    // a comment indicating the label is not defined.  Labels can be referenced
+    // but unbound in some legitimate cases, normally for traps.  Printing them
+    // reduces confusion.
+    void spewOrphans();
+#endif
+
+  private:
+    Sprinter* printer_;
+#ifdef JS_DISASM_SUPPORTED
+    const char* labelIndent_;
+    const char* targetIndent_;
+    uint32_t spewNext_;
+    Node* nodes_;
+
+    // This global tracks the nesting level of assemblers, see comments in
+    // spew() in Disassembler-shared.cpp for why this is desirable.
+    //
+    // The variable is atomic to avoid any kind of complaint from thread
+    // sanitizers etc, (it could also be thread-local).  However, trying to look
+    // at disassembly without using --no-threads is basically insane, so you can
+    // ignore the multi-threading implications here.
+    static mozilla::Atomic<uint32_t> live_;
+#endif
+};
+
+}
+}
+
+#endif // jit_shared_Disassembler_shared_h
--- a/js/src/jit/x64/MacroAssembler-x64.cpp
+++ b/js/src/jit/x64/MacroAssembler-x64.cpp
@@ -68,182 +68,16 @@ MacroAssemblerX64::loadConstantSimd128Fl
     SimdData* val = getSimdData(v);
     if (!val)
         return;
     JmpSrc j = masm.vmovaps_ripr(dest.encoding());
     propagateOOM(val->uses.append(CodeOffset(j.offset())));
 }
 
 void
-MacroAssemblerX64::convertInt64ToDouble(Register64 input, FloatRegister output)
-{
-    // Zero the output register to break dependencies, see convertInt32ToDouble.
-    zeroDouble(output);
-
-    vcvtsq2sd(input.reg, output, output);
-}
-
-void
-MacroAssemblerX64::convertInt64ToFloat32(Register64 input, FloatRegister output)
-{
-    // Zero the output register to break dependencies, see convertInt32ToDouble.
-    zeroFloat32(output);
-
-    vcvtsq2ss(input.reg, output, output);
-}
-
-bool
-MacroAssemblerX64::convertUInt64ToDoubleNeedsTemp()
-{
-    return true;
-}
-
-void
-MacroAssemblerX64::convertUInt64ToDouble(Register64 input, FloatRegister output, Register temp)
-{
-    // Zero the output register to break dependencies, see convertInt32ToDouble.
-    zeroDouble(output);
-
-    // If the input's sign bit is not set we use vcvtsq2sd directly.
-    // Else, we divide by 2 and keep the LSB, convert to double, and multiply
-    // the result by 2.
-    Label done;
-    Label isSigned;
-
-    testq(input.reg, input.reg);
-    j(Assembler::Signed, &isSigned);
-    vcvtsq2sd(input.reg, output, output);
-    jump(&done);
-
-    bind(&isSigned);
-
-    ScratchRegisterScope scratch(asMasm());
-    mov(input.reg, scratch);
-    mov(input.reg, temp);
-    shrq(Imm32(1), scratch);
-    andq(Imm32(1), temp);
-    orq(temp, scratch);
-
-    vcvtsq2sd(scratch, output, output);
-    vaddsd(output, output, output);
-
-    bind(&done);
-}
-
-void
-MacroAssemblerX64::convertUInt64ToFloat32(Register64 input, FloatRegister output, Register temp)
-{
-    // Zero the output register to break dependencies, see convertInt32ToDouble.
-    zeroFloat32(output);
-
-    // See comment in convertUInt64ToDouble.
-    Label done;
-    Label isSigned;
-
-    testq(input.reg, input.reg);
-    j(Assembler::Signed, &isSigned);
-    vcvtsq2ss(input.reg, output, output);
-    jump(&done);
-
-    bind(&isSigned);
-
-    ScratchRegisterScope scratch(asMasm());
-    mov(input.reg, scratch);
-    mov(input.reg, temp);
-    shrq(Imm32(1), scratch);
-    andq(Imm32(1), temp);
-    orq(temp, scratch);
-
-    vcvtsq2ss(scratch, output, output);
-    vaddss(output, output, output);
-
-    bind(&done);
-}
-
-void
-MacroAssemblerX64::wasmTruncateDoubleToInt64(FloatRegister input, Register64 output, Label* oolEntry,
-                                             Label* oolRejoin, FloatRegister tempReg)
-{
-    vcvttsd2sq(input, output.reg);
-    cmpq(Imm32(1), output.reg);
-    j(Assembler::Overflow, oolEntry);
-    bind(oolRejoin);
-}
-
-void
-MacroAssemblerX64::wasmTruncateFloat32ToInt64(FloatRegister input, Register64 output, Label* oolEntry,
-                                              Label* oolRejoin, FloatRegister tempReg)
-{
-    vcvttss2sq(input, output.reg);
-    cmpq(Imm32(1), output.reg);
-    j(Assembler::Overflow, oolEntry);
-    bind(oolRejoin);
-}
-
-void
-MacroAssemblerX64::wasmTruncateDoubleToUInt64(FloatRegister input, Register64 output, Label* oolEntry,
-                                              Label* oolRejoin, FloatRegister tempReg)
-{
-    // If the input < INT64_MAX, vcvttsd2sq will do the right thing, so
-    // we use it directly. Else, we subtract INT64_MAX, convert to int64,
-    // and then add INT64_MAX to the result.
-
-    Label isLarge;
-
-    ScratchDoubleScope scratch(asMasm());
-    loadConstantDouble(double(0x8000000000000000), scratch);
-    asMasm().branchDouble(Assembler::DoubleGreaterThanOrEqual, input, scratch, &isLarge);
-    vcvttsd2sq(input, output.reg);
-    testq(output.reg, output.reg);
-    j(Assembler::Signed, oolEntry);
-    jump(oolRejoin);
-
-    bind(&isLarge);
-
-    moveDouble(input, tempReg);
-    vsubsd(scratch, tempReg, tempReg);
-    vcvttsd2sq(tempReg, output.reg);
-    testq(output.reg, output.reg);
-    j(Assembler::Signed, oolEntry);
-    asMasm().or64(Imm64(0x8000000000000000), output);
-
-    bind(oolRejoin);
-}
-
-void
-MacroAssemblerX64::wasmTruncateFloat32ToUInt64(FloatRegister input, Register64 output, Label* oolEntry,
-                                               Label* oolRejoin, FloatRegister tempReg)
-{
-    // If the input < INT64_MAX, vcvttss2sq will do the right thing, so
-    // we use it directly. Else, we subtract INT64_MAX, convert to int64,
-    // and then add INT64_MAX to the result.
-
-    Label isLarge;
-
-    ScratchFloat32Scope scratch(asMasm());
-    loadConstantFloat32(float(0x8000000000000000), scratch);
-    asMasm().branchFloat(Assembler::DoubleGreaterThanOrEqual, input, scratch, &isLarge);
-    vcvttss2sq(input, output.reg);
-    testq(output.reg, output.reg);
-    j(Assembler::Signed, oolEntry);
-    jump(oolRejoin);
-
-    bind(&isLarge);
-
-    moveFloat32(input, tempReg);
-    vsubss(scratch, tempReg, tempReg);
-    vcvttss2sq(tempReg, output.reg);
-    testq(output.reg, output.reg);
-    j(Assembler::Signed, oolEntry);
-    asMasm().or64(Imm64(0x8000000000000000), output);
-
-    bind(oolRejoin);
-}
-
-void
 MacroAssemblerX64::bindOffsets(const MacroAssemblerX86Shared::UsesVector& uses)
 {
     for (CodeOffset use : uses) {
         JmpDst dst(currentOffset());
         JmpSrc src(use.offset());
         // Using linkJump here is safe, as explaind in the comment in
         // loadConstantDouble.
         masm.linkJump(src, dst);
@@ -918,16 +752,185 @@ MacroAssembler::wasmTruncateFloat32ToUIn
 
     // Check that the result is in the uint32_t range.
     ScratchRegisterScope scratch(*this);
     move32(Imm32(0xffffffff), scratch);
     cmpq(scratch, output);
     j(Assembler::Above, oolEntry);
 }
 
+void
+MacroAssembler::wasmTruncateDoubleToInt64(FloatRegister input, Register64 output, Label* oolEntry,
+                                          Label* oolRejoin, FloatRegister tempReg)
+{
+    vcvttsd2sq(input, output.reg);
+    cmpq(Imm32(1), output.reg);
+    j(Assembler::Overflow, oolEntry);
+    bind(oolRejoin);
+}
+
+void
+MacroAssembler::wasmTruncateFloat32ToInt64(FloatRegister input, Register64 output, Label* oolEntry,
+                                           Label* oolRejoin, FloatRegister tempReg)
+{
+    vcvttss2sq(input, output.reg);
+    cmpq(Imm32(1), output.reg);
+    j(Assembler::Overflow, oolEntry);
+    bind(oolRejoin);
+}
+
+void
+MacroAssembler::wasmTruncateDoubleToUInt64(FloatRegister input, Register64 output, Label* oolEntry,
+                                           Label* oolRejoin, FloatRegister tempReg)
+{
+    // If the input < INT64_MAX, vcvttsd2sq will do the right thing, so
+    // we use it directly. Else, we subtract INT64_MAX, convert to int64,
+    // and then add INT64_MAX to the result.
+
+    Label isLarge;
+
+    ScratchDoubleScope scratch(*this);
+    loadConstantDouble(double(0x8000000000000000), scratch);
+    branchDouble(Assembler::DoubleGreaterThanOrEqual, input, scratch, &isLarge);
+    vcvttsd2sq(input, output.reg);
+    testq(output.reg, output.reg);
+    j(Assembler::Signed, oolEntry);
+    jump(oolRejoin);
+
+    bind(&isLarge);
+
+    moveDouble(input, tempReg);
+    vsubsd(scratch, tempReg, tempReg);
+    vcvttsd2sq(tempReg, output.reg);
+    testq(output.reg, output.reg);
+    j(Assembler::Signed, oolEntry);
+    or64(Imm64(0x8000000000000000), output);
+
+    bind(oolRejoin);
+}
+
+void
+MacroAssembler::wasmTruncateFloat32ToUInt64(FloatRegister input, Register64 output, Label* oolEntry,
+                                            Label* oolRejoin, FloatRegister tempReg)
+{
+    // If the input < INT64_MAX, vcvttss2sq will do the right thing, so
+    // we use it directly. Else, we subtract INT64_MAX, convert to int64,
+    // and then add INT64_MAX to the result.
+
+    Label isLarge;
+
+    ScratchFloat32Scope scratch(*this);
+    loadConstantFloat32(float(0x8000000000000000), scratch);
+    branchFloat(Assembler::DoubleGreaterThanOrEqual, input, scratch, &isLarge);
+    vcvttss2sq(input, output.reg);
+    testq(output.reg, output.reg);
+    j(Assembler::Signed, oolEntry);
+    jump(oolRejoin);
+
+    bind(&isLarge);
+
+    moveFloat32(input, tempReg);
+    vsubss(scratch, tempReg, tempReg);
+    vcvttss2sq(tempReg, output.reg);
+    testq(output.reg, output.reg);
+    j(Assembler::Signed, oolEntry);
+    or64(Imm64(0x8000000000000000), output);
+
+    bind(oolRejoin);
+}
+
+// ========================================================================
+// Convert floating point.
+
+void
+MacroAssembler::convertInt64ToDouble(Register64 input, FloatRegister output)
+{
+    // Zero the output register to break dependencies, see convertInt32ToDouble.
+    zeroDouble(output);
+
+    vcvtsq2sd(input.reg, output, output);
+}
+
+void
+MacroAssembler::convertInt64ToFloat32(Register64 input, FloatRegister output)
+{
+    // Zero the output register to break dependencies, see convertInt32ToDouble.
+    zeroFloat32(output);
+
+    vcvtsq2ss(input.reg, output, output);
+}
+
+bool
+MacroAssembler::convertUInt64ToDoubleNeedsTemp()
+{
+    return true;
+}
+
+void
+MacroAssembler::convertUInt64ToDouble(Register64 input, FloatRegister output, Register temp)
+{
+    // Zero the output register to break dependencies, see convertInt32ToDouble.
+    zeroDouble(output);
+
+    // If the input's sign bit is not set we use vcvtsq2sd directly.
+    // Else, we divide by 2 and keep the LSB, convert to double, and multiply
+    // the result by 2.
+    Label done;
+    Label isSigned;
+
+    testq(input.reg, input.reg);
+    j(Assembler::Signed, &isSigned);
+    vcvtsq2sd(input.reg, output, output);
+    jump(&done);
+
+    bind(&isSigned);
+
+    ScratchRegisterScope scratch(*this);
+    mov(input.reg, scratch);
+    mov(input.reg, temp);
+    shrq(Imm32(1), scratch);
+    andq(Imm32(1), temp);
+    orq(temp, scratch);
+
+    vcvtsq2sd(scratch, output, output);
+    vaddsd(output, output, output);
+
+    bind(&done);
+}
+
+void
+MacroAssembler::convertUInt64ToFloat32(Register64 input, FloatRegister output, Register temp)
+{
+    // Zero the output register to break dependencies, see convertInt32ToDouble.
+    zeroFloat32(output);
+
+    // See comment in convertUInt64ToDouble.
+    Label done;
+    Label isSigned;
+
+    testq(input.reg, input.reg);
+    j(Assembler::Signed, &isSigned);
+    vcvtsq2ss(input.reg, output, output);
+    jump(&done);
+
+    bind(&isSigned);
+
+    ScratchRegisterScope scratch(*this);
+    mov(input.reg, scratch);
+    mov(input.reg, temp);
+    shrq(Imm32(1), scratch);
+    andq(Imm32(1), temp);
+    orq(temp, scratch);
+
+    vcvtsq2ss(scratch, output, output);
+    vaddss(output, output, output);
+
+    bind(&done);
+}
+
 // ========================================================================
 // Primitive atomic operations.
 
 void
 MacroAssembler::compareExchange64(const Synchronization&, const Address& mem, Register64 expected,
                                   Register64 replacement, Register64 output)
 {
     MOZ_ASSERT(output.reg == rax);
--- a/js/src/jit/x64/MacroAssembler-x64.h
+++ b/js/src/jit/x64/MacroAssembler-x64.h
@@ -908,32 +908,16 @@ class MacroAssemblerX64 : public MacroAs
     }
 
     void loadConstantDouble(double d, FloatRegister dest);
     void loadConstantFloat32(float f, FloatRegister dest);
 
     void loadConstantSimd128Int(const SimdConstant& v, FloatRegister dest);
     void loadConstantSimd128Float(const SimdConstant& v, FloatRegister dest);
 
-    void convertInt64ToDouble(Register64 input, FloatRegister output);
-    void convertInt64ToFloat32(Register64 input, FloatRegister output);
-    static bool convertUInt64ToDoubleNeedsTemp();
-    void convertUInt64ToDouble(Register64 input, FloatRegister output, Register temp);
-    void convertUInt64ToFloat32(Register64 input, FloatRegister output, Register temp);
-
-    void wasmTruncateDoubleToInt64(FloatRegister input, Register64 output, Label* oolEntry,
-                                   Label* oolRejoin, FloatRegister tempDouble);
-    void wasmTruncateDoubleToUInt64(FloatRegister input, Register64 output, Label* oolEntry,
-                                    Label* oolRejoin, FloatRegister tempDouble);
-
-    void wasmTruncateFloat32ToInt64(FloatRegister input, Register64 output, Label* oolEntry,
-                                    Label* oolRejoin, FloatRegister tempDouble);
-    void wasmTruncateFloat32ToUInt64(FloatRegister input, Register64 output, Label* oolEntry,
-                                     Label* oolRejoin, FloatRegister tempDouble);
-
     void loadWasmGlobalPtr(uint32_t globalDataOffset, Register dest) {
         loadPtr(Address(WasmTlsReg, offsetof(wasm::TlsData, globalArea) + globalDataOffset), dest);
     }
     void loadWasmPinnedRegsFromTls() {
         loadPtr(Address(WasmTlsReg, offsetof(wasm::TlsData, memoryBase)), HeapReg);
     }
 
   public:
--- a/js/src/jit/x86/MacroAssembler-x86.cpp
+++ b/js/src/jit/x86/MacroAssembler-x86.cpp
@@ -17,105 +17,16 @@
 #include "jit/MoveEmitter.h"
 
 #include "jsscriptinlines.h"
 #include "jit/MacroAssembler-inl.h"
 
 using namespace js;
 using namespace js::jit;
 
-// vpunpckldq requires 16-byte boundary for memory operand.
-// See convertUInt64ToDouble for the details.
-MOZ_ALIGNED_DECL(static const uint64_t, 16) TO_DOUBLE[4] = {
-    0x4530000043300000LL,
-    0x0LL,
-    0x4330000000000000LL,
-    0x4530000000000000LL
-};
-
-static const double TO_DOUBLE_HIGH_SCALE = 0x100000000;
-
-bool
-MacroAssemblerX86::convertUInt64ToDoubleNeedsTemp()
-{
-    return HasSSE3();
-}
-
-void
-MacroAssemblerX86::convertUInt64ToDouble(Register64 src, FloatRegister dest, Register temp)
-{
-    // SUBPD needs SSE2, HADDPD needs SSE3.
-    if (!HasSSE3()) {
-        MOZ_ASSERT(temp == Register::Invalid());
-
-        // Zero the dest register to break dependencies, see convertInt32ToDouble.
-        zeroDouble(dest);
-
-        asMasm().Push(src.high);
-        asMasm().Push(src.low);
-        fild(Operand(esp, 0));
-
-        Label notNegative;
-        asMasm().branch32(Assembler::NotSigned, src.high, Imm32(0), &notNegative);
-        double add_constant = 18446744073709551616.0; // 2^64
-        store64(Imm64(mozilla::BitwiseCast<uint64_t>(add_constant)), Address(esp, 0));
-        fld(Operand(esp, 0));
-        faddp();
-        bind(&notNegative);
-
-        fstp(Operand(esp, 0));
-        vmovsd(Address(esp, 0), dest);
-        asMasm().freeStack(2 * sizeof(intptr_t));
-        return;
-    }
-
-    // Following operation uses entire 128-bit of dest XMM register.
-    // Currently higher 64-bit is free when we have access to lower 64-bit.
-    MOZ_ASSERT(dest.size() == 8);
-    FloatRegister dest128 = FloatRegister(dest.encoding(), FloatRegisters::Simd128);
-
-    // Assume that src is represented as following:
-    //   src      = 0x HHHHHHHH LLLLLLLL
-
-    // Move src to dest (=dest128) and ScratchInt32x4Reg (=scratch):
-    //   dest     = 0x 00000000 00000000  00000000 LLLLLLLL
-    //   scratch  = 0x 00000000 00000000  00000000 HHHHHHHH
-    vmovd(src.low, dest128);
-    vmovd(src.high, ScratchSimd128Reg);
-
-    // Unpack and interleave dest and scratch to dest:
-    //   dest     = 0x 00000000 00000000  HHHHHHHH LLLLLLLL
-    vpunpckldq(ScratchSimd128Reg, dest128, dest128);
-
-    // Unpack and interleave dest and a constant C1 to dest:
-    //   C1       = 0x 00000000 00000000  45300000 43300000
-    //   dest     = 0x 45300000 HHHHHHHH  43300000 LLLLLLLL
-    // here, each 64-bit part of dest represents following double:
-    //   HI(dest) = 0x 1.00000HHHHHHHH * 2**84 == 2**84 + 0x HHHHHHHH 00000000
-    //   LO(dest) = 0x 1.00000LLLLLLLL * 2**52 == 2**52 + 0x 00000000 LLLLLLLL
-    movePtr(ImmWord((uintptr_t)TO_DOUBLE), temp);
-    vpunpckldq(Operand(temp, 0), dest128, dest128);
-
-    // Subtract a constant C2 from dest, for each 64-bit part:
-    //   C2       = 0x 45300000 00000000  43300000 00000000
-    // here, each 64-bit part of C2 represents following double:
-    //   HI(C2)   = 0x 1.0000000000000 * 2**84 == 2**84
-    //   LO(C2)   = 0x 1.0000000000000 * 2**52 == 2**52
-    // after the operation each 64-bit part of dest represents following:
-    //   HI(dest) = double(0x HHHHHHHH 00000000)
-    //   LO(dest) = double(0x 00000000 LLLLLLLL)
-    vsubpd(Operand(temp, sizeof(uint64_t) * 2), dest128, dest128);
-
-    // Add HI(dest) and LO(dest) in double and store it into LO(dest),
-    //   LO(dest) = double(0x HHHHHHHH 00000000) + double(0x 00000000 LLLLLLLL)
-    //            = double(0x HHHHHHHH LLLLLLLL)
-    //            = double(src)
-    vhaddpd(dest128, dest128);
-}
-
 void
 MacroAssemblerX86::loadConstantDouble(double d, FloatRegister dest)
 {
     if (maybeInlineDouble(d, dest))
         return;
     Double* dbl = getDouble(d);
     if (!dbl)
         return;
@@ -1073,197 +984,288 @@ MacroAssembler::wasmTruncateFloat32ToUIn
     vcvttss2si(ScratchFloat32Reg, output);
 
     branch32(Assembler::Condition::Signed, output, Imm32(0), oolEntry);
     or32(Imm32(0x80000000), output);
 
     bind(&done);
 }
 
-//}}} check_macroassembler_style
+void
+MacroAssembler::wasmTruncateDoubleToInt64(FloatRegister input, Register64 output, Label* oolEntry,
+                                          Label* oolRejoin, FloatRegister tempReg)
+{
+    Label fail, convert;
+    Register temp = output.high;
+
+    // Make sure input fits in (u)int64.
+    reserveStack(2 * sizeof(int32_t));
+    storeDouble(input, Operand(esp, 0));
+    branchDoubleNotInInt64Range(Address(esp, 0), temp, &fail);
+    jump(&convert);
+
+    // Handle failure in ool.
+    bind(&fail);
+    freeStack(2 * sizeof(int32_t));
+    jump(oolEntry);
+    bind(oolRejoin);
+    reserveStack(2 * sizeof(int32_t));
+    storeDouble(input, Operand(esp, 0));
+
+    // Convert the double/float to int64.
+    bind(&convert);
+    truncateDoubleToInt64(Address(esp, 0), Address(esp, 0), temp);
+
+    // Load value into int64 register.
+    load64(Address(esp, 0), output);
+    freeStack(2 * sizeof(int32_t));
+}
+
+void
+MacroAssembler::wasmTruncateFloat32ToInt64(FloatRegister input, Register64 output, Label* oolEntry,
+                                           Label* oolRejoin, FloatRegister tempReg)
+{
+    Label fail, convert;
+    Register temp = output.high;
+
+    // Make sure input fits in (u)int64.
+    reserveStack(2 * sizeof(int32_t));
+    storeFloat32(input, Operand(esp, 0));
+    branchFloat32NotInInt64Range(Address(esp, 0), temp, &fail);
+    jump(&convert);
+
+    // Handle failure in ool.
+    bind(&fail);
+    freeStack(2 * sizeof(int32_t));
+    jump(oolEntry);
+    bind(oolRejoin);
+    reserveStack(2 * sizeof(int32_t));
+    storeFloat32(input, Operand(esp, 0));
+
+    // Convert the double/float to int64.
+    bind(&convert);
+    truncateFloat32ToInt64(Address(esp, 0), Address(esp, 0), temp);
+
+    // Load value into int64 register.
+    load64(Address(esp, 0), output);
+    freeStack(2 * sizeof(int32_t));
+}
+
+void
+MacroAssembler::wasmTruncateDoubleToUInt64(FloatRegister input, Register64 output, Label* oolEntry,
+                                           Label* oolRejoin, FloatRegister tempReg)
+{
+    Label fail, convert;
+    Register temp = output.high;
+
+    // Make sure input fits in (u)int64.
+    reserveStack(2 * sizeof(int32_t));
+    storeDouble(input, Operand(esp, 0));
+    branchDoubleNotInUInt64Range(Address(esp, 0), temp, &fail);
+    jump(&convert);
+
+    // Handle failure in ool.
+    bind(&fail);
+    freeStack(2 * sizeof(int32_t));
+    jump(oolEntry);
+    bind(oolRejoin);
+    reserveStack(2 * sizeof(int32_t));
+    storeDouble(input, Operand(esp, 0));
+
+    // Convert the double/float to int64.
+    bind(&convert);
+    truncateDoubleToUInt64(Address(esp, 0), Address(esp, 0), temp, tempReg);
+
+    // Load value into int64 register.
+    load64(Address(esp, 0), output);
+    freeStack(2 * sizeof(int32_t));
+}
 
 void
-MacroAssemblerX86::convertInt64ToDouble(Register64 input, FloatRegister output)
+MacroAssembler::wasmTruncateFloat32ToUInt64(FloatRegister input, Register64 output, Label* oolEntry,
+                                            Label* oolRejoin, FloatRegister tempReg)
+{
+    Label fail, convert;
+    Register temp = output.high;
+
+    // Make sure input fits in (u)int64.
+    reserveStack(2 * sizeof(int32_t));
+    storeFloat32(input, Operand(esp, 0));
+    branchFloat32NotInUInt64Range(Address(esp, 0), temp, &fail);
+    jump(&convert);
+
+    // Handle failure in ool.
+    bind(&fail);
+    freeStack(2 * sizeof(int32_t));
+    jump(oolEntry);
+    bind(oolRejoin);
+    reserveStack(2 * sizeof(int32_t));
+    storeFloat32(input, Operand(esp, 0));
+
+    // Convert the double/float to int64.
+    bind(&convert);
+    truncateFloat32ToUInt64(Address(esp, 0), Address(esp, 0), temp, tempReg);
+
+    // Load value into int64 register.
+    load64(Address(esp, 0), output);
+    freeStack(2 * sizeof(int32_t));
+}
+
+
+// ========================================================================
+// Convert floating point.
+
+// vpunpckldq requires 16-byte boundary for memory operand.
+// See convertUInt64ToDouble for the details.
+MOZ_ALIGNED_DECL(static const uint64_t, 16) TO_DOUBLE[4] = {
+    0x4530000043300000LL,
+    0x0LL,
+    0x4330000000000000LL,
+    0x4530000000000000LL
+};
+
+bool
+MacroAssembler::convertUInt64ToDoubleNeedsTemp()
+{
+    return HasSSE3();
+}
+
+void
+MacroAssembler::convertUInt64ToDouble(Register64 src, FloatRegister dest, Register temp)
+{
+    // SUBPD needs SSE2, HADDPD needs SSE3.
+    if (!HasSSE3()) {
+        MOZ_ASSERT(temp == Register::Invalid());
+
+        // Zero the dest register to break dependencies, see convertInt32ToDouble.
+        zeroDouble(dest);
+
+        Push(src.high);
+        Push(src.low);
+        fild(Operand(esp, 0));
+
+        Label notNegative;
+        branch32(Assembler::NotSigned, src.high, Imm32(0), &notNegative);
+        double add_constant = 18446744073709551616.0; // 2^64
+        store64(Imm64(mozilla::BitwiseCast<uint64_t>(add_constant)), Address(esp, 0));
+        fld(Operand(esp, 0));
+        faddp();
+        bind(&notNegative);
+
+        fstp(Operand(esp, 0));
+        vmovsd(Address(esp, 0), dest);
+        freeStack(2 * sizeof(intptr_t));
+        return;
+    }
+
+    // Following operation uses entire 128-bit of dest XMM register.
+    // Currently higher 64-bit is free when we have access to lower 64-bit.
+    MOZ_ASSERT(dest.size() == 8);
+    FloatRegister dest128 = FloatRegister(dest.encoding(), FloatRegisters::Simd128);
+
+    // Assume that src is represented as following:
+    //   src      = 0x HHHHHHHH LLLLLLLL
+
+    // Move src to dest (=dest128) and ScratchInt32x4Reg (=scratch):
+    //   dest     = 0x 00000000 00000000  00000000 LLLLLLLL
+    //   scratch  = 0x 00000000 00000000  00000000 HHHHHHHH
+    vmovd(src.low, dest128);
+    vmovd(src.high, ScratchSimd128Reg);
+
+    // Unpack and interleave dest and scratch to dest:
+    //   dest     = 0x 00000000 00000000  HHHHHHHH LLLLLLLL
+    vpunpckldq(ScratchSimd128Reg, dest128, dest128);
+
+    // Unpack and interleave dest and a constant C1 to dest:
+    //   C1       = 0x 00000000 00000000  45300000 43300000
+    //   dest     = 0x 45300000 HHHHHHHH  43300000 LLLLLLLL
+    // here, each 64-bit part of dest represents following double:
+    //   HI(dest) = 0x 1.00000HHHHHHHH * 2**84 == 2**84 + 0x HHHHHHHH 00000000
+    //   LO(dest) = 0x 1.00000LLLLLLLL * 2**52 == 2**52 + 0x 00000000 LLLLLLLL
+    movePtr(ImmWord((uintptr_t)TO_DOUBLE), temp);
+    vpunpckldq(Operand(temp, 0), dest128, dest128);
+
+    // Subtract a constant C2 from dest, for each 64-bit part:
+    //   C2       = 0x 45300000 00000000  43300000 00000000
+    // here, each 64-bit part of C2 represents following double:
+    //   HI(C2)   = 0x 1.0000000000000 * 2**84 == 2**84
+    //   LO(C2)   = 0x 1.0000000000000 * 2**52 == 2**52
+    // after the operation each 64-bit part of dest represents following:
+    //   HI(dest) = double(0x HHHHHHHH 00000000)
+    //   LO(dest) = double(0x 00000000 LLLLLLLL)
+    vsubpd(Operand(temp, sizeof(uint64_t) * 2), dest128, dest128);
+
+    // Add HI(dest) and LO(dest) in double and store it into LO(dest),
+    //   LO(dest) = double(0x HHHHHHHH 00000000) + double(0x 00000000 LLLLLLLL)
+    //            = double(0x HHHHHHHH LLLLLLLL)
+    //            = double(src)
+    vhaddpd(dest128, dest128);
+}
+
+void
+MacroAssembler::convertInt64ToDouble(Register64 input, FloatRegister output)
 {
     // Zero the output register to break dependencies, see convertInt32ToDouble.
     zeroDouble(output);
 
-    asMasm().Push(input.high);
-    asMasm().Push(input.low);
+    Push(input.high);
+    Push(input.low);
     fild(Operand(esp, 0));
 
     fstp(Operand(esp, 0));
     vmovsd(Address(esp, 0), output);
-    asMasm().freeStack(2 * sizeof(intptr_t));
+    freeStack(2 * sizeof(intptr_t));
 }
 
 void
-MacroAssemblerX86::convertInt64ToFloat32(Register64 input, FloatRegister output)
-{
-    // Zero the output register to break dependencies, see convertInt32ToDouble.
-    zeroDouble(output);
-
-    asMasm().Push(input.high);
-    asMasm().Push(input.low);
-    fild(Operand(esp, 0));
-
-    fstp32(Operand(esp, 0));
-    vmovss(Address(esp, 0), output);
-    asMasm().freeStack(2 * sizeof(intptr_t));
-}
-
-void
-MacroAssemblerX86::convertUInt64ToFloat32(Register64 input, FloatRegister output, Register temp)
+MacroAssembler::convertUInt64ToFloat32(Register64 input, FloatRegister output, Register temp)
 {
     // Zero the dest register to break dependencies, see convertInt32ToDouble.
     zeroDouble(output);
 
     // Set the FPU precision to 80 bits.
-    asMasm().reserveStack(2 * sizeof(intptr_t));
+    reserveStack(2 * sizeof(intptr_t));
     fnstcw(Operand(esp, 0));
     load32(Operand(esp, 0), temp);
     orl(Imm32(0x300), temp);
     store32(temp, Operand(esp, sizeof(intptr_t)));
     fldcw(Operand(esp, sizeof(intptr_t)));
 
-    asMasm().Push(input.high);
-    asMasm().Push(input.low);
+    Push(input.high);
+    Push(input.low);
     fild(Operand(esp, 0));
 
     Label notNegative;
-    asMasm().branch32(Assembler::NotSigned, input.high, Imm32(0), &notNegative);
+    branch32(Assembler::NotSigned, input.high, Imm32(0), &notNegative);
     double add_constant = 18446744073709551616.0; // 2^64
     uint64_t add_constant_u64 = mozilla::BitwiseCast<uint64_t>(add_constant);
     store64(Imm64(add_constant_u64), Address(esp, 0));
 
     fld(Operand(esp, 0));
     faddp();
     bind(&notNegative);
 
     fstp32(Operand(esp, 0));
     vmovss(Address(esp, 0), output);
-    asMasm().freeStack(2 * sizeof(intptr_t));
+    freeStack(2 * sizeof(intptr_t));
 
     // Restore FPU precision to the initial value.
     fldcw(Operand(esp, 0));
-    asMasm().freeStack(2 * sizeof(intptr_t));
-}
-
-void
-MacroAssemblerX86::wasmTruncateDoubleToInt64(FloatRegister input, Register64 output, Label* oolEntry,
-                                             Label* oolRejoin, FloatRegister tempReg)
-{
-    Label fail, convert;
-    Register temp = output.high;
-
-    // Make sure input fits in (u)int64.
-    asMasm().reserveStack(2 * sizeof(int32_t));
-    asMasm().storeDouble(input, Operand(esp, 0));
-    asMasm().branchDoubleNotInInt64Range(Address(esp, 0), temp, &fail);
-    jump(&convert);
-
-    // Handle failure in ool.
-    bind(&fail);
-    asMasm().freeStack(2 * sizeof(int32_t));
-    jump(oolEntry);
-    bind(oolRejoin);
-    asMasm().reserveStack(2 * sizeof(int32_t));
-    asMasm().storeDouble(input, Operand(esp, 0));
-
-    // Convert the double/float to int64.
-    bind(&convert);
-    asMasm().truncateDoubleToInt64(Address(esp, 0), Address(esp, 0), temp);
-
-    // Load value into int64 register.
-    load64(Address(esp, 0), output);
-    asMasm().freeStack(2 * sizeof(int32_t));
+    freeStack(2 * sizeof(intptr_t));
 }
 
 void
-MacroAssemblerX86::wasmTruncateFloat32ToInt64(FloatRegister input, Register64 output, Label* oolEntry,
-                                              Label* oolRejoin, FloatRegister tempReg)
+MacroAssembler::convertInt64ToFloat32(Register64 input, FloatRegister output)
 {
-    Label fail, convert;
-    Register temp = output.high;
-
-    // Make sure input fits in (u)int64.
-    asMasm().reserveStack(2 * sizeof(int32_t));
-    asMasm().storeFloat32(input, Operand(esp, 0));
-    asMasm().branchFloat32NotInInt64Range(Address(esp, 0), temp, &fail);
-    jump(&convert);
+    // Zero the output register to break dependencies, see convertInt32ToDouble.
+    zeroDouble(output);
 
-    // Handle failure in ool.
-    bind(&fail);
-    asMasm().freeStack(2 * sizeof(int32_t));
-    jump(oolEntry);
-    bind(oolRejoin);
-    asMasm().reserveStack(2 * sizeof(int32_t));
-    asMasm().storeFloat32(input, Operand(esp, 0));
+    Push(input.high);
+    Push(input.low);
+    fild(Operand(esp, 0));
 
-    // Convert the double/float to int64.
-    bind(&convert);
-    asMasm().truncateFloat32ToInt64(Address(esp, 0), Address(esp, 0), temp);
-
-    // Load value into int64 register.
-    load64(Address(esp, 0), output);
-    asMasm().freeStack(2 * sizeof(int32_t));
+    fstp32(Operand(esp, 0));
+    vmovss(Address(esp, 0), output);
+    freeStack(2 * sizeof(intptr_t));
 }
 
-void
-MacroAssemblerX86::wasmTruncateDoubleToUInt64(FloatRegister input, Register64 output, Label* oolEntry,
-                                              Label* oolRejoin, FloatRegister tempReg)
-{
-    Label fail, convert;
-    Register temp = output.high;
-
-    // Make sure input fits in (u)int64.
-    asMasm().reserveStack(2 * sizeof(int32_t));
-    asMasm().storeDouble(input, Operand(esp, 0));
-    asMasm().branchDoubleNotInUInt64Range(Address(esp, 0), temp, &fail);
-    jump(&convert);
-
-    // Handle failure in ool.
-    bind(&fail);
-    asMasm().freeStack(2 * sizeof(int32_t));
-    jump(oolEntry);
-    bind(oolRejoin);
-    asMasm().reserveStack(2 * sizeof(int32_t));
-    asMasm().storeDouble(input, Operand(esp, 0));
-
-    // Convert the double/float to int64.
-    bind(&convert);
-    asMasm().truncateDoubleToUInt64(Address(esp, 0), Address(esp, 0), temp, tempReg);
-
-    // Load value into int64 register.
-    load64(Address(esp, 0), output);
-    asMasm().freeStack(2 * sizeof(int32_t));
-}
+//}}} check_macroassembler_style
 
-void
-MacroAssemblerX86::wasmTruncateFloat32ToUInt64(FloatRegister input, Register64 output, Label* oolEntry,
-                                               Label* oolRejoin, FloatRegister tempReg)
-{
-    Label fail, convert;
-    Register temp = output.high;
-
-    // Make sure input fits in (u)int64.
-    asMasm().reserveStack(2 * sizeof(int32_t));
-    asMasm().storeFloat32(input, Operand(esp, 0));
-    asMasm().branchFloat32NotInUInt64Range(Address(esp, 0), temp, &fail);
-    jump(&convert);
-
-    // Handle failure in ool.
-    bind(&fail);
-    asMasm().freeStack(2 * sizeof(int32_t));
-    jump(oolEntry);
-    bind(oolRejoin);
-    asMasm().reserveStack(2 * sizeof(int32_t));
-    asMasm().storeFloat32(input, Operand(esp, 0));
-
-    // Convert the double/float to int64.
-    bind(&convert);
-    asMasm().truncateFloat32ToUInt64(Address(esp, 0), Address(esp, 0), temp, tempReg);
-
-    // Load value into int64 register.
-    load64(Address(esp, 0), output);
-    asMasm().freeStack(2 * sizeof(int32_t));
-}
-
--- a/js/src/jit/x86/MacroAssembler-x86.h
+++ b/js/src/jit/x86/MacroAssembler-x86.h
@@ -851,31 +851,16 @@ class MacroAssemblerX86 : public MacroAs
     }
 
     // Note: this function clobbers the source register.
     inline void convertUInt32ToDouble(Register src, FloatRegister dest);
 
     // Note: this function clobbers the source register.
     inline void convertUInt32ToFloat32(Register src, FloatRegister dest);
 
-    void convertUInt64ToFloat32(Register64 src, FloatRegister dest, Register temp);
-    void convertInt64ToFloat32(Register64 src, FloatRegister dest);
-    static bool convertUInt64ToDoubleNeedsTemp();
-    void convertUInt64ToDouble(Register64 src, FloatRegister dest, Register temp);
-    void convertInt64ToDouble(Register64 src, FloatRegister dest);
-
-    void wasmTruncateDoubleToInt64(FloatRegister input, Register64 output, Label* oolEntry,
-                                   Label* oolRejoin, FloatRegister tempDouble);
-    void wasmTruncateDoubleToUInt64(FloatRegister input, Register64 output, Label* oolEntry,
-                                    Label* oolRejoin, FloatRegister tempDouble);
-    void wasmTruncateFloat32ToInt64(FloatRegister input, Register64 output, Label* oolEntry,
-                                    Label* oolRejoin, FloatRegister tempDouble);
-    void wasmTruncateFloat32ToUInt64(FloatRegister input, Register64 output, Label* oolEntry,
-                                     Label* oolRejoin, FloatRegister tempDouble);
-
     void incrementInt32Value(const Address& addr) {
         addl(Imm32(1), payloadOf(addr));
     }
 
     inline void ensureDouble(const ValueOperand& source, FloatRegister dest, Label* failure);
 
     void loadWasmGlobalPtr(uint32_t globalDataOffset, Register dest) {
         loadPtr(Address(WasmTlsReg, offsetof(wasm::TlsData, globalArea) + globalDataOffset), dest);
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -382,20 +382,22 @@ MSG_DEF(JSMSG_WASM_BAD_BUF_ARG,        0
 MSG_DEF(JSMSG_WASM_BAD_MOD_ARG,        0, JSEXN_TYPEERR,     "first argument must be a WebAssembly.Module")
 MSG_DEF(JSMSG_WASM_BAD_BUF_MOD_ARG,    0, JSEXN_TYPEERR,     "first argument must be a WebAssembly.Module, ArrayBuffer or typed array object")
 MSG_DEF(JSMSG_WASM_BAD_DESC_ARG,       1, JSEXN_TYPEERR,     "first argument must be a {0} descriptor")
 MSG_DEF(JSMSG_WASM_BAD_ELEMENT,        0, JSEXN_TYPEERR,     "\"element\" property of table descriptor must be \"anyfunc\"")
 MSG_DEF(JSMSG_WASM_BAD_IMPORT_ARG,     0, JSEXN_TYPEERR,     "second argument must be an object")
 MSG_DEF(JSMSG_WASM_BAD_IMPORT_FIELD,   1, JSEXN_TYPEERR,     "import object field '{0}' is not an Object")
 MSG_DEF(JSMSG_WASM_BAD_TABLE_VALUE,    0, JSEXN_TYPEERR,     "can only assign WebAssembly exported functions to Table")
 MSG_DEF(JSMSG_WASM_BAD_I64_TYPE,       0, JSEXN_TYPEERR,     "cannot pass i64 to or from JS")
+MSG_DEF(JSMSG_WASM_BAD_GLOBAL_TYPE,    0, JSEXN_TYPEERR,     "bad type for a WebAssembly.Global")
 MSG_DEF(JSMSG_WASM_NO_TRANSFER,        0, JSEXN_TYPEERR,     "cannot transfer WebAssembly/asm.js ArrayBuffer")
 MSG_DEF(JSMSG_WASM_STREAM_ERROR,       0, JSEXN_TYPEERR,     "stream error during WebAssembly compilation")
 MSG_DEF(JSMSG_WASM_TEXT_FAIL,          1, JSEXN_SYNTAXERR,   "wasm text error: {0}")
 MSG_DEF(JSMSG_WASM_MISSING_MAXIMUM,    0, JSEXN_TYPEERR,     "'shared' is true but maximum is not specified")
+MSG_DEF(JSMSG_WASM_GLOBAL_IMMUTABLE,   0, JSEXN_TYPEERR,     "can't set value of immutable global")
 
 // Proxy
 MSG_DEF(JSMSG_BAD_TRAP_RETURN_VALUE,   2, JSEXN_TYPEERR,"trap {1} for {0} returned a primitive value")
 MSG_DEF(JSMSG_BAD_GETPROTOTYPEOF_TRAP_RETURN,0,JSEXN_TYPEERR,"proxy getPrototypeOf handler returned a non-object, non-null value")
 MSG_DEF(JSMSG_INCONSISTENT_GETPROTOTYPEOF_TRAP,0,JSEXN_TYPEERR,"proxy getPrototypeOf handler didn't return the target object's prototype")
 MSG_DEF(JSMSG_PROXY_SETPROTOTYPEOF_RETURNED_FALSE, 0, JSEXN_TYPEERR, "proxy setPrototypeOf handler returned false")
 MSG_DEF(JSMSG_PROXY_ISEXTENSIBLE_RETURNED_FALSE,0,JSEXN_TYPEERR,"proxy isExtensible handler must return the same extensibility as target")
 MSG_DEF(JSMSG_INCONSISTENT_SETPROTOTYPEOF_TRAP,0,JSEXN_TYPEERR,"proxy setPrototypeOf handler returned true, even though the target's prototype is immutable because the target is non-extensible")
@@ -433,16 +435,17 @@ MSG_DEF(JSMSG_SC_BAD_CLONE_VERSION,    0
 MSG_DEF(JSMSG_SC_BAD_SERIALIZED_DATA,  1, JSEXN_INTERNALERR, "bad serialized structured data ({0})")
 MSG_DEF(JSMSG_SC_DUP_TRANSFERABLE,     0, JSEXN_TYPEERR, "duplicate transferable for structured clone")
 MSG_DEF(JSMSG_SC_NOT_TRANSFERABLE,     0, JSEXN_TYPEERR, "invalid transferable array for structured clone")
 MSG_DEF(JSMSG_SC_UNSUPPORTED_TYPE,     0, JSEXN_TYPEERR, "unsupported type for structured data")
 MSG_DEF(JSMSG_SC_NOT_CLONABLE,         1, JSEXN_TYPEERR, "{0} cannot be cloned in this context")
 MSG_DEF(JSMSG_SC_SAB_DISABLED,         0, JSEXN_TYPEERR, "SharedArrayBuffer not cloned - shared memory disabled in receiver")
 MSG_DEF(JSMSG_SC_SAB_REFCNT_OFLO,      0, JSEXN_TYPEERR, "SharedArrayBuffer has too many references")
 MSG_DEF(JSMSG_SC_SHMEM_TRANSFERABLE,   0, JSEXN_TYPEERR, "Shared memory objects must not be in the transfer list")
+MSG_DEF(JSMSG_SC_SHMEM_POLICY,         0, JSEXN_TYPEERR, "Policy object must forbid cloning shared memory objects cross-process")
 
 // Debugger
 MSG_DEF(JSMSG_ASSIGN_FUNCTION_OR_NULL, 1, JSEXN_TYPEERR, "value assigned to {0} must be a function or null")
 MSG_DEF(JSMSG_DEBUG_BAD_LINE,          0, JSEXN_TYPEERR, "invalid line number")
 MSG_DEF(JSMSG_DEBUG_BAD_OFFSET,        0, JSEXN_TYPEERR, "invalid script offset")
 MSG_DEF(JSMSG_DEBUG_BAD_REFERENT,      2, JSEXN_TYPEERR, "{0} does not refer to {1}")
 MSG_DEF(JSMSG_DEBUG_BAD_RESUMPTION,    0, JSEXN_TYPEERR, "debugger resumption value must be undefined, {throw: val}, {return: val}, or null")
 MSG_DEF(JSMSG_DEBUG_BAD_YIELD,         0, JSEXN_TYPEERR, "generator yielded invalid value")
--- a/js/src/jsapi-tests/testErrorInterceptor.cpp
+++ b/js/src/jsapi-tests/testErrorInterceptor.cpp
@@ -99,17 +99,18 @@ BEGIN_TEST(testErrorInterceptor)
 
         // Check the final error.
         JS::RootedValue exn(cx);
         CHECK(JS_GetPendingException(cx, &exn));
         JS_ClearPendingException(cx);
 
         js::StringBuffer buffer(cx);
         CHECK(ValueToStringBuffer(cx, exn, buffer));
-        CHECK(equalStrings(cx, buffer.finishString(), gLatestMessage));
+        JS::Rooted<JSFlatString*> flat(cx, buffer.finishString());
+        CHECK(equalStrings(cx, flat, gLatestMessage));
 
         // Cleanup.
         gLatestMessage = nullptr;
     }
 
     // Test again without callback.
     JS_SetErrorInterceptorCallback(cx->runtime(), nullptr);
     for (size_t i = 0; i < mozilla::ArrayLength(SAMPLES); ++i) {
@@ -122,17 +123,18 @@ BEGIN_TEST(testErrorInterceptor)
 
         // Check the final error.
         JS::RootedValue exn(cx);
         CHECK(JS_GetPendingException(cx, &exn));
         JS_ClearPendingException(cx);
 
         js::StringBuffer buffer(cx);
         CHECK(ValueToStringBuffer(cx, exn, buffer));
-        CHECK(js::StringEqualsAscii(buffer.finishString(), TO_STRING[i]));
+        JS::Rooted<JSFlatString*> flat(cx, buffer.finishString());
+        CHECK(js::StringEqualsAscii(flat, TO_STRING[i]));
 
         // Cleanup.
         gLatestMessage = nullptr;
     }
 
     // Cleanup
     JS_SetErrorInterceptorCallback(cx->runtime(), original);
     gLatestMessage = nullptr;
--- a/js/src/jsprototypes.h
+++ b/js/src/jsprototypes.h
@@ -122,12 +122,13 @@ IF_SAB(real,imaginary)(Atomics, InitAtom
     imaginary(WritableStreamDefaultController,  dummy,  dummy) \
     real(ByteLengthQueuingStrategy,             InitViaClassSpec, &js::ByteLengthQueuingStrategy::class_) \
     real(CountQueuingStrategy,  InitViaClassSpec,       &js::CountQueuingStrategy::class_) \
     real(WebAssembly,           InitWebAssemblyClass,   CLASP(WebAssembly)) \
     imaginary(WasmModule,       dummy,                  dummy) \
     imaginary(WasmInstance,     dummy,                  dummy) \
     imaginary(WasmMemory,       dummy,                  dummy) \
     imaginary(WasmTable,        dummy,                  dummy) \
+    imaginary(WasmGlobal,       dummy,                  dummy) \
 
 #define JS_FOR_EACH_PROTOTYPE(macro) JS_FOR_PROTOTYPES(macro,macro)
 
 #endif /* jsprototypes_h */
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -267,16 +267,17 @@ UNIFIED_SOURCES += [
     'jit/Recover.cpp',
     'jit/RegisterAllocator.cpp',
     'jit/RematerializedFrame.cpp',
     'jit/Safepoints.cpp',
     'jit/ScalarReplacement.cpp',
     'jit/shared/Assembler-shared.cpp',
     'jit/shared/BaselineCompiler-shared.cpp',
     'jit/shared/CodeGenerator-shared.cpp',
+    'jit/shared/Disassembler-shared.cpp',
     'jit/shared/Lowering-shared.cpp',
     'jit/SharedIC.cpp',
     'jit/Sink.cpp',
     'jit/Snapshots.cpp',
     'jit/StupidAllocator.cpp',
     'jit/TypedObjectPrediction.cpp',
     'jit/TypePolicy.cpp',
     'jit/ValueNumbering.cpp',
@@ -647,16 +648,21 @@ DIRS += [
 
 FINAL_LIBRARY = 'js'
 
 if CONFIG['NIGHTLY_BUILD']:
     DEFINES['ENABLE_BINARYDATA'] = True
     DEFINES['ENABLE_SIMD'] = True
     DEFINES['ENABLE_WASM_SIGNEXTEND_OPS'] = True
     DEFINES['ENABLE_WASM_THREAD_OPS'] = True
+    # An experiment we want to run on Nightly: Can we change the JS
+    # representation of an exported global from the global's value
+    # to an instance of WebAssembly.Global without breaking existing
+    # wasm content?
+    DEFINES['ENABLE_WASM_GLOBAL'] = True
 
 if CONFIG['JS_BUILD_BINAST']:
     # Using SOURCES as UNIFIED_SOURCES causes mysterious bugs on 32-bit platforms.
     # These parts of BinAST are designed only to test evolutions of the
     # specification.
     SOURCES += ['frontend/BinTokenReaderTester.cpp']
     # These parts of BinAST should eventually move to release.
     SOURCES += [
--- a/js/src/vm/StructuredClone.cpp
+++ b/js/src/vm/StructuredClone.cpp
@@ -1242,20 +1242,24 @@ JSStructuredCloneWriter::writeSharedArra
     MOZ_ASSERT(CheckedUnwrap(obj) && CheckedUnwrap(obj)->is<SharedArrayBufferObject>());
 
     if (!cloneDataPolicy.isSharedArrayBufferAllowed()) {
         JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, JSMSG_SC_NOT_CLONABLE,
                                   "SharedArrayBuffer");
         return false;
     }
 
-    // We must not transfer buffer pointers cross-process.  The cloneDataPolicy
-    // should guard against this; check that it does.
-
-    MOZ_RELEASE_ASSERT(scope <= JS::StructuredCloneScope::SameProcessDifferentThread);
+    // We must not transmit SAB pointers (including for WebAssembly.Memory)
+    // cross-process.  The cloneDataPolicy should have guarded against this;
+    // since it did not then throw, with a very explicit message.
+
+    if (scope > JS::StructuredCloneScope::SameProcessDifferentThread) {
+        JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, JSMSG_SC_SHMEM_POLICY);
+        return false;
+    }
 
     Rooted<SharedArrayBufferObject*> sharedArrayBuffer(context(), &CheckedUnwrap(obj)->as<SharedArrayBufferObject>());
     SharedArrayRawBuffer* rawbuf = sharedArrayBuffer->rawBufferObject();
 
     if (!refsHeld.acquire(context(), rawbuf))
         return false;
 
     // We must serialize the length so that the buffer object arrives in the
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -3738,46 +3738,39 @@ class BaseCompiler final : public BaseCo
         MOZ_CRASH("BaseCompiler platform hook: truncateF64ToI64");
 # endif
         return true;
     }
 #endif // RABALDR_FLOAT_TO_I64_CALLOUT
 
 #ifndef RABALDR_I64_TO_FLOAT_CALLOUT
     RegI32 needConvertI64ToFloatTemp(ValType to, bool isUnsigned) {
-# if defined(JS_CODEGEN_X86)
-        bool needs = isUnsigned &&
-                     ((to == ValType::F64 && AssemblerX86Shared::HasSSE3()) ||
-                      to == ValType::F32);
-# else
-        bool needs = isUnsigned;
+        bool needs = false;
+        if (to == ValType::F64) {
+            needs = isUnsigned && masm.convertUInt64ToDoubleNeedsTemp();
+        } else {
+# if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+            needs = true;
 # endif
+        }
         return needs ? needI32() : RegI32::Invalid();
     }
 
     void convertI64ToF32(RegI64 src, bool isUnsigned, RegF32 dest, RegI32 temp) {
-# if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)
         if (isUnsigned)
             masm.convertUInt64ToFloat32(src, dest, temp);
         else
             masm.convertInt64ToFloat32(src, dest);
-# else
-        MOZ_CRASH("BaseCompiler platform hook: convertI64ToF32");
-# endif
     }
 
     void convertI64ToF64(RegI64 src, bool isUnsigned, RegF64 dest, RegI32 temp) {
-# if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)
         if (isUnsigned)
             masm.convertUInt64ToDouble(src, dest, temp);
         else
             masm.convertInt64ToDouble(src, dest);
-# else
-        MOZ_CRASH("BaseCompiler platform hook: convertI64ToF64");
-# endif
     }
 #endif // RABALDR_I64_TO_FLOAT_CALLOUT
 
     void cmp64Set(Assembler::Condition cond, RegI64 lhs, RegI64 rhs, RegI32 dest) {
 #ifdef JS_PUNBOX64
         masm.cmpPtrSet(cond, lhs.reg, rhs.reg, dest);
 #else
         // TODO / OPTIMIZE (Bug 1316822): This is pretty branchy, we should be
--- a/js/src/wasm/WasmJS.cpp
+++ b/js/src/wasm/WasmJS.cpp
@@ -93,16 +93,71 @@ wasm::HasCompilerSupport(JSContext* cx)
 }
 
 bool
 wasm::HasSupport(JSContext* cx)
 {
     return cx->options().wasm() && HasCompilerSupport(cx);
 }
 
+bool
+wasm::ToWebAssemblyValue(JSContext* cx, ValType targetType, HandleValue v, Val* val)
+{
+    switch (targetType) {
+      case ValType::I32: {
+        int32_t i32;
+        if (!ToInt32(cx, v, &i32))
+            return false;
+        *val = Val(uint32_t(i32));
+        return true;
+      }
+      case ValType::F32: {
+        double d;
+        if (!ToNumber(cx, v, &d))
+            return false;
+        *val = Val(float(d));
+        return true;
+      }
+      case ValType::F64: {
+        double d;
+        if (!ToNumber(cx, v, &d))
+            return false;
+        *val = Val(d);
+        return true;
+      }
+      default: {
+        MOZ_CRASH("unexpected import value type, caller must guard");
+      }
+    }
+}
+
+void
+wasm::ToJSValue(const Val& val, MutableHandleValue value)
+{
+    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: {
+        MOZ_CRASH("unexpected type when translating to a JS value");
+      }
+    }
+}
+
 // ============================================================================
 // Imports
 
 static bool
 ThrowBadImportArg(JSContext* cx)
 {
     JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_IMPORT_ARG);
     return false;
@@ -182,52 +237,32 @@ GetImports(JSContext* cx,
             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());
-            switch (global.type()) {
-              case ValType::I32: {
-                if (!v.isNumber())
-                    return ThrowBadImportType(cx, import.field.get(), "Number");
-                int32_t i32;
-                if (!ToInt32(cx, v, &i32))
-                    return false;
-                val = Val(uint32_t(i32));
-                break;
-              }
-              case ValType::I64: {
+
+#ifdef ENABLE_WASM_GLOBAL
+            if (v.isObject() && v.toObject().is<WasmGlobalObject>())
+                v.set(v.toObject().as<WasmGlobalObject>().value());
+#endif
+
+            if (global.type() == ValType::I64) {
                 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_I64_LINK);
                 return false;
-              }
-              case ValType::F32: {
-                if (!v.isNumber())
-                    return ThrowBadImportType(cx, import.field.get(), "Number");
-                double d;
-                if (!ToNumber(cx, v, &d))
-                    return false;
-                val = Val(float(d));
-                break;
-              }
-              case ValType::F64: {
-                if (!v.isNumber())
-                    return ThrowBadImportType(cx, import.field.get(), "Number");
-                double d;
-                if (!ToNumber(cx, v, &d))
-                    return false;
-                val = Val(d);
-                break;
-              }
-              default: {
-                MOZ_CRASH("unexpected import value type");
-              }
             }
+            if (!v.isNumber())
+                return ThrowBadImportType(cx, import.field.get(), "Number");
+
+            if (!ToWebAssemblyValue(cx, global.type(), v, &val))
+                return false;
+
             if (!globalImports->append(val))
                 return false;
         }
     }
 
     MOZ_ASSERT(globalIndex == globals.length() || !globals[globalIndex].isImport());
 
     return true;
@@ -1880,16 +1915,214 @@ const JSFunctionSpec WasmTableObject::st
 
 Table&
 WasmTableObject::table() const
 {
     return *(Table*)getReservedSlot(TABLE_SLOT).toPrivate();
 }
 
 // ============================================================================
+// WebAssembly.global class and methods
+
+#ifdef ENABLE_WASM_GLOBAL
+
+const ClassOps WasmGlobalObject::classOps_ =
+{
+    nullptr, /* addProperty */
+    nullptr, /* delProperty */
+    nullptr, /* enumerate */
+    nullptr, /* newEnumerate */
+    nullptr, /* resolve */
+    nullptr, /* mayResolve */
+    nullptr, /* finalize */
+    nullptr, /* call */
+    nullptr, /* hasInstance */
+    nullptr, /* construct */
+    nullptr  /* trace */
+};
+
+const Class WasmGlobalObject::class_ =
+{
+    "WebAssembly.Global",
+    JSCLASS_HAS_RESERVED_SLOTS(WasmGlobalObject::RESERVED_SLOTS),
+    &WasmGlobalObject::classOps_
+};
+
+/* static */ WasmGlobalObject*
+WasmGlobalObject::create(JSContext* cx, wasm::ValType type, bool isMutable, HandleValue val)
+{
+    RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmGlobal).toObject());
+
+    AutoSetNewObjectMetadata metadata(cx);
+    Rooted<WasmGlobalObject*> obj(cx, NewObjectWithGivenProto<WasmGlobalObject>(cx, proto));
+    if (!obj)
+        return nullptr;
+
+    obj->initReservedSlot(TYPE_SLOT, Int32Value(int32_t(type)));
+    obj->initReservedSlot(MUTABLE_SLOT, JS::BooleanValue(isMutable));
+    obj->initReservedSlot(VALUE_SLOT, val);
+
+    return obj;
+}
+
+/* static */ bool
+WasmGlobalObject::construct(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    if (!ThrowIfNotConstructing(cx, args, "Global"))
+        return false;
+
+    if (!args.requireAtLeast(cx, "WebAssembly.Global", 1))
+        return false;
+
+    if (!args.get(0).isObject()) {
+        JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_DESC_ARG, "global");
+        return false;
+    }
+
+    RootedObject obj(cx, &args[0].toObject());
+
+    RootedValue typeVal(cx);
+    if (!JS_GetProperty(cx, obj, "type", &typeVal))
+        return false;
+
+    RootedString typeStr(cx, ToString(cx, typeVal));
+    if (!typeStr)
+        return false;
+
+    RootedLinearString typeLinearStr(cx, typeStr->ensureLinear(cx));
+    if (!typeLinearStr)
+        return false;
+
+    ValType globalType;
+    if (StringEqualsAscii(typeLinearStr, "i32")) {
+        globalType = ValType::I32;
+    } else if (StringEqualsAscii(typeLinearStr, "f32")) {
+        globalType = ValType::F32;
+    } else if (StringEqualsAscii(typeLinearStr, "f64")) {
+        globalType = ValType::F64;
+    } else {
+        JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_GLOBAL_TYPE);
+        return false;
+    }
+
+    RootedValue mutableVal(cx);
+    if (!JS_GetProperty(cx, obj, "mutable", &mutableVal))
+        return false;
+
+    bool isMutable = ToBoolean(mutableVal);
+
+    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));
+    if (!global)
+        return false;
+
+    args.rval().setObject(*global);
+    return true;
+}
+
+static bool
+IsGlobal(HandleValue v)
+{
+    return v.isObject() && v.toObject().is<WasmGlobalObject>();
+}
+
+/* static */ bool
+WasmGlobalObject::valueGetterImpl(JSContext* cx, const CallArgs& args)
+{
+    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);
+        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()) {
+        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;
+}
+
+/* static */ bool
+WasmGlobalObject::valueSetter(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    return CallNonGenericMethod<IsGlobal, valueSetterImpl>(cx, args);
+}
+
+const JSPropertySpec WasmGlobalObject::properties[] =
+{
+    JS_PSGS("value", WasmGlobalObject::valueGetter, WasmGlobalObject::valueSetter, 0),
+    JS_PS_END
+};
+
+const JSFunctionSpec WasmGlobalObject::methods[] =
+{
+    JS_SYM_FN(toPrimitive, WasmGlobalObject::valueGetter, 1, JSPROP_READONLY),
+    JS_FS_END
+};
+
+const JSFunctionSpec WasmGlobalObject::static_methods[] =
+{ JS_FS_END };
+
+ValType
+WasmGlobalObject::type() const
+{
+    return static_cast<ValType>(getReservedSlot(TYPE_SLOT).toInt32());
+}
+
+bool
+WasmGlobalObject::isMutable() const
+{
+    return getReservedSlot(MUTABLE_SLOT).toBoolean();
+}
+
+Value
+WasmGlobalObject::value() const
+{
+    return getReservedSlot(VALUE_SLOT);
+}
+
+#endif // ENABLE_WASM_GLOBAL
+
+// ============================================================================
 // WebAssembly class and static methods
 
 #if JS_HAS_TOSOURCE
 static bool
 WebAssembly_toSource(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     args.rval().setString(cx->names().WebAssembly);
@@ -2721,24 +2954,31 @@ js::InitWebAssemblyClass(JSContext* cx, 
     RootedObject wasm(cx, NewObjectWithGivenProto(cx, &WebAssemblyClass, proto, SingletonObject));
     if (!wasm)
         return nullptr;
 
     if (!JS_DefineFunctions(cx, wasm, WebAssembly_static_methods))
         return nullptr;
 
     RootedObject moduleProto(cx), instanceProto(cx), memoryProto(cx), tableProto(cx);
+#ifdef ENABLE_WASM_GLOBAL
+    RootedObject globalProto(cx);
+#endif
     if (!InitConstructor<WasmModuleObject>(cx, wasm, "Module", &moduleProto))
         return nullptr;
     if (!InitConstructor<WasmInstanceObject>(cx, wasm, "Instance", &instanceProto))
         return nullptr;
     if (!InitConstructor<WasmMemoryObject>(cx, wasm, "Memory", &memoryProto))
         return nullptr;
     if (!InitConstructor<WasmTableObject>(cx, wasm, "Table", &tableProto))
         return nullptr;
+#ifdef ENABLE_WASM_GLOBAL
+    if (!InitConstructor<WasmGlobalObject>(cx, wasm, "Global", &globalProto))
+        return nullptr;
+#endif
     if (!InitErrorClass(cx, wasm, "CompileError", JSEXN_WASMCOMPILEERROR))
         return nullptr;
     if (!InitErrorClass(cx, wasm, "LinkError", JSEXN_WASMLINKERROR))
         return nullptr;
     if (!InitErrorClass(cx, wasm, "RuntimeError", JSEXN_WASMRUNTIMEERROR))
         return nullptr;
 
     // Perform the final fallible write of the WebAssembly object to a global
@@ -2748,13 +2988,16 @@ js::InitWebAssemblyClass(JSContext* cx, 
 
     if (!JS_DefineProperty(cx, global, js_WebAssembly_str, wasm, JSPROP_RESOLVING))
         return nullptr;
 
     global->setPrototype(JSProto_WasmModule, ObjectValue(*moduleProto));
     global->setPrototype(JSProto_WasmInstance, ObjectValue(*instanceProto));
     global->setPrototype(JSProto_WasmMemory, ObjectValue(*memoryProto));
     global->setPrototype(JSProto_WasmTable, ObjectValue(*tableProto));
+#ifdef ENABLE_WASM_GLOBAL
+    global->setPrototype(JSProto_WasmGlobal, ObjectValue(*globalProto));
+#endif
     global->setConstructor(JSProto_WebAssembly, ObjectValue(*wasm));
 
     MOZ_ASSERT(global->isStandardClassResolved(JSProto_WebAssembly));
     return wasm;
 }
--- a/js/src/wasm/WasmJS.h
+++ b/js/src/wasm/WasmJS.h
@@ -38,16 +38,25 @@ namespace wasm {
 bool
 HasCompilerSupport(JSContext* cx);
 
 // Return whether WebAssembly is enabled on this platform.
 
 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);
+
 // 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);
 
 // These accessors can be used to probe JS values for being an exported wasm
@@ -270,11 +279,47 @@ 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;
 };
 
+#ifdef ENABLE_WASM_GLOBAL
+
+// 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
+
 } // namespace js
 
 #endif // wasm_js_h
--- a/js/src/wasm/WasmModule.cpp
+++ b/js/src/wasm/WasmModule.cpp
@@ -1047,40 +1047,31 @@ GetGlobalExport(JSContext* cx, const Glo
         break;
       }
       case GlobalKind::Constant: {
         val = global.constantValue();
         break;
       }
     }
 
-    switch (global.type()) {
-      case ValType::I32: {
-        jsval.set(Int32Value(val.i32()));
-        return true;
-      }
-      case ValType::I64: {
+    if (val.type() == ValType::I64) {
         JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_I64_LINK);
         return false;
-      }
-      case ValType::F32: {
-        float f = val.f32();
-        jsval.set(DoubleValue(JS::CanonicalizeNaN(double(f))));
-        return true;
-      }
-      case ValType::F64: {
-        double d = val.f64();
-        jsval.set(DoubleValue(JS::CanonicalizeNaN(d)));
-        return true;
-      }
-      default: {
-        break;
-      }
     }
-    MOZ_CRASH("unexpected type when creating global exports");
+
+    ToJSValue(val, jsval);
+
+#ifdef ENABLE_WASM_GLOBAL
+    Rooted<WasmGlobalObject*> go(cx, WasmGlobalObject::create(cx, ValType::I32, false, jsval));
+    if (!go)
+        return false;
+    jsval.setObject(*go);
+#endif
+
+    return true;
 }
 
 static bool
 CreateExportObject(JSContext* cx,
                    HandleWasmInstanceObject instanceObj,
                    Handle<FunctionVector> funcImports,
                    HandleWasmTableObject tableObj,
                    HandleWasmMemoryObject memoryObj,
--- a/js/src/wasm/WasmStubs.cpp
+++ b/js/src/wasm/WasmStubs.cpp
@@ -1360,40 +1360,46 @@ wasm::GenerateStubs(const ModuleEnvironm
     MacroAssembler masm(MacroAssembler::WasmToken(), alloc);
 
     // Swap in already-allocated empty vectors to avoid malloc/free.
     if (!code->swap(masm))
         return false;
 
     Label throwLabel;
 
+    JitSpew(JitSpew_Codegen, "# Emitting wasm import stubs");
+
     for (uint32_t funcIndex = 0; funcIndex < imports.length(); funcIndex++) {
         const FuncImport& fi = imports[funcIndex];
 
         CallableOffsets interpOffsets;
         if (!GenerateImportInterpExit(masm, fi, funcIndex, &throwLabel, &interpOffsets))
             return false;
         if (!code->codeRanges.emplaceBack(CodeRange::ImportInterpExit, funcIndex, interpOffsets))
             return false;
 
         JitExitOffsets jitOffsets;
         if (!GenerateImportJitExit(masm, fi, &throwLabel, &jitOffsets))
             return false;
         if (!code->codeRanges.emplaceBack(funcIndex, jitOffsets))
             return false;
     }
 
+    JitSpew(JitSpew_Codegen, "# Emitting wasm export stubs");
+
     for (const FuncExport& fe : exports) {
         Offsets offsets;
         if (!GenerateInterpEntry(masm, fe, &offsets))
             return false;
         if (!code->codeRanges.emplaceBack(CodeRange::InterpEntry, fe.funcIndex(), offsets))
             return false;
     }
 
+    JitSpew(JitSpew_Codegen, "# Emitting wasm trap stubs");
+
     for (Trap trap : MakeEnumeratedRange(Trap::Limit)) {
         switch (trap) {
           case Trap::Unreachable:
             break;
           // The TODO list of "old" traps to convert to new traps:
           case Trap::IntegerOverflow:
           case Trap::InvalidConversionToInteger:
           case Trap::IntegerDivideByZero:
@@ -1413,16 +1419,18 @@ wasm::GenerateStubs(const ModuleEnvironm
           }
           case Trap::Limit:
             MOZ_CRASH("impossible");
         }
     }
 
     Offsets offsets;
 
+    JitSpew(JitSpew_Codegen, "# Emitting wasm exit stubs");
+
     if (!GenerateOutOfBoundsExit(masm, &throwLabel, &offsets))
         return false;
     if (!code->codeRanges.emplaceBack(CodeRange::OutOfBoundsExit, offsets))
         return false;
 
     if (!GenerateUnalignedExit(masm, &throwLabel, &offsets))
         return false;
     if (!code->codeRanges.emplaceBack(CodeRange::UnalignedExit, offsets))
--- a/js/xpconnect/loader/XPCOMUtils.jsm
+++ b/js/xpconnect/loader/XPCOMUtils.jsm
@@ -316,16 +316,20 @@ this.XPCOMUtils = {
    * @param aProxy
    *        An object which acts on behalf of the module to be imported until
    *        the module has been imported.
    */
   defineLazyModuleGetter: function XPCU_defineLazyModuleGetter(
                                    aObject, aName, aResource, aSymbol,
                                    aPreLambda, aPostLambda, aProxy)
   {
+    if (arguments.length == 3) {
+      return ChromeUtils.defineModuleGetter(aObject, aName, aResource);
+    }
+
     let proxy = aProxy || {};
 
     if (typeof(aPreLambda) === "function") {
       aPreLambda.apply(proxy);
     }
 
     this.defineLazyGetter(aObject, aName, function XPCU_moduleLambda() {
       var temp = {};
@@ -353,17 +357,17 @@ this.XPCOMUtils = {
    *        An object with a property for each module property to be
    *        imported, where the property name is the name of the
    *        imported symbol and the value is the module URI.
    */
   defineLazyModuleGetters: function XPCU_defineLazyModuleGetters(
                                    aObject, aModules)
   {
     for (let [name, module] of Object.entries(aModules)) {
-      this.defineLazyModuleGetter(aObject, name, module);
+      ChromeUtils.defineModuleGetter(aObject, name, module);
     }
   },
 
   /**
    * Defines a getter on a specified object for preference value. The
    * preference is read the first time that the property is accessed,
    * and is thereafter kept up-to-date using a preference observer.
    *
--- a/js/xpconnect/loader/mozJSComponentLoader.cpp
+++ b/js/xpconnect/loader/mozJSComponentLoader.cpp
@@ -47,16 +47,17 @@
 #include "mozilla/AddonPathService.h"
 #include "mozilla/scache/StartupCache.h"
 #include "mozilla/scache/StartupCacheUtils.h"
 #include "mozilla/MacroForEach.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ScriptPreloader.h"
 #include "mozilla/dom/DOMPrefs.h"
 #include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/ResultExtensions.h"
 #include "mozilla/UniquePtrExtensions.h"
 #include "mozilla/Unused.h"
 
 using namespace mozilla;
 using namespace mozilla::scache;
 using namespace mozilla::loader;
 using namespace xpc;
 using namespace JS;
@@ -175,35 +176,16 @@ ReportOnCallerUTF8(JSContext* callerCont
     }
 
     JS_ReportErrorUTF8(callerContext, "%s", buf.get());
 
     va_end(ap);
     return NS_OK;
 }
 
-static nsresult
-MOZ_FORMAT_PRINTF(2, 3)
-ReportOnCallerUTF8(JSCLContextHelper& helper,
-                   const char* format, ...)
-{
-    va_list ap;
-    va_start(ap, format);
-
-    UniqueChars buf = JS_vsmprintf(format, ap);
-    if (!buf) {
-        va_end(ap);
-        return NS_ERROR_OUT_OF_MEMORY;
-    }
-
-    helper.reportErrorAfterPop(Move(buf));
-    va_end(ap);
-    return NS_OK;
-}
-
 mozJSComponentLoader::mozJSComponentLoader()
     : mModules(16),
       mImports(16),
       mInProgressImports(16),
       mLocations(16),
       mInitialized(false),
       mShareLoaderGlobal(false),
       mLoaderGlobal(dom::RootingCx())
@@ -282,16 +264,35 @@ class MOZ_STACK_CLASS ComponentLoaderInf
   private:
     const nsACString& mLocation;
     nsCOMPtr<nsIIOService> mIOService;
     nsCOMPtr<nsIURI> mURI;
     nsCOMPtr<nsIChannel> mScriptChannel;
     nsCOMPtr<nsIURI> mResolvedURI;
 };
 
+template <typename ...Args>
+static nsresult
+ReportOnCallerUTF8(JSCLContextHelper& helper,
+                   const char* format,
+                   ComponentLoaderInfo& info,
+                   Args... args)
+{
+    nsCString location;
+    MOZ_TRY(info.GetLocation(location));
+
+    UniqueChars buf = JS_smprintf(format, location.get(), args...);
+    if (!buf) {
+        return NS_ERROR_OUT_OF_MEMORY;
+    }
+
+    helper.reportErrorAfterPop(Move(buf));
+    return NS_ERROR_FAILURE;
+}
+
 #undef BEGIN_ENSURE
 #undef ENSURE_DEPS
 #undef ENSURE_DEP
 
 mozJSComponentLoader::~mozJSComponentLoader()
 {
     if (mInitialized) {
         NS_ERROR("'xpcom-shutdown-loaders' was not fired before cleaning up mozJSComponentLoader");
@@ -923,21 +924,21 @@ mozJSComponentLoader::UnloadModules()
 
     for (auto iter = mModules.Iter(); !iter.Done(); iter.Next()) {
         iter.Data()->Clear();
         iter.Remove();
     }
 }
 
 nsresult
-mozJSComponentLoader::Import(const nsACString& registryLocation,
-                             HandleValue targetValArg,
-                             JSContext* cx,
-                             uint8_t optionalArgc,
-                             MutableHandleValue retval)
+mozJSComponentLoader::ImportInto(const nsACString& registryLocation,
+                                 HandleValue targetValArg,
+                                 JSContext* cx,
+                                 uint8_t optionalArgc,
+                                 MutableHandleValue retval)
 {
     MOZ_ASSERT(nsContentUtils::IsCallerChrome());
 
     RootedValue targetVal(cx, targetValArg);
     RootedObject targetObject(cx, nullptr);
     if (optionalArgc) {
         // The caller passed in the optional second argument. Get it.
         if (targetVal.isObject()) {
@@ -1092,36 +1093,178 @@ ResolveModuleObjectPropertyById(JSContex
         }
     }
     return aModObj;
 }
 
 nsresult
 mozJSComponentLoader::ImportInto(const nsACString& aLocation,
                                  HandleObject targetObj,
-                                 JSContext* callercx,
+                                 JSContext* cx,
                                  MutableHandleObject vp)
 {
     vp.set(nullptr);
 
+    JS::RootedObject exports(cx);
+    MOZ_TRY(Import(cx, aLocation, vp, &exports, !targetObj));
+
+    if (targetObj) {
+        JS::Rooted<JS::IdVector> ids(cx, JS::IdVector(cx));
+        if (!JS_Enumerate(cx, exports, &ids)) {
+            return NS_ERROR_OUT_OF_MEMORY;
+        }
+
+        JS::RootedValue value(cx);
+        JS::RootedId id(cx);
+        for (jsid idVal : ids) {
+            id = idVal;
+            if (!JS_GetPropertyById(cx, exports, id, &value) ||
+                !JS_SetPropertyById(cx, targetObj, id, value)) {
+                return NS_ERROR_FAILURE;
+            }
+        }
+    }
+
+    return NS_OK;
+}
+
+nsresult
+mozJSComponentLoader::ExtractExports(JSContext* aCx, ComponentLoaderInfo& aInfo,
+                                     ModuleEntry* aMod,
+                                     JS::MutableHandleObject aExports)
+{
+    // cxhelper must be created before jsapi, so that jsapi is destroyed and
+    // pops any context it has pushed before we report to the caller context.
+    JSCLContextHelper cxhelper(aCx);
+
+    // Even though we are calling JS_SetPropertyById on targetObj, we want
+    // to ensure that we never run script here, so we use an AutoJSAPI and
+    // not an AutoEntryScript.
+    dom::AutoJSAPI jsapi;
+    jsapi.Init();
+    JSContext* cx = jsapi.cx();
+    JSAutoCompartment ac(cx, aMod->obj);
+
+    RootedValue symbols(cx);
+    {
+        RootedObject obj(cx, ResolveModuleObjectProperty(cx, aMod->obj,
+                                                         "EXPORTED_SYMBOLS"));
+        if (!obj || !JS_GetProperty(cx, obj, "EXPORTED_SYMBOLS", &symbols)) {
+            return ReportOnCallerUTF8(cxhelper, ERROR_NOT_PRESENT, aInfo);
+        }
+    }
+
+    bool isArray;
+    if (!JS_IsArrayObject(cx, symbols, &isArray)) {
+        return NS_ERROR_FAILURE;
+    }
+    if (!isArray) {
+        return ReportOnCallerUTF8(cxhelper, ERROR_NOT_AN_ARRAY, aInfo);
+    }
+
+    RootedObject symbolsObj(cx, &symbols.toObject());
+
+    // Iterate over symbols array, installing symbols on targetObj:
+
+    uint32_t symbolCount = 0;
+    if (!JS_GetArrayLength(cx, symbolsObj, &symbolCount)) {
+        return ReportOnCallerUTF8(cxhelper, ERROR_GETTING_ARRAY_LENGTH,
+                                  aInfo);
+    }
+
+#ifdef DEBUG
+    nsAutoCString logBuffer;
+#endif
+
+    aExports.set(JS_NewPlainObject(cx));
+    if (!aExports) {
+        return NS_ERROR_OUT_OF_MEMORY;
+    }
+
+    bool missing = false;
+
+    RootedValue value(cx);
+    RootedId symbolId(cx);
+    RootedObject symbolHolder(cx);
+    for (uint32_t i = 0; i < symbolCount; ++i) {
+        if (!JS_GetElement(cx, symbolsObj, i, &value) ||
+            !value.isString() ||
+            !JS_ValueToId(cx, value, &symbolId)) {
+            return ReportOnCallerUTF8(cxhelper, ERROR_ARRAY_ELEMENT, aInfo, i);
+        }
+
+        symbolHolder = ResolveModuleObjectPropertyById(cx, aMod->obj, symbolId);
+        if (!symbolHolder ||
+            !JS_GetPropertyById(cx, symbolHolder, symbolId, &value)) {
+            JSAutoByteString bytes;
+            RootedString symbolStr(cx, JSID_TO_STRING(symbolId));
+            if (!bytes.encodeUtf8(cx, symbolStr))
+                return NS_ERROR_FAILURE;
+            return ReportOnCallerUTF8(cxhelper, ERROR_GETTING_SYMBOL,
+                                      aInfo, bytes.ptr());
+        }
+
+        if (value.isUndefined()) {
+            missing = true;
+        }
+
+        if (!JS_SetPropertyById(cx, aExports, symbolId, value)) {
+            JSAutoByteString bytes;
+            RootedString symbolStr(cx, JSID_TO_STRING(symbolId));
+            if (!bytes.encodeUtf8(cx, symbolStr))
+                return NS_ERROR_FAILURE;
+            return ReportOnCallerUTF8(cxhelper, ERROR_GETTING_SYMBOL,
+                                      aInfo, bytes.ptr());
+        }
+#ifdef DEBUG
+        if (i == 0) {
+            logBuffer.AssignLiteral("Installing symbols [ ");
+        }
+        JSAutoByteString bytes(cx, JSID_TO_STRING(symbolId));
+        if (!!bytes)
+            logBuffer.Append(bytes.ptr());
+        logBuffer.Append(' ');
+        if (i == symbolCount - 1) {
+            nsCString location;
+            MOZ_TRY(aInfo.GetLocation(location));
+            LOG(("%s] from %s\n", logBuffer.get(), location.get()));
+        }
+#endif
+    }
+
+    // Don't cache the exports object if any of its exported symbols are
+    // missing. If the module hasn't finished loading yet, they may be
+    // defined the next time we try to import it.
+    if (!missing) {
+        aMod->exports = aExports;
+    }
+    return NS_OK;
+}
+
+nsresult
+mozJSComponentLoader::Import(JSContext* aCx, const nsACString& aLocation,
+                             JS::MutableHandleObject aModuleGlobal,
+                             JS::MutableHandleObject aModuleExports,
+                             bool aIgnoreExports)
+{
     nsresult rv;
     if (!mInitialized) {
         rv = ReallyInit();
         NS_ENSURE_SUCCESS(rv, rv);
     }
 
     ComponentLoaderInfo info(aLocation);
 
     rv = info.EnsureKey();
     NS_ENSURE_SUCCESS(rv, rv);
 
     ModuleEntry* mod;
     nsAutoPtr<ModuleEntry> newEntry;
     if (!mImports.Get(info.Key(), &mod) && !mInProgressImports.Get(info.Key(), &mod)) {
-        newEntry = new ModuleEntry(RootingContext::get(callercx));
+        newEntry = new ModuleEntry(RootingContext::get(aCx));
         if (!newEntry)
             return NS_ERROR_OUT_OF_MEMORY;
 
         rv = info.EnsureResolvedURI();
         NS_ENSURE_SUCCESS(rv, rv);
 
         // get the JAR if there is one
         nsCOMPtr<nsIJARURI> jarURI;
@@ -1152,166 +1295,59 @@ mozJSComponentLoader::ImportInto(const n
             return NS_ERROR_UNEXPECTED;
         }
 
         mLocations.Put(newEntry->resolvedURL, new nsCString(info.Key()));
         mInProgressImports.Put(info.Key(), newEntry);
 
         rv = info.EnsureURI();
         NS_ENSURE_SUCCESS(rv, rv);
-        RootedValue exception(callercx);
+        RootedValue exception(aCx);
         rv = ObjectForLocation(info, sourceFile, &newEntry->obj,
                                &newEntry->thisObjectKey,
                                &newEntry->location, true, &exception);
 
         mInProgressImports.Remove(info.Key());
 
         if (NS_FAILED(rv)) {
             if (!exception.isUndefined()) {
                 // An exception was thrown during compilation. Propagate it
                 // out to our caller so they can report it.
-                if (!JS_WrapValue(callercx, &exception))
+                if (!JS_WrapValue(aCx, &exception))
                     return NS_ERROR_OUT_OF_MEMORY;
-                JS_SetPendingException(callercx, exception);
-                return NS_OK;
+                JS_SetPendingException(aCx, exception);
+                return NS_ERROR_FAILURE;
             }
 
             // Something failed, but we don't know what it is, guess.
             return NS_ERROR_FILE_NOT_FOUND;
         }
 
 #ifdef STARTUP_RECORDER_ENABLED
         if (Preferences::GetBool("browser.startup.record", false)) {
             newEntry->importStack =
-                xpc_PrintJSStack(callercx, false, false, false).get();
+                xpc_PrintJSStack(aCx, false, false, false).get();
         }
 #endif
 
         mod = newEntry;
     }
 
     MOZ_ASSERT(mod->obj, "Import table contains entry with no object");
-    vp.set(mod->obj);
-
-    if (targetObj) {
-        // cxhelper must be created before jsapi, so that jsapi is destroyed and
-        // pops any context it has pushed before we report to the caller context.
-        JSCLContextHelper cxhelper(callercx);
-
-        // Even though we are calling JS_SetPropertyById on targetObj, we want
-        // to ensure that we never run script here, so we use an AutoJSAPI and
-        // not an AutoEntryScript.
-        dom::AutoJSAPI jsapi;
-        jsapi.Init();
-        JSContext* cx = jsapi.cx();
-        JSAutoCompartment ac(cx, mod->obj);
-
-        RootedValue symbols(cx);
-        RootedObject exportedSymbolsHolder(cx, ResolveModuleObjectProperty(cx, mod->obj,
-                                                                           "EXPORTED_SYMBOLS"));
-        if (!exportedSymbolsHolder ||
-            !JS_GetProperty(cx, exportedSymbolsHolder,
-                            "EXPORTED_SYMBOLS", &symbols)) {
-            nsCString location;
-            rv = info.GetLocation(location);
-            NS_ENSURE_SUCCESS(rv, rv);
-            return ReportOnCallerUTF8(cxhelper, ERROR_NOT_PRESENT,
-                                      location.get());
-        }
-
-        bool isArray;
-        if (!JS_IsArrayObject(cx, symbols, &isArray)) {
-            return NS_ERROR_FAILURE;
-        }
-        if (!isArray) {
-            nsCString location;
-            rv = info.GetLocation(location);
-            NS_ENSURE_SUCCESS(rv, rv);
-            return ReportOnCallerUTF8(cxhelper, ERROR_NOT_AN_ARRAY,
-                                      location.get());
-        }
-
-        RootedObject symbolsObj(cx, &symbols.toObject());
-
-        // Iterate over symbols array, installing symbols on targetObj:
-
-        uint32_t symbolCount = 0;
-        if (!JS_GetArrayLength(cx, symbolsObj, &symbolCount)) {
-            nsCString location;
-            rv = info.GetLocation(location);
-            NS_ENSURE_SUCCESS(rv, rv);
-            return ReportOnCallerUTF8(cxhelper, ERROR_GETTING_ARRAY_LENGTH,
-                                      location.get());
-        }
-
-#ifdef DEBUG
-        nsAutoCString logBuffer;
-#endif
+    aModuleGlobal.set(mod->obj);
 
-        RootedValue value(cx);
-        RootedId symbolId(cx);
-        RootedObject symbolHolder(cx);
-        for (uint32_t i = 0; i < symbolCount; ++i) {
-            if (!JS_GetElement(cx, symbolsObj, i, &value) ||
-                !value.isString() ||
-                !JS_ValueToId(cx, value, &symbolId)) {
-                nsCString location;
-                rv = info.GetLocation(location);
-                NS_ENSURE_SUCCESS(rv, rv);
-                return ReportOnCallerUTF8(cxhelper, ERROR_ARRAY_ELEMENT,
-                                          location.get(), i);
-            }
-
-            symbolHolder = ResolveModuleObjectPropertyById(cx, mod->obj, symbolId);
-            if (!symbolHolder ||
-                !JS_GetPropertyById(cx, symbolHolder, symbolId, &value)) {
-                JSAutoByteString bytes;
-                RootedString symbolStr(cx, JSID_TO_STRING(symbolId));
-                if (!bytes.encodeUtf8(cx, symbolStr))
-                    return NS_ERROR_FAILURE;
-                nsCString location;
-                rv = info.GetLocation(location);
-                NS_ENSURE_SUCCESS(rv, rv);
-                return ReportOnCallerUTF8(cxhelper, ERROR_GETTING_SYMBOL,
-                                          location.get(), bytes.ptr());
-            }
-
-            JSAutoCompartment target_ac(cx, targetObj);
+    JS::RootedObject exports(aCx, mod->exports);
+    if (!exports && !aIgnoreExports) {
+        MOZ_TRY(ExtractExports(aCx, info, mod, &exports));
+    }
 
-            JS_MarkCrossZoneId(cx, symbolId);
-
-            if (!JS_WrapValue(cx, &value) ||
-                !JS_SetPropertyById(cx, targetObj, symbolId, value)) {
-                JSAutoByteString bytes;
-                RootedString symbolStr(cx, JSID_TO_STRING(symbolId));
-                if (!bytes.encodeUtf8(cx, symbolStr))
-                    return NS_ERROR_FAILURE;
-                nsCString location;
-                rv = info.GetLocation(location);
-                NS_ENSURE_SUCCESS(rv, rv);
-                return ReportOnCallerUTF8(cxhelper, ERROR_SETTING_SYMBOL,
-                                          location.get(), bytes.ptr());
-            }
-#ifdef DEBUG
-            if (i == 0) {
-                logBuffer.AssignLiteral("Installing symbols [ ");
-            }
-            JSAutoByteString bytes(cx, JSID_TO_STRING(symbolId));
-            if (!!bytes)
-                logBuffer.Append(bytes.ptr());
-            logBuffer.Append(' ');
-            if (i == symbolCount - 1) {
-                nsCString location;
-                rv = info.GetLocation(location);
-                NS_ENSURE_SUCCESS(rv, rv);
-                LOG(("%s] from %s\n", logBuffer.get(), location.get()));
-            }
-#endif
-        }
+    if (exports && !JS_WrapObject(aCx, &exports)) {
+        return NS_ERROR_FAILURE;
     }
+    aModuleExports.set(exports);
 
     // Cache this module for later
     if (newEntry) {
         mImports.Put(info.Key(), newEntry);
         newEntry.forget();
     }
 
     return NS_OK;
--- a/js/xpconnect/loader/mozJSComponentLoader.h
+++ b/js/xpconnect/loader/mozJSComponentLoader.h
@@ -57,18 +57,24 @@ class mozJSComponentLoader final : publi
 
     void FindTargetObject(JSContext* aCx,
                           JS::MutableHandleObject aTargetObject);
 
     static already_AddRefed<mozJSComponentLoader> GetOrCreate();
 
     static mozJSComponentLoader* Get() { return sSelf; }
 
-    nsresult Import(const nsACString& aResourceURI, JS::HandleValue aTargetObj,
-                    JSContext* aCx, uint8_t aArgc, JS::MutableHandleValue aRetval);
+    nsresult ImportInto(const nsACString& aResourceURI, JS::HandleValue aTargetObj,
+                        JSContext* aCx, uint8_t aArgc, JS::MutableHandleValue aRetval);
+
+    nsresult Import(JSContext* aCx, const nsACString& aResourceURI,
+                    JS::MutableHandleObject aModuleGlobal,
+                    JS::MutableHandleObject aModuleExports,
+                    bool aIgnoreExports = false);
+
     nsresult Unload(const nsACString& aResourceURI);
     nsresult IsModuleLoaded(const nsACString& aResourceURI, bool* aRetval);
     bool IsLoaderGlobal(JSObject* aObj) {
         return mLoaderGlobal == aObj;
     }
 
     size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
 
@@ -119,17 +125,18 @@ class mozJSComponentLoader final : publi
                         JS::MutableHandleObject vp);
 
     nsCOMPtr<nsIComponentManager> mCompMgr;
 
     class ModuleEntry : public mozilla::Module
     {
     public:
         explicit ModuleEntry(JS::RootingContext* aRootingCx)
-          : mozilla::Module(), obj(aRootingCx), thisObjectKey(aRootingCx)
+          : mozilla::Module(), obj(aRootingCx), exports(aRootingCx),
+            thisObjectKey(aRootingCx)
         {
             mVersion = mozilla::Module::kVersion;
             mCIDs = nullptr;
             mContractIDs = nullptr;
             mCategoryEntries = nullptr;
             getFactoryProc = GetFactory;
             loadProc = nullptr;
             unloadProc = nullptr;
@@ -169,24 +176,29 @@ class mozJSComponentLoader final : publi
 
         size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
         static already_AddRefed<nsIFactory> GetFactory(const mozilla::Module& module,
                                                        const mozilla::Module::CIDEntry& entry);
 
         nsCOMPtr<xpcIJSGetFactory> getfactoryobj;
         JS::PersistentRootedObject obj;
+        JS::PersistentRootedObject exports;
         JS::PersistentRootedScript thisObjectKey;
         char* location;
         nsCString resolvedURL;
 #ifdef STARTUP_RECORDER_ENABLED
         nsCString importStack;
 #endif
     };
 
+    nsresult ExtractExports(JSContext* aCx, ComponentLoaderInfo& aInfo,
+                            ModuleEntry* aMod,
+                            JS::MutableHandleObject aExports);
+
     static size_t DataEntrySizeOfExcludingThis(const nsACString& aKey, ModuleEntry* const& aData,
                                                mozilla::MallocSizeOf aMallocSizeOf, void* arg);
     static size_t ClassEntrySizeOfExcludingThis(const nsACString& aKey,
                                                 const nsAutoPtr<ModuleEntry>& aData,
                                                 mozilla::MallocSizeOf aMallocSizeOf, void* arg);
 
     // Modules are intentionally leaked, but still cleared.
     nsDataHashtable<nsCStringHashKey, ModuleEntry*> mModules;
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -2289,17 +2289,17 @@ nsXPCComponents_Utils::Import(const nsAC
                               MutableHandleValue retval)
 {
     RefPtr<mozJSComponentLoader> moduleloader = mozJSComponentLoader::Get();
     MOZ_ASSERT(moduleloader);
 
     AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING(
       "nsXPCComponents_Utils::Import", OTHER, registryLocation);
 
-    return moduleloader->Import(registryLocation, targetObj, cx, optionalArgc, retval);
+    return moduleloader->ImportInto(registryLocation, targetObj, cx, optionalArgc, retval);
 }
 
 NS_IMETHODIMP
 nsXPCComponents_Utils::IsModuleLoaded(const nsACString& registryLocation, bool* retval)
 {
     RefPtr<mozJSComponentLoader> moduleloader = mozJSComponentLoader::Get();
     MOZ_ASSERT(moduleloader);
     return moduleloader->IsModuleLoaded(registryLocation, retval);
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/tests/unit/test_defineModuleGetter.js
@@ -0,0 +1,115 @@
+"use strict";
+
+function assertIsGetter(obj, prop) {
+  let desc = Object.getOwnPropertyDescriptor(obj, prop);
+
+  ok(desc, `Property ${prop} exists on object`);
+  equal(typeof desc.get, "function", `Getter function exists for property ${prop}`);
+  equal(typeof desc.set, "function", `Setter function exists for property ${prop}`);
+  equal(desc.enumerable, true, `Property ${prop} is enumerable`);
+  equal(desc.configurable, true, `Property ${prop} is configurable`);
+}
+
+function assertIsValue(obj, prop, value) {
+  let desc = Object.getOwnPropertyDescriptor(obj, prop);
+
+  ok(desc, `Property ${prop} exists on object`);
+
+  ok("value" in desc, `${prop} is a data property`);
+  equal(desc.value, value, `${prop} has the expected value`);
+
+  equal(desc.enumerable, true, `Property ${prop} is enumerable`);
+  equal(desc.configurable, true, `Property ${prop} is configurable`);
+  equal(desc.writable, true, `Property ${prop} is writable`);
+}
+
+add_task(async function() {
+  let temp = {};
+  ChromeUtils.import("resource://gre/modules/Services.jsm", temp);
+
+  let obj = {};
+  let child = Object.create(obj);
+  let sealed = Object.seal(Object.create(obj));
+
+
+  // Test valid import
+
+  ChromeUtils.defineModuleGetter(obj, "Services",
+                                 "resource://gre/modules/Services.jsm");
+
+  assertIsGetter(obj, "Services");
+  equal(child.Services, temp.Services, "Getter works on descendent object");
+  assertIsValue(child, "Services", temp.Services);
+  assertIsGetter(obj, "Services");
+
+  Assert.throws(() => sealed.Services, /Object is not extensible/,
+                "Cannot access lazy getter from sealed object");
+  Assert.throws(() => sealed.Services = null, /Object is not extensible/,
+                "Cannot access lazy setter from sealed object");
+  assertIsGetter(obj, "Services");
+
+  equal(obj.Services, temp.Services, "Getter works on object");
+  assertIsValue(obj, "Services", temp.Services);
+
+
+  // Test overwriting via setter
+
+  child = Object.create(obj);
+
+  ChromeUtils.defineModuleGetter(obj, "Services",
+                                 "resource://gre/modules/Services.jsm");
+
+  assertIsGetter(obj, "Services");
+
+  child.Services = "foo";
+  assertIsValue(child, "Services", "foo");
+  assertIsGetter(obj, "Services");
+
+  obj.Services = "foo";
+  assertIsValue(obj, "Services", "foo");
+
+
+  // Test import missing property
+
+  ChromeUtils.defineModuleGetter(obj, "meh",
+                                 "resource://gre/modules/Services.jsm");
+  assertIsGetter(obj, "meh");
+  equal(obj.meh, undefined, "Missing property returns undefined");
+  assertIsValue(obj, "meh", undefined);
+
+
+  // Test import broken module
+
+  ChromeUtils.defineModuleGetter(obj, "broken",
+                                 "resource://test/bogus_exports_type.jsm");
+  assertIsGetter(obj, "broken");
+
+  let errorPattern = /EXPORTED_SYMBOLS is not an array/;
+  Assert.throws(() => child.broken, errorPattern,
+                "Broken import throws on child");
+  Assert.throws(() => child.broken, errorPattern,
+                "Broken import throws on child again");
+  Assert.throws(() => sealed.broken, errorPattern,
+                "Broken import throws on sealed child");
+  Assert.throws(() => obj.broken, errorPattern,
+                "Broken import throws on object");
+  assertIsGetter(obj, "broken");
+
+
+  // Test import missing module
+
+  ChromeUtils.defineModuleGetter(obj, "missing",
+                                 "resource://test/does_not_exist.jsm");
+  assertIsGetter(obj, "missing");
+
+  Assert.throws(() => obj.missing, /NS_ERROR_FILE_NOT_FOUND/,
+                "missing import throws on object");
+  assertIsGetter(obj, "missing");
+
+
+  // Test overwriting broken import via setter
+
+  assertIsGetter(obj, "broken");
+  obj.broken = "foo";
+  assertIsValue(obj, "broken", "foo");
+});
--- a/js/xpconnect/tests/unit/xpcshell.ini
+++ b/js/xpconnect/tests/unit/xpcshell.ini
@@ -62,16 +62,17 @@ support-files =
 [test_bug1151385.js]
 [test_bug1170311.js]
 [test_bug1244222.js]
 [test_bug_442086.js]
 [test_callFunctionWithAsyncStack.js]
 [test_classesByID_instanceof.js]
 [test_compileScript.js]
 [test_deepFreezeClone.js]
+[test_defineModuleGetter.js]
 [test_file.js]
 [test_blob.js]
 [test_blob2.js]
 [test_file2.js]
 [test_import.js]
 [test_import_fail.js]
 [test_interposition.js]
 [test_isModuleLoaded.js]
--- a/netwerk/test/mochitests/test_user_agent_updates.html
+++ b/netwerk/test/mochitests/test_user_agent_updates.html
@@ -307,17 +307,17 @@ const chromeScript = SpecialPowers.loadC
     );
   }
 
 
   addMessageListener("testProfileSave", testProfileSave);
   addMessageListener("testProfileLoad", testProfileLoad);
   addMessageListener("set-overrides", function(overrides) { OVERRIDES = overrides});
   addMessageListener("notify-on-update", () => { _notifyOnUpdate = true });
-}, { wantGlobalProperties: ["TextEncoder", "TextDecoder"]});
+}, { wantGlobalProperties: ["ChromeUtils", "TextEncoder", "TextDecoder"]});
 
 chromeScript.addMessageListener("testProfileSaveDone", SimpleTest.finish);
 chromeScript.addMessageListener("testProfileLoadDone", function() {
   SpecialPowers.pushPrefEnv({
     set: [[PREF_UPDATES_ENABLED, true]]
   }, function () {
     (function waitForLoad() {
       var ifr = document.createElement('IFRAME');
--- a/taskcluster/taskgraph/transforms/release_balrog_publishing.py
+++ b/taskcluster/taskgraph/transforms/release_balrog_publishing.py
@@ -10,12 +10,12 @@ from __future__ import absolute_import, 
 from taskgraph.transforms.base import TransformSequence
 
 transforms = TransformSequence()
 
 
 @transforms.add
 def add_release_eta(config, jobs):
     for job in jobs:
-        if config.params['release_eta'] != '':
+        if config.params['release_eta']:
             job['run']['release-eta'] = config.params['release_eta']
 
         yield job
--- a/testing/mochitest/tests/Harness_sanity/test_SpecialPowersLoadChromeScript.html
+++ b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersLoadChromeScript.html
@@ -35,17 +35,17 @@ function endOfFirstTest() {
   // wantGlobalProperties should add the specified properties to the sandbox
   // that is used to run the chrome script.
   script2 = SpecialPowers.loadChromeScript(_ => {
     addMessageListener("valid-assert", function (message) {
       assert.equal(typeof XMLHttpRequest, "function", "XMLHttpRequest is defined");
       assert.equal(typeof CSS, "undefined", "CSS is not defined");
       sendAsyncMessage("valid-assert-done");
     });
-  }, { wantGlobalProperties: ["XMLHttpRequest"] });
+  }, { wantGlobalProperties: ["ChromeUtils", "XMLHttpRequest"] });
 
   script2.sendAsyncMessage("valid-assert");
   script2.addMessageListener("valid-assert-done", endOfTest);
 
 }
 
 function endOfTest() {
   script2.destroy();
--- a/testing/specialpowers/content/SpecialPowersObserverAPI.js
+++ b/testing/specialpowers/content/SpecialPowersObserverAPI.js
@@ -480,20 +480,18 @@ SpecialPowersObserverAPI.prototype = {
         } else {
           throw new SpecialPowersError("SPLoadChromeScript: Invalid script");
         }
 
         // Setup a chrome sandbox that has access to sendAsyncMessage
         // and addMessageListener in order to communicate with
         // the mochitest.
         let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
-        let sandboxOptions = aMessage.json.sandboxOptions;
-        if (!sandboxOptions) {
-          sandboxOptions = {};
-        }
+        let sandboxOptions = Object.assign({wantGlobalProperties: ["ChromeUtils"]},
+                                           aMessage.json.sandboxOptions);
         let sb = Components.utils.Sandbox(systemPrincipal, sandboxOptions);
         let mm = aMessage.target.frameLoader
                          .messageManager;
         sb.sendAsyncMessage = (name, message) => {
           mm.sendAsyncMessage("SPChromeScriptMessage",
                               { id, name, message });
         };
         sb.addMessageListener = (name, listener) => {
--- a/testing/web-platform/mozilla/tests/wasm/js/harness/index.js
+++ b/testing/web-platform/mozilla/tests/wasm/js/harness/index.js
@@ -217,16 +217,25 @@ function call(instance, name, args) {
 };
 
 function get(instance, name) {
     _assert(instance instanceof Result);
 
     if (instance.isError())
         return instance;
 
+    // Experimental API change.  We try to export WebAssembly.Global instances,
+    // not primitive values.  In that case the Number() cast is necessary here
+    // to convert the Global to a value: the harness examines types carefully
+    // and will not trigger the @@toPrimitive hook on Global, unlike most user
+    // code.
+
+    if (typeof WebAssembly.Global === "function")
+	return ValueResult(Number(instance.value.exports[name]));
+
     return ValueResult(instance.value.exports[name]);
 }
 
 function exports(name, instance) {
     _assert(instance instanceof Result);
 
     if (instance.isError())
         return instance;
--- a/toolkit/components/extensions/ExtensionCommon.jsm
+++ b/toolkit/components/extensions/ExtensionCommon.jsm
@@ -131,16 +131,17 @@ var ExtensionAPIs = {
 
     let {script, schema} = api;
 
     let addonId = `${apiName}@experiments.addons.mozilla.org`;
     api.sandbox = Cu.Sandbox(global, {
       wantXrays: false,
       sandboxName: script,
       addonId,
+      wantGlobalProperties: ["ChromeUtils"],
       metadata: {addonID: addonId},
     });
 
     api.sandbox.ExtensionAPI = ExtensionAPI;
 
     // Create a console getter which lazily provide a ConsoleAPI instance.
     XPCOMUtils.defineLazyGetter(api.sandbox, "console", () => {
       return new ConsoleAPI({prefix: addonId});
@@ -1321,22 +1322,22 @@ class SchemaAPIManager extends EventEmit
    * Create a global object that is used as the shared global for all ext-*.js
    * scripts that are loaded via `loadScript`.
    *
    * @returns {object} A sandbox that is used as the global by `loadScript`.
    */
   _createExtGlobal() {
     let global = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal(), {
       wantXrays: false,
+      wantGlobalProperties: ["ChromeUtils"],
       sandboxName: `Namespace of ext-*.js scripts for ${this.processType} (from: resource://gre/modules/ExtensionCommon.jsm)`,
     });
 
     Object.assign(global, {
       Cc,
-      ChromeUtils,
       ChromeWorker,
       Ci,
       Cr,
       Cu,
       ExtensionAPI,
       ExtensionCommon,
       MatchGlob,
       MatchPattern,
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -295,17 +295,17 @@ XPCOMUtils.defineLazyGetter(this, "gStar
 
   return Services.prefs.getIntPref(PREF_EM_STARTUP_SCAN_SCOPES, 0);
 });
 
 function loadLazyObjects() {
   let uri = "resource://gre/modules/addons/XPIProviderUtils.js";
   let scope = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal(), {
     sandboxName: uri,
-    wantGlobalProperties: ["TextDecoder"],
+    wantGlobalProperties: ["ChromeUtils", "TextDecoder"],
   });
 
   Object.assign(scope, {
     ADDON_SIGNING: AddonSettings.ADDON_SIGNING,
     SIGNED_TYPES,
     BOOTSTRAP_REASONS,
     DB_SCHEMA,
     AddonInternal,
@@ -4232,16 +4232,17 @@ this.XPIProvider = {
       }
       Cu.allowCPOWsInAddon(aId, true);
     }
 
     if (!aFile.exists()) {
       activeAddon.bootstrapScope =
         new Cu.Sandbox(principal, { sandboxName: aFile.path,
                                     addonId: aId,
+                                    wantGlobalProperties: ["ChromeUtils"],
                                     metadata: { addonID: aId } });
       logger.error("Attempted to load bootstrap scope from missing directory " + aFile.path);
       return;
     }
 
     if (isWebExtension(aType)) {
       activeAddon.bootstrapScope = Extension.getBootstrapScope(aId, aFile);
     } else if (aType === "webextension-langpack") {
@@ -4251,16 +4252,17 @@ this.XPIProvider = {
       if (aType == "dictionary")
         uri = "resource://gre/modules/addons/SpellCheckDictionaryBootstrap.js";
       else if (aType == "apiextension")
         uri = "resource://gre/modules/addons/APIExtensionBootstrap.js";
 
       activeAddon.bootstrapScope =
         new Cu.Sandbox(principal, { sandboxName: uri,
                                     addonId: aId,
+                                    wantGlobalProperties: ["ChromeUtils"],
                                     metadata: { addonID: aId, URI: uri } });
 
       try {
         // Copy the reason values from the global object into the bootstrap scope.
         for (let name in BOOTSTRAP_REASONS)
           activeAddon.bootstrapScope[name] = BOOTSTRAP_REASONS[name];
 
         // Add other stuff that extensions want.
--- a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-single-arg-cu-import.js
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-single-arg-cu-import.js
@@ -18,19 +18,18 @@ module.exports = function(context) {
   // Public
   //  --------------------------------------------------------------------------
 
   return {
     "CallExpression": function(node) {
       if (node.callee.type === "MemberExpression") {
         let memexp = node.callee;
         if (memexp.object.type === "Identifier" &&
-            // Only Cu and ChromeUtils, not Components.utils; see bug 1230369.
-            (memexp.object.name === "Cu" ||
-             memexp.object.name === "ChromeUtils") &&
+            // Only Cu, not ChromeUtils or Components.utils; see bug 1230369.
+            memexp.object.name === "Cu" &&
             memexp.property.type === "Identifier" &&
             memexp.property.name === "import" &&
             node.arguments.length === 1) {
           context.report(node, "Single argument Cu.import exposes new " +
                                "globals to all modules");
         }
       }
     }