merge mozilla-inbound to mozilla-central. r=merge a=merge
authorSebastian Hengst <archaeopteryx@coole-files.de>
Fri, 01 Dec 2017 01:25:54 +0200
changeset 394458 a21f4e2ce5186e2dc9ee411b07e9348866b4ef30
parent 394412 fed6b20cf2beeb05c4a8f0e49895ab45ce1d1392 (current diff)
parent 394457 65fdb046a5295f5eb322ed2eb6d4fdc119868c86 (diff)
child 394494 39f83c47474c1ee9efd1a9458d0560973068c4c5
push id33005
push userarchaeopteryx@coole-files.de
push dateThu, 30 Nov 2017 23:31:48 +0000
treeherdermozilla-central@a21f4e2ce518 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone59.0a1
first release with
nightly linux32
a21f4e2ce518 / 59.0a1 / 20171201100115 / files
nightly linux64
a21f4e2ce518 / 59.0a1 / 20171201100115 / files
nightly mac
a21f4e2ce518 / 59.0a1 / 20171201100115 / files
nightly win32
a21f4e2ce518 / 59.0a1 / 20171201100115 / files
nightly win64
a21f4e2ce518 / 59.0a1 / 20171201100115 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central. r=merge a=merge
dom/workers/WorkerPrefs.h
gfx/webrender_bindings/WebRenderAPI.cpp
gfx/webrender_bindings/WebRenderAPI.h
gfx/webrender_bindings/src/bindings.rs
gfx/webrender_bindings/webrender_ffi_generated.h
--- a/dom/file/ipc/tests/mochitest.ini
+++ b/dom/file/ipc/tests/mochitest.ini
@@ -1,8 +1,8 @@
 [DEFAULT]
-support-files =
+support-files = script_file.js
 
 [test_ipcBlob_fileReaderSync.html]
-support-files = script_file.js
 [test_ipcBlob_workers.html]
 [test_ipcBlob_createImageBitmap.html]
 support-files = green.jpg
+[test_ipcBlob_emptyMultiplex.html]
--- a/dom/file/ipc/tests/script_file.js
+++ b/dom/file/ipc/tests/script_file.js
@@ -2,23 +2,41 @@ var { classes: Cc, interfaces: Ci, utils
 Cu.importGlobalProperties(["File"]);
 
 addMessageListener("file.open", function (e) {
   var testFile = Cc["@mozilla.org/file/directory_service;1"]
                    .getService(Ci.nsIDirectoryService)
                    .QueryInterface(Ci.nsIProperties)
                    .get("ProfD", Ci.nsIFile);
   testFile.append("ipc_fileReader_testing");
-  testFile.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0o600);
+  testFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0o600);
 
   var outStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
                       .createInstance(Components.interfaces.nsIFileOutputStream);
   outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate
                  0666, 0);
 
   var fileData = "Hello World!";
   outStream.write(fileData, fileData.length);
   outStream.close();
 
   File.createFromNsIFile(testFile).then(function(file) {
     sendAsyncMessage("file.opened", { file });
   });
 });
+
+addMessageListener("emptyfile.open", function (e) {
+  var testFile = Cc["@mozilla.org/file/directory_service;1"]
+                   .getService(Ci.nsIDirectoryService)
+                   .QueryInterface(Ci.nsIProperties)
+                   .get("ProfD", Ci.nsIFile);
+  testFile.append("ipc_fileReader_testing");
+  testFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0o600);
+
+  var outStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
+                      .createInstance(Components.interfaces.nsIFileOutputStream);
+  outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate
+                 0666, 0);
+
+  File.createFromNsIFile(testFile).then(function(file) {
+    sendAsyncMessage("emptyfile.opened", { file });
+  });
+});
new file mode 100644
--- /dev/null
+++ b/dom/file/ipc/tests/test_ipcBlob_emptyMultiplex.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test an empty IPCBlob together with other parts</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script type="text/javascript">
+
+function checkContent(msg, content) {
+  return new Promise(resolve => {
+    let fr = new FileReader();
+    fr.readAsText(new Blob(content));
+    fr.onloadend = () => {
+      is(fr.result, "Hello world!", "The content matches: " + msg);
+      resolve();
+    };
+  });
+}
+
+SimpleTest.waitForExplicitFinish();
+
+let url = SimpleTest.getTestFileURL("script_file.js");
+
+let script = SpecialPowers.loadChromeScript(url);
+script.addMessageListener("emptyfile.opened", message => {
+  checkContent("middle", ["Hello ", message.file, "world!"]).
+  then(() => checkContent("begin", [message.file, "Hello world!"])).
+  then(() => checkContent("end", ["Hello world!", message.file])).
+  then(() => checkContent("random", [message.file, message.file, "Hello world!", message.file])).
+  then(SimpleTest.finish);
+});
+
+script.sendAsyncMessage("emptyfile.open");
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/media/tests/mochitest/test_peerConnection_scaleResolution.html
+++ b/dom/media/tests/mochitest/test_peerConnection_scaleResolution.html
@@ -70,16 +70,18 @@
     ok(v2.currentTime > 0, "v2.currentTime is moving (" + v2.currentTime + ")");
 
     ok(v1.videoWidth > 0, "source width is positive");
     ok(v1.videoHeight > 0, "source height is positive");
     is(v2.videoWidth, v1.videoWidth / 2, "sink is half the width of source");
     is(v2.videoHeight, v1.videoHeight / 2, "sink is half the height of source");
     stream.getTracks().forEach(track => track.stop());
     v1.srcObject = v2.srcObject = null;
+    pc1.close()
+    pc2.close()
   }
 
   pushPrefs(['media.peerconnection.video.lock_scaling', true]).then(() => {
     if (!navigator.appVersion.includes("Android")) {
       runNetworkTest(() => testScale("VP8").then(() => testScale("H264"))
                     .then(networkTestFinished));
     } else {
       // No support for H.264 on Android in automation, see Bug 1355786
--- a/dom/tests/mochitest/script/file_blocked_script.sjs
+++ b/dom/tests/mochitest/script/file_blocked_script.sjs
@@ -30,22 +30,23 @@ function finishBlockedRequest(request, r
 }
 
 function handleRequest(request, response)
 {
   var query = request.queryString.split('&');
   switch (query[0]) {
     case "blocked":
       var alreadyUnblocked = getGlobalState(query[1]);
+
+      response.processAsync();
       if (alreadyUnblocked) {
         // the unblock request came before the blocked request, just go on and finish synchronously
         finishBlockedRequest(request, response, query);
       } else {
         setGlobalState(response, query[1]);
-        response.processAsync();
       }
       break;
 
     case "unblock":
       response.setStatusLine(request.httpVersion, 200, "OK");
       response.setHeader("Cache-Control", "no-cache", false);
       response.setHeader("Content-Type", "image/png", false);
       response.write("\x89PNG"); // just a broken image is enough for our purpose
--- a/gfx/layers/wr/AsyncImagePipelineManager.cpp
+++ b/gfx/layers/wr/AsyncImagePipelineManager.cpp
@@ -348,17 +348,17 @@ AsyncImagePipelineManager::ApplyAsyncIma
     }
     builder.PopStackingContext();
 
     wr::BuiltDisplayList dl;
     wr::LayoutSize builderContentSize;
     builder.Finalize(builderContentSize, dl);
     mApi->SetDisplayList(gfx::Color(0.f, 0.f, 0.f, 0.f), epoch, LayerSize(pipeline->mScBounds.Width(), pipeline->mScBounds.Height()),
                          pipelineId, builderContentSize,
-                         dl.dl_desc, dl.dl.inner.data, dl.dl.inner.length,
+                         dl.dl_desc, dl.dl,
                          resourceUpdates);
   }
 }
 
 void
 AsyncImagePipelineManager::HoldExternalImage(const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch, WebRenderTextureHost* aTexture)
 {
   if (mDestroyed) {
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -109,16 +109,17 @@ gecko_profiler_register_thread(const cha
 
 void
 gecko_profiler_unregister_thread()
 {
   PROFILER_UNREGISTER_THREAD();
 }
 
 namespace mozilla {
+
 namespace layers {
 
 using namespace mozilla::gfx;
 
 class MOZ_STACK_CLASS AutoWebRenderBridgeParentAsyncMessageSender
 {
 public:
   explicit AutoWebRenderBridgeParentAsyncMessageSender(WebRenderBridgeParent* aWebRenderBridgeParent,
@@ -593,28 +594,31 @@ WebRenderBridgeParent::RecvSetDisplayLis
   mAsyncImageManager->SetCompositionTime(TimeStamp::Now());
   ProcessWebRenderParentCommands(aCommands);
 
   wr::ResourceUpdateQueue resources;
   if (!UpdateResources(aResourceUpdates, aSmallShmems, aLargeShmems, resources)) {
     return IPC_FAIL(this, "Failed to deserialize resource updates");
   }
 
+
+  wr::Vec_u8 dlData(Move(dl));
+
   // If id namespaces do not match, it means the command is obsolete, probably
   // because the tab just moved to a new window.
   // In that case do not send the commands to webrender.
   if (mIdNamespace == aIdNamespace) {
     if (mWidget) {
       LayoutDeviceIntSize size = mWidget->GetClientSize();
       mApi->SetWindowParameters(size);
     }
     gfx::Color clearColor(0.f, 0.f, 0.f, 0.f);
     mApi->SetDisplayList(clearColor, wr::NewEpoch(wrEpoch), LayerSize(aSize.width, aSize.height),
                         mPipelineId, aContentSize,
-                        dlDesc, dl.mData, dl.mLen,
+                        dlDesc, dlData,
                         resources);
 
     ScheduleGenerateFrame();
 
     if (ShouldParentObserveEpoch()) {
       mCompositorBridge->ObserveLayerUpdate(GetLayersId(), GetChildLayerObserverEpoch(), true);
     }
   }
--- a/gfx/webrender_bindings/WebRenderAPI.cpp
+++ b/gfx/webrender_bindings/WebRenderAPI.cpp
@@ -260,29 +260,27 @@ WebRenderAPI::GenerateFrame(const nsTArr
 
 void
 WebRenderAPI::SetDisplayList(gfx::Color aBgColor,
                              Epoch aEpoch,
                              mozilla::LayerSize aViewportSize,
                              wr::WrPipelineId pipeline_id,
                              const LayoutSize& content_size,
                              wr::BuiltDisplayListDescriptor dl_descriptor,
-                             uint8_t *dl_data,
-                             size_t dl_size,
+                             wr::Vec_u8& dl_data,
                              ResourceUpdateQueue& aResources)
 {
   wr_api_set_display_list(mDocHandle,
                           ToColorF(aBgColor),
                           aEpoch,
                           aViewportSize.width, aViewportSize.height,
                           pipeline_id,
                           content_size,
                           dl_descriptor,
-                          dl_data,
-                          dl_size,
+                          &dl_data.inner,
                           aResources.Raw());
 }
 
 void
 WebRenderAPI::ClearDisplayList(Epoch aEpoch, wr::WrPipelineId pipeline_id)
 {
   wr_api_clear_display_list(mDocHandle, aEpoch, pipeline_id);
 }
--- a/gfx/webrender_bindings/WebRenderAPI.h
+++ b/gfx/webrender_bindings/WebRenderAPI.h
@@ -156,18 +156,17 @@ public:
   void SetWindowParameters(LayoutDeviceIntSize size);
 
   void SetDisplayList(gfx::Color aBgColor,
                       Epoch aEpoch,
                       mozilla::LayerSize aViewportSize,
                       wr::WrPipelineId pipeline_id,
                       const wr::LayoutSize& content_size,
                       wr::BuiltDisplayListDescriptor dl_descriptor,
-                      uint8_t *dl_data,
-                      size_t dl_size,
+                      wr::Vec_u8& dl_data,
                       ResourceUpdateQueue& aResources);
 
   void ClearDisplayList(Epoch aEpoch, wr::WrPipelineId pipeline_id);
 
   void SetRootPipeline(wr::PipelineId aPipeline);
 
   void RemovePipeline(wr::PipelineId aPipeline);
 
new file mode 100644
--- /dev/null
+++ b/gfx/webrender_bindings/WebRenderTypes.cpp
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "WebRenderTypes.h"
+
+#include "mozilla/ipc/ByteBuf.h"
+
+namespace mozilla {
+namespace wr {
+
+Vec_u8::Vec_u8(mozilla::ipc::ByteBuf&& aSrc) {
+  inner.data = aSrc.mData;
+  inner.length = aSrc.mLen;
+  inner.capacity = aSrc.mCapacity;
+  aSrc.mData = nullptr;
+  aSrc.mLen = 0;
+  aSrc.mCapacity = 0;
+}
+
+} // namespace wr
+} // namespace mozilla
--- a/gfx/webrender_bindings/WebRenderTypes.h
+++ b/gfx/webrender_bindings/WebRenderTypes.h
@@ -16,16 +16,21 @@
 #include "mozilla/PodOperations.h"
 #include "mozilla/Range.h"
 #include "mozilla/Variant.h"
 #include "Units.h"
 #include "RoundedRect.h"
 #include "nsStyleConsts.h"
 
 namespace mozilla {
+
+namespace ipc {
+class ByteBuf;
+} // namespace ipc
+
 namespace wr {
 
 typedef wr::WrWindowId WindowId;
 typedef wr::WrPipelineId PipelineId;
 typedef wr::WrImageKey ImageKey;
 typedef wr::WrFontKey FontKey;
 typedef wr::WrFontInstanceKey FontInstanceKey;
 typedef wr::WrEpoch Epoch;
@@ -571,16 +576,18 @@ struct Vec_u8 {
     SetEmpty();
   }
   Vec_u8(Vec_u8&) = delete;
   Vec_u8(Vec_u8&& src) {
     inner = src.inner;
     src.SetEmpty();
   }
 
+  explicit Vec_u8(mozilla::ipc::ByteBuf&& aSrc);
+
   Vec_u8&
   operator=(Vec_u8&& src) {
     inner = src.inner;
     src.SetEmpty();
     return *this;
   }
 
   wr::WrVecU8
--- a/gfx/webrender_bindings/moz.build
+++ b/gfx/webrender_bindings/moz.build
@@ -24,16 +24,17 @@ UNIFIED_SOURCES += [
     'Moz2DImageRenderer.cpp',
     'RenderBufferTextureHost.cpp',
     'RendererOGL.cpp',
     'RenderSharedSurfaceTextureHost.cpp',
     'RenderTextureHost.cpp',
     'RenderTextureHostOGL.cpp',
     'RenderThread.cpp',
     'WebRenderAPI.cpp',
+    'WebRenderTypes.cpp',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
     EXPORTS.mozilla.webrender += [
         'RenderMacIOSurfaceTextureHostOGL.h',
     ]
     UNIFIED_SOURCES += [
         'RenderMacIOSurfaceTextureHostOGL.cpp',
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -953,33 +953,29 @@ pub unsafe extern "C" fn wr_api_set_disp
     dh: &mut DocumentHandle,
     color: ColorF,
     epoch: WrEpoch,
     viewport_width: f32,
     viewport_height: f32,
     pipeline_id: WrPipelineId,
     content_size: LayoutSize,
     dl_descriptor: BuiltDisplayListDescriptor,
-    dl_data: *mut u8,
-    dl_size: usize,
+    dl_data: &mut WrVecU8,
     resources: &mut ResourceUpdates,
 ) {
     let resource_updates = mem::replace(resources, ResourceUpdates::new());
 
     let color = if color.a == 0.0 { None } else { Some(color) };
 
     // See the documentation of set_display_list in api.rs. I don't think
     // it makes a difference in gecko at the moment(until APZ is figured out)
     // but I suppose it is a good default.
     let preserve_frame_state = true;
 
-    let dl_slice = make_slice(dl_data, dl_size);
-    let mut dl_vec = Vec::new();
-    // XXX: see if we can get rid of the copy here
-    dl_vec.extend_from_slice(dl_slice);
+    let dl_vec = dl_data.flush_into_vec();
     let dl = BuiltDisplayList::from_data(dl_vec, dl_descriptor);
 
     dh.api.set_display_list(
         dh.document_id,
         epoch,
         color,
         LayoutSize::new(viewport_width, viewport_height),
         (pipeline_id, content_size, dl),
--- a/gfx/webrender_bindings/webrender_ffi_generated.h
+++ b/gfx/webrender_bindings/webrender_ffi_generated.h
@@ -1063,18 +1063,17 @@ WR_INLINE
 void wr_api_set_display_list(DocumentHandle *aDh,
                              ColorF aColor,
                              WrEpoch aEpoch,
                              float aViewportWidth,
                              float aViewportHeight,
                              WrPipelineId aPipelineId,
                              LayoutSize aContentSize,
                              BuiltDisplayListDescriptor aDlDescriptor,
-                             uint8_t *aDlData,
-                             size_t aDlSize,
+                             WrVecU8 *aDlData,
                              ResourceUpdates *aResources)
 WR_FUNC;
 
 WR_INLINE
 void wr_api_set_root_pipeline(DocumentHandle *aDh,
                               WrPipelineId aPipelineId)
 WR_FUNC;
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/asm.js/bug1421565.js
@@ -0,0 +1,25 @@
+// |jit-test| --ion-offthread-compile=off
+
+load(libdir + "asm.js");
+
+if (!isAsmJSCompilationAvailable())
+    quit(0);
+
+if (!('oomTest' in this))
+    quit();
+
+oomTest(
+  function() {
+    eval(`
+      function f(stdlib, foreign, buffer) {
+        "use asm";
+         var i32 = new stdlib.Int32Array(buffer);
+         function set(v) {
+           v=v|0;
+           i32[5] = v;
+         }
+        return set;
+      }
+    `);
+  }
+);
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -734,28 +734,16 @@ JitRuntime::getVMWrapper(const VMFunctio
     MOZ_ASSERT(functionWrappers_->initialized());
     MOZ_ASSERT(trampolineCode_);
 
     JitRuntime::VMWrapperMap::Ptr p = functionWrappers_->readonlyThreadsafeLookup(&f);
     MOZ_ASSERT(p);
     return trampolineCode(p->value());
 }
 
-void
-JitCodeHeader::init(JitCode* jitCode)
-{
-    jitCode_ = jitCode;
-
-#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
-    // On AMD Bobcat processors that may have eratas, insert a NOP slide to reduce crashes
-    if (CPUInfo::NeedAmdBugWorkaround())
-        memset((char *)&nops_, X86Encoding::OneByteOpcodeID::OP_NOP, sizeof(nops_));
-#endif
-}
-
 template <AllowGC allowGC>
 JitCode*
 JitCode::New(JSContext* cx, uint8_t* code, uint32_t bufferSize, uint32_t headerSize,
              ExecutablePool* pool, CodeKind kind)
 {
     JitCode* codeObj = Allocate<JitCode, allowGC>(cx);
     if (!codeObj) {
         pool->release(headerSize + bufferSize, kind);
@@ -774,20 +762,19 @@ JitCode::New<CanGC>(JSContext* cx, uint8
 template
 JitCode*
 JitCode::New<NoGC>(JSContext* cx, uint8_t* code, uint32_t bufferSize, uint32_t headerSize,
                    ExecutablePool* pool, CodeKind kind);
 
 void
 JitCode::copyFrom(MacroAssembler& masm)
 {
-    // Store the JitCode pointer in the JitCodeHeader so we can recover the
-    // gcthing from relocation tables.
-    JitCodeHeader::FromExecutable(code_)->init(this);
-
+    // Store the JitCode pointer right before the code buffer, so we can
+    // recover the gcthing from relocation tables.
+    *(JitCode**)(code_ - sizeof(JitCode*)) = this;
     insnSize_ = masm.instructionsSize();
     masm.executableCopy(code_);
 
     jumpRelocTableBytes_ = masm.jumpRelocationTableBytes();
     masm.copyJumpRelocationTable(code_ + jumpRelocTableOffset());
 
     dataRelocTableBytes_ = masm.dataRelocationTableBytes();
     masm.copyDataRelocationTable(code_ + dataRelocTableOffset());
--- a/js/src/jit/IonCode.h
+++ b/js/src/jit/IonCode.h
@@ -24,40 +24,20 @@
 
 namespace js {
 namespace jit {
 
 class MacroAssembler;
 class PatchableBackedge;
 class IonBuilder;
 class IonICEntry;
-class JitCode;
 
 typedef Vector<JSObject*, 4, JitAllocPolicy> ObjectVector;
 typedef Vector<TraceLoggerEvent, 0, SystemAllocPolicy> TraceLoggerEventVector;
 
-// Header at start of raw code buffer
-struct JitCodeHeader
-{
-    // Link back to corresponding gcthing
-    JitCode*    jitCode_;
-
-    // !!! NOTE !!!
-    // If we are running on AMD Bobcat, insert a NOP-slide at end of the JitCode
-    // header so we can try to recover when the CPU screws up the branch landing
-    // site. See Bug 1281759.
-    void*       nops_;
-
-    void init(JitCode* jitCode);
-
-    static JitCodeHeader* FromExecutable(uint8_t* buffer) {
-        return (JitCodeHeader*)(buffer - sizeof(JitCodeHeader));
-    }
-};
-
 class JitCode : public gc::TenuredCell
 {
   protected:
     uint8_t* code_;
     ExecutablePool* pool_;
     uint32_t bufferSize_;             // Total buffer size. Does not include headerSize_.
     uint32_t insnSize_;               // Instruction stream size.
     uint32_t dataSize_;               // Size of the read-only data area.
@@ -144,17 +124,17 @@ class JitCode : public gc::TenuredCell
 
     template <typename T> T as() const {
         return JS_DATA_TO_FUNC_PTR(T, raw());
     }
 
     void copyFrom(MacroAssembler& masm);
 
     static JitCode* FromExecutable(uint8_t* buffer) {
-        JitCode* code = JitCodeHeader::FromExecutable(buffer)->jitCode_;
+        JitCode* code = *(JitCode**)(buffer - sizeof(JitCode*));
         MOZ_ASSERT(code->raw() == buffer);
         return code;
     }
 
     static size_t offsetOfCode() {
         return offsetof(JitCode, code_);
     }
 
--- a/js/src/jit/IonTypes.h
+++ b/js/src/jit/IonTypes.h
@@ -535,16 +535,33 @@ ValueTypeFromMIRType(MIRType type)
 }
 
 static inline JSValueTag
 MIRTypeToTag(MIRType type)
 {
     return JSVAL_TYPE_TO_TAG(ValueTypeFromMIRType(type));
 }
 
+static inline size_t
+MIRTypeToSize(MIRType type)
+{
+    switch (type) {
+      case MIRType::Int32:
+        return 4;
+      case MIRType::Int64:
+        return 8;
+      case MIRType::Float32:
+        return 4;
+      case MIRType::Double:
+        return 8;
+      default:
+        MOZ_CRASH("MIRTypeToSize - unhandled case");
+    }
+}
+
 static inline const char*
 StringFromMIRType(MIRType type)
 {
   switch (type) {
     case MIRType::Undefined:
       return "Undefined";
     case MIRType::Null:
       return "Null";
--- a/js/src/jit/Linker.cpp
+++ b/js/src/jit/Linker.cpp
@@ -18,45 +18,37 @@ JitCode*
 Linker::newCode(JSContext* cx, CodeKind kind, bool hasPatchableBackedges /* = false */)
 {
     MOZ_ASSERT_IF(hasPatchableBackedges, kind == ION_CODE);
 
     gc::AutoSuppressGC suppressGC(cx);
     if (masm.oom())
         return fail(cx);
 
-    static const size_t ExecutableAllocatorAlignment = sizeof(void*);
-    static_assert(CodeAlignment >= ExecutableAllocatorAlignment,
-                  "Unexpected alignment requirements");
-
-    // We require enough bytes for the code, header, and worst-case alignment padding.
-    size_t bytesNeeded = masm.bytesNeeded() +
-                         sizeof(JitCodeHeader) +
-                         (CodeAlignment - ExecutableAllocatorAlignment);
+    ExecutablePool* pool;
+    size_t bytesNeeded = masm.bytesNeeded() + sizeof(JitCode*) + CodeAlignment;
     if (bytesNeeded >= MAX_BUFFER_SIZE)
         return fail(cx);
 
-    // ExecutableAllocator requires bytesNeeded to be aligned.
-    bytesNeeded = AlignBytes(bytesNeeded, ExecutableAllocatorAlignment);
+    // ExecutableAllocator requires bytesNeeded to be word-size aligned.
+    bytesNeeded = AlignBytes(bytesNeeded, sizeof(void*));
 
     ExecutableAllocator& execAlloc = hasPatchableBackedges
                                      ? cx->runtime()->jitRuntime()->backedgeExecAlloc()
                                      : cx->runtime()->jitRuntime()->execAlloc();
 
-    ExecutablePool* pool;
     uint8_t* result = (uint8_t*)execAlloc.alloc(cx, bytesNeeded, &pool, kind);
     if (!result)
         return fail(cx);
 
-    // The JitCodeHeader will be stored right before the code buffer.
-    uint8_t* codeStart = result + sizeof(JitCodeHeader);
+    // The JitCode pointer will be stored right before the code buffer.
+    uint8_t* codeStart = result + sizeof(JitCode*);
 
     // Bump the code up to a nice alignment.
     codeStart = (uint8_t*)AlignBytes((uintptr_t)codeStart, CodeAlignment);
-    MOZ_ASSERT(codeStart + masm.bytesNeeded() <= result + bytesNeeded);
     uint32_t headerSize = codeStart - result;
     JitCode* code = JitCode::New<allowGC>(cx, codeStart, bytesNeeded - headerSize,
                                           headerSize, pool, kind);
     if (!code)
         return nullptr;
     if (masm.oom())
         return fail(cx);
     awjc.emplace(result, bytesNeeded);
--- a/js/src/jit/MacroAssembler.h
+++ b/js/src/jit/MacroAssembler.h
@@ -1466,26 +1466,34 @@ class MacroAssembler : public MacroAssem
 
     // `ptr` will always be updated.
     void wasmUnalignedStoreI64(const wasm::MemoryAccessDesc& access, Register64 value,
                                Register memoryBase, Register ptr, Register ptrScratch,
                                Register tmp)
         DEFINED_ON(arm);
 
     // wasm specific methods, used in both the wasm baseline compiler and ion.
-    void wasmTruncateDoubleToUInt32(FloatRegister input, Register output, Label* oolEntry) DEFINED_ON(x86, x64, arm, mips32, mips64);
-    void wasmTruncateDoubleToInt32(FloatRegister input, Register output, Label* oolEntry) DEFINED_ON(x86_shared, arm, mips_shared);
-    void outOfLineWasmTruncateDoubleToInt32(FloatRegister input, bool isUnsigned, wasm::BytecodeOffset off, Label* rejoin) DEFINED_ON(x86_shared);
+    void wasmTruncateDoubleToUInt32(FloatRegister input, Register output, Label* oolEntry) PER_ARCH;
+    void wasmTruncateDoubleToInt32(FloatRegister input, Register output, Label* oolEntry) PER_SHARED_ARCH;
+    void outOfLineWasmTruncateDoubleToInt32(FloatRegister input, bool isUnsigned,
+                                            wasm::BytecodeOffset off, Label* rejoin)
+        DEFINED_ON(x86_shared);
 
-    void wasmTruncateFloat32ToUInt32(FloatRegister input, Register output, Label* oolEntry) DEFINED_ON(x86, x64, arm, mips32, mips64);
-    void wasmTruncateFloat32ToInt32(FloatRegister input, Register output, Label* oolEntry) DEFINED_ON(x86_shared, arm, mips_shared);
-    void outOfLineWasmTruncateFloat32ToInt32(FloatRegister input, bool isUnsigned, wasm::BytecodeOffset off, Label* rejoin) 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 outOfLineWasmTruncateDoubleToInt64(FloatRegister input, bool isUnsigned, wasm::BytecodeOffset off, Label* rejoin) DEFINED_ON(x86_shared);
-    void outOfLineWasmTruncateFloat32ToInt64(FloatRegister input, bool isUnsigned, wasm::BytecodeOffset off, Label* rejoin) DEFINED_ON(x86_shared);
+    void outOfLineWasmTruncateDoubleToInt64(FloatRegister input, bool isUnsigned,
+                                            wasm::BytecodeOffset off, Label* rejoin)
+        DEFINED_ON(x86_shared);
+    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);
 
     // WasmTableCallIndexReg must contain the index of the indirect call.
     void wasmCallIndirect(const wasm::CallSiteDesc& desc, const wasm::CalleeDesc& callee, bool needsBoundsCheck);
 
--- a/js/src/jit/arm64/MacroAssembler-arm64.cpp
+++ b/js/src/jit/arm64/MacroAssembler-arm64.cpp
@@ -927,12 +927,39 @@ MacroAssembler::storeUnboxedValue(const 
                                   const BaseIndex& dest, MIRType slotType);
 
 void
 MacroAssembler::comment(const char* msg)
 {
     Assembler::comment(msg);
 }
 
+// ========================================================================
+// wasm support
+
+void
+MacroAssembler::wasmTruncateDoubleToUInt32(FloatRegister input, Register output, Label* oolEntry)
+{
+    MOZ_CRASH("NYI");
+}
+
+void
+MacroAssembler::wasmTruncateDoubleToInt32(FloatRegister input, Register output, Label* oolEntry)
+{
+    MOZ_CRASH("NYI");
+}
+
+void
+MacroAssembler::wasmTruncateFloat32ToUInt32(FloatRegister input, Register output, Label* oolEntry)
+{
+    MOZ_CRASH("NYI");
+}
+
+void
+MacroAssembler::wasmTruncateFloat32ToInt32(FloatRegister input, Register output, Label* oolEntry)
+{
+    MOZ_CRASH("NYI");
+}
+
 //}}} check_macroassembler_style
 
 } // namespace jit
 } // namespace js
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -140,25 +140,16 @@ typedef unsigned BitSize;
 //
 // When those registers are volatile, the baseline compiler will reload them
 // after the call (it will restore the Tls register from the save slot and load
 // the other two from the Tls data).
 
 enum class UseABI { Wasm, System };
 enum class InterModule { False = false, True = true };
 
-#ifdef JS_CODEGEN_ARM64
-// FIXME: This is not correct, indeed for ARM64 there is no reliable
-// StackPointer and we'll need to change the abstractions that use
-// SP-relative addressing.  There's a guard in emitFunction() below to
-// prevent this workaround from having any consequence.  This hack
-// exists only as a stopgap; there is no ARM64 JIT support yet.
-static const Register StackPointer = RealStackPointer;
-#endif
-
 #ifdef JS_CODEGEN_X86
 // The selection of EBX here steps gingerly around: the need for EDX
 // to be allocatable for multiply/divide; ECX to be allocatable for
 // shift/rotate; EAX (= ReturnReg) to be allocatable as the joinreg;
 // EBX not being one of the WasmTableCall registers; and needing a
 // temp register for load/store that has a single-byte persona.
 //
 // The compiler assumes that ScratchRegX86 has a single-byte persona.
@@ -193,145 +184,54 @@ struct RegTypeOf {
 
 template<> struct RegTypeOf<MIRType::Float32> {
     static constexpr RegTypeName value = RegTypeName::Float32;
 };
 template<> struct RegTypeOf<MIRType::Double> {
     static constexpr RegTypeName value = RegTypeName::Float64;
 };
 
-BaseLocalIter::BaseLocalIter(const ValTypeVector& locals,
-                             size_t argsLength,
-                             bool debugEnabled)
-  : locals_(locals),
-    argsLength_(argsLength),
-    argsRange_(locals.begin(), argsLength),
-    argsIter_(argsRange_),
-    index_(0),
-    localSize_(debugEnabled ? DebugFrame::offsetOfFrame() : 0),
-    reservedSize_(localSize_),
-    done_(false)
-{
-    MOZ_ASSERT(argsLength <= locals.length());
-
-    settle();
-}
-
-int32_t
-BaseLocalIter::pushLocal(size_t nbytes)
-{
-    if (nbytes == 8)
-        localSize_ = AlignBytes(localSize_, 8u);
-    else if (nbytes == 16)
-        localSize_ = AlignBytes(localSize_, 16u);
-    localSize_ += nbytes;
-    return localSize_;          // Locals grow down so capture base address
-}
-
-void
-BaseLocalIter::settle()
-{
-    if (index_ < argsLength_) {
-        MOZ_ASSERT(!argsIter_.done());
-        mirType_ = argsIter_.mirType();
-        switch (mirType_) {
-          case MIRType::Int32:
-            if (argsIter_->argInRegister())
-                frameOffset_ = pushLocal(4);
-            else
-                frameOffset_ = -(argsIter_->offsetFromArgBase() + sizeof(Frame));
-            break;
-          case MIRType::Int64:
-            if (argsIter_->argInRegister())
-                frameOffset_ = pushLocal(8);
-            else
-                frameOffset_ = -(argsIter_->offsetFromArgBase() + sizeof(Frame));
-            break;
-          case MIRType::Double:
-            if (argsIter_->argInRegister())
-                frameOffset_ = pushLocal(8);
-            else
-                frameOffset_ = -(argsIter_->offsetFromArgBase() + sizeof(Frame));
-            break;
-          case MIRType::Float32:
-            if (argsIter_->argInRegister())
-                frameOffset_ = pushLocal(4);
-            else
-                frameOffset_ = -(argsIter_->offsetFromArgBase() + sizeof(Frame));
-            break;
-          default:
-            MOZ_CRASH("Argument type");
-        }
-        return;
-    }
-
-    MOZ_ASSERT(argsIter_.done());
-    if (index_ < locals_.length()) {
-        switch (locals_[index_]) {
-          case ValType::I32:
-            mirType_ = jit::MIRType::Int32;
-            frameOffset_ = pushLocal(4);
-            break;
-          case ValType::F32:
-            mirType_ = jit::MIRType::Float32;
-            frameOffset_ = pushLocal(4);
-            break;
-          case ValType::F64:
-            mirType_ = jit::MIRType::Double;
-            frameOffset_ = pushLocal(8);
-            break;
-          case ValType::I64:
-            mirType_ = jit::MIRType::Int64;
-            frameOffset_ = pushLocal(8);
-            break;
-          default:
-            MOZ_CRASH("Compiler bug: Unexpected local type");
-        }
-        return;
-    }
-
-    done_ = true;
-}
-
-void
-BaseLocalIter::operator++(int)
-{
-    MOZ_ASSERT(!done_);
-    index_++;
-    if (!argsIter_.done())
-        argsIter_++;
-    settle();
-}
-
 // The strongly typed register wrappers are especially useful to distinguish
 // float registers from double registers, but they also clearly distinguish
 // 32-bit registers from 64-bit register pairs on 32-bit systems.
 
 struct RegI32 : public Register
 {
     RegI32() : Register(Register::Invalid()) {}
     explicit RegI32(Register reg) : Register(reg) {}
+    bool isValid() const { return *this != Invalid(); }
+    bool isInvalid() const { return !isValid(); }
+    static RegI32 Invalid() { return RegI32(Register::Invalid()); }
 };
 
 struct RegI64 : public Register64
 {
     RegI64() : Register64(Register64::Invalid()) {}
     explicit RegI64(Register64 reg) : Register64(reg) {}
+    bool isValid() const { return *this != Invalid(); }
+    bool isInvalid() const { return !isValid(); }
+    static RegI64 Invalid() { return RegI64(Register64::Invalid()); }
 };
 
 struct RegF32 : public FloatRegister
 {
     RegF32() : FloatRegister() {}
     explicit RegF32(FloatRegister reg) : FloatRegister(reg) {}
+    bool isValid() const { return *this != Invalid(); }
+    bool isInvalid() const { return !isValid(); }
+    static RegF32 Invalid() { return RegF32(InvalidFloatReg); }
 };
 
 struct RegF64 : public FloatRegister
 {
     RegF64() : FloatRegister() {}
     explicit RegF64(FloatRegister reg) : FloatRegister(reg) {}
+    bool isValid() const { return *this != Invalid(); }
+    bool isInvalid() const { return !isValid(); }
+    static RegF64 Invalid() { return RegF64(InvalidFloatReg); }
 };
 
 struct AnyReg
 {
     explicit AnyReg(RegI32 r) { tag = I32; i32_ = r; }
     explicit AnyReg(RegI64 r) { tag = I64; i64_ = r; }
     explicit AnyReg(RegF32 r) { tag = F32; f32_ = r; }
     explicit AnyReg(RegF64 r) { tag = F64; f64_ = r; }
@@ -782,17 +682,18 @@ class ScratchF32 : public ScratchFloat32
 #if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_ARM)
 // On x86 we do not have a dedicated masm scratch register; on ARM, we need one
 // in addition to the one defined by masm because masm uses it too often.
 class ScratchI32
 {
 # ifdef DEBUG
     BaseRegAlloc& ra;
   public:
-    explicit ScratchI32(BaseRegAlloc& ra) : ra(ra) {
+    explicit ScratchI32(BaseRegAlloc& ra) : ra(ra)
+    {
         MOZ_ASSERT(!ra.scratchRegisterTaken());
         ra.setScratchRegisterTaken(true);
     }
     ~ScratchI32() {
         MOZ_ASSERT(ra.scratchRegisterTaken());
         ra.setScratchRegisterTaken(false);
     }
 # else
@@ -819,65 +720,631 @@ class ScratchI32 : public ScratchRegiste
 
 #if defined(JS_CODEGEN_X86)
 // ScratchEBX is a mnemonic device: For some atomic ops we really need EBX,
 // no other register will do.  And we would normally have to allocate that
 // register using ScratchI32 since normally the scratch register is EBX.
 // But the whole point of ScratchI32 is to hide that relationship.  By using
 // the ScratchEBX alias, we document that at that point we require the
 // scratch register to be EBX.
-typedef ScratchI32 ScratchEBX;
-#endif
-
-class BaseCompiler final : public BaseCompilerInterface
-{
-    typedef Vector<NonAssertingLabel, 8, SystemAllocPolicy> LabelVector;
-    typedef Vector<MIRType, 8, SystemAllocPolicy> MIRTypeVector;
+using ScratchEBX = ScratchI32;
+#endif
+
+// The stack frame.
+//
+// The frame has four parts ("below" means at lower addresses):
+//
+//  - the Header, comprising the Frame and DebugFrame elements;
+//  - the Local area, allocated below the header with various forms of
+//    alignment;
+//  - the Stack area, comprising the temporary storage the compiler uses
+//    for register spilling, allocated below the Local area;
+//  - the Arguments area, comprising memory allocated for outgoing calls,
+//    allocated below the stack area.
+//
+// The header is addressed off the stack pointer.  masm.framePushed() is always
+// correct, and masm.getStackPointer() + masm.framePushed() always addresses the
+// Frame, with the DebugFrame optionally below it.
+//
+// The local area is laid out by BaseLocalIter and is allocated and deallocated
+// by standard prologue and epilogue functions that manipulate the stack
+// pointer, but it is accessed via BaseStackFrame.
+//
+// The stack area is maintained by and accessed via BaseStackFrame.  On some
+// systems, the stack memory may be allocated in chunks because the SP needs a
+// specific alignment.
+//
+// The arguments area is allocated and deallocated via BaseStackFrame (see
+// comments later) but is accessed directly off the stack pointer.
+
+// BaseLocalIter iterates over a vector of types of locals and provides offsets
+// from the Frame address for those locals, and associated data.
+//
+// The implementation of BaseLocalIter is the property of the BaseStackFrame.
+// But it is also exposed for eg the debugger to use.
+
+BaseLocalIter::BaseLocalIter(const ValTypeVector& locals,
+                             size_t argsLength,
+                             bool debugEnabled)
+  : locals_(locals),
+    argsLength_(argsLength),
+    argsRange_(locals.begin(), argsLength),
+    argsIter_(argsRange_),
+    index_(0),
+    localSize_(debugEnabled ? DebugFrame::offsetOfFrame() : 0),
+    reservedSize_(localSize_),
+    done_(false)
+{
+    MOZ_ASSERT(argsLength <= locals.length());
+
+    settle();
+}
+
+int32_t
+BaseLocalIter::pushLocal(size_t nbytes)
+{
+    MOZ_ASSERT(nbytes % 4 == 0 && nbytes <= 16);
+    localSize_ = AlignBytes(localSize_, nbytes) + nbytes;
+    return localSize_;          // Locals grow down so capture base address
+}
+
+void
+BaseLocalIter::settle()
+{
+    if (index_ < argsLength_) {
+        MOZ_ASSERT(!argsIter_.done());
+        mirType_ = argsIter_.mirType();
+        switch (mirType_) {
+          case MIRType::Int32:
+          case MIRType::Int64:
+          case MIRType::Double:
+          case MIRType::Float32:
+            if (argsIter_->argInRegister())
+                frameOffset_ = pushLocal(MIRTypeToSize(mirType_));
+            else
+                frameOffset_ = -(argsIter_->offsetFromArgBase() + sizeof(Frame));
+            break;
+          default:
+            MOZ_CRASH("Argument type");
+        }
+        return;
+    }
+
+    MOZ_ASSERT(argsIter_.done());
+    if (index_ < locals_.length()) {
+        switch (locals_[index_]) {
+          case ValType::I32:
+          case ValType::I64:
+          case ValType::F32:
+          case ValType::F64:
+            mirType_ = ToMIRType(locals_[index_]);
+            frameOffset_ = pushLocal(MIRTypeToSize(mirType_));
+            break;
+          default:
+            MOZ_CRASH("Compiler bug: Unexpected local type");
+        }
+        return;
+    }
+
+    done_ = true;
+}
+
+void
+BaseLocalIter::operator++(int)
+{
+    MOZ_ASSERT(!done_);
+    index_++;
+    if (!argsIter_.done())
+        argsIter_++;
+    settle();
+}
+
+// Abstraction of the baseline compiler's stack frame (except for the Frame /
+// DebugFrame parts).  See comments above for more.
+
+class BaseStackFrame
+{
+    MacroAssembler& masm;
+
+    // Size of local area in bytes (stable after beginFunction).
+    uint32_t localSize_;
+
+    // Low byte offset of local area for true locals (not parameters).
+    uint32_t varLow_;
+
+    // High byte offset + 1 of local area for true locals.
+    uint32_t varHigh_;
+
+    // The largest stack height, not necessarily zero-based.  Read this for its
+    // true value only when code generation is finished.
+    uint32_t maxStackHeight_;
+
+    // Patch point where we check for stack overflow.
+    CodeOffset stackAddOffset_;
+
+    // The stack pointer, cached for brevity.
+    Register sp_;
+
+  public:
+
+    explicit BaseStackFrame(MacroAssembler& masm)
+      : masm(masm),
+        localSize_(UINT32_MAX),
+        varLow_(UINT32_MAX),
+        varHigh_(UINT32_MAX),
+        maxStackHeight_(0),
+        stackAddOffset_(0),
+        sp_(masm.getStackPointer())
+    {}
+
+    //////////////////////////////////////////////////////////////////////
+    //
+    // The local area.
+
+    // Locals - the static part of the frame.
 
     struct Local
     {
-        Local() : type_(MIRType::None), offs_(UINT32_MAX) {}
-        Local(MIRType type, uint32_t offs) : type_(type), offs_(offs) {}
-
-        void init(MIRType type_, uint32_t offs_) {
-            this->type_ = type_;
-            this->offs_ = offs_;
+        // Type of the value.
+        const MIRType type;
+
+        // Byte offset from Frame "into" the locals, ie positive for true locals
+        // and negative for incoming args that read directly from the arg area.
+        // It assumes the stack is growing down and that locals are on the stack
+        // at lower addresses than Frame, and is the offset from Frame of the
+        // lowest-addressed byte of the local.
+        const int32_t offs;
+
+        Local(MIRType type, int32_t offs) : type(type), offs(offs) {}
+    };
+
+    typedef Vector<Local, 8, SystemAllocPolicy> LocalVector;
+
+  private:
+
+    // Offset off of sp_ for `local`.
+    int32_t localOffset(const Local& local) {
+        return localOffset(local.offs);
+    }
+
+    // Offset off of sp_ for a local with offset `offset` from Frame.
+    int32_t localOffset(int32_t offset) {
+        return masm.framePushed() - offset;
+    }
+
+  public:
+
+    void endFunctionPrologue() {
+        MOZ_ASSERT(masm.framePushed() == localSize_);
+        MOZ_ASSERT(localSize_ != UINT32_MAX);
+        MOZ_ASSERT(localSize_ % WasmStackAlignment == 0);
+        maxStackHeight_ = localSize_;
+    }
+
+    // Initialize `localInfo` based on the types of `locals` and `args`.
+    bool setupLocals(const ValTypeVector& locals, const ValTypeVector& args, bool debugEnabled,
+                     LocalVector* localInfo)
+    {
+        MOZ_ASSERT(maxStackHeight_ != UINT32_MAX);
+
+        if (!localInfo->reserve(locals.length()))
+            return false;
+
+        DebugOnly<uint32_t> index = 0;
+        BaseLocalIter i(locals, args.length(), debugEnabled);
+        varLow_ = i.reservedSize();
+        for (; !i.done() && i.index() < args.length(); i++) {
+            MOZ_ASSERT(i.isArg());
+            MOZ_ASSERT(i.index() == index);
+            localInfo->infallibleEmplaceBack(i.mirType(), i.frameOffset());
+            varLow_ = i.currentLocalSize();
+            index++;
+        }
+
+        varHigh_ = varLow_;
+        for (; !i.done() ; i++) {
+            MOZ_ASSERT(!i.isArg());
+            MOZ_ASSERT(i.index() == index);
+            localInfo->infallibleEmplaceBack(i.mirType(), i.frameOffset());
+            varHigh_ = i.currentLocalSize();
+            index++;
         }
 
-        MIRType  type_;              // Type of the value, or MIRType::None
-        uint32_t offs_;              // Zero-based frame offset of value, or UINT32_MAX
-
-        MIRType type() const { MOZ_ASSERT(type_ != MIRType::None); return type_; }
-        uint32_t offs() const { MOZ_ASSERT(offs_ != UINT32_MAX); return offs_; }
-    };
+        localSize_ = AlignBytes(varHigh_, WasmStackAlignment);
+
+        return true;
+    }
+
+    // The fixed amount of memory, in bytes, allocated on the stack below the
+    // Frame for purposes such as locals and other fixed values.  Includes all
+    // necessary alignment.
+
+    uint32_t initialSize() const {
+        MOZ_ASSERT(localSize_ != UINT32_MAX);
+
+        return localSize_;
+    }
+
+    void zeroLocals(BaseRegAlloc& ra);
+
+    void loadLocalI32(const Local& src, RegI32 dest) {
+        masm.load32(Address(sp_, localOffset(src)), dest);
+    }
+
+#ifndef JS_64BIT
+    void loadLocalI64Low(const Local& src, RegI32 dest) {
+        masm.load32(Address(sp_, localOffset(src) + INT64LOW_OFFSET), dest);
+    }
+
+    void loadLocalI64High(const Local& src, RegI32 dest) {
+        masm.load32(Address(sp_, localOffset(src) + INT64HIGH_OFFSET), dest);
+    }
+#endif
+
+    void loadLocalI64(const Local& src, RegI64 dest) {
+        masm.load64(Address(sp_, localOffset(src)), dest);
+    }
+
+    void loadLocalPtr(const Local& src, Register dest) {
+        masm.loadPtr(Address(sp_, localOffset(src)), dest);
+    }
+
+    void loadLocalF64(const Local& src, RegF64 dest) {
+        masm.loadDouble(Address(sp_, localOffset(src)), dest);
+    }
+
+    void loadLocalF32(const Local& src, RegF32 dest) {
+        masm.loadFloat32(Address(sp_, localOffset(src)), dest);
+    }
+
+    void storeLocalI32(RegI32 src, const Local& dest) {
+        masm.store32(src, Address(sp_, localOffset(dest)));
+    }
+
+    void storeLocalI64(RegI64 src, const Local& dest) {
+        masm.store64(src, Address(sp_, localOffset(dest)));
+    }
+
+    void storeLocalPtr(Register src, const Local& dest) {
+        masm.storePtr(src, Address(sp_, localOffset(dest)));
+    }
+
+    void storeLocalF64(RegF64 src, const Local& dest) {
+        masm.storeDouble(src, Address(sp_, localOffset(dest)));
+    }
+
+    void storeLocalF32(RegF32 src, const Local& dest) {
+        masm.storeFloat32(src, Address(sp_, localOffset(dest)));
+    }
+
+    //////////////////////////////////////////////////////////////////////
+    //
+    // The stack area - the dynamic part of the frame.
+
+  private:
+
+    // Offset off of sp_ for the slot at stack area location `offset`
+    int32_t stackOffset(int32_t offset) {
+        return masm.framePushed() - offset;
+    }
+
+  public:
+
+    // Sizes of items in the stack area.
+    //
+    // The size values come from the implementations of Push() in
+    // MacroAssembler-x86-shared.cpp and MacroAssembler-arm-shared.cpp, and from
+    // VFPRegister::size() in Architecture-arm.h.
+    //
+    // On ARM unlike on x86 we push a single for float.
+
+    static const size_t StackSizeOfPtr    = sizeof(intptr_t);
+    static const size_t StackSizeOfInt64  = sizeof(int64_t);
+#ifdef JS_CODEGEN_ARM
+    static const size_t StackSizeOfFloat  = sizeof(float);
+#else
+    static const size_t StackSizeOfFloat  = sizeof(double);
+#endif
+    static const size_t StackSizeOfDouble = sizeof(double);
+
+    // We won't know until after we've generated code how big the frame will be
+    // (we may need arbitrary spill slots and outgoing param slots) so emit a
+    // patchable add that is patched in endFunction().
+    //
+    // Note the platform scratch register may be used by branchPtr(), so
+    // generally tmp0 and tmp1 must be something else.
+
+    void allocStack(Register tmp0, Register tmp1, Label* stackOverflowLabel) {
+        stackAddOffset_ = masm.add32ToPtrWithPatch(sp_, tmp0);
+        masm.wasmEmitStackCheck(tmp0, tmp1, stackOverflowLabel);
+    }
+
+    void patchAllocStack() {
+        masm.patchAdd32ToPtr(stackAddOffset_,
+                             Imm32(-int32_t(maxStackHeight_ - localSize_)));
+    }
+
+    // Very large frames are implausible, probably an attack.
+    bool checkStackHeight() {
+        // 256KB ought to be enough for anyone.
+        return maxStackHeight_ <= 256 * 1024;
+    }
+
+    // The current height of the stack area, not necessarily zero-based.
+    uint32_t stackHeight() const {
+        return masm.framePushed();
+    }
+
+    // Set the frame height.  This must only be called with a value returned
+    // from stackHeight().
+    void setStackHeight(uint32_t amount) {
+        masm.setFramePushed(amount);
+    }
+
+    uint32_t pushPtr(Register r) {
+        DebugOnly<uint32_t> stackBefore = stackHeight();
+        masm.Push(r);
+        maxStackHeight_ = Max(maxStackHeight_, stackHeight());
+        MOZ_ASSERT(stackBefore + StackSizeOfPtr == stackHeight());
+        return stackHeight();
+    }
+
+    uint32_t pushFloat32(FloatRegister r) {
+        DebugOnly<uint32_t> stackBefore = stackHeight();
+        masm.Push(r);
+        maxStackHeight_ = Max(maxStackHeight_, stackHeight());
+        MOZ_ASSERT(stackBefore + StackSizeOfFloat == stackHeight());
+        return stackHeight();
+    }
+
+    uint32_t pushDouble(FloatRegister r) {
+        DebugOnly<uint32_t> stackBefore = stackHeight();
+        masm.Push(r);
+        maxStackHeight_ = Max(maxStackHeight_, stackHeight());
+        MOZ_ASSERT(stackBefore + StackSizeOfDouble == stackHeight());
+        return stackHeight();
+    }
+
+   void popPtr(Register r) {
+        DebugOnly<uint32_t> stackBefore = stackHeight();
+        masm.Pop(r);
+        MOZ_ASSERT(stackBefore - StackSizeOfPtr == stackHeight());
+    }
+
+    void popFloat32(FloatRegister r) {
+        DebugOnly<uint32_t> stackBefore = stackHeight();
+        masm.Pop(r);
+        MOZ_ASSERT(stackBefore - StackSizeOfFloat == stackHeight());
+    }
+
+    void popDouble(FloatRegister r) {
+        DebugOnly<uint32_t> stackBefore = stackHeight();
+        masm.Pop(r);
+        MOZ_ASSERT(stackBefore - StackSizeOfDouble == stackHeight());
+    }
+
+    void popBytes(size_t bytes) {
+        if (bytes > 0)
+            masm.freeStack(bytes);
+    }
+
+    // Before branching to an outer control label, pop the execution stack to
+    // the level expected by that region, but do not update masm.framePushed()
+    // as that will happen as compilation leaves the block.
+
+    void popStackBeforeBranch(uint32_t destStackHeight) {
+        uint32_t stackHere = stackHeight();
+        if (stackHere > destStackHeight)
+            masm.addToStackPtr(Imm32(stackHere - destStackHeight));
+    }
+
+    bool willPopStackBeforeBranch(uint32_t destStackHeight) {
+        uint32_t stackHere = stackHeight();
+        return stackHere > destStackHeight;
+    }
+
+    // Before exiting a nested control region, pop the execution stack
+    // to the level expected by the nesting region, and free the
+    // stack.
+
+    void popStackOnBlockExit(uint32_t destStackHeight, bool deadCode) {
+        uint32_t stackHere = stackHeight();
+        if (stackHere > destStackHeight) {
+            if (deadCode)
+                masm.setFramePushed(destStackHeight);
+            else
+                masm.freeStack(stackHere - destStackHeight);
+        }
+    }
+
+    void loadStackI32(int32_t offset, RegI32 dest) {
+        masm.load32(Address(sp_, stackOffset(offset)), dest);
+    }
+
+    void loadStackI64(int32_t offset, RegI64 dest) {
+        masm.load64(Address(sp_, stackOffset(offset)), dest);
+    }
+
+#ifndef JS_64BIT
+    void loadStackI64Low(int32_t offset, RegI32 dest) {
+        masm.load32(Address(sp_, stackOffset(offset - INT64LOW_OFFSET)), dest);
+    }
+
+    void loadStackI64High(int32_t offset, RegI32 dest) {
+        masm.load32(Address(sp_, stackOffset(offset - INT64HIGH_OFFSET)), dest);
+    }
+#endif
+
+    // Disambiguation: this loads a "Ptr" value from the stack, it does not load
+    // the "StackPtr".
+
+    void loadStackPtr(int32_t offset, Register dest) {
+        masm.loadPtr(Address(sp_, stackOffset(offset)), dest);
+    }
+
+    void loadStackF64(int32_t offset, RegF64 dest) {
+        masm.loadDouble(Address(sp_, stackOffset(offset)), dest);
+    }
+
+    void loadStackF32(int32_t offset, RegF32 dest) {
+        masm.loadFloat32(Address(sp_, stackOffset(offset)), dest);
+    }
+
+    //////////////////////////////////////////////////////////////////////
+    //
+    // The argument area - for outgoing calls.
+    //
+    // We abstract these operations as an optimization: we can merge the freeing
+    // of the argument area and dropping values off the stack after a call.  But
+    // they always amount to manipulating the real stack pointer by some amount.
+
+    // This is always equivalent to a masm.reserveStack() call.
+    void allocArgArea(size_t size) {
+        if (size)
+            masm.reserveStack(size);
+    }
+
+    // This is always equivalent to a sequence of masm.freeStack() calls.
+    void freeArgAreaAndPopBytes(size_t argSize, size_t dropSize) {
+        if (argSize + dropSize)
+            masm.freeStack(argSize + dropSize);
+    }
+};
+
+void
+BaseStackFrame::zeroLocals(BaseRegAlloc& ra)
+{
+    MOZ_ASSERT(varLow_ != UINT32_MAX);
+
+    if (varLow_ == varHigh_)
+        return;
+
+    static const uint32_t wordSize = sizeof(void*);
+
+    // The adjustments to 'low' by the size of the item being stored compensates
+    // for the fact that locals offsets are the offsets from Frame to the bytes
+    // directly "above" the locals in the locals area.  See comment at Local.
+
+    // On 64-bit systems we may have 32-bit alignment for the local area as it
+    // may be preceded by parameters and prologue/debug data.
+
+    uint32_t low = varLow_;
+    if (low % wordSize) {
+        masm.store32(Imm32(0), Address(sp_, localOffset(low + 4)));
+        low += 4;
+    }
+    MOZ_ASSERT(low % wordSize == 0);
+
+    const uint32_t high = AlignBytes(varHigh_, wordSize);
+    MOZ_ASSERT(high <= localSize_, "localSize_ should be aligned at least that");
+
+    // An UNROLL_LIMIT of 16 is chosen so that we only need an 8-bit signed
+    // immediate to represent the offset in the store instructions in the loop
+    // on x64.
+
+    const uint32_t UNROLL_LIMIT = 16;
+    const uint32_t initWords = (high - low) / wordSize;
+    const uint32_t tailWords = initWords % UNROLL_LIMIT;
+    const uint32_t loopHigh = high - (tailWords * wordSize);
+
+    // With only one word to initialize, just store an immediate zero.
+
+    if (initWords == 1) {
+        masm.storePtr(ImmWord(0), Address(sp_, localOffset(low + wordSize)));
+        return;
+    }
+
+    // For other cases, it's best to have a zero in a register.
+    //
+    // One can do more here with SIMD registers (store 16 bytes at a time) or
+    // with instructions like STRD on ARM (store 8 bytes at a time), but that's
+    // for another day.
+
+    RegI32 zero = ra.needI32();
+    masm.mov(ImmWord(0), zero);
+
+    // For the general case we want to have a loop body of UNROLL_LIMIT stores
+    // and then a tail of less than UNROLL_LIMIT stores.  When initWords is less
+    // than 2*UNROLL_LIMIT the loop trip count is at most 1 and there is no
+    // benefit to having the pointer calculations and the compare-and-branch.
+    // So we completely unroll when we have initWords < 2 * UNROLL_LIMIT.  (In
+    // this case we'll end up using 32-bit offsets on x64 for up to half of the
+    // stores, though.)
+
+    // Fully-unrolled case.
+
+    if (initWords < 2 * UNROLL_LIMIT)  {
+        for (uint32_t i = low; i < high; i += wordSize)
+            masm.storePtr(zero, Address(sp_, localOffset(i + wordSize)));
+        ra.freeI32(zero);
+        return;
+    }
+
+    // Unrolled loop with a tail. Stores will use negative offsets. That's OK
+    // for x86 and ARM, at least.
+
+    // Compute pointer to the highest-addressed slot on the frame.
+    RegI32 p = ra.needI32();
+    masm.computeEffectiveAddress(Address(sp_, localOffset(low + wordSize)),
+                                 p);
+
+    // Compute pointer to the lowest-addressed slot on the frame that will be
+    // initialized by the loop body.
+    RegI32 lim = ra.needI32();
+    masm.computeEffectiveAddress(Address(sp_, localOffset(loopHigh + wordSize)), lim);
+
+    // The loop body.  Eventually we'll have p == lim and exit the loop.
+    Label again;
+    masm.bind(&again);
+    for (uint32_t i = 0; i < UNROLL_LIMIT; ++i)
+        masm.storePtr(zero, Address(p, -(wordSize * i)));
+    masm.subPtr(Imm32(UNROLL_LIMIT * wordSize), p);
+    masm.branchPtr(Assembler::LessThan, lim, p, &again);
+
+    // The tail.
+    for (uint32_t i = 0; i < tailWords; ++i)
+        masm.storePtr(zero, Address(p, -(wordSize * i)));
+
+    ra.freeI32(p);
+    ra.freeI32(lim);
+    ra.freeI32(zero);
+}
+
+// The baseline compiler proper.
+
+class BaseCompiler final : public BaseCompilerInterface
+{
+    typedef BaseStackFrame::Local Local;
+    typedef Vector<NonAssertingLabel, 8, SystemAllocPolicy> LabelVector;
+    typedef Vector<MIRType, 8, SystemAllocPolicy> MIRTypeVector;
 
     // Bit set used for simple bounds check elimination.  Capping this at 64
     // locals makes sense; even 32 locals would probably be OK in practice.
     //
     // For more information about BCE, see the block comment above
     // popMemoryAccess(), below.
 
     typedef uint64_t BCESet;
 
     // Control node, representing labels and stack heights at join points.
 
     struct Control
     {
         Control()
-            : framePushed(UINT32_MAX),
+            : stackHeight(UINT32_MAX),
               stackSize(UINT32_MAX),
               bceSafeOnEntry(0),
               bceSafeOnExit(~BCESet(0)),
               deadOnArrival(false),
               deadThenBranch(false)
         {}
 
         NonAssertingLabel label;        // The "exit" label
         NonAssertingLabel otherLabel;   // Used for the "else" branch of if-then-else
-        uint32_t framePushed;           // From masm
+        uint32_t stackHeight;           // From BaseStackFrame
         uint32_t stackSize;             // Value stack height
         BCESet bceSafeOnEntry;          // Bounds check info flowing into the item
         BCESet bceSafeOnExit;           // Bounds check info flowing out of the item
         bool deadOnArrival;             // deadCode_ was set on entry to the region
         bool deadThenBranch;            // deadCode_ was set on exit from "then"
     };
 
     struct BaseCompilePolicy
@@ -899,33 +1366,33 @@ class BaseCompiler final : public BaseCo
     // code density and branch prediction friendliness will be less
     // important.
 
     class OutOfLineCode : public TempObject
     {
       private:
         NonAssertingLabel entry_;
         NonAssertingLabel rejoin_;
-        uint32_t framePushed_;
+        uint32_t stackHeight_;
 
       public:
-        OutOfLineCode() : framePushed_(UINT32_MAX) {}
+        OutOfLineCode() : stackHeight_(UINT32_MAX) {}
 
         Label* entry() { return &entry_; }
         Label* rejoin() { return &rejoin_; }
 
-        void setFramePushed(uint32_t framePushed) {
-            MOZ_ASSERT(framePushed_ == UINT32_MAX);
-            framePushed_ = framePushed;
+        void setStackHeight(uint32_t stackHeight) {
+            MOZ_ASSERT(stackHeight_ == UINT32_MAX);
+            stackHeight_ = stackHeight;
         }
 
-        void bind(MacroAssembler& masm) {
-            MOZ_ASSERT(framePushed_ != UINT32_MAX);
-            masm.bind(&entry_);
-            masm.setFramePushed(framePushed_);
+        void bind(BaseStackFrame* fr, MacroAssembler* masm) {
+            MOZ_ASSERT(stackHeight_ != UINT32_MAX);
+            masm->bind(&entry_);
+            fr->setStackHeight(stackHeight_);
         }
 
         // The generate() method must be careful about register use
         // because it will be invoked when there is a register
         // assignment in the BaseCompiler that does not correspond
         // to the available registers when the generated OOL code is
         // executed.  The register allocator *must not* be called.
         //
@@ -935,17 +1402,17 @@ class BaseCompiler final : public BaseCo
         // Input, output, and temp registers are embedded in the OOL
         // object and are known to the code generator.
         //
         // Scratch registers are available to use in OOL code.
         //
         // All other registers must be explicitly saved and restored
         // by the OOL code before being used.
 
-        virtual void generate(MacroAssembler& masm) = 0;
+        virtual void generate(MacroAssembler* masm) = 0;
     };
 
     enum class LatentOp {
         None,
         Compare,
         Eqz
     };
 
@@ -967,45 +1434,41 @@ class BaseCompiler final : public BaseCo
     };
 
     const ModuleEnvironment&    env_;
     BaseOpIter                  iter_;
     const FuncCompileInput&     func_;
     size_t                      lastReadCallSite_;
     TempAllocator&              alloc_;
     const ValTypeVector&        locals_;         // Types of parameters and locals
-    int32_t                     localSize_;      // Size of local area in bytes (stable after beginFunction)
-    int32_t                     varLow_;         // Low byte offset of local area for true locals (not parameters)
-    int32_t                     varHigh_;        // High byte offset + 1 of local area for true locals
-    int32_t                     maxFramePushed_; // Max value of masm.framePushed() observed
     bool                        deadCode_;       // Flag indicating we should decode & discard the opcode
     bool                        debugEnabled_;
     BCESet                      bceSafe_;        // Locals that have been bounds checked and not updated since
     ValTypeVector               SigD_;
     ValTypeVector               SigF_;
     MIRTypeVector               SigP_;
     MIRTypeVector               SigPI_;
     MIRTypeVector               SigPII_;
     MIRTypeVector               SigPIIL_;
     MIRTypeVector               SigPILL_;
     NonAssertingLabel           returnLabel_;
     NonAssertingLabel           stackOverflowLabel_;
-    CodeOffset                  stackAddOffset_;
     CompileMode                 mode_;
 
     LatentOp                    latentOp_;       // Latent operation for branch (seen next)
     ValType                     latentType_;     // Operand type, if latentOp_ is true
     Assembler::Condition        latentIntCmp_;   // Comparison operator, if latentOp_ == Compare, int types
     Assembler::DoubleCondition  latentDoubleCmp_;// Comparison operator, if latentOp_ == Compare, float types
 
     FuncOffsets                 offsets_;
     MacroAssembler&             masm;            // No '_' suffix - too tedious...
     BaseRegAlloc                ra;              // Ditto
-
-    Vector<Local, 8, SystemAllocPolicy> localInfo_;
+    BaseStackFrame              fr;
+
+    BaseStackFrame::LocalVector localInfo_;
     Vector<OutOfLineCode*, 8, SystemAllocPolicy> outOfLine_;
 
     // On specific platforms we sometimes need to use specific registers.
 
 #ifdef JS_CODEGEN_X64
     RegI64 specific_rax;
     RegI64 specific_rcx;
     RegI64 specific_rdx;
@@ -1066,88 +1529,234 @@ class BaseCompiler final : public BaseCo
 
     ////////////////////////////////////////////////////////////
     //
     // Out of line code management.
 
     MOZ_MUST_USE OutOfLineCode* addOutOfLineCode(OutOfLineCode* ool) {
         if (!ool || !outOfLine_.append(ool))
             return nullptr;
-        ool->setFramePushed(masm.framePushed());
+        ool->setStackHeight(fr.stackHeight());
         return ool;
     }
 
     MOZ_MUST_USE bool generateOutOfLineCode() {
         for (uint32_t i = 0; i < outOfLine_.length(); i++) {
             OutOfLineCode* ool = outOfLine_[i];
-            ool->bind(masm);
-            ool->generate(masm);
+            ool->bind(&fr, &masm);
+            ool->generate(&masm);
         }
 
         return !masm.oom();
     }
 
+    // Utility.
+
+    const Local& localFromSlot(uint32_t slot, MIRType type) {
+        MOZ_ASSERT(localInfo_[slot].type == type);
+        return localInfo_[slot];
+    }
+
     ////////////////////////////////////////////////////////////
     //
-    // The stack frame.
-
-    // SP-relative load and store.
-
-    int32_t localOffsetToSPOffset(int32_t offset) {
-        return masm.framePushed() - offset;
-    }
-
-    void storeToFrameI32(Register r, int32_t offset) {
-        masm.store32(r, Address(StackPointer, localOffsetToSPOffset(offset)));
-    }
-
-    void storeToFrameI64(Register64 r, int32_t offset) {
-        masm.store64(r, Address(StackPointer, localOffsetToSPOffset(offset)));
-    }
-
-    void storeToFramePtr(Register r, int32_t offset) {
-        masm.storePtr(r, Address(StackPointer, localOffsetToSPOffset(offset)));
-    }
-
-    void storeToFrameF64(FloatRegister r, int32_t offset) {
-        masm.storeDouble(r, Address(StackPointer, localOffsetToSPOffset(offset)));
-    }
-
-    void storeToFrameF32(FloatRegister r, int32_t offset) {
-        masm.storeFloat32(r, Address(StackPointer, localOffsetToSPOffset(offset)));
-    }
-
-    void loadFromFrameI32(Register r, int32_t offset) {
-        masm.load32(Address(StackPointer, localOffsetToSPOffset(offset)), r);
-    }
-
-    void loadFromFrameI64(Register64 r, int32_t offset) {
-        masm.load64(Address(StackPointer, localOffsetToSPOffset(offset)), r);
-    }
-
-    void loadFromFramePtr(Register r, int32_t offset) {
-        masm.loadPtr(Address(StackPointer, localOffsetToSPOffset(offset)), r);
-    }
-
-    void loadFromFrameF64(FloatRegister r, int32_t offset) {
-        masm.loadDouble(Address(StackPointer, localOffsetToSPOffset(offset)), r);
-    }
-
-    void loadFromFrameF32(FloatRegister r, int32_t offset) {
-        masm.loadFloat32(Address(StackPointer, localOffsetToSPOffset(offset)), r);
-    }
-
-    int32_t frameOffsetFromSlot(uint32_t slot, MIRType type) {
-        MOZ_ASSERT(localInfo_[slot].type() == type);
-        return localInfo_[slot].offs();
+    // High-level register management.
+
+    bool isAvailableI32(RegI32 r) { return ra.isAvailableI32(r); }
+    bool isAvailableI64(RegI64 r) { return ra.isAvailableI64(r); }
+    bool isAvailableF32(RegF32 r) { return ra.isAvailableF32(r); }
+    bool isAvailableF64(RegF64 r) { return ra.isAvailableF64(r); }
+
+    MOZ_MUST_USE RegI32 needI32() { return ra.needI32(); }
+    MOZ_MUST_USE RegI64 needI64() { return ra.needI64(); }
+    MOZ_MUST_USE RegF32 needF32() { return ra.needF32(); }
+    MOZ_MUST_USE RegF64 needF64() { return ra.needF64(); }
+
+    void needI32(RegI32 specific) { ra.needI32(specific); }
+    void needI64(RegI64 specific) { ra.needI64(specific); }
+    void needF32(RegF32 specific) { ra.needF32(specific); }
+    void needF64(RegF64 specific) { ra.needF64(specific); }
+
+#if defined(JS_CODEGEN_ARM)
+    MOZ_MUST_USE RegI64 needI64Pair() { return ra.needI64Pair(); }
+#endif
+
+    void freeI32(RegI32 r) { ra.freeI32(r); }
+    void freeI64(RegI64 r) { ra.freeI64(r); }
+    void freeF32(RegF32 r) { ra.freeF32(r); }
+    void freeF64(RegF64 r) { ra.freeF64(r); }
+
+    void freeI64Except(RegI64 r, RegI32 except) {
+#ifdef JS_PUNBOX64
+        MOZ_ASSERT(r.reg == except);
+#else
+        MOZ_ASSERT(r.high == except || r.low == except);
+        freeI64(r);
+        needI32(except);
+#endif
+    }
+
+    void maybeFreeI32(RegI32 r) {
+        if (r.isValid())
+            freeI32(r);
+    }
+
+    void maybeFreeI64(RegI64 r) {
+        if (r.isValid())
+            freeI64(r);
+    }
+
+    void maybeFreeF64(RegF64 r) {
+        if (r.isValid())
+            freeF64(r);
+    }
+
+    void needI32NoSync(RegI32 r) {
+        MOZ_ASSERT(isAvailableI32(r));
+        needI32(r);
+    }
+
+    // TODO / OPTIMIZE: need2xI32() can be optimized along with needI32()
+    // to avoid sync(). (Bug 1316802)
+
+    void need2xI32(RegI32 r0, RegI32 r1) {
+        needI32(r0);
+        needI32(r1);
+    }
+
+    void need2xI64(RegI64 r0, RegI64 r1) {
+        needI64(r0);
+        needI64(r1);
+    }
+
+    RegI32 fromI64(RegI64 r) {
+        return RegI32(lowPart(r));
+    }
+
+#ifdef JS_64BIT
+    RegI64 fromI32(RegI32 r) {
+        return RegI64(Register64(r));
+    }
+#endif
+
+    RegI64 widenI32(RegI32 r) {
+        MOZ_ASSERT(!isAvailableI32(r));
+#ifdef JS_PUNBOX64
+        return fromI32(r);
+#else
+        RegI32 high = needI32();
+        return RegI64(Register64(high, r));
+#endif
+    }
+
+    RegI32 narrowI64(RegI64 r) {
+#if defined(JS_PUNBOX64)
+        return RegI32(r.reg);
+#else
+        freeI32(RegI32(r.high));
+        return RegI32(r.low);
+#endif
+    }
+
+    RegI32 lowPart(RegI64 r) {
+#ifdef JS_PUNBOX64
+        return RegI32(r.reg);
+#else
+        return RegI32(r.low);
+#endif
+    }
+
+    RegI32 maybeHighPart(RegI64 r) {
+#ifdef JS_PUNBOX64
+        return RegI32::Invalid();
+#else
+        return RegI32(r.high);
+#endif
+    }
+
+    void maybeClearHighPart(RegI64 r) {
+#if !defined(JS_PUNBOX64)
+        moveImm32(0, RegI32(r.high));
+#endif
+    }
+
+    void moveI32(RegI32 src, RegI32 dest) {
+        if (src != dest)
+            masm.move32(src, dest);
+    }
+
+    void moveI64(RegI64 src, RegI64 dest) {
+        if (src != dest)
+            masm.move64(src, dest);
+    }
+
+    void moveF64(RegF64 src, RegF64 dest) {
+        if (src != dest)
+            masm.moveDouble(src, dest);
+    }
+
+    void moveF32(RegF32 src, RegF32 dest) {
+        if (src != dest)
+            masm.moveFloat32(src, dest);
+    }
+
+    void maybeReserveJoinRegI(ExprType type) {
+        if (type == ExprType::I32)
+            needI32(joinRegI32);
+        else if (type == ExprType::I64)
+            needI64(joinRegI64);
+    }
+
+    void maybeUnreserveJoinRegI(ExprType type) {
+        if (type == ExprType::I32)
+            freeI32(joinRegI32);
+        else if (type == ExprType::I64)
+            freeI64(joinRegI64);
+    }
+
+    void maybeReserveJoinReg(ExprType type) {
+        switch (type) {
+          case ExprType::I32:
+            needI32(joinRegI32);
+            break;
+          case ExprType::I64:
+            needI64(joinRegI64);
+            break;
+          case ExprType::F32:
+            needF32(joinRegF32);
+            break;
+          case ExprType::F64:
+            needF64(joinRegF64);
+            break;
+          default:
+            break;
+        }
+    }
+
+    void maybeUnreserveJoinReg(ExprType type) {
+        switch (type) {
+          case ExprType::I32:
+            freeI32(joinRegI32);
+            break;
+          case ExprType::I64:
+            freeI64(joinRegI64);
+            break;
+          case ExprType::F32:
+            freeF32(joinRegF32);
+            break;
+          case ExprType::F64:
+            freeF64(joinRegF64);
+            break;
+          default:
+            break;
+        }
     }
 
     ////////////////////////////////////////////////////////////
     //
-    // Value stack and high-level register allocation.
+    // Value stack and spilling.
     //
     // The value stack facilitates some on-the-fly register allocation
     // and immediate-constant use.  It tracks constants, latent
     // references to locals, register contents, and values on the CPU
     // stack.
     //
     // The stack can be flushed to memory using sync().  This is handy
     // to avoid problems with control flow and messy register usage
@@ -1235,352 +1844,199 @@ class BaseCompiler final : public BaseCo
 
     Vector<Stk, 8, SystemAllocPolicy> stk_;
 
     Stk& push() {
         stk_.infallibleEmplaceBack(Stk());
         return stk_.back();
     }
 
-    RegI32 invalidI32() {
-        return RegI32(Register::Invalid());
-    }
-
-    RegI64 invalidI64() {
-        return RegI64(Register64::Invalid());
-    }
-
-    RegF64 invalidF64() {
-        return RegF64(InvalidFloatReg);
-    }
-
-    RegI32 fromI64(RegI64 r) {
-        return RegI32(lowPart(r));
-    }
-
-    RegI64 widenI32(RegI32 r) {
-        MOZ_ASSERT(!isAvailableI32(r));
-#ifdef JS_PUNBOX64
-        return RegI64(Register64(r));
-#else
-        RegI32 high = needI32();
-        return RegI64(Register64(high, r));
-#endif
-    }
-
-    RegI32 narrowI64(RegI64 r) {
-#if defined(JS_PUNBOX64)
-        return RegI32(r.reg);
-#else
-        freeI32(RegI32(r.high));
-        return RegI32(r.low);
-#endif
-    }
-
-    Register lowPart(RegI64 r) {
-#ifdef JS_PUNBOX64
-        return r.reg;
-#else
-        return r.low;
-#endif
-    }
-
-    Register maybeHighPart(RegI64 r) {
-#ifdef JS_PUNBOX64
-        return Register::Invalid();
-#else
-        return r.high;
-#endif
-    }
-
-    void maybeClearHighPart(RegI64 r) {
-#if !defined(JS_PUNBOX64)
-        masm.move32(Imm32(0), r.high);
-#endif
-    }
-
-    bool isAvailableI32(RegI32 r) { return ra.isAvailableI32(r); }
-    bool isAvailableI64(RegI64 r) { return ra.isAvailableI64(r); }
-    bool isAvailableF32(RegF32 r) { return ra.isAvailableF32(r); }
-    bool isAvailableF64(RegF64 r) { return ra.isAvailableF64(r); }
-
-    MOZ_MUST_USE RegI32 needI32() { return ra.needI32(); }
-    MOZ_MUST_USE RegI64 needI64() { return ra.needI64(); }
-    MOZ_MUST_USE RegF32 needF32() { return ra.needF32(); }
-    MOZ_MUST_USE RegF64 needF64() { return ra.needF64(); }
-
-    void needI32(RegI32 specific) { ra.needI32(specific); }
-    void needI64(RegI64 specific) { ra.needI64(specific); }
-    void needF32(RegF32 specific) { ra.needF32(specific); }
-    void needF64(RegF64 specific) { ra.needF64(specific); }
-
-#if defined(JS_CODEGEN_ARM)
-    MOZ_MUST_USE RegI64 needI64Pair() { return ra.needI64Pair(); }
-#endif
-
-    void freeI32(RegI32 r) { ra.freeI32(r); }
-    void freeI64(RegI64 r) { ra.freeI64(r); }
-    void freeF32(RegF32 r) { ra.freeF32(r); }
-    void freeF64(RegF64 r) { ra.freeF64(r); }
-
-    void freeI64Except(RegI64 r, RegI32 except) {
-#ifdef JS_PUNBOX64
-        MOZ_ASSERT(r.reg == except);
-#else
-        MOZ_ASSERT(r.high == except || r.low == except);
-        freeI64(r);
-        needI32(except);
-#endif
-    }
-
-    void maybeFreeI32(RegI32 r) {
-        if (r != invalidI32())
-            freeI32(r);
-    }
-
-    void maybeFreeI64(RegI64 r) {
-        if (r != invalidI64())
-            freeI64(r);
-    }
-
-    void needI32NoSync(RegI32 r) {
-        MOZ_ASSERT(isAvailableI32(r));
-        needI32(r);
-    }
-
-    // TODO / OPTIMIZE: need2xI32() can be optimized along with needI32()
-    // to avoid sync(). (Bug 1316802)
-
-    void need2xI32(RegI32 r0, RegI32 r1) {
-        needI32(r0);
-        needI32(r1);
-    }
-
-    void need2xI64(RegI64 r0, RegI64 r1) {
-        needI64(r0);
-        needI64(r1);
-    }
-
-    void moveI32(RegI32 src, RegI32 dest) {
-        if (src != dest)
-            masm.move32(src, dest);
-    }
-
-    void moveI64(RegI64 src, RegI64 dest) {
-        if (src != dest)
-            masm.move64(src, dest);
-    }
-
-    void moveF64(RegF64 src, RegF64 dest) {
-        if (src != dest)
-            masm.moveDouble(src, dest);
-    }
-
-    void moveF32(RegF32 src, RegF32 dest) {
-        if (src != dest)
-            masm.moveFloat32(src, dest);
-    }
-
-    void setI64(int64_t v, RegI64 r) {
-        masm.move64(Imm64(v), r);
-    }
-
-    void loadConstI32(Register r, Stk& src) {
-        masm.mov(ImmWord(uint32_t(src.i32val())), r);
-    }
-
-    void loadConstI32(Register r, int32_t v) {
-        masm.mov(ImmWord(uint32_t(v)), r);
-    }
-
-    void loadMemI32(Register r, Stk& src) {
-        loadFromFrameI32(r, src.offs());
-    }
-
-    void loadLocalI32(Register r, Stk& src) {
-        loadFromFrameI32(r, frameOffsetFromSlot(src.slot(), MIRType::Int32));
-    }
-
-    void loadRegisterI32(Register r, Stk& src) {
-        if (src.i32reg() != r)
-            masm.move32(src.i32reg(), r);
-    }
-
-    void loadConstI64(Register64 r, Stk &src) {
-        masm.move64(Imm64(src.i64val()), r);
-    }
-
-    void loadMemI64(Register64 r, Stk& src) {
-        loadFromFrameI64(r, src.offs());
-    }
-
-    void loadLocalI64(Register64 r, Stk& src) {
-        loadFromFrameI64(r, frameOffsetFromSlot(src.slot(), MIRType::Int64));
-    }
-
-    void loadRegisterI64(Register64 r, Stk& src) {
-        if (src.i64reg() != r)
-            masm.move64(src.i64reg(), r);
-    }
-
-    void loadConstF64(FloatRegister r, Stk &src) {
+    void loadConstI32(Stk& src, RegI32 dest) {
+        moveImm32(src.i32val(), dest);
+    }
+
+    void loadMemI32(Stk& src, RegI32 dest) {
+        fr.loadStackI32(src.offs(), dest);
+    }
+
+    void loadLocalI32(Stk& src, RegI32 dest) {
+        fr.loadLocalI32(localFromSlot(src.slot(), MIRType::Int32), dest);
+    }
+
+    void loadRegisterI32(Stk& src, RegI32 dest) {
+        moveI32(src.i32reg(), dest);
+    }
+
+    void loadConstI64(Stk &src, RegI64 dest) {
+        moveImm64(src.i64val(), dest);
+    }
+
+    void loadMemI64(Stk& src, RegI64 dest) {
+        fr.loadStackI64(src.offs(), dest);
+    }
+
+    void loadLocalI64(Stk& src, RegI64 dest) {
+        fr.loadLocalI64(localFromSlot(src.slot(), MIRType::Int64), dest);
+    }
+
+    void loadRegisterI64(Stk& src, RegI64 dest) {
+        moveI64(src.i64reg(), dest);
+    }
+
+    void loadConstF64(Stk &src, RegF64 dest) {
         double d;
         src.f64val(&d);
-        masm.loadConstantDouble(d, r);
-    }
-
-    void loadMemF64(FloatRegister r, Stk& src) {
-        loadFromFrameF64(r, src.offs());
-    }
-
-    void loadLocalF64(FloatRegister r, Stk& src) {
-        loadFromFrameF64(r, frameOffsetFromSlot(src.slot(), MIRType::Double));
-    }
-
-    void loadRegisterF64(FloatRegister r, Stk& src) {
-        if (src.f64reg() != r)
-            masm.moveDouble(src.f64reg(), r);
-    }
-
-    void loadConstF32(FloatRegister r, Stk &src) {
+        masm.loadConstantDouble(d, dest);
+    }
+
+    void loadMemF64(Stk& src, RegF64 dest) {
+        fr.loadStackF64(src.offs(), dest);
+    }
+
+    void loadLocalF64(Stk& src, RegF64 dest) {
+        fr.loadLocalF64(localFromSlot(src.slot(), MIRType::Double), dest);
+    }
+
+    void loadRegisterF64(Stk& src, RegF64 dest) {
+        moveF64(src.f64reg(), dest);
+    }
+
+    void loadConstF32(Stk &src, RegF32 dest) {
         float f;
         src.f32val(&f);
-        masm.loadConstantFloat32(f, r);
-    }
-
-    void loadMemF32(FloatRegister r, Stk& src) {
-        loadFromFrameF32(r, src.offs());
-    }
-
-    void loadLocalF32(FloatRegister r, Stk& src) {
-        loadFromFrameF32(r, frameOffsetFromSlot(src.slot(), MIRType::Float32));
-    }
-
-    void loadRegisterF32(FloatRegister r, Stk& src) {
-        if (src.f32reg() != r)
-            masm.moveFloat32(src.f32reg(), r);
-    }
-
-    void loadI32(Register r, Stk& src) {
+        masm.loadConstantFloat32(f, dest);
+    }
+
+    void loadMemF32(Stk& src, RegF32 dest) {
+        fr.loadStackF32(src.offs(), dest);
+    }
+
+    void loadLocalF32(Stk& src, RegF32 dest) {
+        fr.loadLocalF32(localFromSlot(src.slot(), MIRType::Float32), dest);
+    }
+
+    void loadRegisterF32(Stk& src, RegF32 dest) {
+        moveF32(src.f32reg(), dest);
+    }
+
+    void loadI32(Stk& src, RegI32 dest) {
         switch (src.kind()) {
           case Stk::ConstI32:
-            loadConstI32(r, src);
+            loadConstI32(src, dest);
             break;
           case Stk::MemI32:
-            loadMemI32(r, src);
+            loadMemI32(src, dest);
             break;
           case Stk::LocalI32:
-            loadLocalI32(r, src);
+            loadLocalI32(src, dest);
             break;
           case Stk::RegisterI32:
-            loadRegisterI32(r, src);
+            loadRegisterI32(src, dest);
             break;
           case Stk::None:
           default:
             MOZ_CRASH("Compiler bug: Expected I32 on stack");
         }
     }
 
-    void loadI64(Register64 r, Stk& src) {
+    void loadI64(Stk& src, RegI64 dest) {
         switch (src.kind()) {
           case Stk::ConstI64:
-            loadConstI64(r, src);
+            loadConstI64(src, dest);
             break;
           case Stk::MemI64:
-            loadMemI64(r, src);
+            loadMemI64(src, dest);
             break;
           case Stk::LocalI64:
-            loadLocalI64(r, src);
+            loadLocalI64(src, dest);
             break;
           case Stk::RegisterI64:
-            loadRegisterI64(r, src);
+            loadRegisterI64(src, dest);
             break;
           case Stk::None:
           default:
             MOZ_CRASH("Compiler bug: Expected I64 on stack");
         }
     }
 
 #if !defined(JS_PUNBOX64)
-    void loadI64Low(Register r, Stk& src) {
+    void loadI64Low(Stk& src, RegI32 dest) {
         switch (src.kind()) {
           case Stk::ConstI64:
-            masm.move32(Imm64(src.i64val()).low(), r);
+            moveImm32(int32_t(src.i64val()), dest);
             break;
           case Stk::MemI64:
-            loadFromFrameI32(r, src.offs() - INT64LOW_OFFSET);
+            fr.loadStackI64Low(src.offs(), dest);
             break;
           case Stk::LocalI64:
-            loadFromFrameI32(r, frameOffsetFromSlot(src.slot(), MIRType::Int64) - INT64LOW_OFFSET);
+            fr.loadLocalI64Low(localFromSlot(src.slot(), MIRType::Int64), dest);
             break;
           case Stk::RegisterI64:
-            if (src.i64reg().low != r)
-                masm.move32(src.i64reg().low, r);
+            moveI32(RegI32(src.i64reg().low), dest);
             break;
           case Stk::None:
           default:
             MOZ_CRASH("Compiler bug: Expected I64 on stack");
         }
     }
 
-    void loadI64High(Register r, Stk& src) {
+    void loadI64High(Stk& src, RegI32 dest) {
         switch (src.kind()) {
           case Stk::ConstI64:
-            masm.move32(Imm64(src.i64val()).hi(), r);
+            moveImm32(int32_t(src.i64val() >> 32), dest);
             break;
           case Stk::MemI64:
-            loadFromFrameI32(r, src.offs() - INT64HIGH_OFFSET);
+            fr.loadStackI64High(src.offs(), dest);
             break;
           case Stk::LocalI64:
-            loadFromFrameI32(r, frameOffsetFromSlot(src.slot(), MIRType::Int64) - INT64HIGH_OFFSET);
+            fr.loadLocalI64High(localFromSlot(src.slot(), MIRType::Int64), dest);
             break;
           case Stk::RegisterI64:
-            if (src.i64reg().high != r)
-                masm.move32(src.i64reg().high, r);
+            moveI32(RegI32(src.i64reg().high), dest);
             break;
           case Stk::None:
           default:
             MOZ_CRASH("Compiler bug: Expected I64 on stack");
         }
     }
 #endif
 
-    void loadF64(FloatRegister r, Stk& src) {
+    void loadF64(Stk& src, RegF64 dest) {
         switch (src.kind()) {
           case Stk::ConstF64:
-            loadConstF64(r, src);
+            loadConstF64(src, dest);
             break;
           case Stk::MemF64:
-            loadMemF64(r, src);
+            loadMemF64(src, dest);
             break;
           case Stk::LocalF64:
-            loadLocalF64(r, src);
+            loadLocalF64(src, dest);
             break;
           case Stk::RegisterF64:
-            loadRegisterF64(r, src);
+            loadRegisterF64(src, dest);
             break;
           case Stk::None:
           default:
             MOZ_CRASH("Compiler bug: expected F64 on stack");
         }
     }
 
-    void loadF32(FloatRegister r, Stk& src) {
+    void loadF32(Stk& src, RegF32 dest) {
         switch (src.kind()) {
           case Stk::ConstF32:
-            loadConstF32(r, src);
+            loadConstF32(src, dest);
             break;
           case Stk::MemF32:
-            loadMemF32(r, src);
+            loadMemF32(src, dest);
             break;
           case Stk::LocalF32:
-            loadLocalF32(r, src);
+            loadLocalF32(src, dest);
             break;
           case Stk::RegisterF32:
-            loadRegisterF32(r, src);
+            loadRegisterF32(src, dest);
             break;
           case Stk::None:
           default:
             MOZ_CRASH("Compiler bug: expected F32 on stack");
         }
     }
 
     // Flush all local and register value stack elements to memory.
@@ -1617,87 +2073,84 @@ class BaseCompiler final : public BaseCo
             }
         }
 
         for (size_t i = start; i < lim; i++) {
             Stk& v = stk_[i];
             switch (v.kind()) {
               case Stk::LocalI32: {
                 ScratchI32 scratch(*this);
-                loadLocalI32(scratch, v);
-                masm.Push(scratch);
-                v.setOffs(Stk::MemI32, masm.framePushed());
+                loadLocalI32(v, scratch);
+                uint32_t offs = fr.pushPtr(scratch);
+                v.setOffs(Stk::MemI32, offs);
                 break;
               }
               case Stk::RegisterI32: {
-                masm.Push(v.i32reg());
+                uint32_t offs = fr.pushPtr(v.i32reg());
                 freeI32(v.i32reg());
-                v.setOffs(Stk::MemI32, masm.framePushed());
+                v.setOffs(Stk::MemI32, offs);
                 break;
               }
               case Stk::LocalI64: {
                 ScratchI32 scratch(*this);
 #ifdef JS_PUNBOX64
-                loadI64(Register64(scratch), v);
-                masm.Push(scratch);
+                loadI64(v, fromI32(scratch));
+                uint32_t offs = fr.pushPtr(scratch);
 #else
-                int32_t offset = frameOffsetFromSlot(v.slot(), MIRType::Int64);
-                loadFromFrameI32(scratch, offset - INT64HIGH_OFFSET);
-                masm.Push(scratch);
-                loadFromFrameI32(scratch, offset - INT64LOW_OFFSET);
-                masm.Push(scratch);
-#endif
-                v.setOffs(Stk::MemI64, masm.framePushed());
+                fr.loadLocalI64High(localFromSlot(v.slot(), MIRType::Int64), scratch);
+                fr.pushPtr(scratch);
+                fr.loadLocalI64Low(localFromSlot(v.slot(), MIRType::Int64), scratch);
+                uint32_t offs = fr.pushPtr(scratch);
+#endif
+                v.setOffs(Stk::MemI64, offs);
                 break;
               }
               case Stk::RegisterI64: {
 #ifdef JS_PUNBOX64
-                masm.Push(v.i64reg().reg);
+                uint32_t offs = fr.pushPtr(v.i64reg().reg);
                 freeI64(v.i64reg());
 #else
-                masm.Push(v.i64reg().high);
-                masm.Push(v.i64reg().low);
+                fr.pushPtr(v.i64reg().high);
+                uint32_t offs = fr.pushPtr(v.i64reg().low);
                 freeI64(v.i64reg());
 #endif
-                v.setOffs(Stk::MemI64, masm.framePushed());
+                v.setOffs(Stk::MemI64, offs);
                 break;
               }
               case Stk::LocalF64: {
                 ScratchF64 scratch(*this);
-                loadF64(scratch, v);
-                masm.Push(scratch);
-                v.setOffs(Stk::MemF64, masm.framePushed());
+                loadF64(v, scratch);
+                uint32_t offs = fr.pushDouble(scratch);
+                v.setOffs(Stk::MemF64, offs);
                 break;
               }
               case Stk::RegisterF64: {
-                masm.Push(v.f64reg());
+                uint32_t offs = fr.pushDouble(v.f64reg());
                 freeF64(v.f64reg());
-                v.setOffs(Stk::MemF64, masm.framePushed());
+                v.setOffs(Stk::MemF64, offs);
                 break;
               }
               case Stk::LocalF32: {
                 ScratchF32 scratch(*this);
-                loadF32(scratch, v);
-                masm.Push(scratch);
-                v.setOffs(Stk::MemF32, masm.framePushed());
+                loadF32(v, scratch);
+                uint32_t offs = fr.pushFloat32(scratch);
+                v.setOffs(Stk::MemF32, offs);
                 break;
               }
               case Stk::RegisterF32: {
-                masm.Push(v.f32reg());
+                uint32_t offs = fr.pushFloat32(v.f32reg());
                 freeF32(v.f32reg());
-                v.setOffs(Stk::MemF32, masm.framePushed());
+                v.setOffs(Stk::MemF32, offs);
                 break;
               }
               default: {
                 break;
               }
             }
         }
-
-        maxFramePushed_ = Max(maxFramePushed_, int32_t(masm.framePushed()));
     }
 
     // This is an optimization used to avoid calling sync() for
     // setLocal(): if the local does not exist unresolved on the stack
     // then we can skip the sync.
 
     bool hasLocal(uint32_t slot) {
         for (size_t i = stk_.length(); i > 0; i--) {
@@ -1786,32 +2239,33 @@ class BaseCompiler final : public BaseCo
         x.setSlot(Stk::LocalF64, slot);
     }
 
     void pushLocalF32(uint32_t slot) {
         Stk& x = push();
         x.setSlot(Stk::LocalF32, slot);
     }
 
-    // PRIVATE.  Call only from other popI32() variants.
-    // v must be the stack top.
-
-    void popI32(Stk& v, RegI32 r) {
+    // Call only from other popI32() variants.
+    // v must be the stack top.  May pop the CPU stack.
+
+    void popI32(Stk& v, RegI32 dest) {
+        MOZ_ASSERT(&v == &stk_.back());
         switch (v.kind()) {
           case Stk::ConstI32:
-            loadConstI32(r, v);
+            loadConstI32(v, dest);
             break;
           case Stk::LocalI32:
-            loadLocalI32(r, v);
+            loadLocalI32(v, dest);
             break;
           case Stk::MemI32:
-            masm.Pop(r);
+            fr.popPtr(dest);
             break;
           case Stk::RegisterI32:
-            loadRegisterI32(r, v);
+            loadRegisterI32(v, dest);
             break;
           case Stk::None:
           default:
             MOZ_CRASH("Compiler bug: expected int on stack");
         }
     }
 
     MOZ_MUST_USE RegI32 popI32() {
@@ -1834,37 +2288,38 @@ class BaseCompiler final : public BaseCo
             if (v.kind() == Stk::RegisterI32)
                 freeI32(v.i32reg());
         }
 
         stk_.popBack();
         return specific;
     }
 
-    // PRIVATE.  Call only from other popI64() variants.
-    // v must be the stack top.
-
-    void popI64(Stk& v, RegI64 r) {
+    // Call only from other popI64() variants.
+    // v must be the stack top.  May pop the CPU stack.
+
+    void popI64(Stk& v, RegI64 dest) {
+        MOZ_ASSERT(&v == &stk_.back());
         switch (v.kind()) {
           case Stk::ConstI64:
-            loadConstI64(r, v);
+            loadConstI64(v, dest);
             break;
           case Stk::LocalI64:
-            loadLocalI64(r, v);
+            loadLocalI64(v, dest);
             break;
           case Stk::MemI64:
 #ifdef JS_PUNBOX64
-            masm.Pop(r.reg);
+            fr.popPtr(dest.reg);
 #else
-            masm.Pop(r.low);
-            masm.Pop(r.high);
+            fr.popPtr(dest.low);
+            fr.popPtr(dest.high);
 #endif
             break;
           case Stk::RegisterI64:
-            loadRegisterI64(r, v);
+            loadRegisterI64(v, dest);
             break;
           case Stk::None:
           default:
             MOZ_CRASH("Compiler bug: expected long on stack");
         }
     }
 
     MOZ_MUST_USE RegI64 popI64() {
@@ -1892,32 +2347,33 @@ class BaseCompiler final : public BaseCo
             if (v.kind() == Stk::RegisterI64)
                 freeI64(v.i64reg());
         }
 
         stk_.popBack();
         return specific;
     }
 
-    // PRIVATE.  Call only from other popF64() variants.
-    // v must be the stack top.
-
-    void popF64(Stk& v, RegF64 r) {
+    // Call only from other popF64() variants.
+    // v must be the stack top.  May pop the CPU stack.
+
+    void popF64(Stk& v, RegF64 dest) {
+        MOZ_ASSERT(&v == &stk_.back());
         switch (v.kind()) {
           case Stk::ConstF64:
-            loadConstF64(r, v);
+            loadConstF64(v, dest);
             break;
           case Stk::LocalF64:
-            loadLocalF64(r, v);
+            loadLocalF64(v, dest);
             break;
           case Stk::MemF64:
-            masm.Pop(r);
+            fr.popDouble(dest);
             break;
           case Stk::RegisterF64:
-            loadRegisterF64(r, v);
+            loadRegisterF64(v, dest);
             break;
           case Stk::None:
           default:
             MOZ_CRASH("Compiler bug: expected double on stack");
         }
     }
 
     MOZ_MUST_USE RegF64 popF64() {
@@ -1940,32 +2396,33 @@ class BaseCompiler final : public BaseCo
             if (v.kind() == Stk::RegisterF64)
                 freeF64(v.f64reg());
         }
 
         stk_.popBack();
         return specific;
     }
 
-    // PRIVATE.  Call only from other popF32() variants.
-    // v must be the stack top.
-
-    void popF32(Stk& v, RegF32 r) {
+    // Call only from other popF32() variants.
+    // v must be the stack top.  May pop the CPU stack.
+
+    void popF32(Stk& v, RegF32 dest) {
+        MOZ_ASSERT(&v == &stk_.back());
         switch (v.kind()) {
           case Stk::ConstF32:
-            loadConstF32(r, v);
+            loadConstF32(v, dest);
             break;
           case Stk::LocalF32:
-            loadLocalF32(r, v);
+            loadLocalF32(v, dest);
             break;
           case Stk::MemF32:
-            masm.Pop(r);
+            fr.popFloat32(dest);
             break;
           case Stk::RegisterF32:
-            loadRegisterF32(r, v);
+            loadRegisterF32(v, dest);
             break;
           case Stk::None:
           default:
             MOZ_CRASH("Compiler bug: expected float on stack");
         }
     }
 
     MOZ_MUST_USE RegF32 popF32() {
@@ -2183,115 +2640,30 @@ class BaseCompiler final : public BaseCo
             freeF64(r->f64());
             break;
           case AnyReg::F32:
             freeF32(r->f32());
             break;
         }
     }
 
-    void maybeReserveJoinRegI(ExprType type) {
-        if (type == ExprType::I32)
-            needI32(joinRegI32);
-        else if (type == ExprType::I64)
-            needI64(joinRegI64);
-    }
-
-    void maybeUnreserveJoinRegI(ExprType type) {
-        if (type == ExprType::I32)
-            freeI32(joinRegI32);
-        else if (type == ExprType::I64)
-            freeI64(joinRegI64);
-    }
-
-    void maybeReserveJoinReg(ExprType type) {
-        switch (type) {
-          case ExprType::I32:
-            needI32(joinRegI32);
-            break;
-          case ExprType::I64:
-            needI64(joinRegI64);
-            break;
-          case ExprType::F32:
-            needF32(joinRegF32);
-            break;
-          case ExprType::F64:
-            needF64(joinRegF64);
-            break;
-          default:
-            break;
-        }
-    }
-
-    void maybeUnreserveJoinReg(ExprType type) {
-        switch (type) {
-          case ExprType::I32:
-            freeI32(joinRegI32);
-            break;
-          case ExprType::I64:
-            freeI64(joinRegI64);
-            break;
-          case ExprType::F32:
-            freeF32(joinRegF32);
-            break;
-          case ExprType::F64:
-            freeF64(joinRegF64);
-            break;
-          default:
-            break;
-        }
-    }
-
     // Return the amount of execution stack consumed by the top numval
     // values on the value stack.
 
     size_t stackConsumed(size_t numval) {
         size_t size = 0;
         MOZ_ASSERT(numval <= stk_.length());
         for (uint32_t i = stk_.length() - 1; numval > 0; numval--, i--) {
-            // The size computations come from the implementation of Push() in
-            // MacroAssembler-x86-shared.cpp and MacroAssembler-arm-shared.cpp,
-            // and from VFPRegister::size() in Architecture-arm.h.
-            //
-            // On ARM unlike on x86 we push a single for float.
-
             Stk& v = stk_[i];
             switch (v.kind()) {
-              case Stk::MemI32:
-#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_ARM)
-                size += sizeof(intptr_t);
-#else
-                MOZ_CRASH("BaseCompiler platform hook: stackConsumed I32");
-#endif
-                break;
-              case Stk::MemI64:
-#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_ARM)
-                size += sizeof(int64_t);
-#else
-                MOZ_CRASH("BaseCompiler platform hook: stackConsumed I64");
-#endif
-                break;
-              case Stk::MemF64:
-#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_ARM)
-                size += sizeof(double);
-#else
-                MOZ_CRASH("BaseCompiler platform hook: stackConsumed F64");
-#endif
-                break;
-              case Stk::MemF32:
-#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)
-                size += sizeof(double);
-#elif defined(JS_CODEGEN_ARM)
-                size += sizeof(float);
-#else
-                MOZ_CRASH("BaseCompiler platform hook: stackConsumed F32");
-#endif
-                break;
-              default:
-                break;
+              case Stk::MemI32: size += BaseStackFrame::StackSizeOfPtr;    break;
+              case Stk::MemI64: size += BaseStackFrame::StackSizeOfInt64;  break;
+              case Stk::MemF64: size += BaseStackFrame::StackSizeOfDouble; break;
+              case Stk::MemF32: size += BaseStackFrame::StackSizeOfFloat;  break;
+              default: break;
             }
         }
         return size;
     }
 
     void popValueStackTo(uint32_t stackSize) {
         for (uint32_t i = stk_.length(); i > stackSize; i--) {
             Stk& v = stk_[i-1];
@@ -2314,48 +2686,20 @@ class BaseCompiler final : public BaseCo
         }
         stk_.shrinkTo(stackSize);
     }
 
     void popValueStackBy(uint32_t items) {
         popValueStackTo(stk_.length() - items);
     }
 
-    // Before branching to an outer control label, pop the execution
-    // stack to the level expected by that region, but do not free the
-    // stack as that will happen as compilation leaves the block.
-
-    void popStackBeforeBranch(uint32_t framePushed) {
-        uint32_t frameHere = masm.framePushed();
-        if (frameHere > framePushed)
-            masm.addPtr(ImmWord(frameHere - framePushed), StackPointer);
-    }
-
-    bool willPopStackBeforeBranch(uint32_t framePushed) {
-        uint32_t frameHere = masm.framePushed();
-        return frameHere > framePushed;
-    }
-
-    // Before exiting a nested control region, pop the execution stack
-    // to the level expected by the nesting region, and free the
-    // stack.
-
-    void popStackOnBlockExit(uint32_t framePushed) {
-        uint32_t frameHere = masm.framePushed();
-        if (frameHere > framePushed) {
-            if (deadCode_)
-                masm.adjustStack(frameHere - framePushed);
-            else
-                masm.freeStack(frameHere - framePushed);
-        }
-    }
-
-    void popStackIfMemory() {
+    void dropValue() {
         if (peek(0).isMem())
-            masm.freeStack(stackConsumed(1));
+            fr.popBytes(stackConsumed(1));
+        popValueStackBy(1);
     }
 
     // Peek at the stack, for calls.
 
     Stk& peek(uint32_t relativeDepth) {
         return stk_[stk_.length()-1-relativeDepth];
     }
 
@@ -2391,19 +2735,19 @@ class BaseCompiler final : public BaseCo
 
     ////////////////////////////////////////////////////////////
     //
     // Control stack
 
     void initControl(Control& item)
     {
         // Make sure the constructor was run properly
-        MOZ_ASSERT(item.framePushed == UINT32_MAX && item.stackSize == UINT32_MAX);
-
-        item.framePushed = masm.framePushed();
+        MOZ_ASSERT(item.stackHeight == UINT32_MAX && item.stackSize == UINT32_MAX);
+
+        item.stackHeight = fr.stackHeight();
         item.stackSize = stk_.length();
         item.deadOnArrival = deadCode_;
         item.bceSafeOnEntry = bceSafe_;
     }
 
     Control& controlItem() {
         return iter_.controlItem();
     }
@@ -2431,89 +2775,77 @@ class BaseCompiler final : public BaseCo
     //
     // Function prologue and epilogue.
 
     void beginFunction() {
         JitSpew(JitSpew_Codegen, "# Emitting wasm baseline code");
 
         SigIdDesc sigId = env_.funcSigs[func_.index]->id;
         if (mode_ == CompileMode::Tier1)
-            GenerateFunctionPrologue(masm, localSize_, sigId, &offsets_, mode_, func_.index);
+            GenerateFunctionPrologue(masm, fr.initialSize(), sigId, &offsets_, mode_, func_.index);
         else
-            GenerateFunctionPrologue(masm, localSize_, sigId, &offsets_);
-
-        MOZ_ASSERT(masm.framePushed() == uint32_t(localSize_));
-
-        maxFramePushed_ = localSize_;
+            GenerateFunctionPrologue(masm, fr.initialSize(), sigId, &offsets_);
+
+        fr.endFunctionPrologue();
 
         if (debugEnabled_) {
             // Initialize funcIndex and flag fields of DebugFrame.
             size_t debugFrame = masm.framePushed() - DebugFrame::offsetOfFrame();
             masm.store32(Imm32(func_.index),
                          Address(masm.getStackPointer(), debugFrame + DebugFrame::offsetOfFuncIndex()));
             masm.storePtr(ImmWord(0),
                           Address(masm.getStackPointer(), debugFrame + DebugFrame::offsetOfFlagsWord()));
         }
 
-        // We won't know until after we've generated code how big the frame will
-        // be (we may need arbitrary spill slots and outgoing param slots) so
-        // emit a patchable add that is patched in endFunction().
-        //
-        // ScratchReg may be used by branchPtr(), so use ABINonArgReg0/1 for
-        // temporaries.
-
-        stackAddOffset_ = masm.add32ToPtrWithPatch(StackPointer, ABINonArgReg0);
-        masm.wasmEmitStackCheck(ABINonArgReg0, ABINonArgReg1, &stackOverflowLabel_);
+        fr.allocStack(ABINonArgReg0, ABINonArgReg1, &stackOverflowLabel_);
 
         // Copy arguments from registers to stack.
 
         const ValTypeVector& args = sig().args();
 
         for (ABIArgIter<const ValTypeVector> i(args); !i.done(); i++) {
             Local& l = localInfo_[i.index()];
             switch (i.mirType()) {
               case MIRType::Int32:
                 if (i->argInRegister())
-                    storeToFrameI32(i->gpr(), l.offs());
+                    fr.storeLocalI32(RegI32(i->gpr()), l);
                 break;
               case MIRType::Int64:
                 if (i->argInRegister())
-                    storeToFrameI64(i->gpr64(), l.offs());
+                    fr.storeLocalI64(RegI64(i->gpr64()), l);
                 break;
               case MIRType::Double:
                 if (i->argInRegister())
-                    storeToFrameF64(i->fpu(), l.offs());
+                    fr.storeLocalF64(RegF64(i->fpu()), l);
                 break;
               case MIRType::Float32:
                 if (i->argInRegister())
-                    storeToFrameF32(i->fpu(), l.offs());
+                    fr.storeLocalF32(RegF32(i->fpu()), l);
                 break;
               default:
                 MOZ_CRASH("Function argument type");
             }
         }
 
-        if (varLow_ < varHigh_)
-            emitInitStackLocals();
+        fr.zeroLocals(ra);
 
         if (debugEnabled_)
             insertBreakablePoint(CallSiteDesc::EnterFrame);
     }
 
     void saveResult() {
         MOZ_ASSERT(debugEnabled_);
         size_t debugFrameOffset = masm.framePushed() - DebugFrame::offsetOfFrame();
-        Address resultsAddress(StackPointer, debugFrameOffset + DebugFrame::offsetOfResults());
+        Address resultsAddress(masm.getStackPointer(), debugFrameOffset + DebugFrame::offsetOfResults());
         switch (sig().ret()) {
           case ExprType::Void:
             break;
           case ExprType::I32:
             masm.store32(RegI32(ReturnReg), resultsAddress);
             break;
-
           case ExprType::I64:
             masm.store64(RegI64(ReturnReg64), resultsAddress);
             break;
           case ExprType::F64:
             masm.storeDouble(RegF64(ReturnDoubleReg), resultsAddress);
             break;
           case ExprType::F32:
             masm.storeFloat32(RegF32(ReturnFloat32Reg), resultsAddress);
@@ -2521,17 +2853,17 @@ class BaseCompiler final : public BaseCo
           default:
             MOZ_CRASH("Function return type");
         }
     }
 
     void restoreResult() {
         MOZ_ASSERT(debugEnabled_);
         size_t debugFrameOffset = masm.framePushed() - DebugFrame::offsetOfFrame();
-        Address resultsAddress(StackPointer, debugFrameOffset + DebugFrame::offsetOfResults());
+        Address resultsAddress(masm.getStackPointer(), debugFrameOffset + DebugFrame::offsetOfResults());
         switch (sig().ret()) {
           case ExprType::Void:
             break;
           case ExprType::I32:
             masm.load32(resultsAddress, RegI32(ReturnReg));
             break;
           case ExprType::I64:
             masm.load64(resultsAddress, RegI64(ReturnReg64));
@@ -2548,69 +2880,66 @@ class BaseCompiler final : public BaseCo
     }
 
     bool endFunction() {
         // Always branch to stackOverflowLabel_ or returnLabel_.
         masm.breakpoint();
 
         // Patch the add in the prologue so that it checks against the correct
         // frame size. Flush the constant pool in case it needs to be patched.
-        MOZ_ASSERT(maxFramePushed_ >= localSize_);
         masm.flush();
 
         // Precondition for patching.
         if (masm.oom())
             return false;
-        masm.patchAdd32ToPtr(stackAddOffset_, Imm32(-int32_t(maxFramePushed_ - localSize_)));
+
+        fr.patchAllocStack();
 
         // Since we just overflowed the stack, to be on the safe side, pop the
         // stack so that, when the trap exit stub executes, it is a safe
         // distance away from the end of the native stack. If debugEnabled_ is
         // set, we pop all locals space except allocated for DebugFrame to
         // maintain the invariant that, when debugEnabled_, all wasm::Frames
         // are valid wasm::DebugFrames which is observable by WasmHandleThrow.
         masm.bind(&stackOverflowLabel_);
-        int32_t debugFrameReserved = debugEnabled_ ? DebugFrame::offsetOfFrame() : 0;
-        MOZ_ASSERT(localSize_ >= debugFrameReserved);
-        if (localSize_ > debugFrameReserved)
-            masm.addToStackPtr(Imm32(localSize_ - debugFrameReserved));
+        uint32_t debugFrameReserved = debugEnabled_ ? DebugFrame::offsetOfFrame() : 0;
+        MOZ_ASSERT(fr.initialSize() >= debugFrameReserved);
+        if (fr.initialSize() > debugFrameReserved)
+            masm.addToStackPtr(Imm32(fr.initialSize() - debugFrameReserved));
         BytecodeOffset prologueTrapOffset(func_.lineOrBytecode);
         masm.jump(TrapDesc(prologueTrapOffset, Trap::StackOverflow, debugFrameReserved));
 
         masm.bind(&returnLabel_);
 
         if (debugEnabled_) {
             // Store and reload the return value from DebugFrame::return so that
             // it can be clobbered, and/or modified by the debug trap.
             saveResult();
             insertBreakablePoint(CallSiteDesc::Breakpoint);
             insertBreakablePoint(CallSiteDesc::LeaveFrame);
             restoreResult();
         }
 
-        GenerateFunctionEpilogue(masm, localSize_, &offsets_);
+        GenerateFunctionEpilogue(masm, fr.initialSize(), &offsets_);
 
 #if defined(JS_ION_PERF)
         // FIXME - profiling code missing.  No bug for this.
 
         // Note the end of the inline code and start of the OOL code.
         //gen->perfSpewer().noteEndInlineCode(masm);
 #endif
 
         if (!generateOutOfLineCode())
             return false;
 
         masm.wasmEmitTrapOutOfLineCode();
 
         offsets_.end = masm.currentOffset();
 
-        // A frame greater than 256KB is implausible, probably an attack,
-        // so fail the compilation.
-
-        if (maxFramePushed_ > 256 * 1024)
+        if (!fr.checkStackHeight())
             return false;
 
         return !masm.oom();
     }
 
     //////////////////////////////////////////////////////////////////////
     //
     // Calls.
@@ -2653,24 +2982,27 @@ class BaseCompiler final : public BaseCo
             call.hardFP = true;
 # else
             call.hardFP = false;
 # endif
             call.abi.setUseHardFp(call.hardFP);
 #endif
         }
 
+        // Use masm.framePushed() because the value we want here does not depend
+        // on the height of the frame's stack area, but the actual size of the
+        // allocated frame.
         call.frameAlignAdjustment = ComputeByteAlignment(masm.framePushed() + sizeof(Frame),
                                                          JitStackAlignment);
     }
 
     void endCall(FunctionCall& call, size_t stackSpace)
     {
         size_t adjustment = call.stackArgAreaSize + call.frameAlignAdjustment;
-        masm.freeStack(stackSpace + adjustment);
+        fr.freeArgAreaAndPopBytes(adjustment, stackSpace);
 
         if (call.reloadMachineStateAfter) {
             // On x86 there are no pinned registers, so don't waste time
             // reloading the Tls.
 #ifndef JS_CODEGEN_X86
             masm.loadWasmTlsRegFromFrame();
             masm.loadWasmPinnedRegsFromTls();
 #endif
@@ -2692,18 +3024,17 @@ class BaseCompiler final : public BaseCo
         return AlignBytes(i.stackBytesConsumedSoFar(), 16u);
     }
 
     void startCallArgs(FunctionCall& call, size_t stackArgAreaSize)
     {
         call.stackArgAreaSize = stackArgAreaSize;
 
         size_t adjustment = call.stackArgAreaSize + call.frameAlignAdjustment;
-        if (adjustment)
-            masm.reserveStack(adjustment);
+        fr.allocArgArea(adjustment);
     }
 
     const ABIArg reservePointerArgument(FunctionCall& call) {
         return call.abi.next(MIRType::Pointer);
     }
 
     // TODO / OPTIMIZE (Bug 1316821): Note passArg is used only in one place.
     // (Or it was, until Luke wandered through, but that can be fixed again.)
@@ -2727,93 +3058,91 @@ class BaseCompiler final : public BaseCo
     // args based on the info we read.
 
     void passArg(FunctionCall& call, ValType type, Stk& arg) {
         switch (type) {
           case ValType::I32: {
             ABIArg argLoc = call.abi.next(MIRType::Int32);
             if (argLoc.kind() == ABIArg::Stack) {
                 ScratchI32 scratch(*this);
-                loadI32(scratch, arg);
-                masm.store32(scratch, Address(StackPointer, argLoc.offsetFromArgBase()));
+                loadI32(arg, scratch);
+                masm.store32(scratch, Address(masm.getStackPointer(), argLoc.offsetFromArgBase()));
             } else {
-                loadI32(argLoc.gpr(), arg);
+                loadI32(arg, RegI32(argLoc.gpr()));
             }
             break;
           }
           case ValType::I64: {
             ABIArg argLoc = call.abi.next(MIRType::Int64);
             if (argLoc.kind() == ABIArg::Stack) {
                 ScratchI32 scratch(*this);
-#if defined(JS_CODEGEN_X64)
-                loadI64(Register64(scratch), arg);
-                masm.movq(scratch, Operand(StackPointer, argLoc.offsetFromArgBase()));
-#elif defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_ARM)
-                loadI64Low(scratch, arg);
-                masm.store32(scratch, LowWord(Address(StackPointer, argLoc.offsetFromArgBase())));
-                loadI64High(scratch, arg);
-                masm.store32(scratch, HighWord(Address(StackPointer, argLoc.offsetFromArgBase())));
+#ifdef JS_PUNBOX64
+                loadI64(arg, fromI32(scratch));
+                masm.storePtr(scratch, Address(masm.getStackPointer(), argLoc.offsetFromArgBase()));
 #else
-                MOZ_CRASH("BaseCompiler platform hook: passArg I64");
+                loadI64Low(arg, scratch);
+                masm.store32(scratch, LowWord(Address(masm.getStackPointer(), argLoc.offsetFromArgBase())));
+                loadI64High(arg, scratch);
+                masm.store32(scratch, HighWord(Address(masm.getStackPointer(), argLoc.offsetFromArgBase())));
 #endif
             } else {
-                loadI64(argLoc.gpr64(), arg);
+                loadI64(arg, RegI64(argLoc.gpr64()));
             }
             break;
           }
           case ValType::F64: {
             ABIArg argLoc = call.abi.next(MIRType::Double);
             switch (argLoc.kind()) {
               case ABIArg::Stack: {
                 ScratchF64 scratch(*this);
-                loadF64(scratch, arg);
-                masm.storeDouble(scratch, Address(StackPointer, argLoc.offsetFromArgBase()));
+                loadF64(arg, scratch);
+                masm.storeDouble(scratch, Address(masm.getStackPointer(), argLoc.offsetFromArgBase()));
                 break;
               }
 #if defined(JS_CODEGEN_REGISTER_PAIR)
               case ABIArg::GPR_PAIR: {
 # ifdef JS_CODEGEN_ARM
                 ScratchF64 scratch(*this);
-                loadF64(scratch, arg);
+                loadF64(arg, scratch);
                 masm.ma_vxfer(scratch, argLoc.evenGpr(), argLoc.oddGpr());
                 break;
 # else
                 MOZ_CRASH("BaseCompiler platform hook: passArg F64 pair");
 # endif
               }
 #endif
               case ABIArg::FPU: {
-                loadF64(argLoc.fpu(), arg);
+                loadF64(arg, RegF64(argLoc.fpu()));
                 break;
               }
               case ABIArg::GPR: {
                 MOZ_CRASH("Unexpected parameter passing discipline");
               }
               case ABIArg::Uninitialized:
                 MOZ_CRASH("Uninitialized ABIArg kind");
             }
             break;
           }
           case ValType::F32: {
             ABIArg argLoc = call.abi.next(MIRType::Float32);
             switch (argLoc.kind()) {
               case ABIArg::Stack: {
                 ScratchF32 scratch(*this);
-                loadF32(scratch, arg);
-                masm.storeFloat32(scratch, Address(StackPointer, argLoc.offsetFromArgBase()));
+                loadF32(arg, scratch);
+                masm.storeFloat32(scratch, Address(masm.getStackPointer(), argLoc.offsetFromArgBase()));
                 break;
               }
               case ABIArg::GPR: {
                 ScratchF32 scratch(*this);
-                loadF32(scratch, arg);
+                loadF32(arg, scratch);
                 masm.moveFloat32ToGPR(scratch, argLoc.gpr());
                 break;
               }
               case ABIArg::FPU: {
-                loadF32(argLoc.fpu(), arg);
+                loadF32(arg, RegF32(argLoc.fpu()));
                 break;
               }
 #if defined(JS_CODEGEN_REGISTER_PAIR)
               case ABIArg::GPR_PAIR: {
                 MOZ_CRASH("Unexpected parameter passing discipline");
               }
 #endif
               case ABIArg::Uninitialized:
@@ -2842,17 +3171,17 @@ class BaseCompiler final : public BaseCo
     void callIndirect(uint32_t sigIndex, Stk& indexVal, const FunctionCall& call)
     {
         const SigWithId& sig = env_.sigs[sigIndex];
         MOZ_ASSERT(sig.id.kind() != SigIdDesc::Kind::None);
 
         MOZ_ASSERT(env_.tables.length() == 1);
         const TableDesc& table = env_.tables[0];
 
-        loadI32(WasmTableCallIndexReg, indexVal);
+        loadI32(indexVal, RegI32(WasmTableCallIndexReg));
 
         CallSiteDesc desc(call.lineOrBytecode, CallSiteDesc::Dynamic);
         CalleeDesc callee = CalleeDesc::wasmTable(table, sig.id);
         masm.wasmCallIndirect(desc, callee, NeedsBoundsCheck(true));
     }
 
     // Precondition: sync()
 
@@ -2877,39 +3206,54 @@ class BaseCompiler final : public BaseCo
         CallSiteDesc desc(call.lineOrBytecode, CallSiteDesc::Symbolic);
         masm.wasmCallBuiltinInstanceMethod(desc, instanceArg, builtin);
     }
 
     //////////////////////////////////////////////////////////////////////
     //
     // Sundry low-level code generators.
 
+    // The compiler depends on moveImm32() clearing the high bits of a 64-bit
+    // register on 64-bit systems.
+
+    void moveImm32(int32_t v, RegI32 dest) {
+        masm.mov(ImmWord(uint32_t(v)), dest);
+    }
+
+    void moveImm64(int64_t v, RegI64 dest) {
+        masm.move64(Imm64(v), dest);
+    }
+
+    void moveImmF32(float f, RegF32 dest) {
+        masm.loadConstantFloat32(f, dest);
+    }
+
+    void moveImmF64(double d, RegF64 dest) {
+        masm.loadConstantDouble(d, dest);
+    }
+
     void addInterruptCheck()
     {
         // Always use signals for interrupts with Asm.JS/Wasm
         MOZ_RELEASE_ASSERT(HaveSignalHandlers());
     }
 
-    void jumpTable(LabelVector& labels, Label* theTable) {
+    void jumpTable(const LabelVector& labels, Label* theTable) {
         // Flush constant pools to ensure that the table is never interrupted by
         // constant pool entries.
         masm.flush();
 
         masm.bind(theTable);
 
-#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_ARM)
         for (uint32_t i = 0; i < labels.length(); i++) {
             CodeLabel cl;
             masm.writeCodePointer(cl.patchAt());
             cl.target()->bind(labels[i].offset());
             masm.addCodeLabel(cl);
         }
-#else
-        MOZ_CRASH("BaseCompiler platform hook: jumpTable");
-#endif
     }
 
     void tableSwitch(Label* theTable, RegI32 switchValue, Label* dispatchCode) {
         masm.bind(dispatchCode);
 
 #if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)
         ScratchI32 scratch(*this);
         CodeLabel tableCl;
@@ -2984,17 +3328,17 @@ class BaseCompiler final : public BaseCo
         if (call.usesSystemAbi && !call.hardFP)
             masm.ma_vxfer(r0, r1, rv);
 #endif
         return rv;
     }
 
     void returnCleanup(bool popStack) {
         if (popStack)
-            popStackBeforeBranch(controlOutermost().framePushed);
+            fr.popStackBeforeBranch(controlOutermost().stackHeight);
         masm.jump(&returnLabel_);
     }
 
     void pop2xI32ForIntMulDiv(RegI32* r0, RegI32* r1) {
 #if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
         // srcDest must be eax, and edx will be clobbered.
         need2xI32(specific_eax, specific_edx);
         *r1 = popI32();
@@ -3026,17 +3370,17 @@ class BaseCompiler final : public BaseCo
         masm.branchTest64(Assembler::Zero, r, r, scratch, trap(Trap::IntegerDivideByZero));
     }
 
     void checkDivideSignedOverflowI32(RegI32 rhs, RegI32 srcDest, Label* done, bool zeroOnOverflow) {
         Label notMin;
         masm.branch32(Assembler::NotEqual, srcDest, Imm32(INT32_MIN), &notMin);
         if (zeroOnOverflow) {
             masm.branch32(Assembler::NotEqual, rhs, Imm32(-1), &notMin);
-            masm.move32(Imm32(0), srcDest);
+            moveImm32(0, srcDest);
             masm.jump(done);
         } else {
             masm.branch32(Assembler::Equal, rhs, Imm32(-1), trap(Trap::IntegerOverflow));
         }
         masm.bind(&notMin);
     }
 
     void checkDivideSignedOverflowI64(RegI64 rhs, RegI64 srcDest, Label* done, bool zeroOnOverflow) {
@@ -3127,68 +3471,70 @@ class BaseCompiler final : public BaseCo
         *r1 = widenI32(specific_ecx);
         *r1 = popI64ToSpecific(*r1);
         *r0 = popI64();
 #else
         pop2xI64(r0, r1);
 #endif
     }
 
-    bool rotate64NeedsTemp() const {
+    RegI32 needRotate64Temp() {
 #if defined(JS_CODEGEN_X86)
-        return true;
+        return needI32();
+#elif defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM)
+        return RegI32::Invalid();
 #else
-        return false;
+        MOZ_CRASH("BaseCompiler platform hook: needRotate64Temp");
 #endif
     }
 
     void maskShiftCount32(RegI32 r) {
 #if defined(JS_CODEGEN_ARM)
         masm.and32(Imm32(31), r);
 #endif
     }
 
-    bool popcnt32NeedsTemp() const {
+    RegI32 needPopcnt32Temp() {
 #if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
-        return !AssemblerX86Shared::HasPOPCNT();
+        return AssemblerX86Shared::HasPOPCNT() ? RegI32::Invalid() : needI32();
 #elif defined(JS_CODEGEN_ARM)
-        return true;
+        return needI32();
 #else
-        MOZ_CRASH("BaseCompiler platform hook: popcnt32NeedsTemp");
-#endif
-    }
-
-    bool popcnt64NeedsTemp() const {
+        MOZ_CRASH("BaseCompiler platform hook: needPopcnt32Temp");
+#endif
+    }
+
+    RegI32 needPopcnt64Temp() {
 #if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
-        return !AssemblerX86Shared::HasPOPCNT();
+        return AssemblerX86Shared::HasPOPCNT() ? RegI32::Invalid() : needI32();
 #elif defined(JS_CODEGEN_ARM)
-        return true;
+        return needI32();
 #else
-        MOZ_CRASH("BaseCompiler platform hook: popcnt64NeedsTemp");
+        MOZ_CRASH("BaseCompiler platform hook: needPopcnt64Temp");
 #endif
     }
 
     RegI64 popI32ForSignExtendI64() {
 #if defined(JS_CODEGEN_X86)
         need2xI32(specific_edx, specific_eax);
         RegI32 r0 = popI32ToSpecific(specific_eax);
-        RegI64 x0 = RegI64(Register64(specific_edx, specific_eax));
+        RegI64 x0 = specific_edx_eax;
         (void)r0;               // x0 is the widening of r0
 #else
         RegI32 r0 = popI32();
         RegI64 x0 = widenI32(r0);
 #endif
         return x0;
     }
 
     RegI64 popI64ForSignExtendI64() {
 #if defined(JS_CODEGEN_X86)
         need2xI32(specific_edx, specific_eax);
         // Low on top, high underneath
-        return popI64ToSpecific(RegI64(Register64(specific_edx, specific_eax)));
+        return popI64ToSpecific(specific_edx_eax);
 #else
         return popI64();
 #endif
     }
 
     class OutOfLineTruncateF32OrF64ToI32 : public OutOfLineCode
     {
         AnyReg src;
@@ -3199,75 +3545,69 @@ class BaseCompiler final : public BaseCo
       public:
         OutOfLineTruncateF32OrF64ToI32(AnyReg src, RegI32 dest, bool isUnsigned, BytecodeOffset off)
           : src(src),
             dest(dest),
             isUnsigned(isUnsigned),
             off(off)
         {}
 
-        virtual void generate(MacroAssembler& masm) {
+        virtual void generate(MacroAssembler* masm) {
             bool isFloat = src.tag == AnyReg::F32;
             FloatRegister fsrc = isFloat ? static_cast<FloatRegister>(src.f32())
                                          : static_cast<FloatRegister>(src.f64());
 #if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
             if (isFloat)
-                masm.outOfLineWasmTruncateFloat32ToInt32(fsrc, isUnsigned, off, rejoin());
+                masm->outOfLineWasmTruncateFloat32ToInt32(fsrc, isUnsigned, off, rejoin());
             else
-                masm.outOfLineWasmTruncateDoubleToInt32(fsrc, isUnsigned, off, rejoin());
+                masm->outOfLineWasmTruncateDoubleToInt32(fsrc, isUnsigned, off, rejoin());
 #elif defined(JS_CODEGEN_ARM)
-            masm.outOfLineWasmTruncateToIntCheck(fsrc,
-                                                 isFloat ? MIRType::Float32 : MIRType::Double,
-                                                 MIRType::Int32, isUnsigned, rejoin(), off);
+            masm->outOfLineWasmTruncateToIntCheck(fsrc,
+                                                  isFloat ? MIRType::Float32 : MIRType::Double,
+                                                  MIRType::Int32, isUnsigned, rejoin(), off);
 #else
             (void)isUnsigned;
             (void)off;
             (void)isFloat;
             (void)fsrc;
             MOZ_CRASH("BaseCompiler platform hook: OutOfLineTruncateF32OrF64ToI32 wasm");
 #endif
         }
     };
 
     MOZ_MUST_USE bool truncateF32ToI32(RegF32 src, RegI32 dest, bool isUnsigned) {
         BytecodeOffset off = bytecodeOffset();
-        OutOfLineCode* ool;
-#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM)
-        ool = new(alloc_) OutOfLineTruncateF32OrF64ToI32(AnyReg(src), dest, isUnsigned, off);
-        ool = addOutOfLineCode(ool);
+        OutOfLineCode* ool =
+            addOutOfLineCode(new(alloc_) OutOfLineTruncateF32OrF64ToI32(AnyReg(src),
+                                                                        dest,
+                                                                        isUnsigned,
+                                                                        off));
         if (!ool)
             return false;
         if (isUnsigned)
             masm.wasmTruncateFloat32ToUInt32(src, dest, ool->entry());
         else
             masm.wasmTruncateFloat32ToInt32(src, dest, ool->entry());
-#else
-        (void)off;
-        MOZ_CRASH("BaseCompiler platform hook: truncateF32ToI32 wasm");
-#endif
         masm.bind(ool->rejoin());
         return true;
     }
 
     MOZ_MUST_USE bool truncateF64ToI32(RegF64 src, RegI32 dest, bool isUnsigned) {
         BytecodeOffset off = bytecodeOffset();
-        OutOfLineCode* ool;
-#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM)
-        ool = new(alloc_) OutOfLineTruncateF32OrF64ToI32(AnyReg(src), dest, isUnsigned, off);
-        ool = addOutOfLineCode(ool);
+        OutOfLineCode* ool =
+            addOutOfLineCode(new(alloc_) OutOfLineTruncateF32OrF64ToI32(AnyReg(src),
+                                                                        dest,
+                                                                        isUnsigned,
+                                                                        off));
         if (!ool)
             return false;
         if (isUnsigned)
             masm.wasmTruncateDoubleToUInt32(src, dest, ool->entry());
         else
             masm.wasmTruncateDoubleToInt32(src, dest, ool->entry());
-#else
-        (void)off;
-        MOZ_CRASH("BaseCompiler platform hook: truncateF64ToI32 wasm");
-#endif
         masm.bind(ool->rejoin());
         return true;
     }
 
     // This does not generate a value; if the truncation failed then it traps.
 
     class OutOfLineTruncateCheckF32OrF64ToI64 : public OutOfLineCode
     {
@@ -3277,43 +3617,51 @@ class BaseCompiler final : public BaseCo
 
       public:
         OutOfLineTruncateCheckF32OrF64ToI64(AnyReg src, bool isUnsigned, BytecodeOffset off)
           : src(src),
             isUnsigned(isUnsigned),
             off(off)
         {}
 
-        virtual void generate(MacroAssembler& masm) {
+        virtual void generate(MacroAssembler* masm) {
 #if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
             if (src.tag == AnyReg::F32)
-                masm.outOfLineWasmTruncateFloat32ToInt64(src.f32(), isUnsigned, off, rejoin());
+                masm->outOfLineWasmTruncateFloat32ToInt64(src.f32(), isUnsigned, off, rejoin());
             else if (src.tag == AnyReg::F64)
-                masm.outOfLineWasmTruncateDoubleToInt64(src.f64(), isUnsigned, off, rejoin());
+                masm->outOfLineWasmTruncateDoubleToInt64(src.f64(), isUnsigned, off, rejoin());
             else
                 MOZ_CRASH("unexpected type");
 #elif defined(JS_CODEGEN_ARM)
             if (src.tag == AnyReg::F32)
-                masm.outOfLineWasmTruncateToIntCheck(src.f32(), MIRType::Float32,
-                                                     MIRType::Int64, isUnsigned, rejoin(), off);
+                masm->outOfLineWasmTruncateToIntCheck(src.f32(), MIRType::Float32,
+                                                      MIRType::Int64, isUnsigned, rejoin(), off);
             else if (src.tag == AnyReg::F64)
-                masm.outOfLineWasmTruncateToIntCheck(src.f64(), MIRType::Double, MIRType::Int64,
-                                                     isUnsigned, rejoin(), off);
+                masm->outOfLineWasmTruncateToIntCheck(src.f64(), MIRType::Double, MIRType::Int64,
+                                                      isUnsigned, rejoin(), off);
             else
                 MOZ_CRASH("unexpected type");
 #else
             (void)src;
             (void)isUnsigned;
             (void)off;
             MOZ_CRASH("BaseCompiler platform hook: OutOfLineTruncateCheckF32OrF64ToI64");
 #endif
         }
     };
 
 #ifndef RABALDR_FLOAT_TO_I64_CALLOUT
+    MOZ_MUST_USE RegF64 needTempForFloatingToI64(bool isUnsigned) {
+# if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        if (isUnsigned)
+            return needF64();
+# endif
+        return RegF64::Invalid();
+    }
+
     MOZ_MUST_USE bool truncateF32ToI64(RegF32 src, RegI64 dest, bool isUnsigned, RegF64 temp) {
 # if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)
         OutOfLineCode* ool =
             addOutOfLineCode(new (alloc_) OutOfLineTruncateCheckF32OrF64ToI64(AnyReg(src),
                                                                               isUnsigned,
                                                                               bytecodeOffset()));
         if (!ool)
             return false;
@@ -3346,24 +3694,25 @@ class BaseCompiler final : public BaseCo
 # else
         MOZ_CRASH("BaseCompiler platform hook: truncateF64ToI64");
 # endif
         return true;
     }
 #endif // RABALDR_FLOAT_TO_I64_CALLOUT
 
 #ifndef RABALDR_I64_TO_FLOAT_CALLOUT
-    bool convertI64ToFloatNeedsTemp(ValType to, bool isUnsigned) const {
+    RegI32 needConvertI64ToFloatTemp(ValType to, bool isUnsigned) {
 # if defined(JS_CODEGEN_X86)
-        return isUnsigned &&
-               ((to == ValType::F64 && AssemblerX86Shared::HasSSE3()) ||
-               to == ValType::F32);
+        bool needs = isUnsigned &&
+                     ((to == ValType::F64 && AssemblerX86Shared::HasSSE3()) ||
+                      to == ValType::F32);
 # else
-        return isUnsigned;
+        bool needs = isUnsigned;
 # 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);
@@ -3380,44 +3729,38 @@ class BaseCompiler final : public BaseCo
             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) {
-#if defined(JS_CODEGEN_X64)
-        masm.cmpq(rhs.reg, lhs.reg);
-        masm.emitSet(cond, dest);
-#elif defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_ARM)
+#ifdef JS_PUNBOX64
+        masm.cmpPtrSet(cond, lhs.reg, rhs.reg, dest);
+#else
         // TODO / OPTIMIZE (Bug 1316822): This is pretty branchy, we should be
         // able to do better.
         Label done, condTrue;
         masm.branch64(cond, lhs, rhs, &condTrue);
-        masm.move32(Imm32(0), dest);
+        moveImm32(0, dest);
         masm.jump(&done);
         masm.bind(&condTrue);
-        masm.move32(Imm32(1), dest);
+        moveImm32(1, dest);
         masm.bind(&done);
-#else
-        MOZ_CRASH("BaseCompiler platform hook: cmp64Set");
 #endif
     }
 
     void eqz64(RegI64 src, RegI32 dest) {
-#if defined(JS_CODEGEN_X64)
-        masm.cmpq(Imm32(0), src.reg);
-        masm.emitSet(Assembler::Equal, dest);
-#elif defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_ARM)
+#ifdef JS_PUNBOX64
+        masm.cmpPtrSet(Assembler::Equal, src.reg, ImmWord(0), dest);
+#else
         masm.or32(src.high, src.low);
         masm.cmp32(src.low, Imm32(0));
         masm.emitSet(Assembler::Equal, dest);
-#else
-        MOZ_CRASH("BaseCompiler platform hook: eqz64");
 #endif
     }
 
     void unreachableTrap()
     {
         masm.jump(trap(Trap::Unreachable));
 #ifdef DEBUG
         masm.breakpoint();
@@ -3563,21 +3906,21 @@ class BaseCompiler final : public BaseCo
                               trap(Trap::UnalignedAccess));
         }
 
         // Ensure no tls if we don't need it.
 
 #ifdef WASM_HUGE_MEMORY
         // We have HeapReg and no bounds checking and need load neither
         // memoryBase nor boundsCheckLimit from tls.
-        MOZ_ASSERT_IF(check->omitBoundsCheck, tls == invalidI32());
+        MOZ_ASSERT_IF(check->omitBoundsCheck, tls.isInvalid());
 #endif
 #ifdef JS_CODEGEN_ARM
         // We have HeapReg on ARM and don't need to load the memoryBase from tls.
-        MOZ_ASSERT_IF(check->omitBoundsCheck, tls == invalidI32());
+        MOZ_ASSERT_IF(check->omitBoundsCheck, tls.isInvalid());
 #endif
 
         // Bounds check if required.
 
 #ifndef WASM_HUGE_MEMORY
         if (!check->omitBoundsCheck) {
             masm.wasmBoundsCheck(Assembler::AboveOrEqual, ptr,
                                  Address(tls, offsetof(TlsData, boundsCheckLimit)),
@@ -3647,17 +3990,17 @@ class BaseCompiler final : public BaseCo
 #elif defined(JS_CODEGEN_ARM)
         if (IsUnaligned(*access)) {
             switch (dest.tag) {
               case AnyReg::I64:
                 masm.wasmUnalignedLoadI64(*access, HeapReg, ptr, ptr, dest.i64(), tmp1);
                 break;
               case AnyReg::F32:
                 masm.wasmUnalignedLoadFP(*access, HeapReg, ptr, ptr, dest.f32(), tmp1, tmp2,
-                                         Register::Invalid());
+                                         RegI32::Invalid());
                 break;
               case AnyReg::F64:
                 masm.wasmUnalignedLoadFP(*access, HeapReg, ptr, ptr, dest.f64(), tmp1, tmp2, tmp3);
                 break;
               default:
                 masm.wasmUnalignedLoad(*access, HeapReg, ptr, ptr, dest.i32(), tmp1);
                 break;
             }
@@ -3669,38 +4012,39 @@ class BaseCompiler final : public BaseCo
         }
 #else
         MOZ_CRASH("BaseCompiler platform hook: load");
 #endif
 
         return true;
     }
 
-    void needStoreTemps(const MemoryAccessDesc& access, ValType srcType, RegI32* tmp) {
+    RegI32 needStoreTemp(const MemoryAccessDesc& access, ValType srcType) {
 #if defined(JS_CODEGEN_ARM)
         if (IsUnaligned(access) && srcType != ValType::I32)
-            *tmp = needI32();
-#endif
+            return needI32();
+#endif
+        return RegI32::Invalid();
     }
 
     // ptr and src must not be the same register.
     // This may destroy ptr and src.
     MOZ_MUST_USE bool store(MemoryAccessDesc* access, AccessCheck* check, RegI32 tls, RegI32 ptr,
                             AnyReg src, RegI32 tmp)
     {
         prepareMemoryAccess(access, check, tls, ptr);
 
         // Emit the store
 #if defined(JS_CODEGEN_X64)
-        MOZ_ASSERT(tmp == invalidI32());
+        MOZ_ASSERT(tmp.isInvalid());
         Operand dstAddr(HeapReg, ptr, TimesOne, access->offset());
 
         masm.wasmStore(*access, src.any(), dstAddr);
 #elif defined(JS_CODEGEN_X86)
-        MOZ_ASSERT(tmp == invalidI32());
+        MOZ_ASSERT(tmp.isInvalid());
         masm.addPtr(Address(tls, offsetof(TlsData, memoryBase)), ptr);
         Operand dstAddr(ptr, access->offset());
 
         if (access->type() == Scalar::Int64) {
             masm.wasmStoreI64(*access, src.i64(), dstAddr);
         } else {
             AnyRegister value;
             if (src.tag == AnyReg::I64) {
@@ -3727,22 +4071,22 @@ class BaseCompiler final : public BaseCo
                 break;
               case AnyReg::F32:
                 masm.wasmUnalignedStoreFP(*access, src.f32(), HeapReg, ptr, ptr, tmp);
                 break;
               case AnyReg::F64:
                 masm.wasmUnalignedStoreFP(*access, src.f64(), HeapReg, ptr, ptr, tmp);
                 break;
               default:
-                MOZ_ASSERT(tmp == invalidI32());
+                MOZ_ASSERT(tmp.isInvalid());
                 masm.wasmUnalignedStore(*access, src.i32(), HeapReg, ptr, ptr);
                 break;
             }
         } else {
-            MOZ_ASSERT(tmp == invalidI32());
+            MOZ_ASSERT(tmp.isInvalid());
             if (access->type() == Scalar::Int64)
                 masm.wasmStoreI64(*access, src.i64(), HeapReg, ptr, ptr);
             else if (src.tag == AnyReg::I64)
                 masm.wasmStore(*access, AnyRegister(src.i64().low), HeapReg, ptr, ptr);
             else
                 masm.wasmStore(*access, src.any(), HeapReg, ptr, ptr);
         }
 #else
@@ -3755,17 +4099,17 @@ class BaseCompiler final : public BaseCo
 #if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM)
 
 # define ATOMIC_PTR(name, access, tls, ptr)                             \
     BaseIndex name(HeapReg, (ptr), TimesOne, (access)->offset())
 
 #elif defined(JS_CODEGEN_X86)
 
 # define ATOMIC_PTR(name, access, tls, ptr)                             \
-    MOZ_ASSERT((tls) != invalidI32());                                  \
+    MOZ_ASSERT((tls).isValid());                                        \
     masm.addPtr(Address((tls), offsetof(TlsData, memoryBase)), (ptr));  \
     Address name((ptr), (access)->offset())
 
 #else
 
 # define ATOMIC_PTR(name, access, tls, ptr)                       \
     MOZ_CRASH("BaseCompiler platform hook: address computation"); \
     Address srcAddr
@@ -3812,27 +4156,28 @@ class BaseCompiler final : public BaseCo
         freeI32(specific_ecx);
 #elif defined(JS_CODEGEN_ARM)
         freeI64(rv);
 #else
         MOZ_CRASH("BaseCompiler porting interface: xchg64");
 #endif
     }
 
-    void needAtomicRMWTemps(AtomicOp op, MemoryAccessDesc* access, RegI32* tmp) {
+    RegI32 needAtomicRMWTemp(AtomicOp op, MemoryAccessDesc* access) {
 #if defined(JS_CODEGEN_X86)
         // Handled specially in atomicRMW
         if (access->byteSize() == 1)
-            return;
+            return RegI32::Invalid();
 #endif
 #if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
         if (op != AtomicFetchAddOp && op != AtomicFetchSubOp)
-            *tmp = needI32();
+            return needI32();
+        return RegI32::Invalid();
 #elif defined(JS_CODEGEN_ARM)
-        *tmp = needI32();
+        return needI32();
 #else
         MOZ_CRASH("BaseCompiler platform hook: atomicRMWTemps");
 #endif
     }
 
     void
     atomicRMW(AtomicOp op, MemoryAccessDesc* access, AccessCheck* check, RegI32 tls, RegI32 ptr,
               RegI32 rv, RegI32 rd, RegI32 tmp)
@@ -3841,20 +4186,20 @@ class BaseCompiler final : public BaseCo
         ATOMIC_PTR(srcAddr, access, tls, ptr);
 
         switch (access->type()) {
           case Scalar::Uint8: {
             RegI32 v = rv;
             RegI32 d = rd;
 #ifdef JS_CODEGEN_X86
             // The temp, if used, must be a byte register.
-            MOZ_ASSERT(tmp == invalidI32());
+            MOZ_ASSERT(tmp.isInvalid());
             ScratchEBX scratch(*this);
             if (op != AtomicFetchAddOp && op != AtomicFetchSubOp)
-                tmp = RegI32(scratch);
+                tmp = scratch;
 #endif
             switch (op) {
               case AtomicFetchAddOp: masm.atomicFetchAdd8ZeroExtend(v, srcAddr, tmp, d); break;
               case AtomicFetchSubOp: masm.atomicFetchSub8ZeroExtend(v, srcAddr, tmp, d); break;
               case AtomicFetchAndOp: masm.atomicFetchAnd8ZeroExtend(v, srcAddr, tmp, d); break;
               case AtomicFetchOrOp:  masm.atomicFetchOr8ZeroExtend(v, srcAddr, tmp, d); break;
               case AtomicFetchXorOp: masm.atomicFetchXor8ZeroExtend(v, srcAddr, tmp, d); break;
               default: MOZ_CRASH("No such op");
@@ -3885,24 +4230,25 @@ class BaseCompiler final : public BaseCo
             break;
           }
           default: {
             MOZ_CRASH("Bad type for atomic operation");
           }
         }
     }
 
-    void needAtomicRMW64Temps(AtomicOp op, RegI64* tmp) {
+    RegI64 needAtomicRMW64Temp(AtomicOp op) {
 #if defined(JS_CODEGEN_X86)
         MOZ_CRASH("Do not call on x86");
 #elif defined(JS_CODEGEN_X64)
         if (op != AtomicFetchAddOp && op != AtomicFetchSubOp)
-            *tmp = needI64();
+            return needI64();
+        return RegI64::Invalid();
 #elif defined(JS_CODEGEN_ARM)
-        *tmp = needI64Pair();
+        return needI64Pair();
 #else
         MOZ_CRASH("BaseCompiler platform hook: atomicRMW64Temps");
 #endif
     }
 
     // On x86, T is Address.  On other platforms, it is Register64.
     // U is BaseIndex or Address.
     template <typename T, typename U>
@@ -3928,17 +4274,17 @@ class BaseCompiler final : public BaseCo
         switch (access->type()) {
           case Scalar::Uint8: {
 #if defined(JS_CODEGEN_X86)
             ScratchEBX scratch(*this);
             MOZ_ASSERT(rd == specific_eax);
             if (!ra.isSingleByteI32(rnew)) {
                 // The replacement value must have a byte persona.
                 masm.movl(rnew, scratch);
-                rnew = RegI32(scratch);
+                rnew = scratch;
             }
 #endif
             masm.compareExchange8ZeroExtend(srcAddr, rexpect, rnew, rd);
             break;
           }
           case Scalar::Uint16:
             masm.compareExchange16ZeroExtend(srcAddr, rexpect, rnew, rd);
             break;
@@ -4070,16 +4416,19 @@ class BaseCompiler final : public BaseCo
         return iter_.done();
     }
 
     BytecodeOffset bytecodeOffset() const {
         return iter_.bytecodeOffset();
     }
 
     TrapDesc trap(Trap t) const {
+        // Use masm.framePushed() because the value needed by the trap machinery
+        // is the size of the frame overall, not the height of the stack area of
+        // the frame.
         return TrapDesc(bytecodeOffset(), t, masm.framePushed());
     }
 
     ////////////////////////////////////////////////////////////
     //
     // Machinery for optimized conditional branches.
     //
     // To disable this optimization it is enough always to return false from
@@ -4107,24 +4456,24 @@ class BaseCompiler final : public BaseCo
             } f32;
             struct {
                 RegF64 lhs;
                 RegF64 rhs;
             } f64;
         };
 
         Label* const label;        // The target of the branch, never NULL
-        const int32_t framePushed; // Either NoPop, or the value to pop to along the taken edge
+        const int32_t stackHeight; // Either NoPop, or the value to pop to along the taken edge
         const bool invertBranch;   // If true, invert the sense of the branch
         const ExprType resultType; // The result propagated along the edges, or Void
 
-        explicit BranchState(Label* label, int32_t framePushed = NoPop,
+        explicit BranchState(Label* label, int32_t stackHeight = NoPop,
                              uint32_t invertBranch = false, ExprType resultType = ExprType::Void)
           : label(label),
-            framePushed(framePushed),
+            stackHeight(stackHeight),
             invertBranch(invertBranch),
             resultType(resultType)
         {}
     };
 
     void setLatentCompare(Assembler::Condition compareOp, ValType operandType) {
         latentOp_ = LatentOp::Compare;
         latentType_ = operandType;
@@ -4170,30 +4519,30 @@ class BaseCompiler final : public BaseCo
         masm.branch64(c, lhs, rhs, l);
     }
 
     // Emit a conditional branch that optionally and optimally cleans up the CPU
     // stack before we branch.
     //
     // Cond is either Assembler::Condition or Assembler::DoubleCondition.
     //
-    // Lhs is Register, Register64, or FloatRegister.
+    // Lhs is RegI32, RegI64, or RegF32, or RegF64.
     //
     // Rhs is either the same as Lhs, or an immediate expression compatible with
     // Lhs "when applicable".
 
     template<typename Cond, typename Lhs, typename Rhs>
     void jumpConditionalWithJoinReg(BranchState* b, Cond cond, Lhs lhs, Rhs rhs)
     {
         Maybe<AnyReg> r = popJoinRegUnlessVoid(b->resultType);
 
-        if (b->framePushed != BranchState::NoPop && willPopStackBeforeBranch(b->framePushed)) {
+        if (b->stackHeight != BranchState::NoPop && fr.willPopStackBeforeBranch(b->stackHeight)) {
             Label notTaken;
             branchTo(b->invertBranch ? cond : Assembler::InvertCondition(cond), lhs, rhs, &notTaken);
-            popStackBeforeBranch(b->framePushed);
+            fr.popStackBeforeBranch(b->stackHeight);
             masm.jump(b->label);
             masm.bind(&notTaken);
         } else {
             branchTo(b->invertBranch ? Assembler::InvertCondition(cond) : cond, lhs, rhs, b->label);
         }
 
         pushJoinRegUnlessVoid(r);
     }
@@ -4496,17 +4845,17 @@ BaseCompiler::emitMultiplyI64()
     // srcDest must be rax, and rdx will be clobbered.
     need2xI64(specific_rax, specific_rdx);
     r1 = popI64();
     r0 = popI64ToSpecific(specific_rax);
     freeI64(specific_rdx);
 #elif defined(JS_CODEGEN_X86)
     need2xI32(specific_eax, specific_edx);
     r1 = popI64();
-    r0 = popI64ToSpecific(RegI64(Register64(specific_edx, specific_eax)));
+    r0 = popI64ToSpecific(specific_edx_eax);
     temp = needI32();
 #else
     pop2xI64(&r0, &r1);
     temp = needI32();
 #endif
     masm.mul64(r1, r0, temp);
     maybeFreeI32(temp);
     freeI64(r1);
@@ -4664,17 +5013,17 @@ BaseCompiler::emitQuotientI64()
 {
 # ifdef JS_PUNBOX64
     int64_t c;
     uint_fast8_t power;
     if (popConstPositivePowerOfTwoI64(&c, &power, 0)) {
         if (power != 0) {
             RegI64 r = popI64();
             Label positive;
-            masm.branchTest64(Assembler::NotSigned, r, r, Register::Invalid(),
+            masm.branchTest64(Assembler::NotSigned, r, r, RegI32::Invalid(),
                               &positive);
             masm.add64(Imm64(c-1), r);
             masm.bind(&positive);
 
             masm.rshift64Arithmetic(Imm32(power & 63), r);
             pushI64(r);
         }
     } else {
@@ -4722,18 +5071,17 @@ BaseCompiler::emitRemainderI64()
     int64_t c;
     uint_fast8_t power;
     if (popConstPositivePowerOfTwoI64(&c, &power, 1)) {
         RegI64 r = popI64();
         RegI64 temp = needI64();
         moveI64(r, temp);
 
         Label positive;
-        masm.branchTest64(Assembler::NotSigned, temp, temp,
-                          Register::Invalid(), &positive);
+        masm.branchTest64(Assembler::NotSigned, temp, temp, RegI32::Invalid(), &positive);
         masm.add64(Imm64(c-1), temp);
         masm.bind(&positive);
 
         masm.rshift64Arithmetic(Imm32(power & 63), temp);
         masm.lshift64(Imm32(power & 63), temp);
         masm.sub64(temp, r);
         freeI64(temp);
 
@@ -4800,68 +5148,68 @@ BaseCompiler::emitMinF32()
 {
     RegF32 r0, r1;
     pop2xF32(&r0, &r1);
     // Convert signaling NaN to quiet NaNs.
     //
     // TODO / OPTIMIZE (bug 1316824): Don't do this if one of the operands
     // is known to be a constant.
     ScratchF32 zero(*this);
-    masm.loadConstantFloat32(0.f, zero);
+    moveImmF32(0.f, zero);
     masm.subFloat32(zero, r0);
     masm.subFloat32(zero, r1);
     masm.minFloat32(r1, r0, HandleNaNSpecially(true));
     freeF32(r1);
     pushF32(r0);
 }
 
 void
 BaseCompiler::emitMaxF32()
 {
     RegF32 r0, r1;
     pop2xF32(&r0, &r1);
     // Convert signaling NaN to quiet NaNs.
     //
     // TODO / OPTIMIZE (bug 1316824): see comment in emitMinF32.
     ScratchF32 zero(*this);
-    masm.loadConstantFloat32(0.f, zero);
+    moveImmF32(0.f, zero);
     masm.subFloat32(zero, r0);
     masm.subFloat32(zero, r1);
     masm.maxFloat32(r1, r0, HandleNaNSpecially(true));
     freeF32(r1);
     pushF32(r0);
 }
 
 void
 BaseCompiler::emitMinF64()
 {
     RegF64 r0, r1;
     pop2xF64(&r0, &r1);
     // Convert signaling NaN to quiet NaNs.
     //
     // TODO / OPTIMIZE (bug 1316824): see comment in emitMinF32.
     ScratchF64 zero(*this);
-    masm.loadConstantDouble(0, zero);
+    moveImmF64(0, zero);
     masm.subDouble(zero, r0);
     masm.subDouble(zero, r1);
     masm.minDouble(r1, r0, HandleNaNSpecially(true));
     freeF64(r1);
     pushF64(r0);
 }
 
 void
 BaseCompiler::emitMaxF64()
 {
     RegF64 r0, r1;
     pop2xF64(&r0, &r1);
     // Convert signaling NaN to quiet NaNs.
     //
     // TODO / OPTIMIZE (bug 1316824): see comment in emitMinF32.
     ScratchF64 zero(*this);
-    masm.loadConstantDouble(0, zero);
+    moveImmF64(0, zero);
     masm.subDouble(zero, r0);
     masm.subDouble(zero, r1);
     masm.maxDouble(r1, r0, HandleNaNSpecially(true));
     freeF64(r1);
     pushF64(r0);
 }
 
 void
@@ -5127,19 +5475,17 @@ BaseCompiler::emitRotrI32()
 }
 
 void
 BaseCompiler::emitRotrI64()
 {
     int64_t c;
     if (popConstI64(&c)) {
         RegI64 r = popI64();
-        RegI32 temp;
-        if (rotate64NeedsTemp())
-            temp = needI32();
+        RegI32 temp = needRotate64Temp();
         masm.rotateRight64(Imm32(c & 63), r, r, temp);
         maybeFreeI32(temp);
         pushI64(r);
     } else {
         RegI64 r0, r1;
         pop2xI64ForShiftOrRotate(&r0, &r1);
         masm.rotateRight64(lowPart(r1), r0, r0, maybeHighPart(r1));
         freeI64(r1);
@@ -5165,19 +5511,17 @@ BaseCompiler::emitRotlI32()
 }
 
 void
 BaseCompiler::emitRotlI64()
 {
     int64_t c;
     if (popConstI64(&c)) {
         RegI64 r = popI64();
-        RegI32 temp;
-        if (rotate64NeedsTemp())
-            temp = needI32();
+        RegI32 temp = needRotate64Temp();
         masm.rotateLeft64(Imm32(c & 63), r, r, temp);
         maybeFreeI32(temp);
         pushI64(r);
     } else {
         RegI64 r0, r1;
         pop2xI64ForShiftOrRotate(&r0, &r1);
         masm.rotateLeft64(lowPart(r1), r0, r0, maybeHighPart(r1));
         freeI64(r1);
@@ -5242,37 +5586,29 @@ BaseCompiler::emitCtzI64()
     maybeClearHighPart(r0);
     pushI64(r0);
 }
 
 void
 BaseCompiler::emitPopcntI32()
 {
     RegI32 r0 = popI32();
-    if (popcnt32NeedsTemp()) {
-        RegI32 tmp = needI32();
-        masm.popcnt32(r0, r0, tmp);
-        freeI32(tmp);
-    } else {
-        masm.popcnt32(r0, r0, invalidI32());
-    }
+    RegI32 tmp = needPopcnt32Temp();
+    masm.popcnt32(r0, r0, tmp);
+    maybeFreeI32(tmp);
     pushI32(r0);
 }
 
 void
 BaseCompiler::emitPopcntI64()
 {
     RegI64 r0 = popI64();
-    if (popcnt64NeedsTemp()) {
-        RegI32 tmp = needI32();
-        masm.popcnt64(r0, r0, tmp);
-        freeI32(tmp);
-    } else {
-        masm.popcnt64(r0, r0, invalidI32());
-    }
+    RegI32 tmp = needPopcnt64Temp();
+    masm.popcnt64(r0, r0, tmp);
+    maybeFreeI32(tmp);
     pushI64(r0);
 }
 
 void
 BaseCompiler::emitAbsF32()
 {
     RegF32 r0 = popF32();
     masm.absFloat32(r0, r0);
@@ -5347,45 +5683,35 @@ BaseCompiler::emitTruncateF64ToI32()
 
 #ifndef RABALDR_FLOAT_TO_I64_CALLOUT
 template<bool isUnsigned>
 bool
 BaseCompiler::emitTruncateF32ToI64()
 {
     RegF32 r0 = popF32();
     RegI64 x0 = needI64();
-    if (isUnsigned) {
-        RegF64 tmp = needF64();
-        if (!truncateF32ToI64(r0, x0, isUnsigned, tmp))
-            return false;
-        freeF64(tmp);
-    } else {
-        if (!truncateF32ToI64(r0, x0, isUnsigned, invalidF64()))
-            return false;
-    }
+    RegF64 tmp = needTempForFloatingToI64(isUnsigned);
+    if (!truncateF32ToI64(r0, x0, isUnsigned, tmp))
+        return false;
+    maybeFreeF64(tmp);
     freeF32(r0);
     pushI64(x0);
     return true;
 }
 
 template<bool isUnsigned>
 bool
 BaseCompiler::emitTruncateF64ToI64()
 {
     RegF64 r0 = popF64();
     RegI64 x0 = needI64();
-    if (isUnsigned) {
-        RegF64 tmp = needF64();
-        if (!truncateF64ToI64(r0, x0, isUnsigned, tmp))
-            return false;
-        freeF64(tmp);
-    } else {
-        if (!truncateF64ToI64(r0, x0, isUnsigned, invalidF64()))
-            return false;
-    }
+    RegF64 tmp = needTempForFloatingToI64(isUnsigned);
+    if (!truncateF64ToI64(r0, x0, isUnsigned, tmp))
+        return false;
+    maybeFreeF64(tmp);
     freeF64(r0);
     pushI64(x0);
     return true;
 }
 #endif // RABALDR_FLOAT_TO_I64_CALLOUT
 
 void
 BaseCompiler::emitWrapI64ToI32()
@@ -5515,19 +5841,17 @@ BaseCompiler::emitConvertI64ToF32()
     pushF32(f0);
 }
 
 void
 BaseCompiler::emitConvertU64ToF32()
 {
     RegI64 r0 = popI64();
     RegF32 f0 = needF32();
-    RegI32 temp;
-    if (convertI64ToFloatNeedsTemp(ValType::F32, IsUnsigned(true)))
-        temp = needI32();
+    RegI32 temp = needConvertI64ToFloatTemp(ValType::F32, IsUnsigned(true));
     convertI64ToF32(r0, IsUnsigned(true), f0, temp);
     maybeFreeI32(temp);
     freeI64(r0);
     pushF32(f0);
 }
 #endif
 
 void
@@ -5571,19 +5895,17 @@ BaseCompiler::emitConvertI64ToF64()
     pushF64(d0);
 }
 
 void
 BaseCompiler::emitConvertU64ToF64()
 {
     RegI64 r0 = popI64();
     RegF64 d0 = needF64();
-    RegI32 temp;
-    if (convertI64ToFloatNeedsTemp(ValType::F64, IsUnsigned(true)))
-        temp = needI32();
+    RegI32 temp = needConvertI64ToFloatTemp(ValType::F64, IsUnsigned(true));
     convertI64ToF64(r0, IsUnsigned(true), d0, temp);
     maybeFreeI32(temp);
     freeI64(r0);
     pushF64(d0);
 }
 #endif // RABALDR_I64_TO_FLOAT_CALLOUT
 
 void
@@ -5803,17 +6125,17 @@ BaseCompiler::endBlock(ExprType type)
     // Save the value.
     Maybe<AnyReg> r;
     if (!deadCode_) {
         r = popJoinRegUnlessVoid(type);
         block.bceSafeOnExit &= bceSafe_;
     }
 
     // Leave the block.
-    popStackOnBlockExit(block.framePushed);
+    fr.popStackOnBlockExit(block.stackHeight, deadCode_);
     popValueStackTo(block.stackSize);
 
     // Bind after cleanup: branches out will have popped the stack.
     if (block.label.used()) {
         masm.bind(&block.label);
         // No value was provided by the fallthrough but the branch out will
         // have stored one in joinReg, so capture that.
         if (deadCode_)
@@ -5856,17 +6178,17 @@ BaseCompiler::endLoop(ExprType type)
 
     Maybe<AnyReg> r;
     if (!deadCode_) {
         r = popJoinRegUnlessVoid(type);
         // block.bceSafeOnExit need not be updated because it won't be used for
         // the fallthrough path.
     }
 
-    popStackOnBlockExit(block.framePushed);
+    fr.popStackOnBlockExit(block.stackHeight, deadCode_);
     popValueStackTo(block.stackSize);
 
     // bceSafe_ stays the same along the fallthrough path because branches to
     // loops branch to the top.
 
     // Retain the value stored in joinReg by all paths.
     if (!deadCode_)
         pushJoinRegUnlessVoid(r);
@@ -5909,17 +6231,17 @@ BaseCompiler::emitIf()
     return true;
 }
 
 void
 BaseCompiler::endIfThen()
 {
     Control& ifThen = controlItem();
 
-    popStackOnBlockExit(ifThen.framePushed);
+    fr.popStackOnBlockExit(ifThen.stackHeight, deadCode_);
     popValueStackTo(ifThen.stackSize);
 
     if (ifThen.otherLabel.used())
         masm.bind(&ifThen.otherLabel);
 
     if (ifThen.label.used())
         masm.bind(&ifThen.label);
 
@@ -5947,17 +6269,17 @@ BaseCompiler::emitElse()
     // Exit the "then" branch.
 
     ifThenElse.deadThenBranch = deadCode_;
 
     Maybe<AnyReg> r;
     if (!deadCode_)
         r = popJoinRegUnlessVoid(thenType);
 
-    popStackOnBlockExit(ifThenElse.framePushed);
+    fr.popStackOnBlockExit(ifThenElse.stackHeight, deadCode_);
     popValueStackTo(ifThenElse.stackSize);
 
     if (!deadCode_)
         masm.jump(&ifThenElse.label);
 
     if (ifThenElse.otherLabel.used())
         masm.bind(&ifThenElse.otherLabel);
 
@@ -5986,17 +6308,17 @@ BaseCompiler::endIfThenElse(ExprType typ
     // we want to find there.  The "then" arm has the same constraint.
 
     Maybe<AnyReg> r;
     if (!deadCode_) {
         r = popJoinRegUnlessVoid(type);
         ifThenElse.bceSafeOnExit &= bceSafe_;
     }
 
-    popStackOnBlockExit(ifThenElse.framePushed);
+    fr.popStackOnBlockExit(ifThenElse.stackHeight, deadCode_);
     popValueStackTo(ifThenElse.stackSize);
 
     if (ifThenElse.label.used())
         masm.bind(&ifThenElse.label);
 
     bool joinLive = !ifThenElse.deadOnArrival &&
                     (!ifThenElse.deadThenBranch || !deadCode_ || ifThenElse.label.bound());
 
@@ -6050,17 +6372,17 @@ BaseCompiler::emitBr()
     Control& target = controlItem(relativeDepth);
     target.bceSafeOnExit &= bceSafe_;
 
     // Save any value in the designated join register, where the
     // normal block exit code will also leave it.
 
     Maybe<AnyReg> r = popJoinRegUnlessVoid(type);
 
-    popStackBeforeBranch(target.framePushed);
+    fr.popStackBeforeBranch(target.stackHeight);
     masm.jump(&target.label);
 
     // The register holding the join value is free for the remainder
     // of this block.
 
     freeJoinRegUnlessVoid(r);
 
     deadCode_ = true;
@@ -6080,17 +6402,17 @@ BaseCompiler::emitBrIf()
     if (deadCode_) {
         resetLatentOp();
         return true;
     }
 
     Control& target = controlItem(relativeDepth);
     target.bceSafeOnExit &= bceSafe_;
 
-    BranchState b(&target.label, target.framePushed, InvertBranch(false), type);
+    BranchState b(&target.label, target.stackHeight, InvertBranch(false), type);
     emitBranchSetup(&b);
     emitBranchPerform(&b);
 
     return true;
 }
 
 bool
 BaseCompiler::emitBrTable()
@@ -6115,17 +6437,17 @@ BaseCompiler::emitBrTable()
 
     Maybe<AnyReg> r = popJoinRegUnlessVoid(branchValueType);
 
     Label dispatchCode;
     masm.branch32(Assembler::Below, rc, Imm32(depths.length()), &dispatchCode);
 
     // This is the out-of-range stub.  rc is dead here but we don't need it.
 
-    popStackBeforeBranch(controlItem(defaultDepth).framePushed);
+    fr.popStackBeforeBranch(controlItem(defaultDepth).stackHeight);
     controlItem(defaultDepth).bceSafeOnExit &= bceSafe_;
     masm.jump(&controlItem(defaultDepth).label);
 
     // Emit stubs.  rc is dead in all of these but we don't need it.
     //
     // The labels in the vector are in the TempAllocator and will
     // be freed by and by.
     //
@@ -6134,17 +6456,17 @@ BaseCompiler::emitBrTable()
 
     LabelVector stubs;
     if (!stubs.reserve(depths.length()))
         return false;
 
     for (uint32_t depth : depths) {
         stubs.infallibleEmplaceBack(NonAssertingLabel());
         masm.bind(&stubs.back());
-        popStackBeforeBranch(controlItem(depth).framePushed);
+        fr.popStackBeforeBranch(controlItem(depth).stackHeight);
         controlItem(depth).bceSafeOnExit &= bceSafe_;
         masm.jump(&controlItem(depth).label);
     }
 
     // Emit table.
 
     Label theTable;
     jumpTable(stubs, &theTable);
@@ -6167,18 +6489,17 @@ bool
 BaseCompiler::emitDrop()
 {
     if (!iter_.readDrop())
         return false;
 
     if (deadCode_)
         return true;
 
-    popStackIfMemory();
-    popValueStackBy(1);
+    dropValue();
     return true;
 }
 
 void
 BaseCompiler::doReturn(ExprType type, bool popStack)
 {
     switch (type) {
       case ExprType::Void: {
@@ -6540,20 +6861,20 @@ BaseCompiler::emitConvertFloatingToInt64
 
     RegI64 rv = captureReturnedI64();
 
     RegF64 inputVal = popF64();
 
     bool isUnsigned = callee == SymbolicAddress::TruncateDoubleToUint64;
 
     // The OOL check just succeeds or fails, it does not generate a value.
-    OutOfLineCode* ool = new (alloc_) OutOfLineTruncateCheckF32OrF64ToI64(AnyReg(inputVal),
+    OutOfLineCode* ool =
+        addOutOfLineCode(new (alloc_) OutOfLineTruncateCheckF32OrF64ToI64(AnyReg(inputVal),
                                                                           isUnsigned,
-                                                                          bytecodeOffset());
-    ool = addOutOfLineCode(ool);
+                                                                          bytecodeOffset()));
     if (!ool)
         return false;
 
     masm.branch64(Assembler::Equal, rv, Imm64(0x8000000000000000), ool->entry());
     masm.bind(ool->rejoin());
 
     pushI64(rv);
     freeF64(inputVal);
@@ -6603,47 +6924,47 @@ BaseCompiler::emitSetOrTeeLocal(uint32_t
     if (deadCode_)
         return true;
 
     bceLocalIsUpdated(slot);
     switch (locals_[slot]) {
       case ValType::I32: {
         RegI32 rv = popI32();
         syncLocal(slot);
-        storeToFrameI32(rv, frameOffsetFromSlot(slot, MIRType::Int32));
+        fr.storeLocalI32(rv, localFromSlot(slot, MIRType::Int32));
         if (isSetLocal)
             freeI32(rv);
         else
             pushI32(rv);
         break;
       }
       case ValType::I64: {
         RegI64 rv = popI64();
         syncLocal(slot);
-        storeToFrameI64(rv, frameOffsetFromSlot(slot, MIRType::Int64));
+        fr.storeLocalI64(rv, localFromSlot(slot, MIRType::Int64));
         if (isSetLocal)
             freeI64(rv);
         else
             pushI64(rv);
         break;
       }
       case ValType::F64: {
         RegF64 rv = popF64();
         syncLocal(slot);
-        storeToFrameF64(rv, frameOffsetFromSlot(slot, MIRType::Double));
+        fr.storeLocalF64(rv, localFromSlot(slot, MIRType::Double));
         if (isSetLocal)
             freeF64(rv);
         else
             pushF64(rv);
         break;
       }
       case ValType::F32: {
         RegF32 rv = popF32();
         syncLocal(slot);
-        storeToFrameF32(rv, frameOffsetFromSlot(slot, MIRType::Float32));
+        fr.storeLocalF32(rv, localFromSlot(slot, MIRType::Float32));
         if (isSetLocal)
             freeF32(rv);
         else
             pushF32(rv);
         break;
       }
       default:
         MOZ_CRASH("Local variable type");
@@ -6867,17 +7188,17 @@ BaseCompiler::popMemoryAccess(MemoryAcce
         // beneficial.
 
         if (ea <= UINT32_MAX) {
             addr = uint32_t(ea);
             access->clearOffset();
         }
 
         RegI32 r = needI32();
-        loadConstI32(r, int32_t(addr));
+        moveImm32(int32_t(addr), r);
         return r;
     }
 
     uint32_t local;
     if (peekLocalI32(&local))
         bceCheckLocal(access, check, local);
 
     return popI32();
@@ -6983,18 +7304,18 @@ BaseCompiler::emitLoad(ValType type, Sca
     return loadCommon(&access, type);
 }
 
 bool
 BaseCompiler::storeCommon(MemoryAccessDesc* access, ValType resultType)
 {
     AccessCheck check;
 
-    RegI32 tls, tmp;
-    needStoreTemps(*access, resultType, &tmp);
+    RegI32 tls;
+    RegI32 tmp = needStoreTemp(*access, resultType);
 
     switch (resultType) {
       case ValType::I32: {
         RegI32 rv = popI32();
         RegI32 rp = popMemoryAccess(access, &check);
         tls = maybeLoadTlsForAccess(check);
         if (!store(access, &check, tls, rp, AnyReg(rv), tmp))
             return false;
@@ -7097,19 +7418,19 @@ BaseCompiler::emitSelect()
         // normally pop before executing the branch.  On x86 this is one value
         // too many, so we need to generate more complicated code here, and for
         // simplicity's sake we do so even if the branch operands are not Int64.
         // However, the resulting control flow diamond is complicated since the
         // arms of the diamond will have to stay synchronized with respect to
         // their evaluation stack and regalloc state.  To simplify further, we
         // use a double branch and a temporary boolean value for now.
         RegI32 tmp = needI32();
-        loadConstI32(tmp, 0);
+        moveImm32(0, tmp);
         emitBranchPerform(&b);
-        loadConstI32(tmp, 1);
+        moveImm32(1, tmp);
         masm.bind(&done);
 
         Label trueValue;
         RegI64 r0, r1;
         pop2xI64(&r0, &r1);
         masm.branch32(Assembler::Equal, tmp, Imm32(0), &trueValue);
         moveI64(r1, r0);
         masm.bind(&trueValue);
@@ -7201,19 +7522,19 @@ BaseCompiler::emitCompareF32(Assembler::
 
     if (sniffConditionalControlCmp(compareOp, compareType))
         return;
 
     Label across;
     RegF32 r0, r1;
     pop2xF32(&r0, &r1);
     RegI32 i0 = needI32();
-    masm.mov(ImmWord(1), i0);
+    moveImm32(1, i0);
     masm.branchFloat(compareOp, r0, r1, &across);
-    masm.mov(ImmWord(0), i0);
+    moveImm32(0, i0);
     masm.bind(&across);
     freeF32(r0);
     freeF32(r1);
     pushI32(i0);
 }
 
 void
 BaseCompiler::emitCompareF64(Assembler::DoubleCondition compareOp, ValType compareType)
@@ -7222,19 +7543,19 @@ BaseCompiler::emitCompareF64(Assembler::
 
     if (sniffConditionalControlCmp(compareOp, compareType))
         return;
 
     Label across;
     RegF64 r0, r1;
     pop2xF64(&r0, &r1);
     RegI32 i0 = needI32();
-    masm.mov(ImmWord(1), i0);
+    moveImm32(1, i0);
     masm.branchDouble(compareOp, r0, r1, &across);
-    masm.mov(ImmWord(0), i0);
+    moveImm32(0, i0);
     masm.bind(&across);
     freeF64(r0);
     freeF64(r1);
     pushI32(i0);
 }
 
 void
 BaseCompiler::emitInstanceCall(uint32_t lineOrBytecode, const MIRTypeVector& sig,
@@ -7491,18 +7812,17 @@ BaseCompiler::emitAtomicRMW(ValType type
         RegI32 rv = narrowing ? popI64ToI32() : popI32();
         RegI32 rp = popMemoryAccess(&access, &check);
         RegI32 output = needI32();
 #else
         RegI32 rv, rp, output;
         MOZ_CRASH("BaseCompiler porting interface: atomic rmw");
 #endif
         RegI32 tls = maybeLoadTlsForAccess(check);
-        RegI32 tmp;
-        needAtomicRMWTemps(op, &access, &tmp);
+        RegI32 tmp = needAtomicRMWTemp(op, &access);
 
         atomicRMW(op, &access, &check, tls, rp, rv, output, tmp);
 
         maybeFreeI32(tls);
         maybeFreeI32(tmp);
         freeI32(rp);
         if (rv != output)
             freeI32(rv);
@@ -7538,26 +7858,26 @@ BaseCompiler::emitAtomicRMW(ValType type
 
     RegI32 tls = specific_edi;
     RegI32 memoryBase = specific_edi;     // Yes, same
     masm.loadWasmTlsRegFromFrame(tls);
 
     prepareMemoryAccess(&access, &check, tls, ptr);
     masm.movl(Operand(Address(tls, offsetof(TlsData, memoryBase))), memoryBase);
 
-    masm.Push(ecx);
-    masm.Push(ebx);
+    fr.pushPtr(ecx);
+    fr.pushPtr(ebx);
 
     RegI64 rd = specific_edx_eax;
 
     BaseIndex srcAddr(memoryBase, ptr, TimesOne, access.offset());
     Address value(esp, 0);
     atomicRMW64(op, value, srcAddr, tmp, rd);
 
-    masm.freeStack(8);
+    fr.popBytes(8);
 
     pushI64(rd);
     freeI32(specific_ecx);
     freeI32(specific_edi);
     freeI32(specific_esi);
 
 #else // !JS_CODEGEN_X86
 
@@ -7574,18 +7894,17 @@ BaseCompiler::emitAtomicRMW(ValType type
     RegI64 rd = needI64Pair();
 #  else
     RegI64 rv, rd;
     RegI32 rp;
     MOZ_CRASH("BaseCompiler porting interface: 64-bit atomic RMW");
 # endif
 
     RegI32 tls = maybeLoadTlsForAccess(check);
-    RegI64 tmp;
-    needAtomicRMW64Temps(op, &tmp);
+    RegI64 tmp = needAtomicRMW64Temp(op);
 
     prepareMemoryAccess(&access, &check, tls, rp);
     ATOMIC_PTR(srcAddr, &access, tls, rp);
 
     atomicRMW64(op, rv, srcAddr, tmp, rd);
 
     pushI64(rd);
 
@@ -8492,146 +8811,41 @@ BaseCompiler::emitFunction()
         return false;
 
     if (!endFunction())
         return false;
 
     return true;
 }
 
-void
-BaseCompiler::emitInitStackLocals()
-{
-    MOZ_ASSERT(varLow_ < varHigh_, "there should be stack locals to initialize");
-
-    static const uint32_t wordSize = sizeof(void*);
-
-    // A local's localOffset always points above it in the frame, so that when
-    // translated to a stack address we end up with an address pointing to the
-    // base of the local.  Thus to go from a raw frame offset to an SP offset we
-    // first add K to the frame offset to obtain a localOffset for a slot of
-    // size K, and then map that to an SP offset.  Hence all the adjustments to
-    // `low` in the offset calculations below.
-
-    // On 64-bit systems we may have 32-bit alignment for the local area as it
-    // may be preceded by parameters and prologue/debug data.
-
-    uint32_t low = varLow_;
-    if (low % wordSize) {
-        masm.store32(Imm32(0), Address(StackPointer, localOffsetToSPOffset(low + 4)));
-        low += 4;
-    }
-    MOZ_ASSERT(low % wordSize == 0);
-
-    const uint32_t high = AlignBytes(varHigh_, wordSize);
-    MOZ_ASSERT(high <= uint32_t(localSize_), "localSize_ should be aligned at least that");
-
-    // An unrollLimit of 16 is chosen so that we only need an 8-bit signed
-    // immediate to represent the offset in the store instructions in the loop
-    // on x64.
-
-    const uint32_t unrollLimit = 16;
-    const uint32_t initWords = (high - low) / wordSize;
-    const uint32_t tailWords = initWords % unrollLimit;
-    const uint32_t loopHigh = high - (tailWords * wordSize);
-
-    // With only one word to initialize, just store an immediate zero.
-
-    if (initWords == 1) {
-        masm.storePtr(ImmWord(0), Address(StackPointer, localOffsetToSPOffset(low + wordSize)));
-        return;
-    }
-
-    // For other cases, it's best to have a zero in a register.
-    //
-    // One can do more here with SIMD registers (store 16 bytes at a time) or
-    // with instructions like STRD on ARM (store 8 bytes at a time), but that's
-    // for another day.
-
-    RegI32 zero = needI32();
-    masm.mov(ImmWord(0), zero);
-
-    // For the general case we want to have a loop body of unrollLimit stores
-    // and then a tail of less than unrollLimit stores.  When initWords is less
-    // than 2*unrollLimit the loop trip count is at most 1 and there is no
-    // benefit to having the pointer calculations and the compare-and-branch.
-    // So we completely unroll when we have initWords < 2 * unrollLimit.  (In
-    // this case we'll end up using 32-bit offsets on x64 for up to half of the
-    // stores, though.)
-
-    // Fully-unrolled case.
-
-    if (initWords < 2 * unrollLimit)  {
-        for (uint32_t i = low; i < high; i += wordSize)
-            masm.storePtr(zero, Address(StackPointer, localOffsetToSPOffset(i + wordSize)));
-        freeI32(zero);
-        return;
-    }
-
-    // Unrolled loop with a tail. Stores will use negative offsets. That's OK
-    // for x86 and ARM, at least.
-
-    // Compute pointer to the highest-addressed slot on the frame.
-    RegI32 p = needI32();
-    masm.computeEffectiveAddress(Address(StackPointer, localOffsetToSPOffset(low + wordSize)),
-                                 p);
-
-    // Compute pointer to the lowest-addressed slot on the frame that will be
-    // initialized by the loop body.
-    RegI32 lim = needI32();
-    masm.computeEffectiveAddress(Address(StackPointer,
-                                         localOffsetToSPOffset(loopHigh + wordSize)),
-                                 lim);
-
-    // The loop body.  Eventually we'll have p == lim and exit the loop.
-    Label again;
-    masm.bind(&again);
-    for (uint32_t i = 0; i < unrollLimit; ++i)
-        masm.storePtr(zero, Address(p, -(wordSize * i)));
-    masm.subPtr(Imm32(unrollLimit * wordSize), p);
-    masm.branchPtr(Assembler::LessThan, lim, p, &again);
-
-    // The tail.
-    for (uint32_t i = 0; i < tailWords; ++i)
-        masm.storePtr(zero, Address(p, -(wordSize * i)));
-
-    freeI32(p);
-    freeI32(lim);
-    freeI32(zero);
-}
-
 BaseCompiler::BaseCompiler(const ModuleEnvironment& env,
                            Decoder& decoder,
                            const FuncCompileInput& func,
                            const ValTypeVector& locals,
                            bool debugEnabled,
                            TempAllocator* alloc,
                            MacroAssembler* masm,
                            CompileMode mode)
     : env_(env),
       iter_(env, decoder),
       func_(func),
       lastReadCallSite_(0),
       alloc_(*alloc),
       locals_(locals),
-      localSize_(0),
-      varLow_(0),
-      varHigh_(0),
-      maxFramePushed_(0),
       deadCode_(false),
       debugEnabled_(debugEnabled),
       bceSafe_(0),
-      stackAddOffset_(0),
       mode_(mode),
       latentOp_(LatentOp::None),
       latentType_(ValType::I32),
       latentIntCmp_(Assembler::Equal),
       latentDoubleCmp_(Assembler::DoubleEqual),
       masm(*masm),
       ra(*this),
+      fr(*masm),
 #ifdef JS_CODEGEN_X64
       specific_rax(RegI64(Register64(rax))),
       specific_rcx(RegI64(Register64(rcx))),
       specific_rdx(RegI64(Register64(rdx))),
 #endif
 #if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)
       specific_eax(RegI32(eax)),
       specific_ecx(RegI32(ecx)),
@@ -8676,39 +8890,19 @@ BaseCompiler::init()
         return false;
     }
     if (!SigPILL_.append(MIRType::Pointer) || !SigPILL_.append(MIRType::Int32) ||
         !SigPILL_.append(MIRType::Int64) || !SigPILL_.append(MIRType::Int64))
     {
         return false;
     }
 
-    if (!localInfo_.resize(locals_.length()))
+    if (!fr.setupLocals(locals_, sig().args(), debugEnabled_, &localInfo_))
         return false;
 
-    const ValTypeVector& args = sig().args();
-    BaseLocalIter i(locals_, args.length(), debugEnabled_);
-    varLow_ = i.reservedSize();
-    for (; !i.done() && i.index() < args.length(); i++) {
-        MOZ_ASSERT(i.isArg());
-        Local& l = localInfo_[i.index()];
-        l.init(i.mirType(), i.frameOffset());
-        varLow_ = i.currentLocalSize();
-    }
-
-    varHigh_ = varLow_;
-    for (; !i.done() ; i++) {
-        MOZ_ASSERT(!i.isArg());
-        Local& l = localInfo_[i.index()];
-        l.init(i.mirType(), i.frameOffset());
-        varHigh_ = i.currentLocalSize();
-    }
-
-    localSize_ = AlignBytes(varHigh_, 16u);
-
     addInterruptCheck();
 
     return true;
 }
 
 FuncOffsets
 BaseCompiler::finish()
 {
--- a/js/src/wasm/WasmIonCompile.cpp
+++ b/js/src/wasm/WasmIonCompile.cpp
@@ -892,16 +892,18 @@ class FunctionCompiler
         if (access->isPlainAsmJS()) {
             MOZ_ASSERT(access->offset() == 0);
             MWasmLoadTls* boundsCheckLimit = maybeLoadBoundsCheckLimit();
             load = MAsmJSLoadHeap::New(alloc(), memoryBase, base, boundsCheckLimit, access->type());
         } else {
             checkOffsetAndAlignmentAndBounds(access, &base);
             load = MWasmLoad::New(alloc(), memoryBase, base, *access, ToMIRType(result));
         }
+        if (!load)
+            return nullptr;
         curBlock_->add(load);
         return load;
     }
 
     void store(MDefinition* base, MemoryAccessDesc* access, MDefinition* v)
     {
         if (inDeadCode())
             return;
@@ -912,16 +914,18 @@ class FunctionCompiler
             MOZ_ASSERT(access->offset() == 0);
             MWasmLoadTls* boundsCheckLimit = maybeLoadBoundsCheckLimit();
             store = MAsmJSStoreHeap::New(alloc(), memoryBase, base, boundsCheckLimit,
                                          access->type(), v);
         } else {
             checkOffsetAndAlignmentAndBounds(access, &base);
             store = MWasmStore::New(alloc(), memoryBase, base, *access, v);
         }
+        if (!store)
+            return;
         curBlock_->add(store);
     }
 
     MDefinition* atomicCompareExchangeHeap(MDefinition* base, MemoryAccessDesc* access,
                                            ValType result, MDefinition* oldv, MDefinition* newv)
     {
         if (inDeadCode())
             return nullptr;
@@ -936,16 +940,18 @@ class FunctionCompiler
             auto* cvtNewv = MWrapInt64ToInt32::New(alloc(), newv, /*bottomHalf=*/ true);
             curBlock_->add(cvtNewv);
             newv = cvtNewv;
         }
 
         MWasmLoadTls* memoryBase = maybeLoadMemoryBase();
         MInstruction* cas = MWasmCompareExchangeHeap::New(alloc(), bytecodeOffset(), memoryBase,
                                                           base, *access, oldv, newv, tlsPointer_);
+        if (!cas)
+            return nullptr;
         curBlock_->add(cas);
 
         if (isSmallerAccessForI64(result, access)) {
             cas = MExtendInt32ToInt64::New(alloc(), cas, true);
             curBlock_->add(cas);
         }
 
         return cas;
@@ -963,16 +969,18 @@ class FunctionCompiler
             auto* cvtValue = MWrapInt64ToInt32::New(alloc(), value, /*bottomHalf=*/ true);
             curBlock_->add(cvtValue);
             value = cvtValue;
         }
 
         MWasmLoadTls* memoryBase = maybeLoadMemoryBase();
         MInstruction* xchg = MWasmAtomicExchangeHeap::New(alloc(), bytecodeOffset(), memoryBase,
                                                           base, *access, value, tlsPointer_);
+        if (!xchg)
+            return nullptr;
         curBlock_->add(xchg);
 
         if (isSmallerAccessForI64(result, access)) {
             xchg = MExtendInt32ToInt64::New(alloc(), xchg, true);
             curBlock_->add(xchg);
         }
 
         return xchg;
@@ -990,16 +998,18 @@ class FunctionCompiler
             auto* cvtValue = MWrapInt64ToInt32::New(alloc(), value, /*bottomHalf=*/ true);
             curBlock_->add(cvtValue);
             value = cvtValue;
         }
 
         MWasmLoadTls* memoryBase = maybeLoadMemoryBase();
         MInstruction* binop = MWasmAtomicBinopHeap::New(alloc(), bytecodeOffset(), op, memoryBase,
                                                         base, *access, value, tlsPointer_);
+        if (!binop)
+            return nullptr;
         curBlock_->add(binop);
 
         if (isSmallerAccessForI64(result, access)) {
             binop = MExtendInt32ToInt64::New(alloc(), binop, true);
             curBlock_->add(binop);
         }
 
         return binop;
--- a/testing/mozharness/mozharness/mozilla/testing/verify_tools.py
+++ b/testing/mozharness/mozharness/mozilla/testing/verify_tools.py
@@ -104,16 +104,18 @@ class VerifyToolsMixin(object):
         import manifest as wptmanifest
         tests_root = os.path.join(dirs['abs_wpttest_dir'], "tests")
         man_path = os.path.join(dirs['abs_wpttest_dir'], "meta", "MANIFEST.json")
         man = wptmanifest.manifest.load(tests_root, man_path)
 
         repo_tests_path = os.path.join("testing", "web-platform", "tests")
         tests_path = os.path.join("tests", "web-platform", "tests")
         for (type, path, test) in man:
+            if type not in ["testharness", "reftest", "wdspec"]:
+                continue
             repo_path = os.path.join(repo_tests_path, path)
             # manifest paths use os.sep (like backslash on Windows) but
             # automation-relevance uses posixpath.sep
             repo_path = repo_path.replace(os.sep, posixpath.sep)
             if repo_path in changed_files:
                 self.info("found web-platform test file '%s', type %s" % (path, type))
                 suite_files = self.verify_suites.get(type)
                 if not suite_files:
--- a/toolkit/components/payments/test/browser/browser.ini
+++ b/toolkit/components/payments/test/browser/browser.ini
@@ -4,9 +4,10 @@ prefs =
   dom.payments.request.enabled=true
 skip-if = !e10s # Bug 1365964 - Payment Request isn't implemented for non-e10s
 support-files =
   blank_page.html
 
 [browser_host_name.js]
 [browser_request_summary.js]
 [browser_show_dialog.js]
+skip-if = os == 'win' && debug # bug 1418385
 [browser_total.js]
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -2133,16 +2133,20 @@ nsWindow::UserActivity()
 {
   if (!mIdleService) {
     mIdleService = do_GetService("@mozilla.org/widget/idleservice;1");
   }
 
   if (mIdleService) {
     mIdleService->ResetIdleTimeOut(0);
   }
+
+  if (FindTopLevel() != nsWindow::TopWindow()) {
+    BringToFront();
+  }
 }
 
 TextEventDispatcherListener*
 nsWindow::GetNativeTextEventDispatcherListener()
 {
     nsWindow* top = FindTopLevel();
     MOZ_ASSERT(top);
 
--- a/xpcom/io/nsMultiplexInputStream.cpp
+++ b/xpcom/io/nsMultiplexInputStream.cpp
@@ -50,24 +50,41 @@ public:
   NS_DECL_NSIMULTIPLEXINPUTSTREAM
   NS_DECL_NSISEEKABLESTREAM
   NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
   NS_DECL_NSICLONEABLEINPUTSTREAM
   NS_DECL_NSIASYNCINPUTSTREAM
 
   void AsyncWaitCompleted();
 
+  struct StreamData
+  {
+    void Initialize(nsIInputStream* aStream)
+    {
+      mStream = aStream;
+      mAsyncStream = do_QueryInterface(aStream);
+      mSeekableStream = do_QueryInterface(aStream);
+    }
+
+    nsCOMPtr<nsIInputStream> mStream;
+
+    // This can be null.
+    nsCOMPtr<nsIAsyncInputStream> mAsyncStream;
+    // This can be null.
+    nsCOMPtr<nsISeekableStream> mSeekableStream;
+  };
+
 private:
   ~nsMultiplexInputStream()
   {
   }
 
   // This method updates mSeekableStreams, mIPCSerializableStreams,
   // mCloneableStreams and mAsyncInputStreams values.
-  void UpdateQIMap(nsIInputStream* aStream, int32_t aCount);
+  void UpdateQIMap(StreamData& aStream, int32_t aCount);
 
   struct MOZ_STACK_CLASS ReadSegmentsState
   {
     nsCOMPtr<nsIInputStream> mThisStream;
     uint32_t mOffset;
     nsWriteSegmentFun mWriter;
     void* mClosure;
     bool mDone;
@@ -78,17 +95,19 @@ private:
                             uint32_t aCount, uint32_t* aWriteCount);
 
   bool IsSeekable() const;
   bool IsIPCSerializable() const;
   bool IsCloneable() const;
   bool IsAsyncInputStream() const;
 
   Mutex mLock; // Protects access to all data members.
-  nsTArray<nsCOMPtr<nsIInputStream>> mStreams;
+
+  nsTArray<StreamData> mStreams;
+
   uint32_t mCurrentStream;
   bool mStartedReadingCurrent;
   nsresult mStatus;
   nsCOMPtr<nsIInputStreamCallback> mAsyncWaitCallback;
 
   uint32_t mSeekableStreams;
   uint32_t mIPCSerializableStreams;
   uint32_t mCloneableStreams;
@@ -116,29 +135,30 @@ NS_INTERFACE_MAP_BEGIN(nsMultiplexInputS
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CI_INTERFACE_GETTER(nsMultiplexInputStream,
                             nsIMultiplexInputStream,
                             nsIInputStream,
                             nsISeekableStream)
 
 static nsresult
-AvailableMaybeSeek(nsIInputStream* aStream, uint64_t* aResult)
+AvailableMaybeSeek(nsMultiplexInputStream::StreamData& aStream,
+                   uint64_t* aResult)
 {
-  nsresult rv = aStream->Available(aResult);
+  nsresult rv = aStream.mStream->Available(aResult);
   if (rv == NS_BASE_STREAM_CLOSED) {
     // Blindly seek to the current position if Available() returns
     // NS_BASE_STREAM_CLOSED.
     // If nsIFileInputStream is closed in Read() due to CLOSE_ON_EOF flag,
     // Seek() could reopen the file if REOPEN_ON_REWIND flag is set.
-    nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(aStream);
-    if (seekable) {
-      nsresult rvSeek = seekable->Seek(nsISeekableStream::NS_SEEK_CUR, 0);
+    if (aStream.mSeekableStream) {
+      nsresult rvSeek =
+        aStream.mSeekableStream->Seek(nsISeekableStream::NS_SEEK_CUR, 0);
       if (NS_SUCCEEDED(rvSeek)) {
-        rv = aStream->Available(aResult);
+        rv = aStream.mStream->Available(aResult);
       }
     }
   }
   return rv;
 }
 
 static nsresult
 TellMaybeSeek(nsISeekableStream* aSeekable, int64_t* aResult)
@@ -175,38 +195,55 @@ nsMultiplexInputStream::GetCount(uint32_
   *aCount = mStreams.Length();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsMultiplexInputStream::AppendStream(nsIInputStream* aStream)
 {
   MutexAutoLock lock(mLock);
-  if (!mStreams.AppendElement(aStream)) {
+
+  StreamData* streamData = mStreams.AppendElement();
+  if (NS_WARN_IF(!streamData)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
-  UpdateQIMap(aStream, 1);
+  streamData->Initialize(aStream);
+
+  UpdateQIMap(*streamData, 1);
+
+  if (mStatus == NS_BASE_STREAM_CLOSED) {
+    // We were closed, but now we have more data to read.
+    mStatus = NS_OK;
+  }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsMultiplexInputStream::InsertStream(nsIInputStream* aStream, uint32_t aIndex)
 {
   MutexAutoLock lock(mLock);
-  if (!mStreams.InsertElementAt(aIndex, aStream)) {
+
+  StreamData* streamData = mStreams.InsertElementAt(aIndex);
+  if (NS_WARN_IF(!streamData)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
+  streamData->Initialize(aStream);
+
   if (mCurrentStream > aIndex ||
       (mCurrentStream == aIndex && mStartedReadingCurrent)) {
     ++mCurrentStream;
+  } else if (mStatus == NS_BASE_STREAM_CLOSED) {
+    // We were closed, but now we have more data to read.
+    mStatus = NS_OK;
   }
 
-  UpdateQIMap(aStream, 1);
+  UpdateQIMap(*streamData, 1);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsMultiplexInputStream::RemoveStream(uint32_t aIndex)
 {
   MutexAutoLock lock(mLock);
   if (aIndex >= mStreams.Length()) {
@@ -224,66 +261,102 @@ nsMultiplexInputStream::RemoveStream(uin
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsMultiplexInputStream::GetStream(uint32_t aIndex, nsIInputStream** aResult)
 {
   MutexAutoLock lock(mLock);
-  *aResult = mStreams.SafeElementAt(aIndex, nullptr);
-  if (NS_WARN_IF(!*aResult)) {
+
+  if (aIndex >= mStreams.Length()) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
-  NS_ADDREF(*aResult);
+  StreamData& streamData = mStreams.ElementAt(aIndex);
+
+  nsCOMPtr<nsIInputStream> stream = streamData.mStream;
+  stream.forget(aResult);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsMultiplexInputStream::Close()
 {
   MutexAutoLock lock(mLock);
   mStatus = NS_BASE_STREAM_CLOSED;
 
   nsresult rv = NS_OK;
 
   uint32_t len = mStreams.Length();
   for (uint32_t i = 0; i < len; ++i) {
-    nsresult rv2 = mStreams[i]->Close();
+    nsresult rv2 = mStreams[i].mStream->Close();
     // We still want to close all streams, but we should return an error
     if (NS_FAILED(rv2)) {
       rv = rv2;
     }
   }
 
   return rv;
 }
 
 NS_IMETHODIMP
 nsMultiplexInputStream::Available(uint64_t* aResult)
 {
+  *aResult = 0;
+
   MutexAutoLock lock(mLock);
   if (NS_FAILED(mStatus)) {
     return mStatus;
   }
 
   uint64_t avail = 0;
+  nsresult rv = NS_BASE_STREAM_CLOSED;
 
   uint32_t len = mStreams.Length();
   for (uint32_t i = mCurrentStream; i < len; i++) {
     uint64_t streamAvail;
-    mStatus = AvailableMaybeSeek(mStreams[i], &streamAvail);
-    if (NS_WARN_IF(NS_FAILED(mStatus))) {
+    rv = AvailableMaybeSeek(mStreams[i], &streamAvail);
+    if (rv == NS_BASE_STREAM_CLOSED) {
+      // If a stream is closed, we continue with the next one.
+      // If this is the last stream, we want to return this error code.
+      continue;
+    }
+
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      mStatus = rv;
       return mStatus;
     }
+
+    // If the current stream is async, we have to return what we have so far
+    // without processing the following streams. This is needed because
+    // ::Available should return only what is currently available. In case of an
+    // nsIAsyncInputStream, we have to call AsyncWait() in order to read more.
+    if (mStreams[i].mAsyncStream) {
+      avail += streamAvail;
+      break;
+    }
+
+    if (streamAvail == 0) {
+      // Nothing to read for this stream. Let's move to the next one.
+      continue;
+    }
+
     avail += streamAvail;
   }
-  *aResult = avail;
-  return NS_OK;
+
+  // We still have something to read. We don't want to return an error code yet.
+  if (avail) {
+    *aResult = avail;
+    return NS_OK;
+  }
+
+  // Let's propagate the last error message.
+  mStatus = rv;
+  return rv;
 }
 
 NS_IMETHODIMP
 nsMultiplexInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* aResult)
 {
   MutexAutoLock lock(mLock);
   // It is tempting to implement this method in terms of ReadSegments, but
   // that would prevent this class from being used with streams that only
@@ -298,17 +371,17 @@ nsMultiplexInputStream::Read(char* aBuf,
     return mStatus;
   }
 
   nsresult rv = NS_OK;
 
   uint32_t len = mStreams.Length();
   while (mCurrentStream < len && aCount) {
     uint32_t read;
-    rv = mStreams[mCurrentStream]->Read(aBuf, aCount, &read);
+    rv = mStreams[mCurrentStream].mStream->Read(aBuf, aCount, &read);
 
     // XXX some streams return NS_BASE_STREAM_CLOSED to indicate EOF.
     // (This is a bug in those stream implementations)
     if (rv == NS_BASE_STREAM_CLOSED) {
       NS_NOTREACHED("Input stream's Read method returned NS_BASE_STREAM_CLOSED");
       rv = NS_OK;
       read = 0;
     } else if (NS_FAILED(rv)) {
@@ -351,17 +424,18 @@ nsMultiplexInputStream::ReadSegments(nsW
   state.mOffset = 0;
   state.mWriter = aWriter;
   state.mClosure = aClosure;
   state.mDone = false;
 
   uint32_t len = mStreams.Length();
   while (mCurrentStream < len && aCount) {
     uint32_t read;
-    rv = mStreams[mCurrentStream]->ReadSegments(ReadSegCb, &state, aCount, &read);
+    rv = mStreams[mCurrentStream].mStream->ReadSegments(ReadSegCb, &state,
+                                                        aCount, &read);
 
     // XXX some streams return NS_BASE_STREAM_CLOSED to indicate EOF.
     // (This is a bug in those stream implementations)
     if (rv == NS_BASE_STREAM_CLOSED) {
       NS_NOTREACHED("Input stream's Read method returned NS_BASE_STREAM_CLOSED");
       rv = NS_OK;
       read = 0;
     }
@@ -411,24 +485,21 @@ nsMultiplexInputStream::ReadSegCb(nsIInp
 NS_IMETHODIMP
 nsMultiplexInputStream::IsNonBlocking(bool* aNonBlocking)
 {
   MutexAutoLock lock(mLock);
 
   uint32_t len = mStreams.Length();
   if (len == 0) {
     // Claim to be non-blocking, since we won't block the caller.
-    // On the other hand we'll never return NS_BASE_STREAM_WOULD_BLOCK,
-    // so maybe we should claim to be blocking?  It probably doesn't
-    // matter in practice.
     *aNonBlocking = true;
     return NS_OK;
   }
   for (uint32_t i = 0; i < len; ++i) {
-    nsresult rv = mStreams[i]->IsNonBlocking(aNonBlocking);
+    nsresult rv = mStreams[i].mStream->IsNonBlocking(aNonBlocking);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     // If one is non-blocking the entire stream becomes non-blocking
     // (except that we don't implement nsIAsyncInputStream, so there's
     //  not much for the caller to do if Read returns "would block")
     if (*aNonBlocking) {
       return NS_OK;
@@ -452,18 +523,17 @@ nsMultiplexInputStream::Seek(int32_t aWh
   bool oldStartedReadingCurrent = mStartedReadingCurrent;
 
   if (aWhence == NS_SEEK_SET) {
     int64_t remaining = aOffset;
     if (aOffset == 0) {
       mCurrentStream = 0;
     }
     for (uint32_t i = 0; i < mStreams.Length(); ++i) {
-      nsCOMPtr<nsISeekableStream> stream =
-        do_QueryInterface(mStreams[i]);
+      nsCOMPtr<nsISeekableStream> stream = mStreams[i].mSeekableStream;
       if (!stream) {
         return NS_ERROR_FAILURE;
       }
 
       // See if all remaining streams should be rewound
       if (remaining == 0) {
         if (i < oldCurrentStream ||
             (i == oldCurrentStream && oldStartedReadingCurrent)) {
@@ -535,56 +605,50 @@ nsMultiplexInputStream::Seek(int32_t aWh
     }
 
     return NS_OK;
   }
 
   if (aWhence == NS_SEEK_CUR && aOffset > 0) {
     int64_t remaining = aOffset;
     for (uint32_t i = mCurrentStream; remaining && i < mStreams.Length(); ++i) {
-      nsCOMPtr<nsISeekableStream> stream =
-        do_QueryInterface(mStreams[i]);
-
       uint64_t avail;
       rv = AvailableMaybeSeek(mStreams[i], &avail);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
 
       int64_t seek = XPCOM_MIN((int64_t)avail, remaining);
 
-      rv = stream->Seek(NS_SEEK_CUR, seek);
+      rv = mStreams[i].mSeekableStream->Seek(NS_SEEK_CUR, seek);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
 
       mCurrentStream = i;
       mStartedReadingCurrent = true;
 
       remaining -= seek;
     }
 
     return NS_OK;
   }
 
   if (aWhence == NS_SEEK_CUR && aOffset < 0) {
     int64_t remaining = -aOffset;
     for (uint32_t i = mCurrentStream; remaining && i != (uint32_t)-1; --i) {
-      nsCOMPtr<nsISeekableStream> stream =
-        do_QueryInterface(mStreams[i]);
-
       int64_t pos;
-      rv = TellMaybeSeek(stream, &pos);
+      rv = TellMaybeSeek(mStreams[i].mSeekableStream, &pos);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
 
       int64_t seek = XPCOM_MIN(pos, remaining);
 
-      rv = stream->Seek(NS_SEEK_CUR, -seek);
+      rv = mStreams[i].mSeekableStream->Seek(NS_SEEK_CUR, -seek);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
 
       mCurrentStream = i;
       mStartedReadingCurrent = seek != -pos;
 
       remaining -= seek;
@@ -600,18 +664,17 @@ nsMultiplexInputStream::Seek(int32_t aWh
   }
 
   if (aWhence == NS_SEEK_END) {
     if (aOffset > 0) {
       return NS_ERROR_INVALID_ARG;
     }
     int64_t remaining = aOffset;
     for (uint32_t i = mStreams.Length() - 1; i != (uint32_t)-1; --i) {
-      nsCOMPtr<nsISeekableStream> stream =
-        do_QueryInterface(mStreams[i]);
+      nsCOMPtr<nsISeekableStream> stream = mStreams[i].mSeekableStream;
 
       // See if all remaining streams should be seeked to end
       if (remaining == 0) {
         if (i >= oldCurrentStream) {
           rv = stream->Seek(NS_SEEK_END, 0);
           if (NS_WARN_IF(NS_FAILED(rv))) {
             return rv;
           }
@@ -691,23 +754,22 @@ nsMultiplexInputStream::Tell(int64_t* aR
     return mStatus;
   }
 
   nsresult rv;
   int64_t ret64 = 0;
   uint32_t i, last;
   last = mStartedReadingCurrent ? mCurrentStream + 1 : mCurrentStream;
   for (i = 0; i < last; ++i) {
-    nsCOMPtr<nsISeekableStream> stream = do_QueryInterface(mStreams[i]);
-    if (NS_WARN_IF(!stream)) {
+    if (NS_WARN_IF(!mStreams[i].mSeekableStream)) {
       return NS_ERROR_NO_INTERFACE;
     }
 
     int64_t pos;
-    rv = TellMaybeSeek(stream, &pos);
+    rv = TellMaybeSeek(mStreams[i].mSeekableStream, &pos);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     ret64 += pos;
   }
   *aResult =  ret64;
 
   return NS_OK;
@@ -859,20 +921,18 @@ nsMultiplexInputStream::AsyncWait(nsIInp
   mAsyncWaitCallback = aCallback;
 
   if (!mAsyncWaitCallback) {
       return NS_OK;
   }
 
   nsTArray<nsCOMPtr<nsIAsyncInputStream>> asyncStreams;
   for (uint32_t i = mCurrentStream; i < mStreams.Length(); ++i) {
-    nsCOMPtr<nsIAsyncInputStream> asyncStream =
-      do_QueryInterface(mStreams.SafeElementAt(i, nullptr));
-    if (asyncStream) {
-      asyncStreams.AppendElement(asyncStream);
+    if (mStreams[i].mAsyncStream) {
+      asyncStreams.AppendElement(mStreams[i].mAsyncStream);
     }
   }
 
   if (!aEventTarget) {
     aEventTarget = SystemGroup::EventTargetFor(TaskCategory::Other);
   }
 
   if (asyncStreams.IsEmpty()) {
@@ -930,17 +990,17 @@ nsMultiplexInputStream::Serialize(InputS
   uint32_t streamCount = mStreams.Length();
 
   if (streamCount) {
     InfallibleTArray<InputStreamParams>& streams = params.streams();
 
     streams.SetCapacity(streamCount);
     for (uint32_t index = 0; index < streamCount; index++) {
       InputStreamParams childStreamParams;
-      InputStreamHelper::SerializeInputStream(mStreams[index],
+      InputStreamHelper::SerializeInputStream(mStreams[index].mStream,
                                               childStreamParams,
                                               aFileDescriptors);
 
       streams.AppendElement(childStreamParams);
     }
   }
 
   params.currentStream() = mCurrentStream;
@@ -992,17 +1052,18 @@ Maybe<uint64_t>
 nsMultiplexInputStream::ExpectedSerializedLength()
 {
   MutexAutoLock lock(mLock);
 
   bool lengthValueExists = false;
   uint64_t expectedLength = 0;
   uint32_t streamCount = mStreams.Length();
   for (uint32_t index = 0; index < streamCount; index++) {
-    nsCOMPtr<nsIIPCSerializableInputStream> stream = do_QueryInterface(mStreams[index]);
+    nsCOMPtr<nsIIPCSerializableInputStream> stream =
+      do_QueryInterface(mStreams[index].mStream);
     if (!stream) {
       continue;
     }
     Maybe<uint64_t> length = stream->ExpectedSerializedLength();
     if (length.isNothing()) {
       continue;
     }
     lengthValueExists = true;
@@ -1019,17 +1080,18 @@ nsMultiplexInputStream::GetCloneable(boo
   //right now.
   if (mCurrentStream > 0 || mStartedReadingCurrent) {
     *aCloneable = false;
     return NS_OK;
   }
 
   uint32_t len = mStreams.Length();
   for (uint32_t i = 0; i < len; ++i) {
-    nsCOMPtr<nsICloneableInputStream> cis = do_QueryInterface(mStreams[i]);
+    nsCOMPtr<nsICloneableInputStream> cis =
+      do_QueryInterface(mStreams[i].mStream);
     if (!cis || !cis->GetCloneable()) {
       *aCloneable = false;
       return NS_OK;
     }
   }
 
   *aCloneable = true;
   return NS_OK;
@@ -1046,17 +1108,18 @@ nsMultiplexInputStream::Clone(nsIInputSt
     return NS_ERROR_FAILURE;
   }
 
   RefPtr<nsMultiplexInputStream> clone = new nsMultiplexInputStream();
 
   nsresult rv;
   uint32_t len = mStreams.Length();
   for (uint32_t i = 0; i < len; ++i) {
-    nsCOMPtr<nsICloneableInputStream> substream = do_QueryInterface(mStreams[i]);
+    nsCOMPtr<nsICloneableInputStream> substream =
+      do_QueryInterface(mStreams[i].mStream);
     if (NS_WARN_IF(!substream)) {
       return NS_ERROR_FAILURE;
     }
 
     nsCOMPtr<nsIInputStream> clonedSubstream;
     rv = substream->Clone(getter_AddRefs(clonedSubstream));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
@@ -1067,40 +1130,42 @@ nsMultiplexInputStream::Clone(nsIInputSt
       return rv;
     }
   }
 
   clone.forget(aClone);
   return NS_OK;
 }
 
-#define MAYBE_UPDATE_VALUE(x, y)                        \
-  {                                                     \
-    nsCOMPtr<y> substream = do_QueryInterface(aStream); \
-    if (substream) {                                    \
-      if (aCount == 1) {                                \
-        ++x;                                            \
-      } else if (x > 0) {                               \
-        --x;                                            \
-      } else {                                          \
-        MOZ_CRASH("A nsIInputStream changed QI map when stored in a nsMultiplexInputStream!"); \
-      }                                                 \
-    }                                                   \
+#define MAYBE_UPDATE_VALUE_REAL(x, y) \
+  if (y) {                            \
+    if (aCount == 1) {                \
+      ++x;                            \
+    } else if (x > 0) {               \
+      --x;                            \
+    } else {                          \
+      MOZ_CRASH("A nsIInputStream changed QI map when stored in a nsMultiplexInputStream!"); \
+    }                                 \
+  }
+
+#define MAYBE_UPDATE_VALUE(x, y)                                \
+  {                                                             \
+    nsCOMPtr<y> substream = do_QueryInterface(aStream.mStream); \
+    MAYBE_UPDATE_VALUE_REAL(x, substream)                       \
   }
 
 void
-nsMultiplexInputStream::UpdateQIMap(nsIInputStream* aStream, int32_t aCount)
+nsMultiplexInputStream::UpdateQIMap(StreamData& aStream, int32_t aCount)
 {
-  MOZ_ASSERT(aStream);
   MOZ_ASSERT(aCount == -1 || aCount == 1);
 
-  MAYBE_UPDATE_VALUE(mSeekableStreams, nsISeekableStream);
-  MAYBE_UPDATE_VALUE(mIPCSerializableStreams, nsIIPCSerializableInputStream);
-  MAYBE_UPDATE_VALUE(mCloneableStreams, nsICloneableInputStream);
-  MAYBE_UPDATE_VALUE(mAsyncInputStreams, nsIAsyncInputStream);
+  MAYBE_UPDATE_VALUE_REAL(mSeekableStreams, aStream.mSeekableStream)
+  MAYBE_UPDATE_VALUE(mIPCSerializableStreams, nsIIPCSerializableInputStream)
+  MAYBE_UPDATE_VALUE(mCloneableStreams, nsICloneableInputStream)
+  MAYBE_UPDATE_VALUE_REAL(mAsyncInputStreams, aStream.mAsyncStream)
 }
 
 #undef MAYBE_UPDATE_VALUE
 
 bool
 nsMultiplexInputStream::IsSeekable() const
 {
   return mStreams.Length() == mSeekableStreams;
--- a/xpcom/tests/gtest/TestMultiplexInputStream.cpp
+++ b/xpcom/tests/gtest/TestMultiplexInputStream.cpp
@@ -208,8 +208,189 @@ TEST(TestMultiplexInputStream, AsyncWait
   ASSERT_FALSE(cb->Called());
   ais->CloseWithStatus(NS_ERROR_FAILURE);
   ASSERT_FALSE(cb->Called());
 
   // Eventually it is called.
   MOZ_ALWAYS_TRUE(mozilla::SpinEventLoopUntil([&]() { return cb->Called(); }));
   ASSERT_TRUE(cb->Called());
 }
+
+class ClosedStream final : public nsIInputStream
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  ClosedStream() {}
+
+  NS_IMETHOD
+  Available(uint64_t* aLength) override
+  {
+    return NS_BASE_STREAM_CLOSED;
+  }
+
+  NS_IMETHOD
+  Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) override
+  {
+    MOZ_CRASH("This should not be called!");
+    return NS_OK;
+  }
+
+  NS_IMETHOD
+  ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+               uint32_t aCount, uint32_t *aResult) override
+  {
+    MOZ_CRASH("This should not be called!");
+    return NS_OK;
+  }
+
+  NS_IMETHOD
+  Close() override { return NS_OK; }
+
+  NS_IMETHOD
+  IsNonBlocking(bool* aNonBlocking) override
+  {
+    *aNonBlocking = true;
+    return NS_OK;
+  }
+
+private:
+  ~ClosedStream() = default;
+};
+
+NS_IMPL_ISUPPORTS(ClosedStream, nsIInputStream)
+
+class AsyncStream final : public nsIAsyncInputStream
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  explicit AsyncStream(int64_t aSize) : mState(eBlocked), mSize(aSize) {}
+
+  void
+  Unblock()
+  {
+    mState = eUnblocked;
+  }
+
+  NS_IMETHOD
+  Available(uint64_t* aLength) override
+  {
+   *aLength = mState == eBlocked ? 0 : mSize;
+   return mState == eClosed ? NS_BASE_STREAM_CLOSED : NS_OK;
+  }
+
+  NS_IMETHOD
+  Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) override
+  {
+    MOZ_CRASH("This should not be called!");
+    return NS_OK;
+  }
+
+  NS_IMETHOD
+  ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+               uint32_t aCount, uint32_t *aResult) override
+  {
+    MOZ_CRASH("This should not be called!");
+    return NS_OK;
+  }
+
+  NS_IMETHOD
+  Close() override
+  {
+    mState = eClosed;
+    return NS_OK;
+  }
+
+  NS_IMETHOD
+  IsNonBlocking(bool* aNonBlocking) override
+  {
+    *aNonBlocking = true;
+    return NS_OK;
+  }
+
+  NS_IMETHOD
+  AsyncWait(nsIInputStreamCallback* aCallback,
+            uint32_t aFlags, uint32_t aRequestedCount,
+            nsIEventTarget* aEventTarget) override
+  {
+    MOZ_CRASH("This should not be called!");
+    return NS_OK;
+  }
+
+  NS_IMETHOD
+  CloseWithStatus(nsresult aStatus) override
+  {
+    return NS_OK;
+  }
+
+private:
+  ~AsyncStream() = default;
+
+  enum {
+    eBlocked,
+    eUnblocked,
+    eClosed
+  } mState;
+
+  uint64_t mSize;
+};
+
+NS_IMPL_ISUPPORTS(AsyncStream, nsIInputStream, nsIAsyncInputStream)
+
+TEST(TestMultiplexInputStream, Available) {
+  nsCOMPtr<nsIMultiplexInputStream> multiplexStream =
+    do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1");
+
+  nsCOMPtr<nsIInputStream> s = do_QueryInterface(multiplexStream);
+  ASSERT_TRUE(!!s);
+
+  nsCOMPtr<nsIAsyncInputStream> as = do_QueryInterface(multiplexStream);
+  ASSERT_TRUE(!as);
+
+  uint64_t length;
+
+  // The stream returns NS_BASE_STREAM_CLOSED if there are no substreams.
+  nsresult rv = s->Available(&length);
+  ASSERT_EQ(NS_BASE_STREAM_CLOSED, rv);
+
+  rv = multiplexStream->AppendStream(new ClosedStream());
+  ASSERT_EQ(NS_OK, rv);
+
+  uint64_t asyncSize = 2;
+  RefPtr<AsyncStream> asyncStream = new AsyncStream(2);
+  rv = multiplexStream->AppendStream(asyncStream);
+  ASSERT_EQ(NS_OK, rv);
+
+  nsCString buffer;
+  buffer.Assign("World!!!");
+
+  nsCOMPtr<nsIInputStream> stringStream;
+  rv = NS_NewCStringInputStream(getter_AddRefs(stringStream), buffer);
+  ASSERT_EQ(NS_OK, rv);
+
+  rv = multiplexStream->AppendStream(stringStream);
+  ASSERT_EQ(NS_OK, rv);
+
+  // Now we are async.
+  as = do_QueryInterface(multiplexStream);
+  ASSERT_TRUE(!!as);
+
+  // Available should skip the closed stream and return 0 because the
+  // asyncStream returns 0 and it's async.
+  rv = s->Available(&length);
+  ASSERT_EQ(NS_OK, rv);
+  ASSERT_EQ((uint64_t)0, length);
+
+  asyncStream->Unblock();
+
+  // Now we should return only the size of the async stream because we don't
+  // know when this is completed.
+  rv = s->Available(&length);
+  ASSERT_EQ(NS_OK, rv);
+  ASSERT_EQ(asyncSize, length);
+
+  asyncStream->Close();
+
+  rv = s->Available(&length);
+  ASSERT_EQ(NS_OK, rv);
+  ASSERT_EQ(buffer.Length(), length);
+}