merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 09 Dec 2016 13:32:03 +0100
changeset 325483 44ab7f53ead5b57ef0d2e4de911567e4b5886d0a
parent 325434 88e0fb654a104419dd66774d422b20692639919e (current diff)
parent 325482 1e905d6da8356a1da48d43afb4bbbc54b4b5ae02 (diff)
child 325484 233b0fa247c014abf2cbc0815e7f78d9067d30d8
child 325525 9db5834b7c36c53e3af2307b4c8551979e415ead
child 325586 d16033cf3011f19a7e2d0dc8a706c8ebb28f56cf
push id24
push usermaklebus@msu.edu
push dateTue, 20 Dec 2016 03:11:33 +0000
reviewersmerge
milestone53.0a1
merge mozilla-inbound to mozilla-central a=merge
--- a/.flake8
+++ b/.flake8
@@ -1,5 +1,5 @@
 [flake8]
 # See http://pep8.readthedocs.io/en/latest/intro.html#configuration
-ignore = E121, E123, E126, E133, E226, E241, E242, E704, W503, E402
+ignore = E121, E123, E126, E129, E133, E226, E241, E242, E704, W503, E402
 max-line-length = 99
 filename = *.py, +.lint
--- a/accessible/tests/mochitest/tree/test_tabbrowser.xul
+++ b/accessible/tests/mochitest/tree/test_tabbrowser.xul
@@ -103,27 +103,37 @@
           // NB: The (3) buttons are not visible, unless manually hovered,
           //     probably due to size reduction in this test.
           tabsAccTree.children.splice(0, 0,
             {
               // xul:tab ("about:")
               role: ROLE_PAGETAB,
               children: [
                 {
+                  // xul:text, i.e. the tab label text
+                  role: ROLE_TEXT_LEAF,
+                  children: []
+                },
+                {
                   // xul:toolbarbutton ("Close Tab")
                   role: ROLE_PUSHBUTTON,
                   children: []
                 }
               ]
             },
             {
               // tab ("about:mozilla")
               role: ROLE_PAGETAB,
               children: [
                 {
+                  // xul:text, i.e. the tab label text
+                  role: ROLE_TEXT_LEAF,
+                  children: []
+                },
+                {
                   // xul:toolbarbutton ("Close Tab")
                   role: ROLE_PUSHBUTTON,
                   children: []
                 }
               ]
             },
             {
               // xul:toolbarbutton ("Open a new tab")
--- a/browser/base/content/tabbrowser.css
+++ b/browser/base/content/tabbrowser.css
@@ -34,22 +34,34 @@
 .tab-sharing-icon-overlay[sharing]:not([selected]),
 .tab-icon-overlay[soundplaying][pinned],
 .tab-icon-overlay[muted][pinned],
 .tab-icon-overlay[blocked][pinned],
 .tab-icon-overlay[crashed] {
   display: -moz-box;
 }
 
-.tab-label[pinned] {
+.tab-label {
+  white-space: nowrap;
+}
+
+.tab-label-container {
+  overflow: hidden;
+}
+
+.tab-label-container[pinned] {
   width: 0;
-  margin-left: 0 !important;
-  margin-right: 0 !important;
-  padding-left: 0 !important;
-  padding-right: 0 !important;
+}
+
+.tab-label-container[textoverflow]:not([pinned]) {
+  mask-image: linear-gradient(to left, transparent, black 1em);
+}
+
+.tab-label-container[textoverflow]:not([pinned]):-moz-locale-dir(rtl) {
+  mask-image: linear-gradient(to right, transparent, black 1em);
 }
 
 .tab-stack {
   vertical-align: top; /* for pinned tabs */
 }
 
 tabpanels {
   background-color: transparent;
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1433,28 +1433,26 @@
       </method>
 
 
       <method name="setTabTitleLoading">
         <parameter name="aTab"/>
         <body>
           <![CDATA[
             aTab.label = this.mStringBundle.getString("tabs.connecting");
-            aTab.crop = "end";
-            this._tabAttrModified(aTab, ["label", "crop"]);
+            this._tabAttrModified(aTab, ["label"]);
           ]]>
         </body>
       </method>
 
       <method name="setTabTitle">
         <parameter name="aTab"/>
         <body>
           <![CDATA[
             var browser = this.getBrowserForTab(aTab);
-            var crop = "end";
             var title = browser.contentTitle;
 
             if (!title) {
               if (browser.currentURI.spec) {
                 try {
                   title = this.mURIFixup.createExposableURI(browser.currentURI).spec;
                 } catch (ex) {
                   title = browser.currentURI.spec;
@@ -1466,35 +1464,30 @@
                 // Let's try to unescape it using a character set
                 // in case the URI is not ASCII.
                 try {
                   var characterSet = browser.characterSet;
                   const textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
                                                  .getService(Components.interfaces.nsITextToSubURI);
                   title = textToSubURI.unEscapeNonAsciiURI(characterSet, title);
                 } catch (ex) { /* Do nothing. */ }
-
-                crop = "center";
-
               } else if (aTab.hasAttribute("customizemode")) {
                 let brandBundle = document.getElementById("bundle_brand");
                 let brandShortName = brandBundle.getString("brandShortName");
                 title = gNavigatorBundle.getFormattedString("customizeMode.tabTitle",
                                                             [ brandShortName ]);
               } else // Still no title?  Fall back to our untitled string.
                 title = this.mStringBundle.getString("tabs.emptyTabTitle");
             }
 
-            if (aTab.label == title &&
-                aTab.crop == crop)
+            if (aTab.label == title)
               return false;
 
             aTab.label = title;
-            aTab.crop = crop;
-            this._tabAttrModified(aTab, ["label", "crop"]);
+            this._tabAttrModified(aTab, ["label"]);
 
             if (aTab.selected)
               this.updateTitlebar();
 
             return true;
           ]]>
         </body>
       </method>
@@ -2192,17 +2185,16 @@
               t.setAttribute("label", aURI);
             }
 
             if (aUserContextId) {
               t.setAttribute("usercontextid", aUserContextId);
               ContextualIdentityService.setTabStyle(t);
             }
 
-            t.setAttribute("crop", "end");
             t.setAttribute("onerror", "this.removeAttribute('image');");
             t.className = "tabbrowser-tab";
 
             this.tabContainer._unlockTabSizing();
 
             // When overflowing, new tabs are scrolled into view smoothly, which
             // doesn't go well together with the width transition. So we skip the
             // transition in that case.
@@ -5282,31 +5274,37 @@
           }
           return [tabStart, tabEnd];
         ]]></body>
       </method>
     </implementation>
 
     <handlers>
       <handler event="underflow" phase="capturing"><![CDATA[
+        if (event.target != this)
+          return;
+
         if (event.detail == 0)
           return; // Ignore vertical events
 
         var tabs = document.getBindingParent(this);
         tabs.removeAttribute("overflow");
 
         if (tabs._lastTabClosedByMouse)
           tabs._expandSpacerBy(this._scrollButtonDown.clientWidth);
 
         for (let tab of Array.from(tabs.tabbrowser._removingTabs))
           tabs.tabbrowser.removeTab(tab);
 
         tabs._positionPinnedTabs();
       ]]></handler>
       <handler event="overflow"><![CDATA[
+        if (event.target != this)
+          return;
+
         if (event.detail == 0)
           return; // Ignore vertical events
 
         var tabs = document.getBindingParent(this);
         tabs.setAttribute("overflow", "true");
         tabs._positionPinnedTabs();
         tabs._handleTabSelect(false);
       ]]></handler>
@@ -5350,17 +5348,16 @@
 
     <implementation implements="nsIDOMEventListener, nsIObserver">
       <constructor>
         <![CDATA[
           this.mTabClipWidth = Services.prefs.getIntPref("browser.tabs.tabClipWidth");
 
           var tab = this.firstChild;
           tab.label = this.tabbrowser.mStringBundle.getString("tabs.emptyTabTitle");
-          tab.setAttribute("crop", "end");
           tab.setAttribute("onerror", "this.removeAttribute('image');");
 
           window.addEventListener("resize", this, false);
           window.addEventListener("load", this, false);
 
           try {
             this._tabAnimationLoggingEnabled = Services.prefs.getBoolPref("browser.tabs.animationLogging.enabled");
           } catch (ex) {
@@ -6725,20 +6722,25 @@
           <xul:image xbl:inherits="sharing,selected=visuallyselected"
                      anonid="sharing-icon"
                      class="tab-sharing-icon-overlay"
                      role="presentation"/>
           <xul:image xbl:inherits="crashed,busy,soundplaying,soundplaying-scheduledremoval,pinned,muted,blocked,selected=visuallyselected"
                      anonid="overlay-icon"
                      class="tab-icon-overlay"
                      role="presentation"/>
-          <xul:label flex="1"
-                     xbl:inherits="value=label,crop,accesskey,fadein,pinned,selected=visuallyselected,attention"
-                     class="tab-text tab-label"
-                     role="presentation"/>
+          <xul:hbox class="tab-label-container"
+                    xbl:inherits="pinned,selected=visuallyselected"
+                    onoverflow="this.setAttribute('textoverflow', 'true');"
+                    onunderflow="this.removeAttribute('textoverflow');"
+                    flex="1">
+            <xul:label class="tab-text tab-label"
+                       xbl:inherits="xbl:text=label,accesskey,fadein,pinned,selected=visuallyselected,attention"
+                       role="presentation"/>
+          </xul:hbox>
           <xul:image xbl:inherits="soundplaying,soundplaying-scheduledremoval,pinned,muted,blocked,selected=visuallyselected"
                      anonid="soundplaying-icon"
                      class="tab-icon-sound"
                      role="presentation"/>
           <xul:toolbarbutton anonid="close-button"
                              xbl:inherits="fadein,pinned,selected=visuallyselected"
                              class="tab-close-button close-icon"/>
         </xul:hbox>
@@ -7107,17 +7109,17 @@
         ]]></body>
       </method>
 
       <method name="_setMenuitemAttributes">
         <parameter name="aMenuitem"/>
         <parameter name="aTab"/>
         <body><![CDATA[
           aMenuitem.setAttribute("label", aTab.label);
-          aMenuitem.setAttribute("crop", aTab.getAttribute("crop"));
+          aMenuitem.setAttribute("crop", "end");
 
           if (aTab.hasAttribute("busy")) {
             aMenuitem.setAttribute("busy", aTab.getAttribute("busy"));
             aMenuitem.removeAttribute("image");
           } else {
             aMenuitem.setAttribute("image", aTab.getAttribute("image"));
             aMenuitem.removeAttribute("busy");
           }
--- a/browser/base/content/test/general/browser_overflowScroll.js
+++ b/browser/base/content/test/general/browser_overflowScroll.js
@@ -32,17 +32,18 @@ function doTest() {
   while (tabs.length < tabCountForOverflow)
     gBrowser.addTab("about:blank", {skipAnimation: true});
   gBrowser.pinTab(tabs[0]);
 
   tabstrip.addEventListener("overflow", runOverflowTests, false);
 }
 
 function runOverflowTests(aEvent) {
-  if (aEvent.detail != 1)
+  if (aEvent.detail != 1 ||
+      aEvent.target != tabstrip)
     return;
 
   tabstrip.removeEventListener("overflow", runOverflowTests, false);
 
   var upButton = tabstrip._scrollButtonUp;
   var downButton = tabstrip._scrollButtonDown;
   var element;
 
--- a/browser/extensions/pdfjs/README.mozilla
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -1,3 +1,3 @@
 This is the pdf.js project output, https://github.com/mozilla/pdf.js
 
-Current extension version is: 1.6.355
+Current extension version is: 1.6.372
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -18,18 +18,18 @@
   define('pdfjs-dist/build/pdf', ['exports'], factory);
  } else if (typeof exports !== 'undefined') {
   factory(exports);
  } else {
   factory(root['pdfjsDistBuildPdf'] = {});
  }
 }(this, function (exports) {
  'use strict';
- var pdfjsVersion = '1.6.355';
- var pdfjsBuild = '451956c';
+ var pdfjsVersion = '1.6.372';
+ var pdfjsBuild = 'aaec4908';
  var pdfjsFilePath = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : null;
  var pdfjsLibs = {};
  (function pdfjsWrapper() {
   (function (root, factory) {
    factory(root.pdfjsSharedUtil = {});
   }(this, function (exports) {
    var globalScope = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this;
    var FONT_IDENTITY_MATRIX = [
@@ -1717,17 +1717,17 @@
    function fixMetadata(meta) {
     return meta.replace(/>\\376\\377([^<]+)/g, function (all, codes) {
      var bytes = codes.replace(/\\([0-3])([0-7])([0-7])/g, function (code, d1, d2, d3) {
       return String.fromCharCode(d1 * 64 + d2 * 8 + d3 * 1);
      });
      var chars = '';
      for (var i = 0; i < bytes.length; i += 2) {
       var code = bytes.charCodeAt(i) * 256 + bytes.charCodeAt(i + 1);
-      chars += code >= 32 && code < 127 && code !== 60 && code !== 62 && code !== 38 && false ? String.fromCharCode(code) : '&#x' + (0x10000 + code).toString(16).substring(1) + ';';
+      chars += code >= 32 && code < 127 && code !== 60 && code !== 62 && code !== 38 ? String.fromCharCode(code) : '&#x' + (0x10000 + code).toString(16).substring(1) + ';';
      }
      return '>' + chars;
     });
    }
    function Metadata(meta) {
     if (typeof meta === 'string') {
      meta = fixMetadata(meta);
      var parser = new DOMParser();
--- a/browser/extensions/pdfjs/content/build/pdf.worker.js
+++ b/browser/extensions/pdfjs/content/build/pdf.worker.js
@@ -18,18 +18,18 @@
   define('pdfjs-dist/build/pdf.worker', ['exports'], factory);
  } else if (typeof exports !== 'undefined') {
   factory(exports);
  } else {
   factory(root['pdfjsDistBuildPdfWorker'] = {});
  }
 }(this, function (exports) {
  'use strict';
- var pdfjsVersion = '1.6.355';
- var pdfjsBuild = '451956c';
+ var pdfjsVersion = '1.6.372';
+ var pdfjsBuild = 'aaec4908';
  var pdfjsFilePath = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : null;
  var pdfjsLibs = {};
  (function pdfjsWrapper() {
   (function (root, factory) {
    factory(root.pdfjsCoreArithmeticDecoder = {});
   }(this, function (exports) {
    var ArithmeticDecoder = function ArithmeticDecoderClosure() {
     var QeTable = [
@@ -40449,41 +40449,37 @@
       if (isDict(colorSpaces)) {
        var refcs = colorSpaces.get(cs.name);
        if (refcs) {
         cs = refcs;
        }
       }
      }
      cs = xref.fetchIfRef(cs);
-     var mode;
      if (isName(cs)) {
-      mode = cs.name;
-      this.mode = mode;
-      switch (mode) {
+      switch (cs.name) {
       case 'DeviceGray':
       case 'G':
        return 'DeviceGrayCS';
       case 'DeviceRGB':
       case 'RGB':
        return 'DeviceRgbCS';
       case 'DeviceCMYK':
       case 'CMYK':
        return 'DeviceCmykCS';
       case 'Pattern':
        return [
         'PatternCS',
         null
        ];
       default:
-       error('unrecognized colorspace ' + mode);
+       error('unrecognized colorspace ' + cs.name);
       }
      } else if (isArray(cs)) {
-      mode = xref.fetchIfRef(cs[0]).name;
-      this.mode = mode;
+      var mode = xref.fetchIfRef(cs[0]).name;
       var numComps, params, alt, whitePoint, blackPoint, gamma;
       switch (mode) {
       case 'DeviceGray':
       case 'G':
        return 'DeviceGrayCS';
       case 'DeviceRGB':
       case 'RGB':
        return 'DeviceRgbCS';
@@ -40556,22 +40552,17 @@
         'IndexedCS',
         baseIndexedCS,
         hiVal,
         lookup
        ];
       case 'Separation':
       case 'DeviceN':
        var name = xref.fetchIfRef(cs[1]);
-       numComps = 1;
-       if (isName(name)) {
-        numComps = 1;
-       } else if (isArray(name)) {
-        numComps = name.length;
-       }
+       numComps = isArray(name) ? name.length : 1;
        alt = ColorSpace.parseToIR(cs[2], xref, res);
        var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3]));
        return [
         'AlternateCS',
         numComps,
         alt,
         tintFnIR
        ];
@@ -40649,32 +40640,26 @@
       var usesZeroToOneRange = base.usesZeroToOneRange;
       var isPassthrough = (base.isPassthrough(8) || !usesZeroToOneRange) && alpha01 === 0;
       var pos = isPassthrough ? destOffset : 0;
       var baseBuf = isPassthrough ? dest : new Uint8Array(baseNumComps * count);
       var numComps = this.numComps;
       var scaled = new Float32Array(numComps);
       var tinted = new Float32Array(baseNumComps);
       var i, j;
-      if (usesZeroToOneRange) {
-       for (i = 0; i < count; i++) {
-        for (j = 0; j < numComps; j++) {
-         scaled[j] = src[srcOffset++] * scale;
-        }
-        tintFn(scaled, 0, tinted, 0);
+      for (i = 0; i < count; i++) {
+       for (j = 0; j < numComps; j++) {
+        scaled[j] = src[srcOffset++] * scale;
+       }
+       tintFn(scaled, 0, tinted, 0);
+       if (usesZeroToOneRange) {
         for (j = 0; j < baseNumComps; j++) {
          baseBuf[pos++] = tinted[j] * 255;
         }
-       }
-      } else {
-       for (i = 0; i < count; i++) {
-        for (j = 0; j < numComps; j++) {
-         scaled[j] = src[srcOffset++] * scale;
-        }
-        tintFn(scaled, 0, tinted, 0);
+       } else {
         base.getRgbItem(tinted, 0, baseBuf, pos);
         pos += baseNumComps;
        }
       }
       if (!isPassthrough) {
        base.getRgbBuffer(baseBuf, 0, count, dest, destOffset, 8, alpha01);
       }
      },
@@ -40697,37 +40682,35 @@
     }
     PatternCS.prototype = {};
     return PatternCS;
    }();
    var IndexedCS = function IndexedCSClosure() {
     function IndexedCS(base, highVal, lookup) {
      this.name = 'Indexed';
      this.numComps = 1;
-     this.defaultColor = new Uint8Array([0]);
+     this.defaultColor = new Uint8Array(this.numComps);
      this.base = base;
      this.highVal = highVal;
      var baseNumComps = base.numComps;
      var length = baseNumComps * highVal;
-     var lookupArray;
      if (isStream(lookup)) {
-      lookupArray = new Uint8Array(length);
+      this.lookup = new Uint8Array(length);
       var bytes = lookup.getBytes(length);
-      lookupArray.set(bytes);
+      this.lookup.set(bytes);
      } else if (isString(lookup)) {
-      lookupArray = new Uint8Array(length);
+      this.lookup = new Uint8Array(length);
       for (var i = 0; i < length; ++i) {
-       lookupArray[i] = lookup.charCodeAt(i);
+       this.lookup[i] = lookup.charCodeAt(i);
       }
      } else if (lookup instanceof Uint8Array || lookup instanceof Array) {
-      lookupArray = lookup;
+      this.lookup = lookup;
      } else {
       error('Unrecognized lookup table: ' + lookup);
      }
-     this.lookup = lookupArray;
     }
     IndexedCS.prototype = {
      getRgb: ColorSpace.prototype.getRgb,
      getRgbItem: function IndexedCS_getRgbItem(src, srcOffset, dest, destOffset) {
       var numComps = this.base.numComps;
       var start = src[srcOffset] * numComps;
       this.base.getRgbItem(this.lookup, start, dest, destOffset);
      },
@@ -40753,17 +40736,17 @@
      usesZeroToOneRange: true
     };
     return IndexedCS;
    }();
    var DeviceGrayCS = function DeviceGrayCSClosure() {
     function DeviceGrayCS() {
      this.name = 'DeviceGray';
      this.numComps = 1;
-     this.defaultColor = new Float32Array([0]);
+     this.defaultColor = new Float32Array(this.numComps);
     }
     DeviceGrayCS.prototype = {
      getRgb: ColorSpace.prototype.getRgb,
      getRgbItem: function DeviceGrayCS_getRgbItem(src, srcOffset, dest, destOffset) {
       var c = src[srcOffset] * 255 | 0;
       c = c < 0 ? 0 : c > 255 ? 255 : c;
       dest[destOffset] = dest[destOffset + 1] = dest[destOffset + 2] = c;
      },
@@ -40789,21 +40772,17 @@
      usesZeroToOneRange: true
     };
     return DeviceGrayCS;
    }();
    var DeviceRgbCS = function DeviceRgbCSClosure() {
     function DeviceRgbCS() {
      this.name = 'DeviceRGB';
      this.numComps = 3;
-     this.defaultColor = new Float32Array([
-      0,
-      0,
-      0
-     ]);
+     this.defaultColor = new Float32Array(this.numComps);
     }
     DeviceRgbCS.prototype = {
      getRgb: ColorSpace.prototype.getRgb,
      getRgbItem: function DeviceRgbCS_getRgbItem(src, srcOffset, dest, destOffset) {
       var r = src[srcOffset] * 255 | 0;
       var g = src[srcOffset + 1] * 255 | 0;
       var b = src[srcOffset + 2] * 255 | 0;
       dest[destOffset] = r < 0 ? 0 : r > 255 ? 255 : r;
@@ -40849,22 +40828,18 @@
      var b = c * (0.8842522430003296 * c + 8.078677503112928 * m + 30.89978309703729 * y - 0.23883238689178934 * k + -14.183576799673286) + m * (10.49593273432072 * m + 63.02378494754052 * y + 50.606957656360734 * k - 112.23884253719248) + y * (0.03296041114873217 * y + 115.60384449646641 * k + -193.58209356861505) + k * (-22.33816807309886 * k - 180.12613974708367) + 255 | 0;
      dest[destOffset] = r > 255 ? 255 : r < 0 ? 0 : r;
      dest[destOffset + 1] = g > 255 ? 255 : g < 0 ? 0 : g;
      dest[destOffset + 2] = b > 255 ? 255 : b < 0 ? 0 : b;
     }
     function DeviceCmykCS() {
      this.name = 'DeviceCMYK';
      this.numComps = 4;
-     this.defaultColor = new Float32Array([
-      0,
-      0,
-      0,
-      1
-     ]);
+     this.defaultColor = new Float32Array(this.numComps);
+     this.defaultColor[3] = 1;
     }
     DeviceCmykCS.prototype = {
      getRgb: ColorSpace.prototype.getRgb,
      getRgbItem: function DeviceCmykCS_getRgbItem(src, srcOffset, dest, destOffset) {
       convertToRgb(src, srcOffset, 1, dest, destOffset);
      },
      getRgbBuffer: function DeviceCmykCS_getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
       var scale = 1 / ((1 << bits) - 1);
@@ -40885,17 +40860,17 @@
      usesZeroToOneRange: true
     };
     return DeviceCmykCS;
    }();
    var CalGrayCS = function CalGrayCSClosure() {
     function CalGrayCS(whitePoint, blackPoint, gamma) {
      this.name = 'CalGray';
      this.numComps = 1;
-     this.defaultColor = new Float32Array([0]);
+     this.defaultColor = new Float32Array(this.numComps);
      if (!whitePoint) {
       error('WhitePoint missing - required for color space CalGray');
      }
      blackPoint = blackPoint || [
       0,
       0,
       0
      ];
@@ -40997,17 +40972,17 @@
     ]);
     var tempNormalizeMatrix = new Float32Array(3);
     var tempConvertMatrix1 = new Float32Array(3);
     var tempConvertMatrix2 = new Float32Array(3);
     var DECODE_L_CONSTANT = Math.pow((8 + 16) / 116, 3) / 8.0;
     function CalRGBCS(whitePoint, blackPoint, gamma, matrix) {
      this.name = 'CalRGB';
      this.numComps = 3;
-     this.defaultColor = new Float32Array(3);
+     this.defaultColor = new Float32Array(this.numComps);
      if (!whitePoint) {
       error('WhitePoint missing - required for color space CalRGB');
      }
      blackPoint = blackPoint || new Float32Array(3);
      gamma = gamma || new Float32Array([
       1,
       1,
       1
@@ -41197,21 +41172,17 @@
      usesZeroToOneRange: true
     };
     return CalRGBCS;
    }();
    var LabCS = function LabCSClosure() {
     function LabCS(whitePoint, blackPoint, range) {
      this.name = 'Lab';
      this.numComps = 3;
-     this.defaultColor = new Float32Array([
-      0,
-      0,
-      0
-     ]);
+     this.defaultColor = new Float32Array(this.numComps);
      if (!whitePoint) {
       error('WhitePoint missing - required for color space Lab');
      }
      blackPoint = blackPoint || [
       0,
       0,
       0
      ];
@@ -45616,73 +45587,73 @@
       var glyphsVMetrics = [];
       var defaultVMetrics;
       var i, ii, j, jj, start, code, widths;
       if (properties.composite) {
        defaultWidth = dict.get('DW') || 1000;
        widths = dict.get('W');
        if (widths) {
         for (i = 0, ii = widths.length; i < ii; i++) {
-         start = widths[i++];
+         start = xref.fetchIfRef(widths[i++]);
          code = xref.fetchIfRef(widths[i]);
          if (isArray(code)) {
           for (j = 0, jj = code.length; j < jj; j++) {
-           glyphsWidths[start++] = code[j];
+           glyphsWidths[start++] = xref.fetchIfRef(code[j]);
           }
          } else {
-          var width = widths[++i];
+          var width = xref.fetchIfRef(widths[++i]);
           for (j = start; j <= code; j++) {
            glyphsWidths[j] = width;
           }
          }
         }
        }
        if (properties.vertical) {
-        var vmetrics = dict.get('DW2') || [
+        var vmetrics = dict.getArray('DW2') || [
          880,
          -1000
         ];
         defaultVMetrics = [
          vmetrics[1],
          defaultWidth * 0.5,
          vmetrics[0]
         ];
         vmetrics = dict.get('W2');
         if (vmetrics) {
          for (i = 0, ii = vmetrics.length; i < ii; i++) {
-          start = vmetrics[i++];
+          start = xref.fetchIfRef(vmetrics[i++]);
           code = xref.fetchIfRef(vmetrics[i]);
           if (isArray(code)) {
            for (j = 0, jj = code.length; j < jj; j++) {
             glyphsVMetrics[start++] = [
-             code[j++],
-             code[j++],
-             code[j]
+             xref.fetchIfRef(code[j++]),
+             xref.fetchIfRef(code[j++]),
+             xref.fetchIfRef(code[j])
             ];
            }
           } else {
            var vmetric = [
-            vmetrics[++i],
-            vmetrics[++i],
-            vmetrics[++i]
+            xref.fetchIfRef(vmetrics[++i]),
+            xref.fetchIfRef(vmetrics[++i]),
+            xref.fetchIfRef(vmetrics[++i])
            ];
            for (j = start; j <= code; j++) {
             glyphsVMetrics[j] = vmetric;
            }
           }
          }
         }
        }
       } else {
        var firstChar = properties.firstChar;
        widths = dict.get('Widths');
        if (widths) {
         j = firstChar;
         for (i = 0, ii = widths.length; i < ii; i++) {
-         glyphsWidths[j++] = widths[i];
+         glyphsWidths[j++] = xref.fetchIfRef(widths[i]);
         }
         defaultWidth = parseFloat(descriptor.get('MissingWidth')) || 0;
        } else {
         var baseFontName = dict.get('BaseFont');
         if (isName(baseFontName)) {
          var metrics = this.getBaseFontMetrics(baseFontName.name);
          glyphsWidths = this.buildCharCodeToWidth(metrics.widths, properties);
          defaultWidth = metrics.defaultWidth;
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -1302,17 +1302,17 @@ var pdfjsWebLibs;
     }
     return delta;
    }
    var animationStarted = new Promise(function (resolve) {
     window.requestAnimationFrame(resolve);
    });
    var localized = new Promise(function (resolve, reject) {
     if (!mozL10n) {
-     reject(new Error('mozL10n service is not available.'));
+     resolve();
      return;
     }
     if (mozL10n.getReadyState() !== 'loading') {
      resolve();
      return;
     }
     window.addEventListener('localized', function localized(evt) {
      resolve();
@@ -6712,17 +6712,17 @@ var pdfjsWebLibs;
     if (PDFViewerApplication.pdfViewer.containsElement(evt.target) || appConfig.toolbar.container.contains(evt.target) && evt.target !== appConfig.secondaryToolbar.toggleButton) {
      PDFViewerApplication.secondaryToolbar.close();
     }
    }, true);
    window.addEventListener('keydown', function keydown(evt) {
     if (OverlayManager.active) {
      return;
     }
-    var handled = false;
+    var handled = false, ensureViewerFocused = false;
     var cmd = (evt.ctrlKey ? 1 : 0) | (evt.altKey ? 2 : 0) | (evt.shiftKey ? 4 : 0) | (evt.metaKey ? 8 : 0);
     var pdfViewer = PDFViewerApplication.pdfViewer;
     var isViewerInPresentationMode = pdfViewer && pdfViewer.isInPresentationMode;
     if (cmd === 1 || cmd === 8 || cmd === 5 || cmd === 12) {
      switch (evt.keyCode) {
      case 70:
       if (!PDFViewerApplication.supportsIntegratedFind) {
        PDFViewerApplication.findBar.open();
@@ -6765,42 +6765,58 @@ var pdfjsWebLibs;
      case 96:
       if (!isViewerInPresentationMode) {
        setTimeout(function () {
         pdfViewer.currentScaleValue = DEFAULT_SCALE_VALUE;
        });
        handled = false;
       }
       break;
+     case 38:
+      if (isViewerInPresentationMode || PDFViewerApplication.page > 1) {
+       PDFViewerApplication.page = 1;
+       handled = true;
+       ensureViewerFocused = true;
+      }
+      break;
+     case 40:
+      if (isViewerInPresentationMode || PDFViewerApplication.page < PDFViewerApplication.pagesCount) {
+       PDFViewerApplication.page = PDFViewerApplication.pagesCount;
+       handled = true;
+       ensureViewerFocused = true;
+      }
+      break;
      }
     }
     if (cmd === 3 || cmd === 10) {
      switch (evt.keyCode) {
      case 80:
       PDFViewerApplication.requestPresentationMode();
       handled = true;
       break;
      case 71:
       PDFViewerApplication.appConfig.toolbar.pageNumber.select();
       handled = true;
       break;
      }
     }
     if (handled) {
+     if (ensureViewerFocused && !isViewerInPresentationMode) {
+      pdfViewer.focus();
+     }
      evt.preventDefault();
      return;
     }
     var curElement = document.activeElement || document.querySelector(':focus');
     var curElementTagName = curElement && curElement.tagName.toUpperCase();
     if (curElementTagName === 'INPUT' || curElementTagName === 'TEXTAREA' || curElementTagName === 'SELECT') {
      if (evt.keyCode !== 27) {
       return;
      }
     }
-    var ensureViewerFocused = false;
     if (cmd === 0) {
      switch (evt.keyCode) {
      case 38:
      case 33:
      case 8:
       if (!isViewerInPresentationMode && pdfViewer.currentScaleValue !== 'page-fit') {
        break;
       }
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -2445,16 +2445,17 @@ toolbarbutton.chevron > .toolbarbutton-m
   margin: 0;
 }
 
 %include ../shared/tabs.inc.css
 
 .tab-label {
   margin-top: 1px;
   margin-bottom: 0;
+  -moz-box-flex: 1;
   text-align: center;
 }
 
 @media (-moz-mac-yosemite-theme) {
   /* image preloading hack from shared/tabs.inc.css */
   #tabbrowser-tabs::before {
     background-image:
       url(chrome://browser/skin/yosemite/tab-selected-end-inactive.svg),
@@ -2544,17 +2545,17 @@ toolbarbutton.chevron > .toolbarbutton-m
 /*
  * Force the overlay to create a new stacking context so it always appears on
  * top of the icon.
  */
 .tab-icon-overlay {
   opacity: 0.9999;
 }
 
-.tab-label:not([selected="true"]) {
+.tab-label-container:not([selected="true"]) {
   opacity: .7;
 }
 
 .tabbrowser-tab,
 .tabs-newtab-button {
   font: message-box;
   border: none;
 }
@@ -2570,17 +2571,17 @@ toolbarbutton.chevron > .toolbarbutton-m
 }
 
 .tabs-newtab-button > .toolbarbutton-icon {
   -moz-box-align: center;
   border: solid transparent;
   border-width: 0 11px;
 }
 
-.tabbrowser-tab:focus > .tab-stack > .tab-content > .tab-label:not([pinned]),
+.tabbrowser-tab:focus > .tab-stack > .tab-content > .tab-label-container:not([pinned]),
 .tabbrowser-tab:focus > .tab-stack > .tab-content > .tab-icon-image[pinned],
 .tabbrowser-tab:focus > .tab-stack > .tab-content > .tab-throbber[pinned] {
   box-shadow: @focusRingShadow@;
 }
 
 #TabsToolbar {
   -moz-appearance: none;
   /* overlap the nav-bar's top border */
--- a/browser/themes/shared/tabs.inc.css
+++ b/browser/themes/shared/tabs.inc.css
@@ -186,17 +186,17 @@
 }
 
 .tab-label {
   margin-inline-end: 0;
   margin-inline-start: 0;
 }
 
 .tab-close-button {
-  margin-inline-start: 4px;
+  margin-inline-start: 2px;
   margin-inline-end: -2px;
   padding: 0;
 }
 
 .tab-icon-sound {
   margin-inline-start: 4px;
   width: 16px;
   height: 16px;
--- a/caps/nsPrincipal.cpp
+++ b/caps/nsPrincipal.cpp
@@ -69,17 +69,17 @@ nsPrincipal::InitializeStatics()
 
 nsPrincipal::nsPrincipal()
   : mCodebaseImmutable(false)
   , mDomainImmutable(false)
   , mInitialized(false)
 { }
 
 nsPrincipal::~nsPrincipal()
-{ 
+{
   // let's clear the principal within the csp to avoid a tangling pointer
   if (mCSP) {
     static_cast<nsCSPContext*>(mCSP.get())->clearLoadingPrincipal();
   }
 }
 
 nsresult
 nsPrincipal::Init(nsIURI *aCodebase, const PrincipalOriginAttributes& aOriginAttributes)
@@ -156,30 +156,41 @@ nsPrincipal::GetOriginInternal(nsACStrin
     return NS_OK;
   }
 
   if (NS_SUCCEEDED(rv) && !isChrome) {
     rv = origin->GetScheme(aOrigin);
     NS_ENSURE_SUCCESS(rv, rv);
     aOrigin.AppendLiteral("://");
     aOrigin.Append(hostPort);
+    return NS_OK;
   }
-  else {
-    // If we reached this branch, we can only create an origin if we have a nsIStandardURL.
-    // So, we query to a nsIStandardURL, and fail if we aren't an instance of an nsIStandardURL
-    // nsIStandardURLs have the good property of escaping the '^' character in their specs,
-    // which means that we can be sure that the caret character (which is reserved for delimiting
-    // the end of the spec, and the beginning of the origin attributes) is not present in the
-    // origin string
-    nsCOMPtr<nsIStandardURL> standardURL = do_QueryInterface(origin);
-    NS_ENSURE_TRUE(standardURL, NS_ERROR_FAILURE);
-    rv = origin->GetAsciiSpec(aOrigin);
-    NS_ENSURE_SUCCESS(rv, rv);
+
+  // This URL can be a blobURL. In this case, we should use the 'parent'
+  // principal instead.
+  nsCOMPtr<nsIURIWithPrincipal> uriWithPrincipal = do_QueryInterface(origin);
+  if (uriWithPrincipal) {
+    nsCOMPtr<nsIPrincipal> uriPrincipal;
+    if (uriWithPrincipal) {
+      return uriPrincipal->GetOriginNoSuffix(aOrigin);
+    }
   }
 
+  // If we reached this branch, we can only create an origin if we have a
+  // nsIStandardURL.  So, we query to a nsIStandardURL, and fail if we aren't
+  // an instance of an nsIStandardURL nsIStandardURLs have the good property
+  // of escaping the '^' character in their specs, which means that we can be
+  // sure that the caret character (which is reserved for delimiting the end
+  // of the spec, and the beginning of the origin attributes) is not present
+  // in the origin string
+  nsCOMPtr<nsIStandardURL> standardURL = do_QueryInterface(origin);
+  NS_ENSURE_TRUE(standardURL, NS_ERROR_FAILURE);
+  rv = origin->GetAsciiSpec(aOrigin);
+  NS_ENSURE_SUCCESS(rv, rv);
+
   return NS_OK;
 }
 
 bool
 nsPrincipal::SubsumesInternal(nsIPrincipal* aOther,
                               BasePrincipal::DocumentDomainConsideration aConsideration)
 {
   MOZ_ASSERT(aOther);
--- a/docshell/base/nsIDocShellTreeOwner.idl
+++ b/docshell/base/nsIDocShellTreeOwner.idl
@@ -18,24 +18,21 @@ interface nsIDocShellTreeOwner : nsISupp
 {
 	/**
 	 * Called when a content shell is added to the docshell tree.  This is
 	 * _only_ called for "root" content shells (that is, ones whose parent is a
 	 * chrome shell).
 	 *
 	 * @param aContentShell the shell being added.
 	 * @param aPrimary whether the shell is primary.
-	 * @param aTargetable whether the shell can be a target for named window
-	 *					targeting.
 	 * @param aID the "id" of the shell.  What this actually means is
 	 *			undefined. Don't rely on this for anything.
 	 */
 	void contentShellAdded(in nsIDocShellTreeItem aContentShell,
-						   in boolean aPrimary, in boolean aTargetable,
-						   in AString aID);
+	                       in boolean aPrimary, in AString aID);
 
 	/**
 	 * Called when a content shell is removed from the docshell tree.  This is
 	 * _only_ called for "root" content shells (that is, ones whose parent is a
 	 * chrome shell).  Note that if aContentShell was never added,
 	 * contentShellRemoved should just do nothing.
 	 *
 	 * @param aContentShell the shell being removed.
@@ -92,18 +89,19 @@ interface nsIDocShellTreeOwner : nsISupp
 	/*
 	Gets the current persistence states of the window.
 	*/
 	void getPersistence(out boolean aPersistPosition,
                             out boolean aPersistSize,
                             out boolean aPersistSizeMode);
 
 	/*
-	Gets the number of targettable docshells.
+	Gets the number of tabs currently open in our window, assuming
+	this tree owner has such a concept.
 	*/
-	readonly attribute unsigned long targetableShellCount;
+	readonly attribute unsigned long tabCount;
 
 	/*
 	Returns true if there is a primary content shell or a primary
 	tab parent.
 	*/
 	readonly attribute bool hasPrimaryContent;
 };
--- a/dom/animation/KeyframeUtils.cpp
+++ b/dom/animation/KeyframeUtils.cpp
@@ -507,18 +507,18 @@ KeyframeUtils::ApplySpacing(nsTArray<Key
     firstElement.mComputedOffset = firstElement.mOffset.valueOr(0.0);
     // We will fill in the last keyframe's offset below
   } else {
     Keyframe& lastElement = aKeyframes.LastElement();
     lastElement.mComputedOffset = lastElement.mOffset.valueOr(1.0);
   }
 
   // Fill in remaining missing offsets.
-  const Keyframe* const last = aKeyframes.cend() - 1;
-  const RangedPtr<Keyframe> begin(aKeyframes.begin(), aKeyframes.Length());
+  const Keyframe* const last = &aKeyframes.LastElement();
+  const RangedPtr<Keyframe> begin(aKeyframes.Elements(), aKeyframes.Length());
   RangedPtr<Keyframe> keyframeA = begin;
   while (keyframeA != last) {
     // Find keyframe A and keyframe B *between* which we will apply spacing.
     RangedPtr<Keyframe> keyframeB = keyframeA + 1;
     while (keyframeB->mOffset.isNothing() && keyframeB != last) {
       ++keyframeB;
     }
     keyframeB->mComputedOffset = keyframeB->mOffset.valueOr(1.0);
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -954,21 +954,19 @@ nsFrameLoader::AddTreeItemToTreeOwner(ns
 
   bool retval = false;
   if (aParentType == nsIDocShellTreeItem::typeChrome && isContent) {
     retval = true;
 
     bool is_primary = value.LowerCaseEqualsLiteral("content-primary");
 
     if (aOwner) {
-      bool is_targetable = is_primary ||
-        value.LowerCaseEqualsLiteral("content-targetable");
       mOwnerContent->AddMutationObserver(this);
       mObservingOwnerContent = true;
-      aOwner->ContentShellAdded(aItem, is_primary, is_targetable, value);
+      aOwner->ContentShellAdded(aItem, is_primary, value);
     }
   }
 
   return retval;
 }
 
 static bool
 AllDescendantsOfType(nsIDocShellTreeItem* aParentItem, int32_t aType)
@@ -3280,21 +3278,18 @@ nsFrameLoader::AttributeChanged(nsIDocum
       pm->HidePopupsInDocShell(mDocShell);
   }
 #endif
 
   parentTreeOwner->ContentShellRemoved(mDocShell);
   if (value.LowerCaseEqualsLiteral("content") ||
       StringBeginsWith(value, NS_LITERAL_STRING("content-"),
                        nsCaseInsensitiveStringComparator())) {
-    bool is_targetable = is_primary ||
-      value.LowerCaseEqualsLiteral("content-targetable");
-
-    parentTreeOwner->ContentShellAdded(mDocShell, is_primary,
-                                       is_targetable, value);
+
+    parentTreeOwner->ContentShellAdded(mDocShell, is_primary, value);
   }
 }
 
 /**
  * Send the RequestNotifyAfterRemotePaint message to the current Tab.
  */
 NS_IMETHODIMP
 nsFrameLoader::RequestNotifyAfterRemotePaint()
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -314,18 +314,17 @@ int32_t gTimeoutCnt                     
 static int32_t gMinTimeoutValue;
 static int32_t gMinBackgroundTimeoutValue;
 inline int32_t
 nsGlobalWindow::DOMMinTimeoutValue() const {
   // First apply any back pressure delay that might be in effect.
   int32_t value = std::max(mBackPressureDelayMS, 0);
   // Don't use the background timeout value when there are audio contexts
   // present, so that baackground audio can keep running smoothly. (bug 1181073)
-  bool isBackground = mAudioContexts.IsEmpty() &&
-    (!mOuterWindow || mOuterWindow->IsBackground());
+  bool isBackground = mAudioContexts.IsEmpty() && IsBackgroundInternal();
   return
     std::max(isBackground ? gMinBackgroundTimeoutValue : gMinTimeoutValue, value);
 }
 
 // The number of nested timeouts before we start clamping. HTML5 says 1, WebKit
 // uses 5.
 #define DOM_CLAMP_TIMEOUT_NESTING_LEVEL 5
 
@@ -649,16 +648,21 @@ void nsGlobalWindow::UnthrottleIdleCallb
 
   while (!mThrottledIdleRequestCallbacks.isEmpty()) {
     RefPtr<IdleRequest> request(mThrottledIdleRequestCallbacks.popFirst());
     mIdleRequestCallbacks.insertBack(request);
     NS_IdleDispatchToCurrentThread(request.forget());
   }
 }
 
+bool
+nsGlobalWindow::IsBackgroundInternal() const
+{
+  return !mOuterWindow || mOuterWindow->IsBackground();
+}
 
 namespace mozilla {
 namespace dom {
 extern uint64_t
 NextWindowID();
 } // namespace dom
 } // namespace mozilla
 
@@ -7015,18 +7019,18 @@ nsGlobalWindow::CanMoveResizeWindows(boo
       if (docShell) {
         nsCOMPtr<nsITabChild> child = docShell->GetTabChild();
         if (child) {
           child->SendGetTabCount(&itemCount);
         }
       }
     } else {
       nsCOMPtr<nsIDocShellTreeOwner> treeOwner = GetTreeOwner();
-      if (treeOwner) {
-        treeOwner->GetTargetableShellCount(&itemCount);
+      if (!treeOwner || NS_FAILED(treeOwner->GetTabCount(&itemCount))) {
+        itemCount = 0;
       }
     }
     if (itemCount > 1) {
       return false;
     }
   }
 
   if (mDocShell) {
@@ -12736,17 +12740,17 @@ nsGlobalWindow::SetTimeoutOrInterval(nsI
   timeout->mInterval = interval;
   timeout->mScriptHandler = aHandler;
   timeout->mReason = aReason;
 
   // Now clamp the actual interval we will use for the timer based on
   uint32_t nestingLevel = sNestingLevel + 1;
   uint32_t realInterval = interval;
   if (aIsInterval || nestingLevel >= DOM_CLAMP_TIMEOUT_NESTING_LEVEL ||
-      mBackPressureDelayMS > 0) {
+      mBackPressureDelayMS > 0 || IsBackgroundInternal()) {
     // Don't allow timeouts less than DOMMinTimeoutValue() from
     // now...
     realInterval = std::max(realInterval, uint32_t(DOMMinTimeoutValue()));
   }
 
   TimeDuration delta = TimeDuration::FromMilliseconds(realInterval);
 
   if (IsFrozen()) {
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -1719,16 +1719,18 @@ private:
   // When timers are being throttled and we reduce the thottle delay we must
   // reschedule.  The amount of the old throttle delay must be provided in
   // order to bound how many timers must be examined.
   nsresult ResetTimersForThrottleReduction(int32_t aPreviousThrottleDelayMS);
 
   mozilla::dom::TabGroup* TabGroupInner();
   mozilla::dom::TabGroup* TabGroupOuter();
 
+  bool IsBackgroundInternal() const;
+
 public:
   // Dispatch a runnable related to the global.
   virtual nsresult Dispatch(const char* aName,
                             mozilla::dom::TaskCategory aCategory,
                             already_AddRefed<nsIRunnable>&& aRunnable) override;
 
   virtual already_AddRefed<nsIEventTarget>
   EventTargetFor(mozilla::dom::TaskCategory aCategory) const override;
--- a/dom/base/test/chrome/file_bug990812-1.xul
+++ b/dom/base/test/chrome/file_bug990812-1.xul
@@ -46,17 +46,17 @@ https://bugzilla.mozilla.org/show_bug.cg
           opener.setTimeout("next()");
           window.close();
         }
       });
 
       var browser = document.createElement("browser");
       browser.setAttribute("messagemanagergroup", "test");
       browser.setAttribute("src", "about:mozilla");
-      browser.setAttribute("type", "content-targetable");
+      browser.setAttribute("type", "content");
       document.documentElement.appendChild(browser);
 
       globalMM.removeDelayedFrameScript(FRAME_SCRIPT_GLOBAL);
       messageManager.removeDelayedFrameScript(FRAME_SCRIPT_WINDOW);
       getGroupMessageManager("test").removeDelayedFrameScript(FRAME_SCRIPT_GROUP);
     }
 
   ]]></script>
--- a/dom/base/test/chrome/file_bug990812-2.xul
+++ b/dom/base/test/chrome/file_bug990812-2.xul
@@ -49,11 +49,11 @@ https://bugzilla.mozilla.org/show_bug.cg
       Promise.all([global, window, group]).then(function () {
         opener.setTimeout("next()");
         self.close();
       });
     }
 
   ]]></script>
 
-  <browser messagemanagergroup="test" type="content-targetable" src="about:mozilla" />
+  <browser messagemanagergroup="test" type="content" src="about:mozilla" />
 
 </window>
--- a/dom/base/test/chrome/file_bug990812-3.xul
+++ b/dom/base/test/chrome/file_bug990812-3.xul
@@ -60,12 +60,12 @@ https://bugzilla.mozilla.org/show_bug.cg
       Promise.all([promise1, promise2]).then(function () {
         opener.setTimeout("next()");
         window.close();
       });
     }
 
   ]]></script>
 
-  <browser messagemanagergroup="test1" type="content-targetable" src="about:mozilla" />
-  <browser messagemanagergroup="test2" type="content-targetable" src="about:mozilla" />
+  <browser messagemanagergroup="test1" type="content" src="about:mozilla" />
+  <browser messagemanagergroup="test2" type="content" src="about:mozilla" />
 
 </window>
--- a/dom/base/test/chrome/file_bug990812-4.xul
+++ b/dom/base/test/chrome/file_bug990812-4.xul
@@ -57,12 +57,12 @@ https://bugzilla.mozilla.org/show_bug.cg
       Promise.all([promise1, promise2]).then(function () {
         opener.setTimeout("next()");
         window.close();
       });
     }
 
   ]]></script>
 
-  <browser messagemanagergroup="test1" type="content-targetable" src="about:mozilla" />
-  <browser messagemanagergroup="test2" type="content-targetable" src="about:mozilla" />
+  <browser messagemanagergroup="test1" type="content" src="about:mozilla" />
+  <browser messagemanagergroup="test2" type="content" src="about:mozilla" />
 
 </window>
--- a/dom/base/test/chrome/file_bug990812-5.xul
+++ b/dom/base/test/chrome/file_bug990812-5.xul
@@ -63,15 +63,15 @@ https://bugzilla.mozilla.org/show_bug.cg
       Promise.all([promise1, promise2]).then(function () {
         opener.setTimeout("next()");
         window.close();
       });
     }
 
   ]]></script>
 
-  <browser messagemanagergroup="test1" type="content-targetable" src="about:mozilla" />
-  <browser messagemanagergroup="test1" type="content-targetable" src="about:mozilla" />
+  <browser messagemanagergroup="test1" type="content" src="about:mozilla" />
+  <browser messagemanagergroup="test1" type="content" src="about:mozilla" />
 
-  <browser messagemanagergroup="test2" type="content-targetable" src="about:mozilla" />
-  <browser messagemanagergroup="test2" type="content-targetable" src="about:mozilla" />
+  <browser messagemanagergroup="test2" type="content" src="about:mozilla" />
+  <browser messagemanagergroup="test2" type="content" src="about:mozilla" />
 
 </window>
--- a/dom/base/test/chrome/file_bug990812.xul
+++ b/dom/base/test/chrome/file_bug990812.xul
@@ -40,17 +40,17 @@ https://bugzilla.mozilla.org/show_bug.cg
           opener.setTimeout("next()");
           window.close();
         }
       });
 
       var browser = document.createElement("browser");
       browser.setAttribute("messagemanagergroup", "test");
       browser.setAttribute("src", "about:mozilla");
-      browser.setAttribute("type", "content-targetable");
+      browser.setAttribute("type", "content");
       document.documentElement.appendChild(browser);
 
       globalMM.removeDelayedFrameScript(FRAME_SCRIPT_GLOBAL);
       messageManager.removeDelayedFrameScript(FRAME_SCRIPT_WINDOW);
       getGroupMessageManager("test").removeDelayedFrameScript(FRAME_SCRIPT_GROUP);
     }
 
   ]]></script>
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -892,16 +892,17 @@ AssertReflectorHasGivenProto(JSContext* 
 
 template <class T, GetOrCreateReflectorWrapBehavior wrapBehavior>
 MOZ_ALWAYS_INLINE bool
 DoGetOrCreateDOMReflector(JSContext* cx, T* value,
                           JS::Handle<JSObject*> givenProto,
                           JS::MutableHandle<JS::Value> rval)
 {
   MOZ_ASSERT(value);
+  MOZ_ASSERT_IF(givenProto, js::IsObjectInContextCompartment(givenProto, cx));
   // We can get rid of this when we remove support for hasXPConnectImpls.
   bool couldBeDOMBinding = CouldBeDOMBinding(value);
   JSObject* obj = value->GetWrapper();
   if (obj) {
 #ifdef DEBUG
     AssertReflectorHasGivenProto(cx, obj, givenProto);
     // Have to reget obj because AssertReflectorHasGivenProto can
     // trigger gc so the pointer may now be invalid.
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -3626,16 +3626,17 @@ class CGWrapWithCacheMethod(CGAbstractMe
             aCache->ReleaseWrapper(aObject);
             aCache->ClearWrapper();
             return false;
             """)
 
         return fill(
             """
             $*{assertInheritance}
+            MOZ_ASSERT_IF(aGivenProto, js::IsObjectInContextCompartment(aGivenProto, aCx));
             MOZ_ASSERT(!aCache->GetWrapper(),
                        "You should probably not be using Wrap() directly; use "
                        "GetOrCreateDOMReflector instead");
 
             MOZ_ASSERT(ToSupportsIsOnPrimaryInheritanceChain(aObject, aCache),
                        "nsISupports must be on our primary inheritance chain");
 
             JS::Rooted<JSObject*> global(aCx, FindAssociatedGlobal(aCx, aObject->GetParentObject()));
@@ -3726,16 +3727,17 @@ class CGWrapNonWrapperCacheMethod(CGAbst
         self.properties = properties
 
     def definition_body(self):
         failureCode = "return false;\n"
 
         return fill(
             """
             $*{assertions}
+            MOZ_ASSERT_IF(aGivenProto, js::IsObjectInContextCompartment(aGivenProto, aCx));
 
             JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
             $*{declareProto}
 
             $*{createObject}
 
             $*{unforgeable}
 
--- a/dom/bindings/TypedArray.h
+++ b/dom/bindings/TypedArray.h
@@ -100,23 +100,22 @@ public:
   }
 
   inline bool inited() const {
     return !!mTypedObj;
   }
 
   // About shared memory:
   //
-  // Any DOM TypedArray as well as any DOM ArrayBufferView that does
-  // not represent a JS DataView can map the memory of either a JS
-  // ArrayBuffer or a JS SharedArrayBuffer.  (DataView cannot view
-  // shared memory.)  If the TypedArray maps a SharedArrayBuffer the
-  // Length() and Data() accessors on the DOM view will return zero
-  // and nullptr; to get the actual length and data, call the
-  // LengthAllowShared() and DataAllowShared() accessors instead.
+  // Any DOM TypedArray as well as any DOM ArrayBufferView can map the
+  // memory of either a JS ArrayBuffer or a JS SharedArrayBuffer.  If
+  // the TypedArray maps a SharedArrayBuffer the Length() and Data()
+  // accessors on the DOM view will return zero and nullptr; to get
+  // the actual length and data, call the LengthAllowShared() and
+  // DataAllowShared() accessors instead.
   //
   // Two methods are available for determining if a DOM view maps
   // shared memory.  The IsShared() method is cheap and can be called
   // if the view has been computed; the JS_GetTypedArraySharedness()
   // method is slightly more expensive and can be called on the Obj()
   // value if the view may not have been computed and if the value is
   // known to represent a JS TypedArray.
   //
--- a/dom/canvas/WebGLFramebuffer.cpp
+++ b/dom/canvas/WebGLFramebuffer.cpp
@@ -1283,17 +1283,17 @@ void
 WebGLFramebuffer::FramebufferRenderbuffer(const char* funcName, GLenum attachEnum,
                                           GLenum rbtarget, WebGLRenderbuffer* rb)
 {
     MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
                mContext->mBoundReadFramebuffer == this);
 
     // `attachment`
     const auto maybeAttach = GetAttachPoint(attachEnum);
-    if (!maybeAttach) {
+    if (!maybeAttach || !maybeAttach.value()) {
         mContext->ErrorInvalidEnum("%s: Bad `attachment`: 0x%x.", funcName, attachEnum);
         return;
     }
     const auto& attach = maybeAttach.value();
 
     // `rbTarget`
     if (rbtarget != LOCAL_GL_RENDERBUFFER) {
         mContext->ErrorInvalidEnumInfo("framebufferRenderbuffer: rbtarget:", rbtarget);
@@ -1321,17 +1321,17 @@ WebGLFramebuffer::FramebufferTexture2D(c
                                        GLenum texImageTarget, WebGLTexture* tex,
                                        GLint level)
 {
     MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
                mContext->mBoundReadFramebuffer == this);
 
     // `attachment`
     const auto maybeAttach = GetAttachPoint(attachEnum);
-    if (!maybeAttach) {
+    if (!maybeAttach || !maybeAttach.value()) {
         mContext->ErrorInvalidEnum("%s: Bad `attachment`: 0x%x.", funcName, attachEnum);
         return;
     }
     const auto& attach = maybeAttach.value();
 
     // `texImageTarget`
     if (texImageTarget != LOCAL_GL_TEXTURE_2D &&
         (texImageTarget < LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X ||
@@ -1408,17 +1408,17 @@ void
 WebGLFramebuffer::FramebufferTextureLayer(const char* funcName, GLenum attachEnum,
                                           WebGLTexture* tex, GLint level, GLint layer)
 {
     MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
                mContext->mBoundReadFramebuffer == this);
 
     // `attachment`
     const auto maybeAttach = GetAttachPoint(attachEnum);
-    if (!maybeAttach) {
+    if (!maybeAttach || !maybeAttach.value()) {
         mContext->ErrorInvalidEnum("%s: Bad `attachment`: 0x%x.", funcName, attachEnum);
         return;
     }
     const auto& attach = maybeAttach.value();
 
     // `level`, `layer`
     if (layer < 0)
         return mContext->ErrorInvalidValue("%s: `layer` must be >= 0.", funcName);
--- a/dom/indexedDB/ActorsChild.cpp
+++ b/dom/indexedDB/ActorsChild.cpp
@@ -684,28 +684,33 @@ DeserializeStructuredCloneFiles(
           StructuredCloneFile* file = aFiles.AppendElement();
           MOZ_ASSERT(file);
 
           file->mType = StructuredCloneFile::eStructuredClone;
 
           break;
         }
 
-        case StructuredCloneFile::eWasmBytecode: {
+        case StructuredCloneFile::eWasmBytecode:
+        case StructuredCloneFile::eWasmCompiled: {
           if (aModuleSet) {
             MOZ_ASSERT(blobOrMutableFile.type() == BlobOrMutableFile::Tnull_t);
 
             StructuredCloneFile* file = aFiles.AppendElement();
             MOZ_ASSERT(file);
 
             file->mType = serializedFile.type();
 
             MOZ_ASSERT(moduleIndex < aModuleSet->Length());
             file->mWasmModule = aModuleSet->ElementAt(moduleIndex);
 
+            if (serializedFile.type() == StructuredCloneFile::eWasmCompiled) {
+              moduleIndex++;
+            }
+
             break;
           }
 
           MOZ_ASSERT(blobOrMutableFile.type() ==
                        BlobOrMutableFile::TPBlobChild);
 
           auto* actor =
             static_cast<BlobChild*>(blobOrMutableFile.get_PBlobChild());
@@ -715,78 +720,22 @@ DeserializeStructuredCloneFiles(
 
           RefPtr<Blob> blob = Blob::Create(aDatabase->GetOwner(), blobImpl);
 
           aDatabase->NoteReceivedBlob(blob);
 
           StructuredCloneFile* file = aFiles.AppendElement();
           MOZ_ASSERT(file);
 
-          file->mType = StructuredCloneFile::eWasmBytecode;
+          file->mType = serializedFile.type();
           file->mBlob.swap(blob);
 
           break;
         }
 
-        case StructuredCloneFile::eWasmCompiled: {
-          if (aModuleSet) {
-            MOZ_ASSERT(blobOrMutableFile.type() == BlobOrMutableFile::Tnull_t);
-
-            StructuredCloneFile* file = aFiles.AppendElement();
-            MOZ_ASSERT(file);
-
-            file->mType = serializedFile.type();
-
-            MOZ_ASSERT(moduleIndex < aModuleSet->Length());
-            file->mWasmModule = aModuleSet->ElementAt(moduleIndex++);
-
-            break;
-          }
-
-          MOZ_ASSERT(blobOrMutableFile.type() == BlobOrMutableFile::Tnull_t ||
-                     blobOrMutableFile.type() ==
-                       BlobOrMutableFile::TPBlobChild);
-
-          switch (blobOrMutableFile.type()) {
-            case BlobOrMutableFile::Tnull_t: {
-              StructuredCloneFile* file = aFiles.AppendElement();
-              MOZ_ASSERT(file);
-
-              file->mType = StructuredCloneFile::eWasmCompiled;
-
-              break;
-            }
-
-            case BlobOrMutableFile::TPBlobChild: {
-              auto* actor =
-                static_cast<BlobChild*>(blobOrMutableFile.get_PBlobChild());
-
-              RefPtr<BlobImpl> blobImpl = actor->GetBlobImpl();
-              MOZ_ASSERT(blobImpl);
-
-              RefPtr<Blob> blob = Blob::Create(aDatabase->GetOwner(), blobImpl);
-
-              aDatabase->NoteReceivedBlob(blob);
-
-              StructuredCloneFile* file = aFiles.AppendElement();
-              MOZ_ASSERT(file);
-
-              file->mType = StructuredCloneFile::eWasmCompiled;
-              file->mBlob.swap(blob);
-
-              break;
-            }
-
-            default:
-              MOZ_CRASH("Should never get here!");
-          }
-
-          break;
-        }
-
         default:
           MOZ_CRASH("Should never get here!");
       }
     }
   }
 }
 
 void
@@ -3010,33 +2959,32 @@ PreprocessHelper::Init(const nsTArray<St
     uint32_t compiledIndex = bytecodeIndex + 1;
 
     const StructuredCloneFile& bytecodeFile = aFiles[bytecodeIndex];
     const StructuredCloneFile& compiledFile = aFiles[compiledIndex];
 
     MOZ_ASSERT(bytecodeFile.mType == StructuredCloneFile::eWasmBytecode);
     MOZ_ASSERT(bytecodeFile.mBlob);
     MOZ_ASSERT(compiledFile.mType == StructuredCloneFile::eWasmCompiled);
+    MOZ_ASSERT(compiledFile.mBlob);
 
     ErrorResult errorResult;
 
     nsCOMPtr<nsIInputStream> bytecodeStream;
     bytecodeFile.mBlob->GetInternalStream(getter_AddRefs(bytecodeStream),
                                           errorResult);
     if (NS_WARN_IF(errorResult.Failed())) {
       return errorResult.StealNSResult();
     }
 
     nsCOMPtr<nsIInputStream> compiledStream;
-    if (compiledFile.mBlob) {
-      compiledFile.mBlob->GetInternalStream(getter_AddRefs(compiledStream),
-                                            errorResult);
-      if (NS_WARN_IF(errorResult.Failed())) {
-        return errorResult.StealNSResult();
-      }
+    compiledFile.mBlob->GetInternalStream(getter_AddRefs(compiledStream),
+                                          errorResult);
+    if (NS_WARN_IF(errorResult.Failed())) {
+      return errorResult.StealNSResult();
     }
 
     streamPairs.AppendElement(StreamPair(bytecodeStream, compiledStream));
   }
 
   mStreamPairs = Move(streamPairs);
 
   return NS_OK;
@@ -3099,24 +3047,21 @@ PreprocessHelper::RunOnStreamTransportTh
 
     PRFileDesc* bytecodeFileDesc = GetFileDescriptorFromStream(bytecodeStream);
     if (NS_WARN_IF(!bytecodeFileDesc)) {
       return NS_ERROR_FAILURE;
     }
 
     const nsCOMPtr<nsIInputStream>& compiledStream = streamPair.second;
 
-    PRFileDesc* compiledFileDesc;
-    if (compiledStream) {
-      compiledFileDesc = GetFileDescriptorFromStream(compiledStream);
-      if (NS_WARN_IF(!bytecodeFileDesc)) {
-        return NS_ERROR_FAILURE;
-      }
-    } else {
-      compiledFileDesc = nullptr;
+    MOZ_ASSERT(compiledStream);
+
+    PRFileDesc* compiledFileDesc = GetFileDescriptorFromStream(compiledStream);
+    if (NS_WARN_IF(!compiledFileDesc)) {
+      return NS_ERROR_FAILURE;
     }
 
     JS::BuildIdCharVector buildId;
     bool ok = GetBuildId(&buildId);
     if (NS_WARN_IF(!ok)) {
       return NS_ERROR_FAILURE;
     }
 
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -87,16 +87,17 @@
 #include "nsITimer.h"
 #include "nsIURI.h"
 #include "nsNetUtil.h"
 #include "nsPrintfCString.h"
 #include "nsQueryObject.h"
 #include "nsRefPtrHashtable.h"
 #include "nsStreamUtils.h"
 #include "nsString.h"
+#include "nsStringStream.h"
 #include "nsThreadPool.h"
 #include "nsThreadUtils.h"
 #include "nsXPCOMCID.h"
 #include "PermissionRequestBase.h"
 #include "ProfilerHelpers.h"
 #include "prsystem.h"
 #include "prtime.h"
 #include "ReportInternalError.h"
@@ -8191,22 +8192,16 @@ private:
   ObjectStoreAddOrPutRequestOp(TransactionBase* aTransaction,
                                const RequestParams& aParams);
 
   ~ObjectStoreAddOrPutRequestOp() override = default;
 
   nsresult
   RemoveOldIndexDataValues(DatabaseConnection* aConnection);
 
-  nsresult
-  CopyFileData(nsIInputStream* aInputStream,
-               nsIOutputStream* aOutputStream,
-               char* aBuffer,
-               uint32_t aBufferSize);
-
   bool
   Init(TransactionBase* aTransaction) override;
 
   nsresult
   DoDatabaseWork(DatabaseConnection* aConnection) override;
 
   void
   GetResponse(RequestResponse& aResponse) override;
@@ -9583,16 +9578,70 @@ private:
   }
 
   NS_DECL_NSITHREADOBSERVER
 };
 
 #endif // DEBUG
 
 /*******************************************************************************
+ * Helper classes
+ ******************************************************************************/
+
+class MOZ_STACK_CLASS FileHelper final
+{
+  RefPtr<FileManager> mFileManager;
+
+  nsCOMPtr<nsIFile> mFileDirectory;
+  nsCOMPtr<nsIFile> mJournalDirectory;
+
+public:
+  explicit FileHelper(FileManager* aFileManager)
+    : mFileManager(aFileManager)
+  { }
+
+  nsresult
+  Init();
+
+  already_AddRefed<nsIFile>
+  GetFile(FileInfo* aFileInfo);
+
+  already_AddRefed<nsIFile>
+  GetCheckedFile(FileInfo* aFileInfo);
+
+  already_AddRefed<nsIFile>
+  GetJournalFile(FileInfo* aFileInfo);
+
+  nsresult
+  CreateFileFromStream(nsIFile* aFile,
+                       nsIFile* aJournalFile,
+                       nsIInputStream* aInputStream,
+                       bool aCompress);
+
+  nsresult
+  ReplaceFile(nsIFile* aFile,
+              nsIFile* aNewFile,
+              nsIFile* aNewJournalFile);
+
+  nsresult
+  RemoveFile(nsIFile* aFile,
+             nsIFile* aJournalFile);
+
+  already_AddRefed<FileInfo>
+  GetNewFileInfo();
+
+private:
+  nsresult
+  SyncCopy(nsIInputStream* aInputStream,
+           nsIOutputStream* aOutputStream,
+           char* aBuffer,
+           uint32_t aBufferSize);
+};
+
+/*******************************************************************************
  * Helper Functions
  ******************************************************************************/
 
 bool
 TokenizerIgnoreNothing(char16_t /* aChar */)
 {
   return false;
 }
@@ -9647,78 +9696,200 @@ DeserializeStructuredCloneFile(FileManag
 
   aFile->mFileInfo.swap(fileInfo);
   aFile->mType = type;
 
   return NS_OK;
 }
 
 nsresult
+CheckWasmModule(FileHelper* aFileHelper,
+                StructuredCloneFile* aBytecodeFile,
+                StructuredCloneFile* aCompiledFile)
+{
+  MOZ_ASSERT(!IsOnBackgroundThread());
+  MOZ_ASSERT(aFileHelper);
+  MOZ_ASSERT(aBytecodeFile);
+  MOZ_ASSERT(aCompiledFile);
+  MOZ_ASSERT(aBytecodeFile->mType == StructuredCloneFile::eWasmBytecode);
+  MOZ_ASSERT(aCompiledFile->mType == StructuredCloneFile::eWasmCompiled);
+
+  nsCOMPtr<nsIFile> compiledFile =
+    aFileHelper->GetCheckedFile(aCompiledFile->mFileInfo);
+  if (NS_WARN_IF(!compiledFile)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsresult rv;
+
+  bool match;
+  {
+    ScopedPRFileDesc compiledFileDesc;
+    rv = compiledFile->OpenNSPRFileDesc(PR_RDONLY,
+                                        0644,
+                                        &compiledFileDesc.rwget());
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    JS::BuildIdCharVector buildId;
+    bool ok = GetBuildId(&buildId);
+    if (NS_WARN_IF(!ok)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    match = JS::CompiledWasmModuleAssumptionsMatch(compiledFileDesc,
+                                                   Move(buildId));
+  }
+  if (match) {
+    return NS_OK;
+  }
+
+  // Re-compile the module.  It would be preferable to do this in the child
+  // (content process) instead of here in the parent, but that would be way more
+  // complex and without significant memory allocation or security benefits.
+  // See the discussion starting from
+  // https://bugzilla.mozilla.org/show_bug.cgi?id=1312808#c9 for more details.
+  nsCOMPtr<nsIFile> bytecodeFile =
+    aFileHelper->GetCheckedFile(aBytecodeFile->mFileInfo);
+  if (NS_WARN_IF(!bytecodeFile)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  ScopedPRFileDesc bytecodeFileDesc;
+  rv = bytecodeFile->OpenNSPRFileDesc(PR_RDONLY,
+                                      0644,
+                                      &bytecodeFileDesc.rwget());
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  JS::BuildIdCharVector buildId;
+  bool ok = GetBuildId(&buildId);
+  if (NS_WARN_IF(!ok)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  RefPtr<JS::WasmModule> module = JS::DeserializeWasmModule(bytecodeFileDesc,
+                                                            nullptr,
+                                                            Move(buildId),
+                                                            nullptr,
+                                                            0,
+                                                            0);
+  if (NS_WARN_IF(!module)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  size_t compiledSize;
+  module->serializedSize(nullptr, &compiledSize);
+
+  UniquePtr<uint8_t[]> compiled(new (fallible) uint8_t[compiledSize]);
+  if (NS_WARN_IF(!compiled)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  module->serialize(nullptr, 0, compiled.get(), compiledSize);
+
+  nsCOMPtr<nsIInputStream> inputStream;
+  rv = NS_NewByteInputStream(getter_AddRefs(inputStream),
+                             reinterpret_cast<const char*>(compiled.get()),
+                             compiledSize);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  RefPtr<FileInfo> newFileInfo = aFileHelper->GetNewFileInfo();
+
+  nsCOMPtr<nsIFile> newFile = aFileHelper->GetFile(newFileInfo);
+  if (NS_WARN_IF(!newFile)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsCOMPtr<nsIFile> newJournalFile =
+    aFileHelper->GetJournalFile(newFileInfo);
+  if (NS_WARN_IF(!newJournalFile)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  rv = aFileHelper->CreateFileFromStream(newFile,
+                                         newJournalFile,
+                                         inputStream,
+                                         /* aCompress */ false);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    nsresult rv2 = aFileHelper->RemoveFile(newFile, newJournalFile);
+    if (NS_WARN_IF(NS_FAILED(rv2))) {
+      return rv;
+    }
+    return rv;
+  }
+
+  rv = aFileHelper->ReplaceFile(compiledFile, newFile, newJournalFile);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    nsresult rv2 = aFileHelper->RemoveFile(newFile, newJournalFile);
+    if (NS_WARN_IF(NS_FAILED(rv2))) {
+      return rv;
+    }
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult
 DeserializeStructuredCloneFiles(FileManager* aFileManager,
                                 const nsAString& aText,
                                 nsTArray<StructuredCloneFile>& aResult,
                                 bool* aHasPreprocessInfo)
 {
   MOZ_ASSERT(!IsOnBackgroundThread());
-  MOZ_ASSERT(aHasPreprocessInfo);
 
   nsCharSeparatedTokenizerTemplate<TokenizerIgnoreNothing>
     tokenizer(aText, ' ');
 
   nsAutoString token;
   nsresult rv;
-  nsCOMPtr<nsIFile> directory;
+  Maybe<FileHelper> fileHelper;
 
   while (tokenizer.hasMoreTokens()) {
     token = tokenizer.nextToken();
     MOZ_ASSERT(!token.IsEmpty());
 
     StructuredCloneFile* file = aResult.AppendElement();
     rv = DeserializeStructuredCloneFile(aFileManager, token, file);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
+    if (!aHasPreprocessInfo) {
+      continue;
+    }
+
     if (file->mType == StructuredCloneFile::eWasmBytecode) {
-      MOZ_ASSERT(file->mValid);
-
       *aHasPreprocessInfo = true;
     }
     else if (file->mType == StructuredCloneFile::eWasmCompiled) {
-      if (!directory) {
-        directory = aFileManager->GetCheckedDirectory();
-        if (NS_WARN_IF(!directory)) {
-          return NS_ERROR_FAILURE;
-        }
-      }
-
-      const int64_t fileId = file->mFileInfo->Id();
-      MOZ_ASSERT(fileId > 0);
-
-      nsCOMPtr<nsIFile> nativeFile =
-        aFileManager->GetCheckedFileForId(directory, fileId);
-      if (NS_WARN_IF(!nativeFile)) {
-        return NS_ERROR_FAILURE;
-      }
-
-      ScopedPRFileDesc fileDesc;
-      rv = nativeFile->OpenNSPRFileDesc(PR_RDONLY, 0644, &fileDesc.rwget());
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-
-      JS::BuildIdCharVector buildId;
-      bool ok = GetBuildId(&buildId);
-      if (NS_WARN_IF(!ok)) {
-        return NS_ERROR_FAILURE;
-      }
-
-      MOZ_ASSERT(file->mValid);
-      file->mValid = JS::CompiledWasmModuleAssumptionsMatch(fileDesc,
-                                                            Move(buildId));
+      if (fileHelper.isNothing()) {
+        fileHelper.emplace(aFileManager);
+
+        rv = fileHelper->Init();
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return rv;
+        }
+      }
+
+      MOZ_ASSERT(aResult.Length() > 1);
+      MOZ_ASSERT(aResult[aResult.Length() - 2].mType ==
+                   StructuredCloneFile::eWasmBytecode);
+
+      StructuredCloneFile* previousFile = &aResult[aResult.Length() - 2];
+
+      rv = CheckWasmModule(fileHelper.ptr(), previousFile, file);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
 
       *aHasPreprocessInfo = true;
     }
   }
 
   return NS_OK;
 }
 
@@ -9860,17 +10031,17 @@ SerializeStructuredCloneFiles(
         file->file() = null_t();
         file->type() = StructuredCloneFile::eStructuredClone;
 
         break;
       }
 
       case StructuredCloneFile::eWasmBytecode:
       case StructuredCloneFile::eWasmCompiled: {
-        if (!aForPreprocess || !file.mValid) {
+        if (!aForPreprocess) {
           SerializedStructuredCloneFile* serializedFile =
             aResult.AppendElement(fallible);
           MOZ_ASSERT(serializedFile);
 
           serializedFile->file() = null_t();
           serializedFile->type() = file.mType;
         } else {
           RefPtr<BlobImpl> impl = new BlobImplStoredFile(nativeFile,
@@ -11475,18 +11646,17 @@ UpdateRefcountFunction::ProcessValue(moz
 
   nsString ids;
   rv = aValues->GetString(aIndex, ids);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsTArray<StructuredCloneFile> files;
-  bool dummy;
-  rv = DeserializeStructuredCloneFiles(mFileManager, ids, files, &dummy);
+  rv = DeserializeStructuredCloneFiles(mFileManager, ids, files, nullptr);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   for (uint32_t i = 0; i < files.Length(); i++) {
     const StructuredCloneFile& file = files[i];
 
     const int64_t id = file.mFileInfo->Id();
@@ -25795,73 +25965,16 @@ ObjectStoreAddOrPutRequestOp::RemoveOldI
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
   return NS_OK;
 }
 
-nsresult
-ObjectStoreAddOrPutRequestOp::CopyFileData(nsIInputStream* aInputStream,
-                                           nsIOutputStream* aOutputStream,
-                                           char* aBuffer,
-                                           uint32_t aBufferSize)
-{
-  AssertIsOnConnectionThread();
-  MOZ_ASSERT(aInputStream);
-  MOZ_ASSERT(aOutputStream);
-
-  PROFILER_LABEL("IndexedDB",
-                 "ObjectStoreAddOrPutRequestOp::CopyFileData",
-                 js::ProfileEntry::Category::STORAGE);
-
-  nsresult rv;
-
-  do {
-    uint32_t numRead;
-    rv = aInputStream->Read(aBuffer, aBufferSize, &numRead);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      break;
-    }
-
-    if (!numRead) {
-      break;
-    }
-
-    uint32_t numWrite;
-    rv = aOutputStream->Write(aBuffer, numRead, &numWrite);
-    if (rv == NS_ERROR_FILE_NO_DEVICE_SPACE) {
-      rv = NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
-    }
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      break;
-    }
-
-    if (NS_WARN_IF(numWrite != numRead)) {
-      rv = NS_ERROR_FAILURE;
-      break;
-    }
-  } while (true);
-
-  if (NS_SUCCEEDED(rv)) {
-    rv = aOutputStream->Flush();
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-  }
-
-  nsresult rv2 = aOutputStream->Close();
-  if (NS_WARN_IF(NS_FAILED(rv2))) {
-    return NS_SUCCEEDED(rv) ? rv2 : rv;
-  }
-
-  return rv;
-}
-
 bool
 ObjectStoreAddOrPutRequestOp::Init(TransactionBase* aTransaction)
 {
   AssertIsOnOwningThread();
 
   const nsTArray<IndexUpdateInfo>& indexUpdateInfos =
     mParams.indexUpdateInfos();
 
@@ -26192,201 +26305,78 @@ ObjectStoreAddOrPutRequestOp::DoDatabase
 
     rv = stmt->BindAdoptedBlobByName(NS_LITERAL_CSTRING("data"), dataBuffer,
                                      dataBufferLength);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
-  nsCOMPtr<nsIFile> fileDirectory;
-  nsCOMPtr<nsIFile> journalDirectory;
+  Maybe<FileHelper> fileHelper;
 
   if (mFileManager) {
-    fileDirectory = mFileManager->GetDirectory();
-    if (NS_WARN_IF(!fileDirectory)) {
-      IDB_REPORT_INTERNAL_ERR();
-      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-    }
-
-    journalDirectory = mFileManager->EnsureJournalDirectory();
-    if (NS_WARN_IF(!journalDirectory)) {
+    fileHelper.emplace(mFileManager);
+
+    rv = fileHelper->Init();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
       IDB_REPORT_INTERNAL_ERR();
       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     }
-
-    DebugOnly<bool> exists;
-    MOZ_ASSERT(NS_SUCCEEDED(fileDirectory->Exists(&exists)));
-    MOZ_ASSERT(exists);
-
-    DebugOnly<bool> isDirectory;
-    MOZ_ASSERT(NS_SUCCEEDED(fileDirectory->IsDirectory(&isDirectory)));
-    MOZ_ASSERT(isDirectory);
-
-    MOZ_ASSERT(NS_SUCCEEDED(journalDirectory->Exists(&exists)));
-    MOZ_ASSERT(exists);
-
-    MOZ_ASSERT(NS_SUCCEEDED(journalDirectory->IsDirectory(&isDirectory)));
-    MOZ_ASSERT(isDirectory);
   }
 
   if (!mStoredFileInfos.IsEmpty()) {
     nsAutoString fileIds;
 
     for (uint32_t count = mStoredFileInfos.Length(), index = 0;
          index < count;
          index++) {
       StoredFileInfo& storedFileInfo = mStoredFileInfos[index];
       MOZ_ASSERT(storedFileInfo.mFileInfo);
 
-      const int64_t id = storedFileInfo.mFileInfo->Id();
-
       nsCOMPtr<nsIInputStream> inputStream;
       storedFileInfo.mInputStream.swap(inputStream);
 
       if (inputStream) {
-        MOZ_ASSERT(fileDirectory);
-        MOZ_ASSERT(journalDirectory);
-
-        nsCOMPtr<nsIFile> diskFile =
-          mFileManager->GetFileForId(fileDirectory, id);
-        if (NS_WARN_IF(!diskFile)) {
+        RefPtr<FileInfo>& fileInfo = storedFileInfo.mFileInfo;
+
+        nsCOMPtr<nsIFile> file = fileHelper->GetFile(fileInfo);
+        if (NS_WARN_IF(!file)) {
           IDB_REPORT_INTERNAL_ERR();
           return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
         }
 
-        bool exists;
-        rv = diskFile->Exists(&exists);
-        if (NS_WARN_IF(NS_FAILED(rv))) {
+        nsCOMPtr<nsIFile> journalFile =
+          fileHelper->GetJournalFile(fileInfo);
+        if (NS_WARN_IF(!journalFile)) {
           IDB_REPORT_INTERNAL_ERR();
           return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
         }
 
-        if (exists) {
-          bool isFile;
-          rv = diskFile->IsFile(&isFile);
-          if (NS_WARN_IF(NS_FAILED(rv))) {
-            IDB_REPORT_INTERNAL_ERR();
-            return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-          }
-
-          if (NS_WARN_IF(!isFile)) {
-            IDB_REPORT_INTERNAL_ERR();
-            return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-          }
-
-          uint64_t inputStreamSize;
-          rv = inputStream->Available(&inputStreamSize);
-          if (NS_WARN_IF(NS_FAILED(rv))) {
-            IDB_REPORT_INTERNAL_ERR();
-            return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-          }
-
-          int64_t fileSize;
-          rv = diskFile->GetFileSize(&fileSize);
-          if (NS_WARN_IF(NS_FAILED(rv))) {
-            IDB_REPORT_INTERNAL_ERR();
-            return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-          }
-
-          if (NS_WARN_IF(fileSize < 0)) {
-            IDB_REPORT_INTERNAL_ERR();
-            return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-          }
-
-          if (NS_WARN_IF(uint64_t(fileSize) != inputStreamSize)) {
-            IDB_REPORT_INTERNAL_ERR();
-            return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-          }
-        } else {
-          // Create a journal file first.
-          nsCOMPtr<nsIFile> journalFile =
-            mFileManager->GetFileForId(journalDirectory, id);
-          if (NS_WARN_IF(!journalFile)) {
-            IDB_REPORT_INTERNAL_ERR();
-            return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-          }
-
-          rv = journalFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
-          if (NS_WARN_IF(NS_FAILED(rv))) {
-            IDB_REPORT_INTERNAL_ERR();
-            return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-          }
-
-          // Now try to copy the stream.
-          RefPtr<FileOutputStream> fileOutputStream =
-            FileOutputStream::Create(mPersistenceType,
-                                     mGroup,
-                                     mOrigin,
-                                     diskFile);
-          if (NS_WARN_IF(!fileOutputStream)) {
-            IDB_REPORT_INTERNAL_ERR();
-            return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-          }
-
-          if (storedFileInfo.mType == StructuredCloneFile::eStructuredClone) {
-            RefPtr<SnappyCompressOutputStream> snappyOutputStream =
-              new SnappyCompressOutputStream(fileOutputStream);
-
-            UniquePtr<char[]> buffer(new char[snappyOutputStream->BlockSize()]);
-
-            rv = CopyFileData(inputStream,
-                              snappyOutputStream,
-                              buffer.get(),
-                              snappyOutputStream->BlockSize());
-          } else {
-            char buffer[kFileCopyBufferSize];
-
-            rv = CopyFileData(inputStream,
-                              fileOutputStream,
-                              buffer,
-                              kFileCopyBufferSize);
-          }
-          if (NS_FAILED(rv) &&
-              NS_ERROR_GET_MODULE(rv) != NS_ERROR_MODULE_DOM_INDEXEDDB) {
-            IDB_REPORT_INTERNAL_ERR();
-            rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-          }
-          if (NS_WARN_IF(NS_FAILED(rv))) {
-            // Try to remove the file if the copy failed.
-            nsresult rv2;
-            int64_t fileSize;
-
-            if (mFileManager->EnforcingQuota()) {
-              rv2 = diskFile->GetFileSize(&fileSize);
-              if (NS_WARN_IF(NS_FAILED(rv2))) {
-                return rv;
-              }
-            }
-
-            rv2 = diskFile->Remove(false);
-            if (NS_WARN_IF(NS_FAILED(rv2))) {
-              return rv;
-            }
-
-            rv2 = journalFile->Remove(false);
-            if (NS_WARN_IF(NS_FAILED(rv2))) {
-              return rv;
-            }
-
-            if (mFileManager->EnforcingQuota()) {
-              QuotaManager* quotaManager = QuotaManager::Get();
-              MOZ_ASSERT(quotaManager);
-
-              quotaManager->DecreaseUsageForOrigin(mFileManager->Type(),
-                                                   mFileManager->Group(),
-                                                   mFileManager->Origin(),
-                                                   fileSize);
-            }
-
+        bool compress =
+          storedFileInfo.mType == StructuredCloneFile::eStructuredClone;
+
+        rv = fileHelper->CreateFileFromStream(file,
+                                              journalFile,
+                                              inputStream,
+                                              compress);
+        if (NS_FAILED(rv) &&
+            NS_ERROR_GET_MODULE(rv) != NS_ERROR_MODULE_DOM_INDEXEDDB) {
+          IDB_REPORT_INTERNAL_ERR();
+          rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+        }
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          // Try to remove the file if the copy failed.
+          nsresult rv2 = fileHelper->RemoveFile(file, journalFile);
+          if (NS_WARN_IF(NS_FAILED(rv2))) {
             return rv;
           }
-
-          storedFileInfo.mCopiedSuccessfully = true;
-        }
+          return rv;
+        }
+
+        storedFileInfo.mCopiedSuccessfully = true;
       }
 
       if (index) {
         fileIds.Append(' ');
       }
       storedFileInfo.Serialize(fileIds);
     }
 
@@ -29139,16 +29129,360 @@ DEBUGThreadSlower::AfterProcessNextEvent
 
   MOZ_ALWAYS_TRUE(PR_Sleep(PR_MillisecondsToInterval(kDEBUGThreadSleepMS)) ==
                     PR_SUCCESS);
   return NS_OK;
 }
 
 #endif // DEBUG
 
+nsresult
+FileHelper::Init()
+{
+  MOZ_ASSERT(!IsOnBackgroundThread());
+  MOZ_ASSERT(mFileManager);
+
+  nsCOMPtr<nsIFile> fileDirectory = mFileManager->GetCheckedDirectory();
+  if (NS_WARN_IF(!fileDirectory)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsCOMPtr<nsIFile> journalDirectory = mFileManager->EnsureJournalDirectory();
+  if (NS_WARN_IF(!journalDirectory)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  DebugOnly<bool> exists;
+  MOZ_ASSERT(NS_SUCCEEDED(journalDirectory->Exists(&exists)));
+  MOZ_ASSERT(exists);
+
+  DebugOnly<bool> isDirectory;
+  MOZ_ASSERT(NS_SUCCEEDED(journalDirectory->IsDirectory(&isDirectory)));
+  MOZ_ASSERT(isDirectory);
+
+  mFileDirectory = Move(fileDirectory);
+  mJournalDirectory= Move(journalDirectory);
+
+  return NS_OK;
+}
+
+already_AddRefed<nsIFile>
+FileHelper::GetFile(FileInfo* aFileInfo)
+{
+  MOZ_ASSERT(!IsOnBackgroundThread());
+  MOZ_ASSERT(aFileInfo);
+  MOZ_ASSERT(mFileManager);
+  MOZ_ASSERT(mFileDirectory);
+
+  const int64_t fileId = aFileInfo->Id();
+  MOZ_ASSERT(fileId > 0);
+
+  nsCOMPtr<nsIFile> file =
+    mFileManager->GetFileForId(mFileDirectory, fileId);
+  return file.forget();
+}
+
+already_AddRefed<nsIFile>
+FileHelper::GetCheckedFile(FileInfo* aFileInfo)
+{
+  MOZ_ASSERT(!IsOnBackgroundThread());
+  MOZ_ASSERT(aFileInfo);
+  MOZ_ASSERT(mFileManager);
+  MOZ_ASSERT(mFileDirectory);
+
+  const int64_t fileId = aFileInfo->Id();
+  MOZ_ASSERT(fileId > 0);
+
+  nsCOMPtr<nsIFile> file =
+    mFileManager->GetCheckedFileForId(mFileDirectory, fileId);
+  return file.forget();
+}
+
+already_AddRefed<nsIFile>
+FileHelper::GetJournalFile(FileInfo* aFileInfo)
+{
+  MOZ_ASSERT(!IsOnBackgroundThread());
+  MOZ_ASSERT(aFileInfo);
+  MOZ_ASSERT(mFileManager);
+  MOZ_ASSERT(mJournalDirectory);
+
+  const int64_t fileId = aFileInfo->Id();
+  MOZ_ASSERT(fileId > 0);
+
+  nsCOMPtr<nsIFile> file =
+    mFileManager->GetFileForId(mJournalDirectory, fileId);
+  return file.forget();
+}
+
+nsresult
+FileHelper::CreateFileFromStream(nsIFile* aFile,
+                                 nsIFile* aJournalFile,
+                                 nsIInputStream* aInputStream,
+                                 bool aCompress)
+{
+  MOZ_ASSERT(!IsOnBackgroundThread());
+  MOZ_ASSERT(aFile);
+  MOZ_ASSERT(aJournalFile);
+  MOZ_ASSERT(aInputStream);
+  MOZ_ASSERT(mFileManager);
+  MOZ_ASSERT(mFileDirectory);
+  MOZ_ASSERT(mJournalDirectory);
+
+  bool exists;
+  nsresult rv = aFile->Exists(&exists);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // DOM blobs that are being stored in IDB are cached by calling
+  // IDBDatabase::GetOrCreateFileActorForBlob. So if the same DOM blob is stored
+  // again under a different key or in a different object store, we just add
+  // a new reference instead of creating a new copy (all such stored blobs share
+  // the same id).
+  // However, it can happen that CreateFileFromStream failed due to quota
+  // exceeded error and for some reason the orphaned file couldn't be deleted
+  // immediately. Now, if the operation is being repeated, the DOM blob is
+  // already cached, so it has the same file id which clashes with the orphaned
+  // file. We could do some tricks to restore previous copy loop, but it's safer
+  // to just delete the orphaned file and start from scratch.
+  // This corner case is partially simulated in test_file_copy_failure.js
+  if (exists) {
+    bool isFile;
+    rv = aFile->IsFile(&isFile);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    if (NS_WARN_IF(!isFile)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    rv = aJournalFile->Exists(&exists);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    if (NS_WARN_IF(!exists)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    rv = aJournalFile->IsFile(&isFile);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    if (NS_WARN_IF(!isFile)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    IDB_WARNING("Deleting orphaned file!");
+
+    rv = RemoveFile(aFile, aJournalFile);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  // Create a journal file first.
+  rv = aJournalFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // Now try to copy the stream.
+  RefPtr<FileOutputStream> fileOutputStream =
+    FileOutputStream::Create(mFileManager->Type(),
+                             mFileManager->Group(),
+                             mFileManager->Origin(),
+                             aFile);
+  if (NS_WARN_IF(!fileOutputStream)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if (aCompress) {
+    RefPtr<SnappyCompressOutputStream> snappyOutputStream =
+      new SnappyCompressOutputStream(fileOutputStream);
+
+    UniquePtr<char[]> buffer(new char[snappyOutputStream->BlockSize()]);
+
+    rv = SyncCopy(aInputStream,
+                  snappyOutputStream,
+                  buffer.get(),
+                  snappyOutputStream->BlockSize());
+  } else {
+    char buffer[kFileCopyBufferSize];
+
+    rv = SyncCopy(aInputStream,
+                  fileOutputStream,
+                  buffer,
+                  kFileCopyBufferSize);
+  }
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+FileHelper::ReplaceFile(nsIFile* aFile,
+                        nsIFile* aNewFile,
+                        nsIFile* aNewJournalFile)
+{
+  MOZ_ASSERT(!IsOnBackgroundThread());
+  MOZ_ASSERT(aFile);
+  MOZ_ASSERT(aNewFile);
+  MOZ_ASSERT(aNewJournalFile);
+  MOZ_ASSERT(mFileManager);
+  MOZ_ASSERT(mFileDirectory);
+  MOZ_ASSERT(mJournalDirectory);
+
+  nsresult rv;
+
+  int64_t fileSize;
+
+  if (mFileManager->EnforcingQuota()) {
+    rv = aFile->GetFileSize(&fileSize);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  nsAutoString fileName;
+  rv = aFile->GetLeafName(fileName);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aNewFile->RenameTo(nullptr, fileName);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (mFileManager->EnforcingQuota()) {
+    QuotaManager* quotaManager = QuotaManager::Get();
+    MOZ_ASSERT(quotaManager);
+
+    quotaManager->DecreaseUsageForOrigin(mFileManager->Type(),
+                                         mFileManager->Group(),
+                                         mFileManager->Origin(),
+                                         fileSize);
+  }
+
+  rv = aNewJournalFile->Remove(false);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+FileHelper::RemoveFile(nsIFile* aFile,
+                       nsIFile* aJournalFile)
+{
+  nsresult rv;
+
+  int64_t fileSize;
+
+  if (mFileManager->EnforcingQuota()) {
+    rv = aFile->GetFileSize(&fileSize);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  rv = aFile->Remove(false);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (mFileManager->EnforcingQuota()) {
+    QuotaManager* quotaManager = QuotaManager::Get();
+    MOZ_ASSERT(quotaManager);
+
+    quotaManager->DecreaseUsageForOrigin(mFileManager->Type(),
+                                         mFileManager->Group(),
+                                         mFileManager->Origin(),
+                                         fileSize);
+  }
+
+  rv = aJournalFile->Remove(false);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+already_AddRefed<FileInfo>
+FileHelper::GetNewFileInfo()
+{
+  MOZ_ASSERT(mFileManager);
+
+  return mFileManager->GetNewFileInfo();
+}
+
+nsresult
+FileHelper::SyncCopy(nsIInputStream* aInputStream,
+                     nsIOutputStream* aOutputStream,
+                     char* aBuffer,
+                     uint32_t aBufferSize)
+{
+  MOZ_ASSERT(!IsOnBackgroundThread());
+  MOZ_ASSERT(aInputStream);
+  MOZ_ASSERT(aOutputStream);
+
+  PROFILER_LABEL("IndexedDB",
+                 "FileHelper::SyncCopy",
+                 js::ProfileEntry::Category::STORAGE);
+
+  nsresult rv;
+
+  do {
+    uint32_t numRead;
+    rv = aInputStream->Read(aBuffer, aBufferSize, &numRead);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      break;
+    }
+
+    if (!numRead) {
+      break;
+    }
+
+    uint32_t numWrite;
+    rv = aOutputStream->Write(aBuffer, numRead, &numWrite);
+    if (rv == NS_ERROR_FILE_NO_DEVICE_SPACE) {
+      rv = NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
+    }
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      break;
+    }
+
+    if (NS_WARN_IF(numWrite != numRead)) {
+      rv = NS_ERROR_FAILURE;
+      break;
+    }
+  } while (true);
+
+  if (NS_SUCCEEDED(rv)) {
+    rv = aOutputStream->Flush();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  nsresult rv2 = aOutputStream->Close();
+  if (NS_WARN_IF(NS_FAILED(rv2))) {
+    return NS_SUCCEEDED(rv) ? rv2 : rv;
+  }
+
+  return rv;
+}
+
 } // namespace indexedDB
 } // namespace dom
 } // namespace mozilla
 
 #undef IDB_MOBILE
 #undef IDB_DEBUG_LOG
 #undef ASSERT_UNLESS_FUZZING
 #undef DISABLE_ASSERTS_FOR_FUZZING
--- a/dom/indexedDB/IndexedDatabase.h
+++ b/dom/indexedDB/IndexedDatabase.h
@@ -38,18 +38,16 @@ struct StructuredCloneFile
     eEndGuard
   };
 
   RefPtr<Blob> mBlob;
   RefPtr<IDBMutableFile> mMutableFile;
   RefPtr<JS::WasmModule> mWasmModule;
   RefPtr<FileInfo> mFileInfo;
   FileType mType;
-  // This is currently specific to eWasmCompiled files.
-  bool mValid;
 
   // In IndexedDatabaseInlines.h
   inline
   StructuredCloneFile();
 
   // In IndexedDatabaseInlines.h
   inline
   ~StructuredCloneFile();
--- a/dom/indexedDB/IndexedDatabaseInlines.h
+++ b/dom/indexedDB/IndexedDatabaseInlines.h
@@ -19,17 +19,16 @@
 
 namespace mozilla {
 namespace dom {
 namespace indexedDB {
 
 inline
 StructuredCloneFile::StructuredCloneFile()
   : mType(eBlob)
-  , mValid(true)
 {
   MOZ_COUNT_CTOR(StructuredCloneFile);
 }
 
 inline
 StructuredCloneFile::~StructuredCloneFile()
 {
   MOZ_COUNT_DTOR(StructuredCloneFile);
@@ -37,18 +36,17 @@ StructuredCloneFile::~StructuredCloneFil
 
 inline
 bool
 StructuredCloneFile::operator==(const StructuredCloneFile& aOther) const
 {
   return this->mBlob == aOther.mBlob &&
          this->mMutableFile == aOther.mMutableFile &&
          this->mFileInfo == aOther.mFileInfo &&
-         this->mType == aOther.mType &&
-         this->mValid == aOther.mValid;
+         this->mType == aOther.mType;
 }
 
 inline
 StructuredCloneReadInfo::StructuredCloneReadInfo()
   : mDatabase(nullptr)
   , mHasPreprocessInfo(false)
 {
   MOZ_COUNT_CTOR(StructuredCloneReadInfo);
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/unit/test_file_copy_failure.js
@@ -0,0 +1,75 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var testGenerator = testSteps();
+
+function testSteps()
+{
+  const name = "test_file_copy_failure.js";
+  const objectStoreName = "Blobs";
+  const blob = getBlob(getView(1024));
+
+  info("Opening database");
+
+  let request = indexedDB.open(name);
+  request.onerror = errorHandler;
+  request.onupgradeneeded = continueToNextStepSync;
+  request.onsuccess = unexpectedSuccessHandler;
+  yield undefined;
+
+  // upgradeneeded
+  request.onupgradeneeded = unexpectedSuccessHandler;
+  request.onsuccess = continueToNextStepSync;
+
+  info("Creating objectStore");
+
+  request.result.createObjectStore(objectStoreName);
+
+  yield undefined;
+
+  // success
+  let db = request.result;
+  db.onerror = errorHandler;
+
+  info("Creating orphaned file");
+
+  let filesDir = getChromeFilesDir();
+
+  let journalFile = filesDir.clone();
+  journalFile.append("journals");
+  journalFile.append("1");
+
+  let exists = journalFile.exists();
+  ok(!exists, "Journal file doesn't exist");
+
+  journalFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("0644", 8));
+
+  let file = filesDir.clone();
+  file.append("1");
+
+  exists = file.exists();
+  ok(!exists, "File doesn't exist");
+
+  file.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("0644", 8));
+
+  info("Storing blob");
+
+  let trans = db.transaction(objectStoreName, "readwrite");
+
+  request = trans.objectStore(objectStoreName).add(blob, 1);
+  request.onsuccess = continueToNextStepSync;
+
+  yield undefined;
+
+  trans.oncomplete = continueToNextStepSync;
+
+  yield undefined;
+
+  exists = journalFile.exists();
+  ok(!exists, "Journal file doesn't exist");
+
+  finishTest();
+  yield undefined;
+}
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/unit/test_wasm_recompile.js
@@ -0,0 +1,124 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var testGenerator = testSteps();
+
+function testSteps()
+{
+  const name = "test_wasm_recompile.js";
+
+  const objectStoreName = "Wasm";
+
+  const wasmData = { key: 1, wasm: null };
+
+  // The goal of this test is to prove that wasm is recompiled and the on-disk
+  // copy updated.
+
+  if (!isWasmSupported()) {
+    finishTest();
+    yield undefined;
+  }
+
+  getWasmBinary('(module (func (nop)))');
+  let binary = yield undefined;
+
+  wasmData.wasm = getWasmModule(binary);
+
+  info("Installing profile");
+
+  clearAllDatabases(continueToNextStepSync);
+  yield undefined;
+
+  // The profile was created by an older build (buildId: 20161116145318,
+  // cpuId: X64=0x2). It contains one stored wasm module (file id 1 - bytecode
+  // and file id 2 - compiled/machine code). The file create_db.js in the
+  // package was run locally (specifically it was temporarily added to
+  // xpcshell-parent-process.ini and then executed:
+  // mach xpcshell-test dom/indexedDB/test/unit/create_db.js
+  installPackagedProfile("wasm_recompile_profile");
+
+  let filesDir = getChromeFilesDir();
+
+  let file = filesDir.clone();
+  file.append("2");
+
+  info("Reading out contents of compiled blob");
+
+  let fileReader = new FileReader();
+  fileReader.onload = continueToNextStepSync;
+  fileReader.readAsArrayBuffer(File.createFromNsIFile(file));
+
+  yield undefined;
+
+  let compiledBuffer = fileReader.result;
+
+  info("Opening database");
+
+  let request = indexedDB.open(name);
+  request.onerror = errorHandler;
+  request.onupgradeneeded = unexpectedSuccessHandler;
+  request.onsuccess = continueToNextStepSync;
+  yield undefined;
+
+  // success
+  let db = request.result;
+  db.onerror = errorHandler;
+
+  info("Getting wasm");
+
+  request = db.transaction([objectStoreName])
+              .objectStore(objectStoreName).get(wasmData.key);
+  request.onsuccess = continueToNextStepSync;
+  yield undefined;
+
+  info("Verifying wasm module");
+
+  verifyWasmModule(request.result, wasmData.wasm);
+  yield undefined;
+
+  info("Reading out contents of new compiled blob");
+
+  fileReader = new FileReader();
+  fileReader.onload = continueToNextStepSync;
+  fileReader.readAsArrayBuffer(File.createFromNsIFile(file));
+
+  yield undefined;
+
+  let newCompiledBuffer = fileReader.result;
+
+  info("Verifying blobs differ");
+
+  ok(!compareBuffers(newCompiledBuffer, compiledBuffer), "Blobs differ");
+
+  info("Getting wasm again");
+
+  request = db.transaction([objectStoreName])
+              .objectStore(objectStoreName).get(wasmData.key);
+  request.onsuccess = continueToNextStepSync;
+  yield undefined;
+
+  info("Verifying wasm module");
+
+  verifyWasmModule(request.result, wasmData.wasm);
+  yield undefined;
+
+  info("Reading contents of new compiled blob again");
+
+  fileReader = new FileReader();
+  fileReader.onload = continueToNextStepSync;
+  fileReader.readAsArrayBuffer(File.createFromNsIFile(file));
+
+  yield undefined;
+
+  let newCompiledBuffer2 = fileReader.result;
+
+  info("Verifying blob didn't change");
+
+  ok(compareBuffers(newCompiledBuffer2, newCompiledBuffer),
+     "Blob didn't change");
+
+  finishTest();
+  yield undefined;
+}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5e1144db47be8c85f4545c978a5eb90d3f8293ec
GIT binary patch
literal 5447
zc$}?Q2|SeB8@5Dct1Kni*Rp0yDJ@JQ*$I;{7{)S#VVLY>FJh1_`in_qEjv?|WEsmC
z8N?+^t2Mg}^Pg_ZrEYHB+nL{)Iq&zK?|I&N&-vc>JUSZG+vq4hwt8P@b&3sb2L&gE
ztD6%XWDAxuG}uW&&EzU>e3jf0NO}s&Z3~nX6x&$I^K^cQz`Oy$84QPlU|^Wrzr@=A
zw^&vXxD)iB-DCap?|g(*64f5sOSZVkoqsDxNhsJ2WDRlyeZ}Vc9^1+S(OQ%;RNqij
zQ*-l{ZrtsZh$mFvFvfAbo@66iqU0|0Q;ZVsGG9r#wjlMV?M2B(%)08w7;h0cuB|xj
ztS$fLdMN)BtgM{0jDq}8`D1RbV0%|dH?X;*haJe-6$*7Sw}MOB*g1k-e_`SHM1`qE
z$d(+rtNa#j>8*k+Ye5nCWW>~rx1kr0!`0Lzl2>?On~ij#d?182U+Zw${#MW!?KeJp
zF(>L@q^6+2v-~a}x7-MmMj8;7Gwf-ZZ&f;{!~?_j@h~89<v#ZyQtrhTbP}0TlU>P2
zvZ0ZhYI+htKB%a%7zbcaaj<_Nf1uKi2m^*vooH@4Zh$ubo@|4I#wXtJ`NmpLT3O}d
zID^(+^sg+DUf-Th>D%poZ3kb4#Tm2pM?`Puj=?(OS`f44mA#rZGGkKh!h(vPJ6R;I
z@=3Qk)`NYesZKHC8&aD}Y0wfviR_7OcJx3hmSdynj&5{IX#O?ya*1zI({ihi0H3~o
z(fTvb*md4~;ljf}qK`6)mLV)_Z}Ock*@dJa2wHgcY?gnHB1zj*Dj0WL@Knl0@d`^N
zD>}?WI~-OmJt37=NuLgLY5)C#gd}CBa~$&z^xi1R=RY!Yr^5)4r>Lhd5eng$)7CKc
zX|8&dAFJjX5$`Fo|5f9%u37#4D{KX0Yy+MX5KTjHa5r8-<8T=O)!54!PNUr$iku14
zY`p3v&bce34|1ZsR{8x4I{K$qDPNuAZN{`pG@o==My0ljHoIRGZ;lk#M&0kbs4i(`
zfYQA15Xx`|<2XY*SFhEF%k9PUXic3l;3$u@ev8Xu4$pa%{VrwWmtCC#+oS9m&0r@{
zZ~fsEx*>z*yTUeaQ+8(=UNRcOxm73i%qp6796$;<jWk}FYby5~OkRuEdYUE_UIBYM
z{Klw?u6}e55X!`*==KJeuF~iitUsc>bLTLc6m-Z(@jz{f1tXG);i`F!a&;Wxsz+_l
z3tI0M)&R;GzGcRN>m6kmXJnRXiIm(SJ^bEt&~_khcR|0_7{S7u0d{Ug5%PqCdq%)G
ze9*`u;Lyp(V$x=sqk}a3rzallNM3!+s~}y9dg4mmYB3lvB9T&<c;Nl<nYCF}eZg5u
zmHkJ6FdQMbDKbC`^{lRGvaTX2@;Q(jUjIT31>8}AIQn{F|8tJB`TMG)Ghdy9?Ik8>
z9tkxSNYn3{wGB@G&_8KrhOJ8uA7~RS(cw>sMdx(_rYum;M=rTcosaApelKe4m==2k
z-O@d2hd{=|dn%Z+y6%<(72BXt8!xOny6^SOhwAe;s=E89oE6!s#*6WtV2}Bu6GEY^
zx(1b1ir$Y!68pK1RP{mUj#hfxlr;u}F;&MgWBY!b@-%+SD{b$DfWttJzmoeTk_S1*
zw~@`7|DJbUT^#M)z+0x3fe!6{8}{waDbYs*^l-&Zm&G#o8A1Rp|H~C2K!LnW0F6{h
zcFNWCCPAsZ<07D<uE?~Taoq0xpe3kZ4bv(;0lV&hxoos})!kT1sjw%Cg(w!iewtx^
z?I0y~dhU_$;A<aEd$yu=-7~U>`k-<SIqERD{y@`?B}zWd#<RU%w~ouy4ra6$A<}FM
zoT`SlM>x+BOS@h8QDc&%D=P%_si%J2RmsZ$B=f4+qUL;7Cy&762g3fO8;h4%>Pk|Y
zk+#nr?LDUPuk!D9!5s>6l8Yx4OY~4O5r8;7UxcA(O!Pri^Pybus1|`vyv3cNnN0)G
zlA9Z&=Mw9fuPzgVZd(!lBTo*9h^;;ciw4`tOU33IL>oUg%|=%B$I>JS-Z3<}P!yfo
zlT#KZXxrbP5af_F{=U7`weM-W4^SE_&QfBER#ZCQ)IEarb>^{U){=2#&c{Vp7>D<v
zaXGZj1uPZ!c3;WRaeh_Y9%7;!rk^#K`Fa3@clT~dyCfnF>zM4+YZ|C-;D*5(C3T|a
zW&Pe?d7$Q?WL`;|FRj<Ec!SZ*HV=loyW(9kv*=<UlhA`k!Dor)j!_o@y#pt;c38?Z
zmz~xBqcgkP2Gi)(Y<rVlZ)tC$Rn;jeF_e75aWrxAf?bODL1>mbY5i@<BJ=r@vXQ{@
zq8g8z#h&rL-8l~C#l7gfQ5YBZJ;mec3Zgkvw{|_bek^J1Wp44=ko|Qz7Q9htCmx4x
zITsIgFTzXL%4xZ*TbBm8#w8as!~%IG#b<6{-T$EKp}(bu6y<=V31Em+U^f|lZ1Bi~
z@yysV(2-=h>J%rNb;=X^51-?w5R9zi@uiD*;>_bQZHM$w@LYr0h~g=YmWafM{N9&+
zvbW1H9>7cL{V#*7l^|M>%cj6O&fRR=KmBx@3nFCtLjO+ADx)Zl(KH|T5!&MosSBP4
zyKQZj8$&P;qa@wj1d)q)v0lK{(xzswiDL@7&e<K<{CCyIM8kRt=C-SQnV7C!4wdex
zD=&}+98!5VJrts<z$^Ar=0LES4(Z|Pn9?}8qNPkH&JI+N*3vSN$&}HHix~ewoImW^
zH6kU#sHFs1E8AIES&@*!@L}dyMJ}R5brO%%xIC!e=CwXI#e?Nj=>}G2cUJZd?6Py4
z3Yg-DsG2~NBG)5z#rI}jpNQ}cw=c6jx6Q@<=JbVa@5WDZa~XHlb{D%6-o*>&bw=-+
zUw>L94KUG9y<g3%r9HPjFg2@0%EKUoq%_i8;NjjbU+9tE_c*?|k`%EE(uLz#u#)XI
zthX-HMtA4LYeq8qS{4G07S=-Dnjp3i6)&V|;k0lO&tT{p)#v2)8qe-E7yH;{g67MG
zhU&t3RsOv<5}zH$n&+tI4P?fvLbbcz5`8k@1%3B)C6hacNb8}~<syqx3v;m~_}54G
zsR!PGzZWda=8zv>xt*do9`yOf3ps=ziR2N5`C<<wMGBdXKJmEgyW$0k@&2&L7Ikel
zgTy(jthl%^44R%^G3g*Jll(qS_rJ#2LHfeMlSZQaG?YAff<*mkXzEQEBJm`7w`iVB
z^>!yze4bo$pLJNklwYRq(Jh4V=WEU%)~PqA*&JA1jm*be<d`u1m1e(9nLOX8OnRpI
zW^r{xHLO&Nrnv558w*PdYinm69Sd>`vK*jzEDi|fwi1*IYXIm>9%SE?;067h)h{7l
zWxODRF_V)#1DV+h4hE4Ex3#6Dz3ZR6^<>Wa?|FUnv9SKbyQ{~=N*@K$Q598GzDdsx
z7{h1!Q_r*G#(*}WG2%Sg-8G~^A+QMCOhg@<)Hgg4ar+|0P%8z#vUc2^<;-@Sd&R7K
z*Z}k{S7&7N6eU0)uXIh7IL+&@_KNv~cYIIVtqvyRjECSxg*m5JPR-xEt#JDaqUHX(
zGsrOZ5h0Fwe&8;1?2^nK`rLIr!*OftXM_vbZUOl?L7v($?r>RbmWSWjH`CrzlWw7H
zK8Z0kly*i-2i0pms|RC!_xRE^6NAG7+qs7om|TyZ5^Fe?_zY1fW{ja-R1dviCj2@#
z%h>p`$`P7Ef@)}+L|^lwD;U>%NKKV5HNB<bGQm#TCXyZ+#M8TTJnuXg6;^@PCinjM
zhfIhYJIK&tY$BeMlIdv*Q8^Jaf8$P*s=ZROf&<n@fhqEc#KDnVKusSqaTQ67Z8{MS
zu`fDXQVtQXHPNap?-StA5SRm7zPuuI{T+@+0Of$uku%L?<QII&S)z_~C~&#-w1V^c
ziB~~_LLsN$SUZnHAop%sLGhz`>_<a0LU@2HYD5XyX%j!X1uqky^N0s_u@)kRddJ;n
z*!A|M%=W)%lMTWc_n?TGgdOPK&fbo+GXIMn6RTQks~Rs062?D76y5*0KPjoWsedHT
z**}kVQ%s)u`1_Ywr}?x~zdKa?7>#)on(_v;jl%ucarbY+jrv>MuOfac_#a7l_ODc#
z0mECtZxHl9$Fo_uZ{*RZ{287<W&E#4jB8WG8n>eQq~3p>LU|JfH`PB-{G^iqxu|Z|
z@f)L>{9F8x{#i#hi}j6;oTUC8j{K{|*Vwc&d$#`$M>YtKpNq_9xv?=aH~*tUUn=R(
z`LVekZ}el0f#SRB{Lh%*SK3dQACD3K0UnGL-_%{2Pj&Y@L>k(UPcf1|apV(E>aLI0
F{{cE*<cR<P
--- a/dom/indexedDB/test/unit/xpcshell-head-parent-process.js
+++ b/dom/indexedDB/test/unit/xpcshell-head-parent-process.js
@@ -342,16 +342,41 @@ function installPackagedProfile(packageN
       istream.close();
       bostream.close();
     }
   }
 
   zipReader.close();
 }
 
+function getChromeFilesDir()
+{
+  let dirService = Cc["@mozilla.org/file/directory_service;1"]
+                   .getService(Ci.nsIProperties);
+
+  let profileDir = dirService.get("ProfD", Ci.nsIFile);
+
+  let idbDir = profileDir.clone();
+  idbDir.append("storage");
+  idbDir.append("permanent");
+  idbDir.append("chrome");
+  idbDir.append("idb");
+
+  let idbEntries = idbDir.directoryEntries;
+  while (idbEntries.hasMoreElements()) {
+    let entry = idbEntries.getNext();
+    let file = entry.QueryInterface(Ci.nsIFile);
+    if (file.isDirectory()) {
+      return file;
+    }
+  }
+
+  throw new Error("files directory doesn't exist!");
+}
+
 function getView(size)
 {
   let buffer = new ArrayBuffer(size);
   let view = new Uint8Array(buffer);
   is(buffer.byteLength, size, "Correct byte length");
   return view;
 }
 
--- a/dom/indexedDB/test/unit/xpcshell-parent-process.ini
+++ b/dom/indexedDB/test/unit/xpcshell-parent-process.ini
@@ -21,26 +21,28 @@ support-files =
   GlobalObjectsSandbox.js
   metadata2Restore_profile.zip
   metadataRestore_profile.zip
   schema18upgrade_profile.zip
   schema21upgrade_profile.zip
   schema23upgrade_profile.zip
   snappyUpgrade_profile.zip
   storagePersistentUpgrade_profile.zip
+  wasm_recompile_profile.zip
   xpcshell-shared.ini
 
 [include:xpcshell-shared.ini]
 
 [test_blob_file_backed.js]
 [test_bug1056939.js]
 [test_cleanup_transaction.js]
 [test_database_close_without_onclose.js]
 [test_database_onclose.js]
 [test_defaultStorageUpgrade.js]
+[test_file_copy_failure.js]
 [test_idbSubdirUpgrade.js]
 [test_globalObjects_ipc.js]
 skip-if = toolkit == 'android'
 [test_idle_maintenance.js]
 [test_invalidate.js]
 # disabled for the moment.
 skip-if = true
 [test_lowDiskSpace.js]
@@ -57,8 +59,9 @@ skip-if = true
 [test_snappyUpgrade.js]
 [test_storagePersistentUpgrade.js]
 [test_temporary_storage.js]
 # bug 951017: intermittent failure on Android x86 emulator
 skip-if = os == "android" && processor == "x86"
 [test_view_put_get_values.js]
 [test_wasm_getAll.js]
 [test_wasm_put_get_values.js]
+[test_wasm_recompile.js]
--- a/dom/ipc/URLClassifierParent.cpp
+++ b/dom/ipc/URLClassifierParent.cpp
@@ -13,23 +13,25 @@ using namespace mozilla::dom;
 
 NS_IMPL_ISUPPORTS(URLClassifierParent, nsIURIClassifierCallback)
 
 mozilla::ipc::IPCResult
 URLClassifierParent::StartClassify(nsIPrincipal* aPrincipal,
                                    bool aUseTrackingProtection,
                                    bool* aSuccess)
 {
+  nsresult rv = NS_OK;
+  // Note that in safe mode, the URL classifier service isn't available, so we
+  // should handle the service not being present gracefully.
   nsCOMPtr<nsIURIClassifier> uriClassifier =
-    do_GetService(NS_URICLASSIFIERSERVICE_CONTRACTID);
-  if (!uriClassifier) {
-    return IPC_FAIL_NO_REASON(this);
+    do_GetService(NS_URICLASSIFIERSERVICE_CONTRACTID, &rv);
+  if (NS_SUCCEEDED(rv)) {
+    rv = uriClassifier->Classify(aPrincipal, aUseTrackingProtection,
+                                 this, aSuccess);
   }
-  nsresult rv = uriClassifier->Classify(aPrincipal, aUseTrackingProtection,
-                                        this, aSuccess);
   if (NS_FAILED(rv) || !*aSuccess) {
     // We treat the case where we fail to classify and the case where the
     // classifier returns successfully but doesn't perform a lookup as the
     // classification not yielding any results, so we just kill the child actor
     // without ever calling out callback in both cases.
     // This means that code using this in the child process will only get a hit
     // on its callback if some classification actually happens.
     Unused << Send__delete__(this, void_t());
--- a/dom/media/TrackUnionStream.cpp
+++ b/dom/media/TrackUnionStream.cpp
@@ -53,17 +53,19 @@ TrackUnionStream::TrackUnionStream() :
 
   void TrackUnionStream::RemoveInput(MediaInputPort* aPort)
   {
     STREAM_LOG(LogLevel::Debug, ("TrackUnionStream %p removing input %p", this, aPort));
     for (int32_t i = mTrackMap.Length() - 1; i >= 0; --i) {
       if (mTrackMap[i].mInputPort == aPort) {
         STREAM_LOG(LogLevel::Debug, ("TrackUnionStream %p removing trackmap entry %d", this, i));
         EndTrack(i);
-        for (auto listener : mTrackMap[i].mOwnedDirectListeners) {
+        nsTArray<RefPtr<DirectMediaStreamTrackListener>> listeners(
+          mTrackMap[i].mOwnedDirectListeners);
+        for (auto listener : listeners) {
           // Remove listeners while the entry still exists.
           RemoveDirectTrackListenerImpl(listener, mTrackMap[i].mOutputTrackID);
         }
         mTrackMap.RemoveElementAt(i);
       }
     }
     ProcessedMediaStream::RemoveInput(aPort);
   }
--- a/embedding/browser/nsDocShellTreeOwner.cpp
+++ b/embedding/browser/nsDocShellTreeOwner.cpp
@@ -269,22 +269,20 @@ nsDocShellTreeOwner::EnsureContentTreeOw
 
   if (mWebBrowser) {
     mContentTreeOwner->WebBrowser(mWebBrowser);
   }
 }
 
 NS_IMETHODIMP
 nsDocShellTreeOwner::ContentShellAdded(nsIDocShellTreeItem* aContentShell,
-                                       bool aPrimary, bool aTargetable,
-                                       const nsAString& aID)
+                                       bool aPrimary, const nsAString& aID)
 {
   if (mTreeOwner)
-    return mTreeOwner->ContentShellAdded(aContentShell, aPrimary, aTargetable,
-                                         aID);
+    return mTreeOwner->ContentShellAdded(aContentShell, aPrimary, aID);
 
   EnsureContentTreeOwner();
   aContentShell->SetTreeOwner(mContentTreeOwner);
 
   if (aPrimary) {
     mPrimaryContentShell = aContentShell;
     mPrimaryTabParent = nullptr;
   }
@@ -473,24 +471,23 @@ NS_IMETHODIMP
 nsDocShellTreeOwner::GetPersistence(bool* aPersistPosition,
                                     bool* aPersistSize,
                                     bool* aPersistSizeMode)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
-nsDocShellTreeOwner::GetTargetableShellCount(uint32_t* aResult)
+nsDocShellTreeOwner::GetTabCount(uint32_t* aResult)
 {
   if (mTreeOwner) {
-    mTreeOwner->GetTargetableShellCount(aResult);
-  } else {
-    *aResult = 0;
+    return mTreeOwner->GetTabCount(aResult);
   }
 
+  *aResult = 0;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShellTreeOwner::GetHasPrimaryContent(bool* aResult)
 {
   *aResult = mPrimaryTabParent || mPrimaryContentShell;
   return NS_OK;
--- a/embedding/components/windowwatcher/nsWindowWatcher.cpp
+++ b/embedding/components/windowwatcher/nsWindowWatcher.cpp
@@ -960,16 +960,28 @@ nsWindowWatcher::OpenWindowInternal(mozI
     }
 
     NS_ASSERTION(mWindowCreator,
                  "attempted to open a new window with no WindowCreator");
     rv = NS_ERROR_FAILURE;
     if (mWindowCreator) {
       nsCOMPtr<nsIWebBrowserChrome> newChrome;
 
+      nsCOMPtr<nsPIDOMWindowInner> parentTopInnerWindow;
+      if (parentWindow) {
+        nsCOMPtr<nsPIDOMWindowOuter> parentTopWindow = parentWindow->GetTop();
+        if (parentTopWindow) {
+          parentTopInnerWindow = parentTopWindow->GetCurrentInnerWindow();
+        }
+      }
+
+      if (parentTopInnerWindow) {
+        parentTopInnerWindow->Suspend();
+      }
+
       /* If the window creator is an nsIWindowCreator2, we can give it
          some hints. The only hint at this time is whether the opening window
          is in a situation that's likely to mean this is an unrequested
          popup window we're creating. However we're not completely honest:
          we clear that indicator if the opener is chrome, so that the
          downstream consumer can treat the indicator to mean simply
          that the new window is subject to popup control. */
       nsCOMPtr<nsIWindowCreator2> windowCreator2(
@@ -979,16 +991,20 @@ nsWindowWatcher::OpenWindowInternal(mozI
         rv = CreateChromeWindow(features, parentChrome, chromeFlags,
                                 nullptr, openerWindow, getter_AddRefs(newChrome));
 
       } else {
         rv = mWindowCreator->CreateChromeWindow(parentChrome, chromeFlags,
                                                 getter_AddRefs(newChrome));
       }
 
+      if (parentTopInnerWindow) {
+        parentTopInnerWindow->Resume();
+      }
+
       if (newChrome) {
         nsCOMPtr<nsIXULWindow> xulWin = do_GetInterface(newChrome);
         if (xulWin) {
           nsCOMPtr<nsIXULBrowserWindow> xulBrowserWin;
           xulWin->GetXULBrowserWindow(getter_AddRefs(xulBrowserWin));
           if (xulBrowserWin) {
             nsPIDOMWindowOuter* openerWindow = aForceNoOpener ? nullptr : parentWindow.get();
             xulBrowserWin->ForceInitialBrowserNonRemote(openerWindow);
@@ -1072,30 +1088,48 @@ nsWindowWatcher::OpenWindowInternal(mozI
   //
   // Note: The check for the current JSContext isn't necessarily sensical.
   // It's just designed to preserve old semantics during a mass-conversion
   // patch.
   nsCOMPtr<nsIPrincipal> subjectPrincipal =
     nsContentUtils::GetCurrentJSContext() ? nsContentUtils::SubjectPrincipal() :
                                             nullptr;
 
-  bool shouldCheckPrivateBrowsingId = false;
+  bool isPrivateBrowsingWindow = false;
+
   if (windowIsNew) {
     auto* docShell = static_cast<nsDocShell*>(newDocShell.get());
 
     // If this is not a chrome docShell, we apply originAttributes from the
     // subjectPrincipal unless if it's an expanded or system principal.
     if (subjectPrincipal &&
         !nsContentUtils::IsSystemOrExpandedPrincipal(subjectPrincipal) &&
         docShell->ItemType() != nsIDocShellTreeItem::typeChrome) {
-      shouldCheckPrivateBrowsingId = true;
       DocShellOriginAttributes attrs;
       attrs.InheritFromDocToChildDocShell(BasePrincipal::Cast(subjectPrincipal)->OriginAttributesRef());
+      isPrivateBrowsingWindow = !!attrs.mPrivateBrowsingId;
+      docShell->SetOriginAttributes(attrs);
+    } else {
+      nsCOMPtr<nsIDocShellTreeItem> parentItem;
+      GetWindowTreeItem(aParent, getter_AddRefs(parentItem));
+      nsCOMPtr<nsILoadContext> parentContext = do_QueryInterface(parentItem);
+      if (parentContext) {
+        isPrivateBrowsingWindow = parentContext->UsePrivateBrowsing();
+      }
+    }
 
-      docShell->SetOriginAttributes(attrs);
+    bool autoPrivateBrowsing =
+      Preferences::GetBool("browser.privatebrowsing.autostart");
+
+    if (!autoPrivateBrowsing &&
+        (chromeFlags & nsIWebBrowserChrome::CHROME_NON_PRIVATE_WINDOW)) {
+      isPrivateBrowsingWindow = false;
+    } else if (autoPrivateBrowsing ||
+               (chromeFlags & nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW)) {
+      isPrivateBrowsingWindow = true;
     }
 
     // Now set the opener principal on the new window.  Note that we need to do
     // this no matter whether we were opened from JS; if there is nothing on
     // the JS stack, just use the principal of our parent window.  In those
     // cases we do _not_ set the parent window principal as the owner of the
     // load--since we really don't know who the owner is, just leave it null.
     nsCOMPtr<nsPIDOMWindowOuter> newWindow = do_QueryInterface(*aResult);
@@ -1115,36 +1149,16 @@ nsWindowWatcher::OpenWindowInternal(mozI
                                                // counter even if the above
                                                // assert fails.
           globalWin->SetIsPopupSpamWindow(true);
         }
       }
     }
   }
 
-  // If all windows should be private, make sure the new window is also
-  // private.  Otherwise, see if the caller has explicitly requested a
-  // private or non-private window.
-  bool isPrivateBrowsingWindow =
-    Preferences::GetBool("browser.privatebrowsing.autostart") ||
-    (!!(chromeFlags & nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW) &&
-     !(chromeFlags & nsIWebBrowserChrome::CHROME_NON_PRIVATE_WINDOW));
-
-  // Otherwise, propagate the privacy status of the parent window, if
-  // available, to the child.
-  if (!isPrivateBrowsingWindow &&
-      !(chromeFlags & nsIWebBrowserChrome::CHROME_NON_PRIVATE_WINDOW)) {
-    nsCOMPtr<nsIDocShellTreeItem> parentItem;
-    GetWindowTreeItem(aParent, getter_AddRefs(parentItem));
-    nsCOMPtr<nsILoadContext> parentContext = do_QueryInterface(parentItem);
-    if (parentContext) {
-      isPrivateBrowsingWindow = parentContext->UsePrivateBrowsing();
-    }
-  }
-
   // We rely on CalculateChromeFlags to decide whether remote (out-of-process)
   // tabs should be used.
   bool isRemoteWindow =
     !!(chromeFlags & nsIWebBrowserChrome::CHROME_REMOTE_WINDOW);
 
   if (isNewToplevelWindow) {
     nsCOMPtr<nsIDocShellTreeItem> childRoot;
     newDocShellItem->GetRootTreeItem(getter_AddRefs(childRoot));
@@ -1214,21 +1228,16 @@ nsWindowWatcher::OpenWindowInternal(mozI
   // Copy the current session storage for the current domain.
   if (subjectPrincipal && parentDocShell) {
     nsCOMPtr<nsIDOMStorageManager> parentStorageManager =
       do_QueryInterface(parentDocShell);
     nsCOMPtr<nsIDOMStorageManager> newStorageManager =
       do_QueryInterface(newDocShell);
 
     if (parentStorageManager && newStorageManager) {
-      if (shouldCheckPrivateBrowsingId) {
-        MOZ_DIAGNOSTIC_ASSERT(
-          (subjectPrincipal->GetPrivateBrowsingId() > 0) == isPrivateBrowsingWindow);
-      }
-
       nsCOMPtr<nsIDOMStorage> storage;
       nsCOMPtr<nsPIDOMWindowInner> pInnerWin = parentWindow->GetCurrentInnerWindow();
       parentStorageManager->GetStorage(pInnerWin, subjectPrincipal,
                                        getter_AddRefs(storage));
       if (storage) {
         newStorageManager->CloneStorage(storage);
       }
     }
--- a/extensions/cookie/nsPermissionManager.cpp
+++ b/extensions/cookie/nsPermissionManager.cpp
@@ -599,19 +599,27 @@ IsExpandedPrincipal(nsIPrincipal* aPrinc
   nsCOMPtr<nsIExpandedPrincipal> ep = do_QueryInterface(aPrincipal);
   return !!ep;
 }
 
 } // namespace
 
 ////////////////////////////////////////////////////////////////////////////////
 
-nsPermissionManager::PermissionKey::PermissionKey(nsIPrincipal* aPrincipal)
+nsPermissionManager::PermissionKey*
+nsPermissionManager::PermissionKey::CreateFromPrincipal(nsIPrincipal* aPrincipal,
+                                                        nsresult& aResult)
 {
-  MOZ_ALWAYS_SUCCEEDS(GetOriginFromPrincipal(aPrincipal, mOrigin));
+  nsAutoCString origin;
+  aResult = GetOriginFromPrincipal(aPrincipal, origin);
+  if (NS_WARN_IF(NS_FAILED(aResult))) {
+    return nullptr;
+  }
+
+  return new PermissionKey(origin);
 }
 
 /**
  * Simple callback used by |AsyncClose| to trigger a treatment once
  * the database is closed.
  *
  * Note: Beware that, if you hold onto a |CloseDatabaseListener| from a
  * |nsPermissionManager|, this will create a cycle.
@@ -1579,17 +1587,23 @@ nsPermissionManager::AddInternal(nsIPrin
   }
 
   // look up the type index
   int32_t typeIndex = GetTypeIndex(aType.get(), true);
   NS_ENSURE_TRUE(typeIndex != -1, NS_ERROR_OUT_OF_MEMORY);
 
   // When an entry already exists, PutEntry will return that, instead
   // of adding a new one
-  RefPtr<PermissionKey> key = new PermissionKey(aPrincipal);
+  RefPtr<PermissionKey> key =
+    PermissionKey::CreateFromPrincipal(aPrincipal, rv);
+  if (!key) {
+    MOZ_ASSERT(NS_FAILED(rv));
+    return rv;
+  }
+
   PermissionHashKey* entry = mPermissionTable.PutEntry(key);
   if (!entry) return NS_ERROR_FAILURE;
   if (!entry->GetKey()) {
     mPermissionTable.RemoveEntry(entry);
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   // figure out the transaction type, and get any existing permission value
@@ -2128,20 +2142,24 @@ nsPermissionManager::CommonTestPermissio
 // Returns null if nothing found.
 // Also accepts host on the format "<foo>". This will perform an exact match
 // lookup as the string doesn't contain any dots.
 nsPermissionManager::PermissionHashKey*
 nsPermissionManager::GetPermissionHashKey(nsIPrincipal* aPrincipal,
                                           uint32_t aType,
                                           bool aExactHostMatch)
 {
-  PermissionHashKey* entry = nullptr;
-
-  RefPtr<PermissionKey> key = new PermissionKey(aPrincipal);
-  entry = mPermissionTable.GetEntry(key);
+  nsresult rv;
+  RefPtr<PermissionKey> key =
+    PermissionKey::CreateFromPrincipal(aPrincipal, rv);
+  if (!key) {
+    return nullptr;
+  }
+
+  PermissionHashKey* entry = mPermissionTable.GetEntry(key);
 
   if (entry) {
     PermissionEntry permEntry = entry->GetPermission(aType);
 
     // if the entry is expired, remove and keep looking for others.
     // Note that EXPIRE_SESSION only honors expireTime if it is nonzero.
     if ((permEntry.mExpireType == nsIPermissionManager::EXPIRE_TIME ||
          (permEntry.mExpireType == nsIPermissionManager::EXPIRE_SESSION &&
@@ -2247,17 +2265,22 @@ NS_IMETHODIMP nsPermissionManager::GetEn
 NS_IMETHODIMP nsPermissionManager::GetAllForURI(nsIURI* aURI, nsISimpleEnumerator **aEnum)
 {
   nsCOMArray<nsIPermission> array;
 
   nsCOMPtr<nsIPrincipal> principal;
   nsresult rv = GetPrincipal(aURI, getter_AddRefs(principal));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  RefPtr<PermissionKey> key = new PermissionKey(principal);
+  RefPtr<PermissionKey> key = PermissionKey::CreateFromPrincipal(principal, rv);
+  if (!key) {
+    MOZ_ASSERT(NS_FAILED(rv));
+    return rv;
+  }
+
   PermissionHashKey* entry = mPermissionTable.GetEntry(key);
 
   if (entry) {
     for (const auto& permEntry : entry->GetPermissions()) {
       // Only return custom permissions
       if (permEntry.mPermission == nsIPermissionManager::UNKNOWN_ACTION) {
         continue;
       }
--- a/extensions/cookie/nsPermissionManager.h
+++ b/extensions/cookie/nsPermissionManager.h
@@ -66,17 +66,19 @@ public:
    * PermissionKey is the key used by PermissionHashKey hash table.
    *
    * NOTE: It could be implementing nsIHashable but there is no reason to worry
    * with XPCOM interfaces while we don't need to.
    */
   class PermissionKey
   {
   public:
-    explicit PermissionKey(nsIPrincipal* aPrincipal);
+    static PermissionKey* CreateFromPrincipal(nsIPrincipal* aPrincipal,
+                                              nsresult& aResult);
+
     explicit PermissionKey(const nsACString& aOrigin)
       : mOrigin(aOrigin)
     {
     }
 
     bool operator==(const PermissionKey& aKey) const {
       return mOrigin.Equals(aKey.mOrigin);
     }
--- a/gfx/2d/PathRecording.h
+++ b/gfx/2d/PathRecording.h
@@ -128,15 +128,15 @@ private:
   friend class DrawTargetRecording;
   friend class RecordedPathCreation;
 
   RefPtr<Path> mPath;
   std::vector<PathOp> mPathOps;
   FillRule mFillRule;
 
   // Event recorders that have this path in their event stream.
-  std::vector<DrawEventRecorderPrivate*> mStoredRecorders;
+  std::vector<RefPtr<DrawEventRecorderPrivate>> mStoredRecorders;
 };
 
 } // namespace gfx
 } // namespace mozilla
 
 #endif /* MOZILLA_GFX_PATHRECORDING_H_ */
--- a/gfx/ipc/GPUChild.cpp
+++ b/gfx/ipc/GPUChild.cpp
@@ -77,30 +77,30 @@ GPUChild::EnsureGPUReady()
   if (mGPUReady) {
     return;
   }
 
   GPUDeviceData data;
   SendGetDeviceStatus(&data);
 
   gfxPlatform::GetPlatform()->ImportGPUDeviceData(data);
-  Telemetry::AccumulateTimeDelta(Telemetry::GPU_PROCESS_LAUNCH_TIME_MS, mHost->GetLaunchTime());
+  Telemetry::AccumulateTimeDelta(Telemetry::GPU_PROCESS_LAUNCH_TIME_MS_2, mHost->GetLaunchTime());
   mGPUReady = true;
 }
 
 mozilla::ipc::IPCResult
 GPUChild::RecvInitComplete(const GPUDeviceData& aData)
 {
   // We synchronously requested GPU parameters before this arrived.
   if (mGPUReady) {
     return IPC_OK();
   }
 
   gfxPlatform::GetPlatform()->ImportGPUDeviceData(aData);
-  Telemetry::AccumulateTimeDelta(Telemetry::GPU_PROCESS_LAUNCH_TIME_MS, mHost->GetLaunchTime());
+  Telemetry::AccumulateTimeDelta(Telemetry::GPU_PROCESS_LAUNCH_TIME_MS_2, mHost->GetLaunchTime());
   mGPUReady = true;
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 GPUChild::RecvReportCheckerboard(const uint32_t& aSeverity, const nsCString& aLog)
 {
   layers::CheckerboardEventStorage::Report(aSeverity, std::string(aLog.get()));
--- a/image/DecodePool.cpp
+++ b/image/DecodePool.cpp
@@ -11,16 +11,17 @@
 #include "mozilla/Monitor.h"
 #include "nsCOMPtr.h"
 #include "nsIObserverService.h"
 #include "nsIThreadPool.h"
 #include "nsThreadManager.h"
 #include "nsThreadUtils.h"
 #include "nsXPCOMCIDInternal.h"
 #include "prsystem.h"
+#include "nsIXULRuntime.h"
 
 #include "gfxPrefs.h"
 
 #include "Decoder.h"
 #include "IDecodingTask.h"
 #include "RasterImage.h"
 
 using std::max;
@@ -256,16 +257,21 @@ DecodePool::DecodePool()
       limit = numCores - 1;
     }
   } else {
     limit = static_cast<uint32_t>(prefLimit);
   }
   if (limit > 32) {
     limit = 32;
   }
+  // The parent process where there are content processes doesn't need as many
+  // threads for decoding images.
+  if (limit > 4 && XRE_IsParentProcess() && BrowserTabsRemoteAutostart()) {
+    limit = 4;
+  }
 
   // Initialize the thread pool.
   for (uint32_t i = 0 ; i < limit ; ++i) {
     nsCOMPtr<nsIRunnable> worker = new DecodePoolWorker(mImpl);
     nsCOMPtr<nsIThread> thread;
     nsresult rv = NS_NewThread(getter_AddRefs(thread), worker);
     MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv) && thread,
                        "Should successfully create image decoding threads");
--- a/image/Downscaler.h
+++ b/image/Downscaler.h
@@ -149,39 +149,42 @@ private:
 /**
  * Downscaler requires Skia to work, so we provide a dummy implementation if
  * Skia is disabled that asserts if constructed.
  */
 
 class Downscaler
 {
 public:
-  explicit Downscaler(const nsIntSize&)
+  explicit Downscaler(const nsIntSize&) : mScale(1.0, 1.0)
   {
     MOZ_RELEASE_ASSERT(false, "Skia is not enabled");
   }
 
-  const nsIntSize& OriginalSize() const { return nsIntSize(); }
-  const nsIntSize& TargetSize() const { return nsIntSize(); }
-  const gfxSize& Scale() const { return gfxSize(1.0, 1.0); }
+  const nsIntSize& OriginalSize() const { return mSize; }
+  const nsIntSize& TargetSize() const { return mSize; }
+  const gfxSize& Scale() const { return mScale; }
 
   nsresult BeginFrame(const nsIntSize&, const Maybe<nsIntRect>&, uint8_t*, bool, bool = false)
   {
     return NS_ERROR_FAILURE;
   }
 
   bool IsFrameComplete() const { return false; }
   uint8_t* RowBuffer() { return nullptr; }
   void ClearRow() { }
   void ClearRestOfRow(uint32_t) { }
   void CommitRow() { }
   bool HasInvalidation() const { return false; }
   DownscalerInvalidRect TakeInvalidRect() { return DownscalerInvalidRect(); }
   void ResetForNextProgressivePass() { }
   const nsIntSize FrameSize() const { return nsIntSize(0, 0); }
+private:
+  nsIntSize mSize;
+  gfxSize mScale;
 };
 
 #endif // MOZ_ENABLE_SKIA
 
 
 
 } // namespace image
 } // namespace mozilla
--- a/js/src/devtools/automation/cgc-jittest-timeouts.txt
+++ b/js/src/devtools/automation/cgc-jittest-timeouts.txt
@@ -35,13 +35,14 @@ parser/bug-1263881-3.js
 parser/modifier-yield-without-operand-2.js
 saved-stacks/bug-1006876-too-much-recursion.js
 self-test/assertDeepEq.js
 sunspider/check-string-unpack-code.js
 v8-v5/check-earley-boyer.js
 v8-v5/check-raytrace.js
 v8-v5/check-regexp.js
 v8-v5/check-splay.js
+wasm/spec/br_table.wast.js
 wasm/spec/f32.wast.js
 wasm/spec/f32_cmp.wast.js
 wasm/spec/f64.wast.js
 wasm/spec/f64_cmp.wast.js
 wasm/spec/float_exprs.wast.js
--- a/js/src/gc/Zone.cpp
+++ b/js/src/gc/Zone.cpp
@@ -37,17 +37,16 @@ JS::Zone::Zone(JSRuntime* rt)
     usage(&rt->gc.usage),
     gcDelayBytes(0),
     propertyTree(this),
     baseShapes(this, BaseShapeSet()),
     initialShapes(this, InitialShapeSet()),
     data(nullptr),
     isSystem(false),
     usedByExclusiveThread(false),
-    active(false),
     jitZone_(nullptr),
     gcState_(NoGC),
     gcScheduled_(false),
     gcPreserveCode_(false),
     jitUsingBarriers_(false),
     keepShapeTables_(false),
     listNext_(NotOnList)
 {
@@ -123,21 +122,16 @@ Zone::onTooMuchMalloc()
         GCRuntime& gc = runtimeFromAnyThread()->gc;
         gcMallocGCTriggered = gc.triggerZoneGC(this, JS::gcreason::TOO_MUCH_MALLOC);
     }
 }
 
 void
 Zone::beginSweepTypes(FreeOp* fop, bool releaseTypes)
 {
-    // Periodically release observed types for all scripts. This is safe to
-    // do when there are no frames for the zone on the stack.
-    if (active)
-        releaseTypes = false;
-
     AutoClearTypeInferenceStateOnOOM oom(this);
     types.beginSweep(fop, releaseTypes, oom);
 }
 
 Zone::DebuggerVector*
 Zone::getOrCreateDebuggers(JSContext* cx)
 {
     if (debuggers)
@@ -238,28 +232,42 @@ Zone::discardJitCode(FreeOp* fop, bool d
                 jit::FinishDiscardBaselineScript(fop, script);
 
             /*
              * Warm-up counter for scripts are reset on GC. After discarding code we
              * need to let it warm back up to get information such as which
              * opcodes are setting array holes or accessing getter properties.
              */
             script->resetWarmUpCounter();
+
+            /*
+             * Make it impossible to use the control flow graphs cached on the
+             * BaselineScript. They get deleted.
+             */
+            if (script->hasBaselineScript())
+                script->baselineScript()->setControlFlowGraph(nullptr);
         }
 
         /*
          * When scripts contains pointers to nursery things, the store buffer
          * can contain entries that point into the optimized stub space. Since
          * this method can be called outside the context of a GC, this situation
          * could result in us trying to mark invalid store buffer entries.
          *
          * Defer freeing any allocated blocks until after the next minor GC.
          */
         if (discardBaselineCode)
             jitZone()->optimizedStubSpace()->freeAllAfterMinorGC(fop->runtime());
+
+        /*
+         * Free all control flow graphs that are cached on BaselineScripts.
+         * Assuming this happens on the mainthread and all control flow
+         * graph reads happen on the mainthread, this is save.
+         */
+        jitZone()->cfgSpace()->lifoAlloc().freeAll();
     }
 }
 
 #ifdef JSGC_HASH_TABLE_CHECKS
 void
 JS::Zone::checkUniqueIdTableAfterMovingGC()
 {
     for (UniqueIdMap::Enum e(uniqueIds_); !e.empty(); e.popFront())
--- a/js/src/gc/Zone.h
+++ b/js/src/gc/Zone.h
@@ -412,19 +412,16 @@ struct Zone : public JS::shadow::Zone,
 
     // Per-zone data for use by an embedder.
     void* data;
 
     bool isSystem;
 
     mozilla::Atomic<bool> usedByExclusiveThread;
 
-    // True when there are active frames.
-    bool active;
-
 #ifdef DEBUG
     unsigned gcLastZoneGroupIndex;
 #endif
 
     static js::HashNumber UniqueIdToHash(uint64_t uid) {
         return js::HashNumber(uid >> 32) ^ js::HashNumber(uid & 0xFFFFFFFF);
     }
 
--- a/js/src/jit-test/tests/xdr/async.js
+++ b/js/src/jit-test/tests/xdr/async.js
@@ -1,10 +1,13 @@
 load(libdir + 'bytecode-cache.js');
 
+// Prevent relazification triggered by some zeal modes.
+gczeal(0);
+
 async function f1(a, b) {
   let x = await 10;
   return x;
 };
 var toStringResult = f1.toString();
 
 var test = `
 async function f1(a, b) {
--- a/js/src/jit/BaselineJIT.cpp
+++ b/js/src/jit/BaselineJIT.cpp
@@ -8,16 +8,17 @@
 
 #include "mozilla/BinarySearch.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/MemoryReporting.h"
 
 #include "jit/BaselineCompiler.h"
 #include "jit/BaselineIC.h"
 #include "jit/CompileInfo.h"
+#include "jit/IonControlFlow.h"
 #include "jit/JitCommon.h"
 #include "jit/JitSpewer.h"
 #include "vm/Debugger.h"
 #include "vm/Interpreter.h"
 #include "vm/TraceLogging.h"
 #include "wasm/WasmInstance.h"
 
 #include "jsobjinlines.h"
@@ -71,17 +72,18 @@ BaselineScript::BaselineScript(uint32_t 
     traceLoggerEngineEnabled_(false),
 # endif
     traceLoggerScriptEvent_(),
 #endif
     postDebugPrologueOffset_(postDebugPrologueOffset),
     flags_(0),
     inlinedBytecodeLength_(0),
     maxInliningDepth_(UINT8_MAX),
-    pendingBuilder_(nullptr)
+    pendingBuilder_(nullptr),
+    controlFlowGraph_(nullptr)
 { }
 
 static const unsigned BASELINE_MAX_ARGS_LENGTH = 20000;
 
 static bool
 CheckFrame(InterpreterFrame* fp)
 {
     if (fp->isDebuggerEvalFrame()) {
--- a/js/src/jit/BaselineJIT.h
+++ b/js/src/jit/BaselineJIT.h
@@ -19,16 +19,17 @@
 #include "vm/TraceLogging.h"
 
 namespace js {
 namespace jit {
 
 class StackValue;
 class BaselineICEntry;
 class ICStub;
+class ControlFlowGraph;
 
 class PCMappingSlotInfo
 {
     uint8_t slotInfo_;
 
   public:
     // SlotInfo encoding:
     //  Bits 0 & 1: number of slots at top of stack which are unsynced.
@@ -234,16 +235,18 @@ struct BaselineScript
     // no data yet, and won't affect inlining heuristics in that case. The value
     // is updated when we Ion-compile this script. See makeInliningDecision for
     // more info.
     uint8_t maxInliningDepth_;
 
     // An ion compilation that is ready, but isn't linked yet.
     IonBuilder *pendingBuilder_;
 
+    ControlFlowGraph* controlFlowGraph_;
+
   public:
     // Do not call directly, use BaselineScript::New. This is public for cx->new_.
     BaselineScript(uint32_t prologueOffset, uint32_t epilogueOffset,
                    uint32_t profilerEnterToggleOffset,
                    uint32_t profilerExitToggleOffset,
                    uint32_t postDebugPrologueOffset);
 
     ~BaselineScript() {
@@ -508,16 +511,24 @@ struct BaselineScript
         script->updateBaselineOrIonRaw(maybeRuntime);
     }
     void removePendingIonBuilder(JSScript* script) {
         setPendingIonBuilder(nullptr, script, nullptr);
         if (script->maybeIonScript() == ION_PENDING_SCRIPT)
             script->setIonScript(nullptr, nullptr);
     }
 
+    const ControlFlowGraph* controlFlowGraph() const {
+        return controlFlowGraph_;
+    }
+
+    void setControlFlowGraph(ControlFlowGraph* controlFlowGraph) {
+        controlFlowGraph_ = controlFlowGraph;
+    }
+
 };
 static_assert(sizeof(BaselineScript) % sizeof(uintptr_t) == 0,
               "The data attached to the script must be aligned for fast JIT access.");
 
 inline bool
 IsBaselineEnabled(JSContext* cx)
 {
 #ifdef JS_CODEGEN_NONE
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -1174,24 +1174,41 @@ PrepareAndExecuteRegExp(JSContext* cx, M
     LiveGeneralRegisterSet volatileRegs;
     if (lastIndex.volatile_())
         volatileRegs.add(lastIndex);
     if (input.volatile_())
         volatileRegs.add(input);
     if (regexp.volatile_())
         volatileRegs.add(regexp);
 
+#ifdef JS_TRACE_LOGGING
+    TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime());
+    if (TraceLogTextIdEnabled(TraceLogger_IrregexpExecute)) {
+        masm.push(temp1);
+        masm.movePtr(ImmPtr(logger), temp1);
+        masm.tracelogStartId(temp1, TraceLogger_IrregexpExecute);
+        masm.pop(temp1);
+    }
+#endif
+
     // Execute the RegExp.
     masm.computeEffectiveAddress(Address(masm.getStackPointer(), inputOutputDataStartOffset), temp2);
     masm.PushRegsInMask(volatileRegs);
     masm.setupUnalignedABICall(temp3);
     masm.passABIArg(temp2);
     masm.callWithABI(codePointer);
     masm.PopRegsInMask(volatileRegs);
 
+#ifdef JS_TRACE_LOGGING
+    if (TraceLogTextIdEnabled(TraceLogger_IrregexpExecute)) {
+        masm.movePtr(ImmPtr(logger), temp1);
+        masm.tracelogStopId(temp1, TraceLogger_IrregexpExecute);
+    }
+#endif
+
     Label success;
     masm.branch32(Assembler::Equal, matchResultAddress,
                   Imm32(RegExpRunStatus_Success_NotFound), notFound);
     masm.branch32(Assembler::Equal, matchResultAddress,
                   Imm32(RegExpRunStatus_Error), failure);
 
     // Lazily update the RegExpStatics.
     masm.movePtr(ImmPtr(res), temp1);
--- a/js/src/jit/InlineList.h
+++ b/js/src/jit/InlineList.h
@@ -249,16 +249,20 @@ class InlineListNode : public InlineForw
         // location.
         newNext->prev = this;
         newPrev->next = this;
     }
 
     InlineListNode(const InlineListNode<T>&) = delete;
     void operator=(const InlineListNode<T>&) = delete;
 
+    bool isInList() {
+        return prev != nullptr && this->next != nullptr;
+    }
+
   protected:
     friend class InlineList<T>;
     friend class InlineListIterator<T>;
     friend class InlineListReverseIterator<T>;
 
     InlineListNode<T>* prev;
 };
 
--- a/js/src/jit/IonAnalysis.cpp
+++ b/js/src/jit/IonAnalysis.cpp
@@ -2605,39 +2605,41 @@ static void
 CheckOperand(const MNode* consumer, const MUse* use, int32_t* usesBalance)
 {
     MOZ_ASSERT(use->hasProducer());
     MDefinition* producer = use->producer();
     MOZ_ASSERT(!producer->isDiscarded());
     MOZ_ASSERT(producer->block() != nullptr);
     MOZ_ASSERT(use->consumer() == consumer);
 #ifdef _DEBUG_CHECK_OPERANDS_USES_BALANCE
-    fprintf(stderr, "==Check Operand\n");
-    use->producer()->dump(stderr);
-    fprintf(stderr, "  index: %" PRIuSIZE "\n", use->consumer()->indexOf(use));
-    use->consumer()->dump(stderr);
-    fprintf(stderr, "==End\n");
+    Fprinter print(stderr);
+    print.printf("==Check Operand\n");
+    use->producer()->dump(print);
+    print.printf("  index: %" PRIuSIZE "\n", use->consumer()->indexOf(use));
+    use->consumer()->dump(print);
+    print.printf("==End\n");
 #endif
     --*usesBalance;
 }
 
 static void
 CheckUse(const MDefinition* producer, const MUse* use, int32_t* usesBalance)
 {
     MOZ_ASSERT(!use->consumer()->block()->isDead());
     MOZ_ASSERT_IF(use->consumer()->isDefinition(),
                   !use->consumer()->toDefinition()->isDiscarded());
     MOZ_ASSERT(use->consumer()->block() != nullptr);
     MOZ_ASSERT(use->consumer()->getOperand(use->index()) == producer);
 #ifdef _DEBUG_CHECK_OPERANDS_USES_BALANCE
-    fprintf(stderr, "==Check Use\n");
-    use->producer()->dump(stderr);
-    fprintf(stderr, "  index: %" PRIuSIZE "\n", use->consumer()->indexOf(use));
-    use->consumer()->dump(stderr);
-    fprintf(stderr, "==End\n");
+    Fprinter print(stderr);
+    print.printf("==Check Use\n");
+    use->producer()->dump(print);
+    print.printf("  index: %" PRIuSIZE "\n", use->consumer()->indexOf(use));
+    use->consumer()->dump(print);
+    print.printf("==End\n");
 #endif
     ++*usesBalance;
 }
 
 // To properly encode entry resume points, we have to ensure that all the
 // operands of the entry resume point are located before the safeInsertTop
 // location.
 static void
@@ -4181,20 +4183,20 @@ jit::AnalyzeNewScriptDefiniteProperties(
 
     BaselineInspector inspector(script);
     const JitCompileOptions options(cx);
 
     IonBuilder builder(cx, CompileCompartment::get(cx->compartment()), options, &temp, &graph, constraints,
                        &inspector, &info, optimizationInfo, /* baselineFrame = */ nullptr);
 
     if (!builder.build()) {
-        if (cx->isThrowingOverRecursed() ||
-            cx->isThrowingOutOfMemory() ||
-            builder.abortReason() == AbortReason_Alloc)
-        {
+        if (cx->isThrowingOverRecursed() || cx->isThrowingOutOfMemory())
+            return false;
+        if (builder.abortReason() == AbortReason_Alloc) {
+            ReportOutOfMemory(cx);
             return false;
         }
         MOZ_ASSERT(!cx->isExceptionPending());
         return true;
     }
 
     FinishDefinitePropertiesAnalysis(cx, constraints);
 
@@ -4416,18 +4418,22 @@ jit::AnalyzeArgumentsUsage(JSContext* cx
 
     BaselineInspector inspector(script);
     const JitCompileOptions options(cx);
 
     IonBuilder builder(nullptr, CompileCompartment::get(cx->compartment()), options, &temp, &graph, constraints,
                        &inspector, &info, optimizationInfo, /* baselineFrame = */ nullptr);
 
     if (!builder.build()) {
-        if (cx->isThrowingOverRecursed() || builder.abortReason() == AbortReason_Alloc)
+        if (cx->isThrowingOverRecursed() || cx->isThrowingOutOfMemory())
             return false;
+        if (builder.abortReason() == AbortReason_Alloc) {
+            ReportOutOfMemory(cx);
+            return false;
+        }
         MOZ_ASSERT(!cx->isExceptionPending());
         return true;
     }
 
     if (!SplitCriticalEdges(graph)) {
         ReportOutOfMemory(cx);
         return false;
     }
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -10,16 +10,17 @@
 #include "mozilla/SizePrintfMacros.h"
 
 #include "builtin/Eval.h"
 #include "builtin/TypedObject.h"
 #include "frontend/SourceNotes.h"
 #include "jit/BaselineFrame.h"
 #include "jit/BaselineInspector.h"
 #include "jit/Ion.h"
+#include "jit/IonControlFlow.h"
 #include "jit/IonOptimizationLevels.h"
 #include "jit/JitSpewer.h"
 #include "jit/Lowering.h"
 #include "jit/MIRGraph.h"
 #include "vm/ArgumentsObject.h"
 #include "vm/Opcodes.h"
 #include "vm/RegExpStatics.h"
 #include "vm/TraceLogging.h"
@@ -131,26 +132,29 @@ IonBuilder::IonBuilder(JSContext* analys
     constraints_(constraints),
     analysis_(*temp, info->script()),
     thisTypes(nullptr),
     argTypes(nullptr),
     typeArray(nullptr),
     typeArrayHint(0),
     bytecodeTypeMap(nullptr),
     loopDepth_(loopDepth),
+    blockWorklist(*temp),
+    cfgCurrent(nullptr),
+    cfg(nullptr),
     trackedOptimizationSites_(*temp),
     lexicalCheck_(nullptr),
     callerResumePoint_(nullptr),
     callerBuilder_(nullptr),
-    cfgStack_(*temp),
-    loops_(*temp),
-    switches_(*temp),
-    labels_(*temp),
     iterators_(*temp),
     loopHeaders_(*temp),
+    loopHeaderStack_(*temp),
+#ifdef DEBUG
+    cfgLoopHeaderStack_(*temp),
+#endif
     inspector(inspector),
     inliningDepth_(inliningDepth),
     inlinedBytecodeLength_(0),
     numLoopRestarts_(0),
     failedBoundsCheck_(info->script()->failedBoundsCheck()),
     failedShapeGuard_(info->script()->failedShapeGuard()),
     failedLexicalCheck_(info->script()->failedLexicalCheck()),
     nonStringIteration_(false),
@@ -232,79 +236,16 @@ void
 IonBuilder::spew(const char* message)
 {
     // Don't call PCToLineNumber in release builds.
 #ifdef DEBUG
     JitSpew(JitSpew_IonMIR, "%s @ %s:%d", message, script()->filename(), PCToLineNumber(script(), pc));
 #endif
 }
 
-static inline int32_t
-GetJumpOffset(jsbytecode* pc)
-{
-    MOZ_ASSERT(CodeSpec[JSOp(*pc)].type() == JOF_JUMP);
-    return GET_JUMP_OFFSET(pc);
-}
-
-IonBuilder::CFGState
-IonBuilder::CFGState::If(jsbytecode* join, MTest* test)
-{
-    CFGState state;
-    state.state = IF_TRUE;
-    state.stopAt = join;
-    state.branch.ifFalse = test->ifFalse();
-    state.branch.test = test;
-    return state;
-}
-
-IonBuilder::CFGState
-IonBuilder::CFGState::IfElse(jsbytecode* trueEnd, jsbytecode* falseEnd, MTest* test)
-{
-    MBasicBlock* ifFalse = test->ifFalse();
-
-    CFGState state;
-    // If the end of the false path is the same as the start of the
-    // false path, then the "else" block is empty and we can devolve
-    // this to the IF_TRUE case. We handle this here because there is
-    // still an extra GOTO on the true path and we want stopAt to point
-    // there, whereas the IF_TRUE case does not have the GOTO.
-    state.state = (falseEnd == ifFalse->pc())
-                  ? IF_TRUE_EMPTY_ELSE
-                  : IF_ELSE_TRUE;
-    state.stopAt = trueEnd;
-    state.branch.falseEnd = falseEnd;
-    state.branch.ifFalse = ifFalse;
-    state.branch.test = test;
-    return state;
-}
-
-IonBuilder::CFGState
-IonBuilder::CFGState::AndOr(jsbytecode* join, MBasicBlock* lhs)
-{
-    CFGState state;
-    state.state = AND_OR;
-    state.stopAt = join;
-    state.branch.ifFalse = lhs;
-    state.branch.test = nullptr;
-    return state;
-}
-
-IonBuilder::CFGState
-IonBuilder::CFGState::TableSwitch(jsbytecode* exitpc, MTableSwitch* ins)
-{
-    CFGState state;
-    state.state = TABLE_SWITCH;
-    state.stopAt = exitpc;
-    state.tableswitch.exitpc = exitpc;
-    state.tableswitch.breaks = nullptr;
-    state.tableswitch.ins = ins;
-    state.tableswitch.currentBlock = 0;
-    return state;
-}
-
 JSFunction*
 IonBuilder::getSingleCallTarget(TemporaryTypeSet* calleeTypes)
 {
     if (!calleeTypes)
         return nullptr;
 
     JSObject* obj = calleeTypes->maybeSingleton();
     if (!obj || !obj->is<JSFunction>())
@@ -548,29 +489,24 @@ IonBuilder::canInlineTarget(JSFunction* 
     if (targetKey->unknownProperties()) {
         trackOptimizationOutcome(TrackedOutcome::CantInlineUnknownProps);
         return DontInline(inlineScript, "Target type has unknown properties");
     }
 
     return InliningDecision_Inline;
 }
 
-void
-IonBuilder::popCfgStack()
-{
-    if (cfgStack_.back().isLoop())
-        loops_.popBack();
-    if (cfgStack_.back().state == CFGState::LABEL)
-        labels_.popBack();
-    cfgStack_.popBack();
-}
-
-bool
-IonBuilder::analyzeNewLoopTypes(MBasicBlock* entry, jsbytecode* start, jsbytecode* end)
-{
+bool
+IonBuilder::analyzeNewLoopTypes(const CFGBlock* loopEntryBlock)
+{
+    CFGLoopEntry* loopEntry = loopEntryBlock->stopIns()->toLoopEntry();
+    CFGBlock* cfgBlock = loopEntry->successor();
+    MBasicBlock* entry = blockWorklist[cfgBlock->id()];
+    MOZ_ASSERT(!entry->isDead());
+
     // The phi inputs at the loop head only reflect types for variables that
     // were present at the start of the loop. If the variable changes to a new
     // type within the loop body, and that type is carried around to the loop
     // head, then we need to know about the new type up front.
     //
     // Since SSA information hasn't been constructed for the loop body yet, we
     // need a separate analysis to pick out the types that might flow around
     // the loop header. This is a best-effort analysis that may either over-
@@ -580,17 +516,17 @@ IonBuilder::analyzeNewLoopTypes(MBasicBl
     // under-approximating the types will cause the loop body to be analyzed
     // multiple times as the correct types are deduced (see finishLoop).
 
     // If we restarted processing of an outer loop then get loop header types
     // directly from the last time we have previously processed this loop. This
     // both avoids repeated work from the bytecode traverse below, and will
     // also pick up types discovered while previously building the loop body.
     for (size_t i = 0; i < loopHeaders_.length(); i++) {
-        if (loopHeaders_[i].pc == start) {
+        if (loopHeaders_[i].pc == cfgBlock->startPc()) {
             MBasicBlock* oldEntry = loopHeaders_[i].header;
 
             // If this block has been discarded, its resume points will have
             // already discarded their operands.
             if (!oldEntry->isDead()) {
                 MResumePoint* oldEntryRp = oldEntry->entryResumePoint();
                 size_t stackDepth = oldEntryRp->stackDepth();
                 for (size_t slot = 0; slot < stackDepth; slot++) {
@@ -609,19 +545,25 @@ IonBuilder::analyzeNewLoopTypes(MBasicBl
 
             // Update the most recent header for this loop encountered, in case
             // new types flow to the phis and the loop is processed at least
             // three times.
             loopHeaders_[i].header = entry;
             return true;
         }
     }
-    if (!loopHeaders_.append(LoopHeader(start, entry)))
-        return false;
-
+    if (!loopHeaders_.append(LoopHeader(cfgBlock->startPc(), entry)))
+        return false;
+
+    // Get the start and end pc of this loop.
+    jsbytecode* start = loopEntryBlock->stopPc();
+    start += GetBytecodeLength(start);
+    jsbytecode* end = loopEntry->loopStopPc();
+
+    // Iterate the bytecode quickly to seed possible types in the loopheader.
     jsbytecode* last = nullptr;
     jsbytecode* earlier = nullptr;
     for (jsbytecode* pc = start; pc != end; earlier = last, last = pc, pc += GetBytecodeLength(pc)) {
         uint32_t slot;
         if (*pc == JSOP_SETLOCAL)
             slot = info().localSlot(GET_LOCALNO(pc));
         else if (*pc == JSOP_SETARG)
             slot = info().argSlotUnchecked(GET_ARGNO(pc));
@@ -729,45 +671,16 @@ IonBuilder::analyzeNewLoopTypes(MBasicBl
                     return false;
             }
         }
     }
     return true;
 }
 
 bool
-IonBuilder::pushLoop(CFGState::State initial, jsbytecode* stopAt, MBasicBlock* entry, bool osr,
-                     jsbytecode* loopHead, jsbytecode* initialPc,
-                     jsbytecode* bodyStart, jsbytecode* bodyEnd,
-                     jsbytecode* exitpc, jsbytecode* continuepc)
-{
-    ControlFlowInfo loop(cfgStack_.length(), continuepc);
-    if (!loops_.append(loop))
-        return false;
-
-    CFGState state;
-    state.state = initial;
-    state.stopAt = stopAt;
-    state.loop.bodyStart = bodyStart;
-    state.loop.bodyEnd = bodyEnd;
-    state.loop.exitpc = exitpc;
-    state.loop.continuepc = continuepc;
-    state.loop.entry = entry;
-    state.loop.osr = osr;
-    state.loop.successor = nullptr;
-    state.loop.breaks = nullptr;
-    state.loop.continues = nullptr;
-    state.loop.initialState = initial;
-    state.loop.initialPc = initialPc;
-    state.loop.initialStopAt = stopAt;
-    state.loop.loopHead = loopHead;
-    return cfgStack_.append(state);
-}
-
-bool
 IonBuilder::init()
 {
     {
         LifoAlloc::AutoFallibleScope fallibleAllocator(alloc().lifoAlloc());
         if (!TypeScript::FreezeTypeSets(constraints(), script(), &thisTypes, &argTypes, &typeArray))
             return false;
     }
 
@@ -1438,93 +1351,173 @@ IonBuilder::maybeAddOsrTypeBarriers()
         preheaderPhi->replaceOperand(OSR_PHI_POSITION, def);
         preheaderPhi->setResultType(type);
         preheaderPhi->setResultTypeSet(typeSet);
     }
 
     return true;
 }
 
-// We try to build a control-flow graph in the order that it would be built as
-// if traversing the AST. This leads to a nice ordering and lets us build SSA
-// in one pass, since the bytecode is structured.
-//
-// We traverse the bytecode iteratively, maintaining a current basic block.
-// Each basic block has a mapping of local slots to instructions, as well as a
-// stack depth. As we encounter instructions we mutate this mapping in the
-// current block.
-//
-// Things get interesting when we encounter a control structure. This can be
-// either an IFEQ, downward GOTO, or a decompiler hint stashed away in source
-// notes. Once we encounter such an opcode, we recover the structure of the
-// control flow (its branches and bounds), and push it on a stack.
+enum class CFGState : uint32_t {
+    Alloc = 0,
+    Abort = 1,
+    Success = 2
+};
+
+static CFGState
+GetOrCreateControlFlowGraph(TempAllocator& tempAlloc, JSScript* script,
+                            const ControlFlowGraph** cfgOut)
+{
+    if (script->hasBaselineScript() && script->baselineScript()->controlFlowGraph()) {
+        *cfgOut = script->baselineScript()->controlFlowGraph();
+        return CFGState::Success;
+    }
+
+    ControlFlowGenerator cfgenerator(tempAlloc, script);
+    if (!cfgenerator.init())
+        return CFGState::Alloc;
+
+    if (!cfgenerator.traverseBytecode()) {
+        if (cfgenerator.aborted())
+            return CFGState::Abort;
+        return CFGState::Alloc;
+    }
+
+    // If possible cache the control flow graph on the baseline script.
+    TempAllocator* graphAlloc = nullptr;
+    if (script->hasBaselineScript()) {
+        LifoAlloc& lifoAlloc = script->zone()->jitZone()->cfgSpace()->lifoAlloc();
+        LifoAlloc::AutoFallibleScope fallibleAllocator(&lifoAlloc);
+        graphAlloc = lifoAlloc.new_<TempAllocator>(&lifoAlloc);
+        if (!graphAlloc)
+            return CFGState::Alloc;
+    } else {
+        graphAlloc = &tempAlloc;
+    }
+
+    ControlFlowGraph* cfg = cfgenerator.getGraph(*graphAlloc);
+    if (!cfg)
+        return CFGState::Alloc;
+
+    if (script->hasBaselineScript()) {
+        MOZ_ASSERT(!script->baselineScript()->controlFlowGraph());
+        script->baselineScript()->setControlFlowGraph(cfg);
+    }
+
+    if (JitSpewEnabled(JitSpew_CFG)) {
+        JitSpew(JitSpew_CFG, "Generating graph for %s:%" PRIuSIZE,
+                             script->filename(), script->lineno());
+        Fprinter& print = JitSpewPrinter();
+        cfg->dump(print, script);
+    }
+
+    *cfgOut = cfg;
+    return CFGState::Success;
+}
+
+// We traverse the bytecode using the control flow graph. This structure contains
+// a graph of CFGBlocks in RPO order.
 //
-// As we continue traversing the bytecode, we look for points that would
-// terminate the topmost control flow path pushed on the stack. These are:
-//  (1) The bounds of the current structure (end of a loop or join/edge of a
-//      branch).
-//  (2) A "return", "break", or "continue" statement.
+// Per CFGBlock we take the corresponding MBasicBlock and start iterating the
+// bytecode of that CFGBlock. Each basic block has a mapping of local slots to
+// instructions, as well as a stack depth. As we encounter instructions we
+// mutate this mapping in the current block.
 //
-// For (1), we expect that there is a current block in the progress of being
-// built, and we complete the necessary edges in the CFG. For (2), we expect
-// that there is no active block.
+// Afterwards we visit the control flow instruction. There we add the ending ins
+// of the MBasicBlock and create new MBasicBlocks for the successors. That means
+// adding phi nodes for diamond join points, making sure to propagate types
+// around loops ...
 //
-// For normal diamond join points, we construct Phi nodes as we add
-// predecessors. For loops, care must be taken to propagate Phi nodes back
-// through uses in the loop body.
+// We keep a link between a CFGBlock and the entry MBasicBlock (in blockWorklist).
+// That vector only contains the MBasicBlocks that correspond with a CFGBlock.
+// We can create new MBasicBlocks that don't correspond to a CFGBlock.
 bool
 IonBuilder::traverseBytecode()
 {
-    for (;;) {
-        MOZ_ASSERT(pc < info().limitPC());
-
-        for (;;) {
-            if (!alloc().ensureBallast())
-                return false;
-
-            // Check if we've hit an expected join point or edge in the bytecode.
-            // Leaving one control structure could place us at the edge of another,
-            // thus |while| instead of |if| so we don't skip any opcodes.
-            MOZ_ASSERT_IF(!cfgStack_.empty(), cfgStack_.back().stopAt >= pc);
-            if (!cfgStack_.empty() && cfgStack_.back().stopAt == pc) {
-                ControlStatus status = processCfgStack();
-                if (status == ControlStatus_Error)
-                    return false;
-                if (status == ControlStatus_Abort)
-                    return abort("Aborted while processing control flow");
-                if (!current)
-                    return true;
-                continue;
+    CFGState state = GetOrCreateControlFlowGraph(alloc(), info().script(), &cfg);
+    MOZ_ASSERT_IF(cfg && info().script()->hasBaselineScript(),
+                  info().script()->baselineScript()->controlFlowGraph() == cfg);
+    if (state == CFGState::Alloc) {
+        abortReason_ = AbortReason_Alloc;
+        return false;
+    }
+    if (state == CFGState::Abort)
+        return abort("Couldn't create the CFG of script");
+
+    if (!blockWorklist.growBy(cfg->numBlocks())) {
+        abortReason_ = AbortReason_Alloc;
+        return false;
+    }
+    blockWorklist[0] = current;
+
+    size_t i = 0;
+    while (i < cfg->numBlocks()) {
+        if (!alloc().ensureBallast()) {
+            abortReason_ = AbortReason_Alloc;
+            return false;
+        }
+
+        bool restarted = false;
+        const CFGBlock* cfgblock = cfg->block(i);
+        MBasicBlock* mblock = blockWorklist[i];
+        MOZ_ASSERT(mblock && !mblock->isDead());
+
+        if (!visitBlock(cfgblock, mblock))
+            return false;
+
+        if (!visitControlInstruction(cfgblock->stopIns(), &restarted))
+            return false;
+
+        if (restarted) {
+            // Move back to the start of the loop.
+            while (!blockWorklist[i] || blockWorklist[i]->isDead()) {
+                MOZ_ASSERT(i > 0);
+                i--;
             }
-
-            // Some opcodes need to be handled early because they affect control
-            // flow, terminating the current basic block and/or instructing the
-            // traversal algorithm to continue from a new pc.
-            //
-            //   (1) If the opcode does not affect control flow, then the opcode
-            //       is inspected and transformed to IR. This is the process_opcode
-            //       label.
-            //   (2) A loop could be detected via a forward GOTO. In this case,
-            //       we don't want to process the GOTO, but the following
-            //       instruction.
-            //   (3) A RETURN, STOP, BREAK, or CONTINUE may require processing the
-            //       CFG stack to terminate open branches.
-            //
-            // Similar to above, snooping control flow could land us at another
-            // control flow point, so we iterate until it's time to inspect a real
-            // opcode.
-            ControlStatus status;
-            if ((status = snoopControlFlow(JSOp(*pc))) == ControlStatus_None)
-                break;
-            if (status == ControlStatus_Error)
-                return false;
-            if (status == ControlStatus_Abort)
-                return abort("Aborted while processing control flow");
-            if (!current)
-                return true;
+            MOZ_ASSERT(cfgblock->stopIns()->isBackEdge());
+            MOZ_ASSERT(loopHeaderStack_.back() == blockWorklist[i]);
+        } else {
+            i++;
+        }
+    }
+
+#ifdef DEBUG
+    MOZ_ASSERT(graph().numBlocks() >= blockWorklist.length());
+    for (i = 0; i < cfg->numBlocks(); i++) {
+        MOZ_ASSERT(blockWorklist[i]);
+        MOZ_ASSERT(!blockWorklist[i]->isDead());
+        MOZ_ASSERT_IF(i != 0, blockWorklist[i]->id() != 0);
+    }
+#endif
+
+    cfg = nullptr;
+
+    blockWorklist.clear();
+    return true;
+}
+
+bool
+IonBuilder::visitBlock(const CFGBlock* cfgblock, MBasicBlock* mblock)
+{
+    mblock->setLoopDepth(loopDepth_);
+
+    cfgCurrent = cfgblock;
+    pc = cfgblock->startPc();
+
+    if (mblock->pc() && script()->hasScriptCounts())
+        mblock->setHitCount(script()->getHitCount(mblock->pc()));
+
+    if (!setCurrentAndSpecializePhis(mblock))
+        return false;
+    graph().addBlock(mblock);
+
+    while (pc < cfgblock->stopPc()) {
+        if (!alloc().ensureBallast()) {
+            abortReason_ = AbortReason_Alloc;
+            return false;
         }
 
 #ifdef DEBUG
         // In debug builds, after compiling this op, check that all values
         // popped by this opcode either:
         //
         //   (1) Have the ImplicitlyUsed flag set on them.
         //   (2) Have more uses than before compiling this op (the value is
@@ -1591,107 +1584,224 @@ IonBuilder::traverseBytecode()
                 break;
             }
         }
 #endif
 
         pc += CodeSpec[op].length;
         current->updateTrackedSite(bytecodeSite(pc));
     }
-
-    return true;
-}
-
-IonBuilder::ControlStatus
-IonBuilder::snoopControlFlow(JSOp op)
-{
-    switch (op) {
-      case JSOP_NOP:
-        return maybeLoop(op, info().getNote(gsn, pc));
-
-      case JSOP_POP:
-        return maybeLoop(op, info().getNote(gsn, pc));
-
-      case JSOP_RETURN:
-      case JSOP_RETRVAL:
-        return processReturn(op);
-
-      case JSOP_THROW:
-        return processThrow();
-
-      case JSOP_GOTO:
-      {
-        jssrcnote* sn = info().getNote(gsn, pc);
-        switch (sn ? SN_TYPE(sn) : SRC_NULL) {
-          case SRC_BREAK:
-          case SRC_BREAK2LABEL:
-            return processBreak(op, sn);
-
-          case SRC_CONTINUE:
-            return processContinue(op);
-
-          case SRC_SWITCHBREAK:
-            return processSwitchBreak(op);
-
-          case SRC_WHILE:
-          case SRC_FOR_IN:
-          case SRC_FOR_OF:
-            // while (cond) { }
-            return whileOrForInLoop(sn);
-
-          default:
-            // Hard assert for now - make an error later.
-            MOZ_CRASH("unknown goto case");
-        }
-        break;
-      }
-
-      case JSOP_TABLESWITCH:
-        return tableSwitch(op, info().getNote(gsn, pc));
-
-      case JSOP_IFNE:
-        // We should never reach an IFNE, it's a stopAt point, which will
-        // trigger closing the loop.
-        MOZ_CRASH("we should never reach an ifne!");
-
-      default:
-        break;
-    }
-    return ControlStatus_None;
+    return true;
+}
+
+bool
+IonBuilder::blockIsOSREntry(const CFGBlock* block, const CFGBlock* predecessor)
+{
+    jsbytecode* entryPc = block->startPc();
+
+    if (!info().osrPc())
+        return false;
+
+    if (entryPc == predecessor->startPc()) {
+        // The predecessor is the actual osr entry block. Since it is empty
+        // the current block also starts a the osr pc. But it isn't the osr entry.
+        MOZ_ASSERT(predecessor->stopPc() == predecessor->startPc());
+        return false;
+    }
+
+    MOZ_ASSERT(*info().osrPc() == JSOP_LOOPENTRY);
+    // Skip over the LOOPENTRY to match.
+    return GetNextPc(info().osrPc()) == entryPc;
+}
+
+bool
+IonBuilder::visitGoto(CFGGoto* ins)
+{
+    // Test if this potentially was a fake loop and create OSR entry if that is
+    // the case.
+    const CFGBlock* successor = ins->getSuccessor(0);
+    if (blockIsOSREntry(successor, cfgCurrent)) {
+        MBasicBlock* preheader = newOsrPreheader(current, successor->startPc(), pc);
+        if (!preheader)
+            return false;
+        current->end(MGoto::New(alloc(), preheader));
+        if (!setCurrentAndSpecializePhis(preheader))
+            return false;
+    }
+
+    size_t id = successor->id();
+    bool create = !blockWorklist[id] || blockWorklist[id]->isDead();
+
+    current->popn(ins->popAmount());
+
+    if (create) {
+        blockWorklist[id] = newBlock(current, successor->startPc());
+        if (!blockWorklist[id])
+            return false;
+    }
+
+    MBasicBlock* succ = blockWorklist[id];
+    current->end(MGoto::New(alloc(), succ));
+
+    if (!create) {
+        if (!succ->addPredecessor(alloc(), current))
+            return false;
+    }
+
+    return true;
+}
+
+bool
+IonBuilder::visitBackEdge(CFGBackEdge* ins, bool* restarted)
+{
+    loopDepth_--;
+
+    MBasicBlock* loopEntry = blockWorklist[ins->getSuccessor(0)->id()];
+    current->end(MGoto::New(alloc(), loopEntry));
+
+    MOZ_ASSERT(ins->getSuccessor(0) == cfgLoopHeaderStack_.back());
+
+    // Compute phis in the loop header and propagate them throughout the loop,
+    // including the successor.
+    AbortReason r = loopEntry->setBackedge(alloc(), current);
+    if (r == AbortReason_Alloc) {
+        abortReason_ = AbortReason_Alloc;
+        return false;
+    }
+    if (r == AbortReason_Disable) {
+        // If there are types for variables on the backedge that were not
+        // present at the original loop header, then uses of the variables'
+        // phis may have generated incorrect nodes. The new types have been
+        // incorporated into the header phis, so remove all blocks for the
+        // loop body and restart with the new types.
+        GraphSpewer& gs = graphSpewer();
+        gs.spewPass("beforeloop");
+
+        *restarted = true;
+        if (!restartLoop(ins->getSuccessor(0)))
+            return false;
+
+        gs.spewPass("afterloop");
+        return true;
+    }
+
+    loopHeaderStack_.popBack();
+#ifdef DEBUG
+    cfgLoopHeaderStack_.popBack();
+#endif
+    return true;
+}
+
+bool
+IonBuilder::visitLoopEntry(CFGLoopEntry* loopEntry)
+{
+    unsigned stackPhiCount = loopEntry->stackPhiCount();
+    const CFGBlock* successor = loopEntry->getSuccessor(0);
+    bool osr = blockIsOSREntry(successor, cfgCurrent);
+    if (osr) {
+        MOZ_ASSERT(loopEntry->canOsr());
+        MBasicBlock* preheader = newOsrPreheader(current, successor->startPc(), pc);
+        if (!preheader)
+            return false;
+        current->end(MGoto::New(alloc(), preheader));
+        if (!setCurrentAndSpecializePhis(preheader))
+            return false;
+    }
+
+    loopDepth_++;
+    MBasicBlock* header = newPendingLoopHeader(current, successor->startPc(), osr,
+                                               loopEntry->canOsr(), stackPhiCount);
+    if (!header)
+        return false;
+    blockWorklist[successor->id()] = header;
+
+    current->end(MGoto::New(alloc(), header));
+
+    if (!loopHeaderStack_.append(header))
+        return false;
+#ifdef DEBUG
+    if (!cfgLoopHeaderStack_.append(successor))
+        return false;
+#endif
+
+    if (!analyzeNewLoopTypes(cfgCurrent))
+        return false;
+
+    setCurrent(header);
+    pc = header->pc();
+
+    initLoopEntry();
+    return true;
+}
+
+bool
+IonBuilder::initLoopEntry()
+{
+    current->add(MInterruptCheck::New(alloc()));
+    insertRecompileCheck();
+
+    return true;
+}
+
+bool
+IonBuilder::visitControlInstruction(CFGControlInstruction* ins, bool* restarted)
+{
+    switch (ins->type()) {
+      case CFGControlInstruction::Type_Test:
+        return visitTest(ins->toTest());
+      case CFGControlInstruction::Type_Compare:
+        return visitCompare(ins->toCompare());
+      case CFGControlInstruction::Type_Goto:
+        return visitGoto(ins->toGoto());
+      case CFGControlInstruction::Type_BackEdge:
+        return visitBackEdge(ins->toBackEdge(), restarted);
+      case CFGControlInstruction::Type_LoopEntry:
+        return visitLoopEntry(ins->toLoopEntry());
+      case CFGControlInstruction::Type_Return:
+      case CFGControlInstruction::Type_RetRVal:
+        return visitReturn(ins);
+      case CFGControlInstruction::Type_Try:
+        return visitTry(ins->toTry());
+      case CFGControlInstruction::Type_Throw:
+        return visitThrow(ins->toThrow());
+      case CFGControlInstruction::Type_TableSwitch:
+        return visitTableSwitch(ins->toTableSwitch());
+    }
+    MOZ_CRASH("Unknown Control Instruction");
 }
 
 bool
 IonBuilder::inspectOpcode(JSOp op)
 {
     MOZ_ASSERT(analysis_.maybeInfo(pc), "Compiling unreachable op");
 
     switch (op) {
       case JSOP_NOP:
       case JSOP_NOP_DESTRUCTURING:
       case JSOP_LINENO:
-      case JSOP_LOOPENTRY:
       case JSOP_JUMPTARGET:
-        return true;
-
       case JSOP_LABEL:
-        return jsop_label();
+        return true;
 
       case JSOP_UNDEFINED:
         // If this ever changes, change what JSOP_GIMPLICITTHIS does too.
         pushConstant(UndefinedValue());
         return true;
 
       case JSOP_IFEQ:
-        return jsop_ifeq(JSOP_IFEQ);
-
+      case JSOP_RETURN:
+      case JSOP_RETRVAL:
+      case JSOP_AND:
+      case JSOP_OR:
       case JSOP_TRY:
-        return jsop_try();
-
+      case JSOP_THROW:
+      case JSOP_GOTO:
       case JSOP_CONDSWITCH:
-        return jsop_condswitch();
+      case JSOP_LOOPENTRY:
+        MOZ_CRASH("Shouldn't encounter this opcode.");
 
       case JSOP_BITNOT:
         return jsop_bitnot();
 
       case JSOP_BITAND:
       case JSOP_BITOR:
       case JSOP_BITXOR:
       case JSOP_LSH:
@@ -1713,20 +1823,16 @@ IonBuilder::inspectOpcode(JSOp op)
         return jsop_pos();
 
       case JSOP_NEG:
         return jsop_neg();
 
       case JSOP_TOSTRING:
         return jsop_tostring();
 
-      case JSOP_AND:
-      case JSOP_OR:
-        return jsop_andor(op);
-
       case JSOP_DEFVAR:
         return jsop_defvar(GET_UINT32_INDEX(pc));
 
       case JSOP_DEFLET:
       case JSOP_DEFCONST:
         return jsop_deflexical(GET_UINT32_INDEX(pc));
 
       case JSOP_DEFFUN:
@@ -2204,1409 +2310,54 @@ IonBuilder::inspectOpcode(JSOp op)
     trackActionableAbort("Unsupported bytecode");
 #ifdef DEBUG
     return abort("Unsupported opcode: %s", CodeName[op]);
 #else
     return abort("Unsupported opcode: %d", op);
 #endif
 }
 
-// Given that the current control flow structure has ended forcefully,
-// via a return, break, or continue (rather than joining), propagate the
-// termination up. For example, a return nested 5 loops deep may terminate
-// every outer loop at once, if there are no intervening conditionals:
-//
-// for (...) {
-//   for (...) {
-//     return x;
-//   }
-// }
-//
-// If |current| is nullptr when this function returns, then there is no more
-// control flow to be processed.
-IonBuilder::ControlStatus
-IonBuilder::processControlEnd()
-{
-    MOZ_ASSERT(!current);
-
-    if (cfgStack_.empty()) {
-        // If there is no more control flow to process, then this is the
-        // last return in the function.
-        return ControlStatus_Ended;
-    }
-
-    return processCfgStack();
-}
-
-// Processes the top of the CFG stack. This is used from two places:
-// (1) processControlEnd(), whereby a break, continue, or return may interrupt
-//     an in-progress CFG structure before reaching its actual termination
-//     point in the bytecode.
-// (2) traverseBytecode(), whereby we reach the last instruction in a CFG
-//     structure.
-IonBuilder::ControlStatus
-IonBuilder::processCfgStack()
-{
-    ControlStatus status = processCfgEntry(cfgStack_.back());
-
-    // If this terminated a CFG structure, act like processControlEnd() and
-    // keep propagating upward.
-    while (status == ControlStatus_Ended) {
-        popCfgStack();
-        if (cfgStack_.empty())
-            return status;
-        status = processCfgEntry(cfgStack_.back());
-    }
-
-    // If some join took place, the current structure is finished.
-    if (status == ControlStatus_Joined)
-        popCfgStack();
-
-    return status;
-}
-
-IonBuilder::ControlStatus
-IonBuilder::processCfgEntry(CFGState& state)
-{
-    switch (state.state) {
-      case CFGState::IF_TRUE:
-      case CFGState::IF_TRUE_EMPTY_ELSE:
-        return processIfEnd(state);
-
-      case CFGState::IF_ELSE_TRUE:
-        return processIfElseTrueEnd(state);
-
-      case CFGState::IF_ELSE_FALSE:
-        return processIfElseFalseEnd(state);
-
-      case CFGState::DO_WHILE_LOOP_BODY:
-        return processDoWhileBodyEnd(state);
-
-      case CFGState::DO_WHILE_LOOP_COND:
-        return processDoWhileCondEnd(state);
-
-      case CFGState::WHILE_LOOP_COND:
-        return processWhileCondEnd(state);
-
-      case CFGState::WHILE_LOOP_BODY:
-        return processWhileBodyEnd(state);
-
-      case CFGState::FOR_LOOP_COND:
-        return processForCondEnd(state);
-
-      case CFGState::FOR_LOOP_BODY:
-        return processForBodyEnd(state);
-
-      case CFGState::FOR_LOOP_UPDATE:
-        return processForUpdateEnd(state);
-
-      case CFGState::TABLE_SWITCH:
-        return processNextTableSwitchCase(state);
-
-      case CFGState::COND_SWITCH_CASE:
-        return processCondSwitchCase(state);
-
-      case CFGState::COND_SWITCH_BODY:
-        return processCondSwitchBody(state);
-
-      case CFGState::AND_OR:
-        return processAndOrEnd(state);
-
-      case CFGState::LABEL:
-        return processLabelEnd(state);
-
-      case CFGState::TRY:
-        return processTryEnd(state);
-
-      default:
-        MOZ_CRASH("unknown cfgstate");
-    }
-}
-
-IonBuilder::ControlStatus
-IonBuilder::processIfEnd(CFGState& state)
-{
-    bool thenBranchTerminated = !current;
-    if (!thenBranchTerminated) {
-        // Here, the false block is the join point. Create an edge from the
-        // current block to the false block. Note that a RETURN opcode
-        // could have already ended the block.
-        current->end(MGoto::New(alloc(), state.branch.ifFalse));
-
-        if (!state.branch.ifFalse->addPredecessor(alloc(), current))
-            return ControlStatus_Error;
-    }
-
-    if (!setCurrentAndSpecializePhis(state.branch.ifFalse))
-        return ControlStatus_Error;
-    graph().moveBlockToEnd(current);
-    pc = current->pc();
-
-    if (thenBranchTerminated) {
-        // If we can't reach here via the then-branch, we can filter the types
-        // after the if-statement based on the if-condition.
-        MTest* test = state.branch.test;
-        if (!improveTypesAtTest(test->getOperand(0), test->ifTrue() == current, test))
-            return ControlStatus_Error;
-    }
-
-    return ControlStatus_Joined;
-}
-
-IonBuilder::ControlStatus
-IonBuilder::processIfElseTrueEnd(CFGState& state)
-{
-    // We've reached the end of the true branch of an if-else. Don't
-    // create an edge yet, just transition to parsing the false branch.
-    state.state = CFGState::IF_ELSE_FALSE;
-    state.branch.ifTrue = current;
-    state.stopAt = state.branch.falseEnd;
-    pc = state.branch.ifFalse->pc();
-    if (!setCurrentAndSpecializePhis(state.branch.ifFalse))
-        return ControlStatus_Error;
-    graph().moveBlockToEnd(current);
-
-    MTest* test = state.branch.test;
-    if (!improveTypesAtTest(test->getOperand(0), test->ifTrue() == current, test))
-        return ControlStatus_Error;
-
-    return ControlStatus_Jumped;
-}
-
-IonBuilder::ControlStatus
-IonBuilder::processIfElseFalseEnd(CFGState& state)
-{
-    // Update the state to have the latest block from the false path.
-    state.branch.ifFalse = current;
-
-    // To create the join node, we need an incoming edge that has not been
-    // terminated yet.
-    MBasicBlock* pred = state.branch.ifTrue
-                        ? state.branch.ifTrue
-                        : state.branch.ifFalse;
-    MBasicBlock* other = (pred == state.branch.ifTrue) ? state.branch.ifFalse : state.branch.ifTrue;
-
-    if (!pred)
-        return ControlStatus_Ended;
-
-    // Create a new block to represent the join.
-    MBasicBlock* join = newBlock(pred, state.branch.falseEnd);
-    if (!join)
-        return ControlStatus_Error;
-
-    // Create edges from the true and false blocks as needed.
-    pred->end(MGoto::New(alloc(), join));
-
-    if (other) {
-        other->end(MGoto::New(alloc(), join));
-        if (!join->addPredecessor(alloc(), other))
-            return ControlStatus_Error;
-    }
-
-    // Ignore unreachable remainder of false block if existent.
-    if (!setCurrentAndSpecializePhis(join))
-        return ControlStatus_Error;
-    pc = current->pc();
-    return ControlStatus_Joined;
-}
-
-IonBuilder::ControlStatus
-IonBuilder::processBrokenLoop(CFGState& state)
-{
-    MOZ_ASSERT(!current);
-
-    MOZ_ASSERT(loopDepth_);
-    loopDepth_--;
-
-    // A broken loop is not a real loop (it has no header or backedge), so
-    // reset the loop depth.
-    for (MBasicBlockIterator i(graph().begin(state.loop.entry)); i != graph().end(); i++) {
-        if (i->loopDepth() > loopDepth_)
-            i->setLoopDepth(i->loopDepth() - 1);
-    }
-
-    // If the loop started with a condition (while/for) then even if the
-    // structure never actually loops, the condition itself can still fail and
-    // thus we must resume at the successor, if one exists.
-    if (!setCurrentAndSpecializePhis(state.loop.successor))
-        return ControlStatus_Error;
-    if (current) {
-        MOZ_ASSERT(current->loopDepth() == loopDepth_);
-        graph().moveBlockToEnd(current);
-    }
-
-    // Join the breaks together and continue parsing.
-    if (state.loop.breaks) {
-        MBasicBlock* block = createBreakCatchBlock(state.loop.breaks, state.loop.exitpc);
-        if (!block)
-            return ControlStatus_Error;
-
-        if (current) {
-            current->end(MGoto::New(alloc(), block));
-            if (!block->addPredecessor(alloc(), current))
-                return ControlStatus_Error;
-        }
-
-        if (!setCurrentAndSpecializePhis(block))
-            return ControlStatus_Error;
-    }
-
-    // If the loop is not gated on a condition, and has only returns, we'll
-    // reach this case. For example:
-    // do { ... return; } while ();
-    if (!current)
-        return ControlStatus_Ended;
-
-    // Otherwise, the loop is gated on a condition and/or has breaks so keep
-    // parsing at the successor.
-    pc = current->pc();
-    return ControlStatus_Joined;
-}
-
-IonBuilder::ControlStatus
-IonBuilder::finishLoop(CFGState& state, MBasicBlock* successor)
-{
-    MOZ_ASSERT(current);
-
-    MOZ_ASSERT(loopDepth_);
-    loopDepth_--;
-    MOZ_ASSERT_IF(successor, successor->loopDepth() == loopDepth_);
-
-    // Compute phis in the loop header and propagate them throughout the loop,
-    // including the successor.
-    AbortReason r = state.loop.entry->setBackedge(alloc(), current);
-    if (r == AbortReason_Alloc)
-        return ControlStatus_Error;
-    if (r == AbortReason_Disable) {
-        // If there are types for variables on the backedge that were not
-        // present at the original loop header, then uses of the variables'
-        // phis may have generated incorrect nodes. The new types have been
-        // incorporated into the header phis, so remove all blocks for the
-        // loop body and restart with the new types.
-        return restartLoop(state);
-    }
-
-    if (successor) {
-        graph().moveBlockToEnd(successor);
-        successor->inheritPhis(state.loop.entry);
-    }
-
-    if (state.loop.breaks) {
-        // Propagate phis placed in the header to individual break exit points.
-        DeferredEdge* edge = state.loop.breaks;
-        while (edge) {
-            edge->block->inheritPhis(state.loop.entry);
-            edge = edge->next;
-        }
-
-        // Create a catch block to join all break exits.
-        MBasicBlock* block = createBreakCatchBlock(state.loop.breaks, state.loop.exitpc);
-        if (!block)
-            return ControlStatus_Error;
-
-        if (successor) {
-            // Finally, create an unconditional edge from the successor to the
-            // catch block.
-            successor->end(MGoto::New(alloc(), block));
-            if (!block->addPredecessor(alloc(), successor))
-                return ControlStatus_Error;
-        }
-        successor = block;
-    }
-
-    if (!setCurrentAndSpecializePhis(successor))
-        return ControlStatus_Error;
-
-    // An infinite loop (for (;;) { }) will not have a successor.
-    if (!current)
-        return ControlStatus_Ended;
-
-    pc = current->pc();
-    return ControlStatus_Joined;
-}
-
-IonBuilder::ControlStatus
-IonBuilder::restartLoop(const CFGState& state)
+bool
+IonBuilder::restartLoop(const CFGBlock* cfgHeader)
 {
     AutoTraceLog logCompile(traceLogger(), TraceLogger_IonBuilderRestartLoop);
 
     spew("New types at loop header, restarting loop body");
 
     if (JitOptions.limitScriptSize) {
         if (++numLoopRestarts_ >= MAX_LOOP_RESTARTS)
-            return ControlStatus_Abort;
-    }
-
-    MBasicBlock* header = state.loop.entry;
+            return abort("Aborted while processing control flow");
+    }
+
+    MBasicBlock* header = blockWorklist[cfgHeader->id()];
 
     // Discard unreferenced & pre-allocated resume points.
     replaceMaybeFallbackFunctionGetter(nullptr);
 
     // Remove all blocks in the loop body other than the header, which has phis
     // of the appropriate type and incoming edges to preserve.
-    graph().removeBlocksAfter(header);
+    if (!graph().removeSuccessorBlocks(header))
+        return false;
+    graph().removeBlockFromList(header);
 
     // Remove all instructions from the header itself, and all resume points
     // except the entry resume point.
     header->discardAllInstructions();
     header->discardAllResumePoints(/* discardEntry = */ false);
     header->setStackDepth(header->getPredecessor(0)->stackDepth());
 
-    popCfgStack();
-
-    loopDepth_++;
-
-    // Keep a local copy for these pointers since state will be overwritten in
-    // pushLoop since state is a reference to cfgStack_.back()
-    jsbytecode* condpc = state.loop.condpc;
-    jsbytecode* updatepc = state.loop.updatepc;
-    jsbytecode* updateEnd = state.loop.updateEnd;
-
-    if (!pushLoop(state.loop.initialState, state.loop.initialStopAt, header, state.loop.osr,
-                  state.loop.loopHead, state.loop.initialPc,
-                  state.loop.bodyStart, state.loop.bodyEnd,
-                  state.loop.exitpc, state.loop.continuepc))
-    {
-        return ControlStatus_Error;
-    }
-
-    CFGState& nstate = cfgStack_.back();
-
-    nstate.loop.condpc = condpc;
-    nstate.loop.updatepc = updatepc;
-    nstate.loop.updateEnd = updateEnd;
+    loopDepth_ = header->loopDepth();
 
     // Don't specializePhis(), as the header has been visited before and the
     // phis have already had their type set.
     setCurrent(header);
-
-    if (!jsop_loophead(nstate.loop.loopHead))
-        return ControlStatus_Error;
-
-    pc = nstate.loop.initialPc;
-    return ControlStatus_Jumped;
-}
-
-IonBuilder::ControlStatus
-IonBuilder::processDoWhileBodyEnd(CFGState& state)
-{
-    if (!processDeferredContinues(state))
-        return ControlStatus_Error;
-
-    // No current means control flow cannot reach the condition, so this will
-    // never loop.
-    if (!current)
-        return processBrokenLoop(state);
-
-    MBasicBlock* header = newBlock(current, state.loop.updatepc);
-    if (!header)
-        return ControlStatus_Error;
-    current->end(MGoto::New(alloc(), header));
-
-    state.state = CFGState::DO_WHILE_LOOP_COND;
-    state.stopAt = state.loop.updateEnd;
-    pc = state.loop.updatepc;
-    if (!setCurrentAndSpecializePhis(header))
-        return ControlStatus_Error;
-    return ControlStatus_Jumped;
-}
-
-IonBuilder::ControlStatus
-IonBuilder::processDoWhileCondEnd(CFGState& state)
-{
-    MOZ_ASSERT(JSOp(*pc) == JSOP_IFNE);
-
-    // We're guaranteed a |current|, it's impossible to break or return from
-    // inside the conditional expression.
-    MOZ_ASSERT(current);
-
-    // Pop the last value, and create the successor block.
-    MDefinition* vins = current->pop();
-    MBasicBlock* successor = newBlock(current, GetNextPc(pc), loopDepth_ - 1);
-    if (!successor)
-        return ControlStatus_Error;
-
-    // Test for do {} while(false) and don't create a loop in that case.
-    if (MConstant* vinsConst = vins->maybeConstantValue()) {
-        bool b;
-        if (vinsConst->valueToBoolean(&b) && !b) {
-            current->end(MGoto::New(alloc(), successor));
-            current = nullptr;
-
-            state.loop.successor = successor;
-            return processBrokenLoop(state);
-        }
-    }
-
-    // Create the test instruction and end the current block.
-    MTest* test = newTest(vins, state.loop.entry, successor);
-    current->end(test);
-    return finishLoop(state, successor);
-}
-
-IonBuilder::ControlStatus
-IonBuilder::processWhileCondEnd(CFGState& state)
-{
-    MOZ_ASSERT(JSOp(*pc) == JSOP_IFNE || JSOp(*pc) == JSOP_IFEQ);
-
-    // Balance the stack past the IFNE.
-    MDefinition* ins = current->pop();
-
-    // Create the body and successor blocks.
-    MBasicBlock* body = newBlock(current, state.loop.bodyStart);
-    state.loop.successor = newBlock(current, state.loop.exitpc, loopDepth_ - 1);
-    if (!body || !state.loop.successor)
-        return ControlStatus_Error;
-
-    MTest* test;
-    if (JSOp(*pc) == JSOP_IFNE)
-        test = newTest(ins, body, state.loop.successor);
-    else
-        test = newTest(ins, state.loop.successor, body);
-    current->end(test);
-
-    state.state = CFGState::WHILE_LOOP_BODY;
-    state.stopAt = state.loop.bodyEnd;
-    pc = state.loop.bodyStart;
-    if (!setCurrentAndSpecializePhis(body))
-        return ControlStatus_Error;
-
-    // Filter the types in the loop body.
-    if (!improveTypesAtTest(test->getOperand(0), test->ifTrue() == current, test))
-        return ControlStatus_Error;
-
-    // If this is a for-in loop, unbox the current value as string if possible.
-    if (ins->isIsNoIter()) {
-        MIteratorMore* iterMore = ins->toIsNoIter()->input()->toIteratorMore();
-        jsbytecode* iterMorePc = iterMore->resumePoint()->pc();
-        MOZ_ASSERT(*iterMorePc == JSOP_MOREITER);
-
-        if (!nonStringIteration_ && !inspector->hasSeenNonStringIterMore(iterMorePc)) {
-            MDefinition* val = current->peek(-1);
-            MOZ_ASSERT(val == iterMore);
-            MInstruction* ins = MUnbox::New(alloc(), val, MIRType::String, MUnbox::Fallible,
-                                            Bailout_NonStringInputInvalidate);
-            current->add(ins);
-            current->rewriteAtDepth(-1, ins);
-        }
-    }
-
-    return ControlStatus_Jumped;
-}
-
-IonBuilder::ControlStatus
-IonBuilder::processWhileBodyEnd(CFGState& state)
-{
-    if (!processDeferredContinues(state))
-        return ControlStatus_Error;
-
-    if (!current)
-        return processBrokenLoop(state);
-
-    current->end(MGoto::New(alloc(), state.loop.entry));
-    return finishLoop(state, state.loop.successor);
-}
-
-IonBuilder::ControlStatus
-IonBuilder::processForCondEnd(CFGState& state)
-{
-    MOZ_ASSERT(JSOp(*pc) == JSOP_IFNE);
-
-    // Balance the stack past the IFNE.
-    MDefinition* ins = current->pop();
-
-    // Create the body and successor blocks.
-    MBasicBlock* body = newBlock(current, state.loop.bodyStart);
-    state.loop.successor = newBlock(current, state.loop.exitpc, loopDepth_ - 1);
-    if (!body || !state.loop.successor)
-        return ControlStatus_Error;
-
-    MTest* test = newTest(ins, body, state.loop.successor);
-    current->end(test);
-
-    state.state = CFGState::FOR_LOOP_BODY;
-    state.stopAt = state.loop.bodyEnd;
-    pc = state.loop.bodyStart;
-    if (!setCurrentAndSpecializePhis(body))
-        return ControlStatus_Error;
-    return ControlStatus_Jumped;
-}
-
-IonBuilder::ControlStatus
-IonBuilder::processForBodyEnd(CFGState& state)
-{
-    if (!processDeferredContinues(state))
-        return ControlStatus_Error;
-
-    // If there is no updatepc, just go right to processing what would be the
-    // end of the update clause. Otherwise, |current| might be nullptr; if this is
-    // the case, the udpate is unreachable anyway.
-    if (!state.loop.updatepc || !current)
-        return processForUpdateEnd(state);
-
-    pc = state.loop.updatepc;
-
-    state.state = CFGState::FOR_LOOP_UPDATE;
-    state.stopAt = state.loop.updateEnd;
-    return ControlStatus_Jumped;
-}
-
-IonBuilder::ControlStatus
-IonBuilder::processForUpdateEnd(CFGState& state)
-{
-    // If there is no current, we couldn't reach the loop edge and there was no
-    // update clause.
-    if (!current)
-        return processBrokenLoop(state);
-
-    current->end(MGoto::New(alloc(), state.loop.entry));
-    return finishLoop(state, state.loop.successor);
-}
-
-IonBuilder::DeferredEdge*
-IonBuilder::filterDeadDeferredEdges(DeferredEdge* edge)
-{
-    DeferredEdge* head = edge;
-    DeferredEdge* prev = nullptr;
-
-    while (edge) {
-        if (edge->block->isDead()) {
-            if (prev)
-                prev->next = edge->next;
-            else
-                head = edge->next;
-        } else {
-            prev = edge;
-        }
-        edge = edge->next;
-    }
-
-    // There must be at least one deferred edge from a block that was not
-    // deleted; blocks are deleted when restarting processing of a loop, and
-    // the final version of the loop body will have edges from live blocks.
-    MOZ_ASSERT(head);
-
-    return head;
-}
-
-bool
-IonBuilder::processDeferredContinues(CFGState& state)
-{
-    // If there are any continues for this loop, and there is an update block,
-    // then we need to create a new basic block to house the update.
-    if (state.loop.continues) {
-        DeferredEdge* edge = filterDeadDeferredEdges(state.loop.continues);
-
-        MBasicBlock* update = newBlock(edge->block, loops_.back().continuepc);
-        if (!update)
-            return false;
-
-        if (current) {
-            current->end(MGoto::New(alloc(), update));
-            if (!update->addPredecessor(alloc(), current))
-                return false;
-        }
-
-        // No need to use addPredecessor for first edge,
-        // because it is already predecessor.
-        edge->block->end(MGoto::New(alloc(), update));
-        edge = edge->next;
-
-        // Remaining edges
-        while (edge) {
-            edge->block->end(MGoto::New(alloc(), update));
-            if (!update->addPredecessor(alloc(), edge->block))
-                return false;
-            edge = edge->next;
-        }
-        state.loop.continues = nullptr;
-
-        if (!setCurrentAndSpecializePhis(update))
-            return ControlStatus_Error;
-    }
-
-    return true;
-}
-
-MBasicBlock*
-IonBuilder::createBreakCatchBlock(DeferredEdge* edge, jsbytecode* pc)
-{
-    edge = filterDeadDeferredEdges(edge);
-
-    // Create block, using the first break statement as predecessor
-    MBasicBlock* successor = newBlock(edge->block, pc);
-    if (!successor)
-        return nullptr;
-
-    // No need to use addPredecessor for first edge,
-    // because it is already predecessor.
-    edge->block->end(MGoto::New(alloc(), successor));
-    edge = edge->next;
-
-    // Finish up remaining breaks.
-    while (edge) {
-        MGoto* brk = MGoto::New(alloc().fallible(), successor);
-        if (!brk)
-            return nullptr;
-        edge->block->end(brk);
-        if (!successor->addPredecessor(alloc(), edge->block))
-            return nullptr;
-        edge = edge->next;
-    }
-
-    return successor;
-}
-
-IonBuilder::ControlStatus
-IonBuilder::processNextTableSwitchCase(CFGState& state)
-{
-    MOZ_ASSERT(state.state == CFGState::TABLE_SWITCH);
-
-    state.tableswitch.currentBlock++;
-
-    // Test if there are still unprocessed successors (cases/default)
-    if (state.tableswitch.currentBlock >= state.tableswitch.ins->numBlocks())
-        return processSwitchEnd(state.tableswitch.breaks, state.tableswitch.exitpc);
-
-    // Get the next successor
-    MBasicBlock* successor = state.tableswitch.ins->getBlock(state.tableswitch.currentBlock);
-
-    // Add current block as predecessor if available.
-    // This means the previous case didn't have a break statement.
-    // So flow will continue in this block.
-    if (current) {
-        current->end(MGoto::New(alloc(), successor));
-        if (!successor->addPredecessor(alloc(), current))
-            return ControlStatus_Error;
-    } else {
-        // If this is an actual case statement, optimize by replacing the
-        // input to the switch case with the actual number of the case.
-        // This constant has been emitted when creating the case blocks.
-        if (state.tableswitch.ins->getDefault() != successor) {
-            MConstant* constant = successor->begin()->toConstant();
-            for (uint32_t j = 0; j < successor->stackDepth(); j++) {
-                MDefinition* ins = successor->getSlot(j);
-                if (ins != state.tableswitch.ins->getOperand(0))
-                    continue;
-
-                constant->setDependency(state.tableswitch.ins);
-                successor->setSlot(j, constant);
-            }
-        }
-    }
-
-    // Insert successor after the current block, to maintain RPO.
-    graph().moveBlockToEnd(successor);
-
-    // If this is the last successor the block should stop at the end of the tableswitch
-    // Else it should stop at the start of the next successor
-    if (state.tableswitch.currentBlock+1 < state.tableswitch.ins->numBlocks())
-        state.stopAt = state.tableswitch.ins->getBlock(state.tableswitch.currentBlock+1)->pc();
-    else
-        state.stopAt = state.tableswitch.exitpc;
-
-    if (!setCurrentAndSpecializePhis(successor))
-        return ControlStatus_Error;
-    pc = current->pc();
-    return ControlStatus_Jumped;
-}
-
-IonBuilder::ControlStatus
-IonBuilder::processAndOrEnd(CFGState& state)
-{
-    MOZ_ASSERT(current);
-    MBasicBlock* lhs = state.branch.ifFalse;
-
-    // Create a new block to represent the join.
-    MBasicBlock* join = newBlock(current, state.stopAt);
-    if (!join)
-        return ControlStatus_Error;
-
-    // End the rhs.
-    current->end(MGoto::New(alloc(), join));
-
-    // End the lhs.
-    lhs->end(MGoto::New(alloc(), join));
-    if (!join->addPredecessor(alloc(), state.branch.ifFalse))
-        return ControlStatus_Error;
-
-    // Set the join path as current path.
-    if (!setCurrentAndSpecializePhis(join))
-        return ControlStatus_Error;
-    pc = current->pc();
-    return ControlStatus_Joined;
-}
-
-IonBuilder::ControlStatus
-IonBuilder::processLabelEnd(CFGState& state)
-{
-    MOZ_ASSERT(state.state == CFGState::LABEL);
-
-    // If there are no breaks and no current, controlflow is terminated.
-    if (!state.label.breaks && !current)
-        return ControlStatus_Ended;
-
-    // If there are no breaks to this label, there's nothing to do.
-    if (!state.label.breaks)
-        return ControlStatus_Joined;
-
-    MBasicBlock* successor = createBreakCatchBlock(state.label.breaks, state.stopAt);
-    if (!successor)
-        return ControlStatus_Error;
-
-    if (current) {
-        current->end(MGoto::New(alloc(), successor));
-        if (!successor->addPredecessor(alloc(), current))
-            return ControlStatus_Error;
-    }
-
-    pc = state.stopAt;
-    if (!setCurrentAndSpecializePhis(successor))
-        return ControlStatus_Error;
-    return ControlStatus_Joined;
-}
-
-IonBuilder::ControlStatus
-IonBuilder::processTryEnd(CFGState& state)
-{
-    MOZ_ASSERT(state.state == CFGState::TRY);
-
-    if (!state.try_.successor) {
-        MOZ_ASSERT(!current);
-        return ControlStatus_Ended;
-    }
-
-    if (current) {
-        current->end(MGoto::New(alloc(), state.try_.successor));
-
-        if (!state.try_.successor->addPredecessor(alloc(), current))
-            return ControlStatus_Error;
-    }
-
-    // Start parsing the code after this try-catch statement.
-    if (!setCurrentAndSpecializePhis(state.try_.successor))
-        return ControlStatus_Error;
-    graph().moveBlockToEnd(current);
-    pc = current->pc();
-    return ControlStatus_Joined;
-}
-
-IonBuilder::ControlStatus
-IonBuilder::processBreak(JSOp op, jssrcnote* sn)
-{
-    MOZ_ASSERT(op == JSOP_GOTO);
-
-    MOZ_ASSERT(SN_TYPE(sn) == SRC_BREAK ||
-               SN_TYPE(sn) == SRC_BREAK2LABEL);
-
-    // Find the break target.
-    jsbytecode* target = pc + GetJumpOffset(pc);
-    DebugOnly<bool> found = false;
-
-    if (SN_TYPE(sn) == SRC_BREAK2LABEL) {
-        for (size_t i = labels_.length() - 1; i < labels_.length(); i--) {
-            CFGState& cfg = cfgStack_[labels_[i].cfgEntry];
-            MOZ_ASSERT(cfg.state == CFGState::LABEL);
-            if (cfg.stopAt == target) {
-                cfg.label.breaks = new(alloc()) DeferredEdge(current, cfg.label.breaks);
-                found = true;
-                break;
-            }
-        }
-    } else {
-        for (size_t i = loops_.length() - 1; i < loops_.length(); i--) {
-            CFGState& cfg = cfgStack_[loops_[i].cfgEntry];
-            MOZ_ASSERT(cfg.isLoop());
-            if (cfg.loop.exitpc == target) {
-                cfg.loop.breaks = new(alloc()) DeferredEdge(current, cfg.loop.breaks);
-                found = true;
-                break;
-            }
-        }
-    }
-
-    MOZ_ASSERT(found);
-
-    setCurrent(nullptr);
-    pc += CodeSpec[op].length;
-    return processControlEnd();
-}
-
-static inline jsbytecode*
-EffectiveContinue(jsbytecode* pc)
-{
-    if (JSOp(*pc) == JSOP_GOTO)
-        return pc + GetJumpOffset(pc);
-    return pc;
-}
-
-IonBuilder::ControlStatus
-IonBuilder::processContinue(JSOp op)
-{
-    MOZ_ASSERT(op == JSOP_GOTO);
-
-    // Find the target loop.
-    CFGState* found = nullptr;
-    jsbytecode* target = pc + GetJumpOffset(pc);
-    for (size_t i = loops_.length() - 1; i < loops_.length(); i--) {
-        // +1 to skip JSOP_JUMPTARGET.
-        if (loops_[i].continuepc == target + 1 ||
-            EffectiveContinue(loops_[i].continuepc) == target)
-        {
-            found = &cfgStack_[loops_[i].cfgEntry];
-            break;
-        }
-    }
-
-    // There must always be a valid target loop structure. If not, there's
-    // probably an off-by-something error in which pc we track.
-    MOZ_ASSERT(found);
-    CFGState& state = *found;
-
-    state.loop.continues = new(alloc()) DeferredEdge(current, state.loop.continues);
-
-    setCurrent(nullptr);
-    pc += CodeSpec[op].length;
-    return processControlEnd();
-}
-
-IonBuilder::ControlStatus
-IonBuilder::processSwitchBreak(JSOp op)
-{
-    MOZ_ASSERT(op == JSOP_GOTO);
-
-    // Find the target switch.
-    CFGState* found = nullptr;
-    jsbytecode* target = pc + GetJumpOffset(pc);
-    for (size_t i = switches_.length() - 1; i < switches_.length(); i--) {
-        if (switches_[i].continuepc == target) {
-            found = &cfgStack_[switches_[i].cfgEntry];
-            break;
-        }
-    }
-
-    // There must always be a valid target loop structure. If not, there's
-    // probably an off-by-something error in which pc we track.
-    MOZ_ASSERT(found);
-    CFGState& state = *found;
-
-    DeferredEdge** breaks = nullptr;
-    switch (state.state) {
-      case CFGState::TABLE_SWITCH:
-        breaks = &state.tableswitch.breaks;
-        break;
-      case CFGState::COND_SWITCH_BODY:
-        breaks = &state.condswitch.breaks;
-        break;
-      default:
-        MOZ_CRASH("Unexpected switch state.");
-    }
-
-    *breaks = new(alloc()) DeferredEdge(current, *breaks);
-
-    setCurrent(nullptr);
-    pc += CodeSpec[op].length;
-    return processControlEnd();
-}
-
-IonBuilder::ControlStatus
-IonBuilder::processSwitchEnd(DeferredEdge* breaks, jsbytecode* exitpc)
-{
-    // No break statements, no current.
-    // This means that control flow is cut-off from this point
-    // (e.g. all cases have return statements).
-    if (!breaks && !current)
-        return ControlStatus_Ended;
-
-    // Create successor block.
-    // If there are breaks, create block with breaks as predecessor
-    // Else create a block with current as predecessor
-    MBasicBlock* successor = nullptr;
-    if (breaks)
-        successor = createBreakCatchBlock(breaks, exitpc);
-    else
-        successor = newBlock(current, exitpc);
-
-    if (!successor)
-        return ControlStatus_Error;
-
-    // If there is current, the current block flows into this one.
-    // So current is also a predecessor to this block
-    if (current) {
-        current->end(MGoto::New(alloc(), successor));
-        if (breaks) {
-            if (!successor->addPredecessor(alloc(), current))
-                return ControlStatus_Error;
-        }
-    }
-
-    pc = exitpc;
-    if (!setCurrentAndSpecializePhis(successor))
-        return ControlStatus_Error;
-    return ControlStatus_Joined;
-}
-
-IonBuilder::ControlStatus
-IonBuilder::maybeLoop(JSOp op, jssrcnote* sn)
-{
-    // This function looks at the opcode and source note and tries to
-    // determine the structure of the loop. For some opcodes, like
-    // POP/NOP which are not explicitly control flow, this source note is
-    // optional. For opcodes with control flow, like GOTO, an unrecognized
-    // or not-present source note is a compilation failure.
-    switch (op) {
-      case JSOP_POP:
-        // for (init; ; update?) ...
-        if (sn && SN_TYPE(sn) == SRC_FOR) {
-            current->pop();
-            return forLoop(op, sn);
-        }
-        break;
-
-      case JSOP_NOP:
-        if (sn) {
-            // do { } while (cond)
-            if (SN_TYPE(sn) == SRC_WHILE)
-                return doWhileLoop(op, sn);
-            // Build a mapping such that given a basic block, whose successor
-            // has a phi
-
-            // for (; ; update?)
-            if (SN_TYPE(sn) == SRC_FOR)
-                return forLoop(op, sn);
-        }
-        break;
-
-      default:
-        MOZ_CRASH("unexpected opcode");
-    }
-
-    return ControlStatus_None;
-}
-
-void
-IonBuilder::assertValidLoopHeadOp(jsbytecode* pc)
-{
-#ifdef DEBUG
-    MOZ_ASSERT(JSOp(*pc) == JSOP_LOOPHEAD);
-
-    // Make sure this is the next opcode after the loop header,
-    // unless the for loop is unconditional.
-    CFGState& state = cfgStack_.back();
-    MOZ_ASSERT_IF((JSOp)*(state.loop.entry->pc()) == JSOP_GOTO,
-         GetNextPc(state.loop.entry->pc()) == pc);
-
-    // do-while loops have a source note.
-    jssrcnote* sn = info().getNote(gsn, pc);
-    if (sn) {
-        jsbytecode* ifne = pc + GetSrcNoteOffset(sn, 0);
-
-        jsbytecode* expected_ifne;
-        switch (state.state) {
-          case CFGState::DO_WHILE_LOOP_BODY:
-            expected_ifne = state.loop.updateEnd;
-            break;
-
-          default:
-            MOZ_CRASH("JSOP_LOOPHEAD unexpected source note");
-        }
-
-        // Make sure this loop goes to the same ifne as the loop header's
-        // source notes or GOTO.
-        MOZ_ASSERT(ifne == expected_ifne);
-    } else {
-        MOZ_ASSERT(state.state != CFGState::DO_WHILE_LOOP_BODY);
-    }
-#endif
-}
-
-IonBuilder::ControlStatus
-IonBuilder::doWhileLoop(JSOp op, jssrcnote* sn)
-{
-    // do { } while() loops have the following structure:
-    //    NOP         ; SRC_WHILE (offset to COND)
-    //    LOOPHEAD    ; SRC_WHILE (offset to IFNE)
-    //    LOOPENTRY
-    //    ...         ; body
-    //    ...
-    //    COND        ; start of condition
-    //    ...
-    //    IFNE ->     ; goes to LOOPHEAD
-    int condition_offset = GetSrcNoteOffset(sn, 0);
-    jsbytecode* conditionpc = pc + condition_offset;
-
-    jssrcnote* sn2 = info().getNote(gsn, pc+1);
-    int offset = GetSrcNoteOffset(sn2, 0);
-    jsbytecode* ifne = pc + offset + 1;
-    MOZ_ASSERT(ifne > pc);
-
-    // Verify that the IFNE goes back to a loophead op.
-    jsbytecode* loopHead = GetNextPc(pc);
-    MOZ_ASSERT(JSOp(*loopHead) == JSOP_LOOPHEAD);
-    MOZ_ASSERT(loopHead == ifne + GetJumpOffset(ifne));
-
-    jsbytecode* loopEntry = GetNextPc(loopHead);
-    bool canOsr = LoopEntryCanIonOsr(loopEntry);
-    bool osr = info().hasOsrAt(loopEntry);
-
-    if (osr) {
-        MBasicBlock* preheader = newOsrPreheader(current, loopEntry, pc);
-        if (!preheader)
-            return ControlStatus_Error;
-        current->end(MGoto::New(alloc(), preheader));
-        if (!setCurrentAndSpecializePhis(preheader))
-            return ControlStatus_Error;
-    }
-
-    unsigned stackPhiCount = 0;
-    MBasicBlock* header = newPendingLoopHeader(current, loopEntry, osr, canOsr, stackPhiCount);
-    if (!header)
-        return ControlStatus_Error;
-    current->end(MGoto::New(alloc(), header));
-
-    jsbytecode* loophead = GetNextPc(pc);
-    jsbytecode* bodyStart = GetNextPc(loophead);
-    jsbytecode* bodyEnd = conditionpc;
-    jsbytecode* exitpc = GetNextPc(ifne);
-    if (!analyzeNewLoopTypes(header, bodyStart, exitpc))
-        return ControlStatus_Error;
-    if (!pushLoop(CFGState::DO_WHILE_LOOP_BODY, conditionpc, header, osr,
-                  loopHead, bodyStart, bodyStart, bodyEnd, exitpc, conditionpc))
-    {
-        return ControlStatus_Error;
-    }
-
-    CFGState& state = cfgStack_.back();
-    state.loop.updatepc = conditionpc;
-    state.loop.updateEnd = ifne;
-
-    if (!setCurrentAndSpecializePhis(header))
-        return ControlStatus_Error;
-    if (!jsop_loophead(loophead))
-        return ControlStatus_Error;
-
-    pc = bodyStart;
-    return ControlStatus_Jumped;
-}
-
-IonBuilder::ControlStatus
-IonBuilder::whileOrForInLoop(jssrcnote* sn)
-{
-    // while (cond) { } loops have the following structure:
-    //    GOTO cond   ; SRC_WHILE (offset to IFNE)
-    //    LOOPHEAD
-    //    ...
-    //  cond:
-    //    LOOPENTRY
-    //    ...
-    //    IFNE        ; goes to LOOPHEAD
-    // for (x in y) { } loops are similar; the cond will be a MOREITER.
-    MOZ_ASSERT(SN_TYPE(sn) == SRC_FOR_OF || SN_TYPE(sn) == SRC_FOR_IN || SN_TYPE(sn) == SRC_WHILE);
-    int ifneOffset = GetSrcNoteOffset(sn, 0);
-    jsbytecode* ifne = pc + ifneOffset;
-    MOZ_ASSERT(ifne > pc);
-
-    // Verify that the IFNE goes back to a loophead op.
-    MOZ_ASSERT(JSOp(*GetNextPc(pc)) == JSOP_LOOPHEAD);
-    MOZ_ASSERT(GetNextPc(pc) == ifne + GetJumpOffset(ifne));
-
-    jsbytecode* loopEntry = pc + GetJumpOffset(pc);
-    bool canOsr = LoopEntryCanIonOsr(loopEntry);
-    bool osr = info().hasOsrAt(loopEntry);
-
-    if (osr) {
-        MBasicBlock* preheader = newOsrPreheader(current, loopEntry, pc);
-        if (!preheader)
-            return ControlStatus_Error;
-        current->end(MGoto::New(alloc(), preheader));
-        if (!setCurrentAndSpecializePhis(preheader))
-            return ControlStatus_Error;
-    }
-
-    unsigned stackPhiCount;
-    if (SN_TYPE(sn) == SRC_FOR_OF)
-        stackPhiCount = 2;
-    else if (SN_TYPE(sn) == SRC_FOR_IN)
-        stackPhiCount = 1;
-    else
-        stackPhiCount = 0;
-
-    MBasicBlock* header = newPendingLoopHeader(current, loopEntry, osr, canOsr, stackPhiCount);
-    if (!header)
-        return ControlStatus_Error;
-    current->end(MGoto::New(alloc(), header));
-
-    // Skip past the JSOP_LOOPHEAD for the body start.
-    jsbytecode* loopHead = GetNextPc(pc);
-    jsbytecode* bodyStart = GetNextPc(loopHead);
-    jsbytecode* bodyEnd = pc + GetJumpOffset(pc);
-    jsbytecode* exitpc = GetNextPc(ifne);
-    jsbytecode* continuepc = pc;
-    if (!analyzeNewLoopTypes(header, bodyStart, exitpc))
-        return ControlStatus_Error;
-    if (!pushLoop(CFGState::WHILE_LOOP_COND, ifne, header, osr,
-                  loopHead, bodyEnd, bodyStart, bodyEnd, exitpc, continuepc))
-    {
-        return ControlStatus_Error;
-    }
-
-    // Parse the condition first.
-    if (!setCurrentAndSpecializePhis(header))
-        return ControlStatus_Error;
-    if (!jsop_loophead(loopHead))
-        return ControlStatus_Error;
-
-    pc = bodyEnd;
-    return ControlStatus_Jumped;
-}
-
-IonBuilder::ControlStatus
-IonBuilder::forLoop(JSOp op, jssrcnote* sn)
-{
-    // Skip the NOP.
-    MOZ_ASSERT(op == JSOP_NOP);
-    pc = GetNextPc(pc);
-
-    jsbytecode* condpc = pc + GetSrcNoteOffset(sn, 0);
-    jsbytecode* updatepc = pc + GetSrcNoteOffset(sn, 1);
-    jsbytecode* ifne = pc + GetSrcNoteOffset(sn, 2);
-    jsbytecode* exitpc = GetNextPc(ifne);
-
-    // for loops have the following structures:
-    //
-    //   NOP or POP
-    //   [GOTO cond | NOP]
-    //   LOOPHEAD
-    // body:
-    //    ; [body]
-    // [increment:]
-    //   [{FRESHEN,RECREATE}LEXICALENV, if needed by a lexical env]
-    //    ; [increment]
-    // [cond:]
-    //   LOOPENTRY
-    //   GOTO body
-    //
-    // If there is a condition (condpc != ifne), this acts similar to a while
-    // loop otherwise, it acts like a do-while loop.
-    //
-    // Note that currently Ion doesn't compile pushlexicalenv/poplexicalenv,
-    // necessary prerequisites to {freshen,recreate}lexicalenv.  So the code
-    // below doesn't and needn't consider either op's implications.
-    jsbytecode* bodyStart = pc;
-    jsbytecode* bodyEnd = updatepc;
-    jsbytecode* loopEntry = condpc;
-    if (condpc != ifne) {
-        MOZ_ASSERT(JSOp(*bodyStart) == JSOP_GOTO);
-        MOZ_ASSERT(bodyStart + GetJumpOffset(bodyStart) == condpc);
-        bodyStart = GetNextPc(bodyStart);
-    } else {
-        // No loop condition, such as for(j = 0; ; j++)
-        if (op != JSOP_NOP) {
-            // If the loop starts with POP, we have to skip a NOP.
-            MOZ_ASSERT(JSOp(*bodyStart) == JSOP_NOP);
-            bodyStart = GetNextPc(bodyStart);
-        }
-        loopEntry = GetNextPc(bodyStart);
-    }
-    jsbytecode* loopHead = bodyStart;
-    MOZ_ASSERT(JSOp(*bodyStart) == JSOP_LOOPHEAD);
-    MOZ_ASSERT(ifne + GetJumpOffset(ifne) == bodyStart);
-    bodyStart = GetNextPc(bodyStart);
-
-    bool osr = info().hasOsrAt(loopEntry);
-    bool canOsr = LoopEntryCanIonOsr(loopEntry);
-
-    if (osr) {
-        MBasicBlock* preheader = newOsrPreheader(current, loopEntry, pc);
-        if (!preheader)
-            return ControlStatus_Error;
-        current->end(MGoto::New(alloc(), preheader));
-        if (!setCurrentAndSpecializePhis(preheader))
-            return ControlStatus_Error;
-    }
-
-    unsigned stackPhiCount = 0;
-    MBasicBlock* header = newPendingLoopHeader(current, loopEntry, osr, canOsr, stackPhiCount);
-    if (!header)
-        return ControlStatus_Error;
-    current->end(MGoto::New(alloc(), header));
-
-    // If there is no condition, we immediately parse the body. Otherwise, we
-    // parse the condition.
-    jsbytecode* stopAt;
-    CFGState::State initial;
-    if (condpc != ifne) {
-        pc = condpc;
-        stopAt = ifne;
-        initial = CFGState::FOR_LOOP_COND;
-    } else {
-        pc = bodyStart;
-        stopAt = bodyEnd;
-        initial = CFGState::FOR_LOOP_BODY;
-    }
-
-    if (!analyzeNewLoopTypes(header, bodyStart, exitpc))
-        return ControlStatus_Error;
-    if (!pushLoop(initial, stopAt, header, osr,
-                  loopHead, pc, bodyStart, bodyEnd, exitpc, updatepc))
-    {
-        return ControlStatus_Error;
-    }
-
-    CFGState& state = cfgStack_.back();
-    state.loop.condpc = (condpc != ifne) ? condpc : nullptr;
-    state.loop.updatepc = (updatepc != condpc) ? updatepc : nullptr;
-    if (state.loop.updatepc)
-        state.loop.updateEnd = condpc;
-
-    if (!setCurrentAndSpecializePhis(header))
-        return ControlStatus_Error;
-    if (!jsop_loophead(loopHead))
-        return ControlStatus_Error;
-
-    return ControlStatus_Jumped;
-}
-
-int
-IonBuilder::CmpSuccessors(const void* a, const void* b)
-{
-    const MBasicBlock* a0 = * (MBasicBlock * const*)a;
-    const MBasicBlock* b0 = * (MBasicBlock * const*)b;
-    if (a0->pc() == b0->pc())
-        return 0;
-
-    return (a0->pc() > b0->pc()) ? 1 : -1;
-}
-
-IonBuilder::ControlStatus
-IonBuilder::tableSwitch(JSOp op, jssrcnote* sn)
-{
-    // TableSwitch op contains the following data
-    // (length between data is JUMP_OFFSET_LEN)
-    //
-    // 0: Offset of default case
-    // 1: Lowest number in tableswitch
-    // 2: Highest number in tableswitch
-    // 3: Offset of case low
-    // 4: Offset of case low+1
-    // .: ...
-    // .: Offset of case high
-
-    MOZ_ASSERT(op == JSOP_TABLESWITCH);
-    MOZ_ASSERT(SN_TYPE(sn) == SRC_TABLESWITCH);
-
-    // Pop input.
-    MDefinition* ins = current->pop();
-
-    // Get the default and exit pc
-    jsbytecode* exitpc = pc + GetSrcNoteOffset(sn, 0);
-    jsbytecode* defaultpc = pc + GET_JUMP_OFFSET(pc);
-
-    MOZ_ASSERT(defaultpc > pc && defaultpc <= exitpc);
-
-    // Get the low and high from the tableswitch
-    jsbytecode* pc2 = pc;
-    pc2 += JUMP_OFFSET_LEN;
-    int low = GET_JUMP_OFFSET(pc2);
-    pc2 += JUMP_OFFSET_LEN;
-    int high = GET_JUMP_OFFSET(pc2);
-    pc2 += JUMP_OFFSET_LEN;
-
-    // Create MIR instruction
-    MTableSwitch* tableswitch = MTableSwitch::New(alloc(), ins, low, high);
-
-    // Create default case
-    MBasicBlock* defaultcase = newBlock(current, defaultpc);
-    if (!defaultcase)
-        return ControlStatus_Error;
-
-    if (!tableswitch->addDefault(defaultcase))
-        return ControlStatus_Error;
-
-    if (!tableswitch->addBlock(defaultcase))
-        return ControlStatus_Error;
-
-    // Create cases
-    jsbytecode* casepc = nullptr;
-    for (int i = 0; i < high-low+1; i++) {
-        casepc = pc + GET_JUMP_OFFSET(pc2);
-
-        MOZ_ASSERT(casepc >= pc && casepc <= exitpc);
-        MBasicBlock* caseblock;
-
-        if (casepc == pc) {
-            // If the casepc equals the current pc, it is not a written case,
-            // but a filled gap. That way we can use a tableswitch instead of
-            // condswitch, even if not all numbers are consecutive.
-            // In that case this block goes to the default case
-            caseblock = newBlock(current, defaultpc);
-            if (!caseblock)
-                return ControlStatus_Error;
-            caseblock->end(MGoto::New(alloc(), defaultcase));
-            if (!defaultcase->addPredecessor(alloc(), caseblock))
-                return ControlStatus_Error;
-        } else {
-            // If this is an actual case (not filled gap),
-            // add this block to the list that still needs to get processed.
-            caseblock = newBlock(current, casepc);
-            if (!caseblock)
-                return ControlStatus_Error;
-
-            if (!tableswitch->addBlock(caseblock))
-                return ControlStatus_Error;
-
-            // Add constant to indicate which case this is for use by
-            // processNextTableSwitchCase.
-            MConstant* constant = MConstant::New(alloc(), Int32Value(i + low));
-            caseblock->add(constant);
-        }
-
-        size_t caseIndex;
-        if (!tableswitch->addSuccessor(caseblock, &caseIndex))
-            return ControlStatus_Error;
-
-        if (!tableswitch->addCase(caseIndex))
-            return ControlStatus_Error;
-
-        pc2 += JUMP_OFFSET_LEN;
-    }
-
-    // Move defaultcase to the end, to maintain RPO.
-    graph().moveBlockToEnd(defaultcase);
-
-    MOZ_ASSERT(tableswitch->numCases() == (uint32_t)(high - low + 1));
-    MOZ_ASSERT(tableswitch->numSuccessors() > 0);
-
-    // Sort the list of blocks that still needs to get processed by pc
-    qsort(tableswitch->blocks(), tableswitch->numBlocks(),
-          sizeof(MBasicBlock*), CmpSuccessors);
-
-    // Create info
-    ControlFlowInfo switchinfo(cfgStack_.length(), exitpc);
-    if (!switches_.append(switchinfo))
-        return ControlStatus_Error;
-
-    // Use a state to retrieve some information
-    CFGState state = CFGState::TableSwitch(exitpc, tableswitch);
-
-    // Save the MIR instruction as last instruction of this block.
-    current->end(tableswitch);
-
-    // If there is only one successor the block should stop at the end of the switch
-    // Else it should stop at the start of the next successor
-    if (tableswitch->numBlocks() > 1)
-        state.stopAt = tableswitch->getBlock(1)->pc();
-    if (!setCurrentAndSpecializePhis(tableswitch->getBlock(0)))
-        return ControlStatus_Error;
-
-    if (!cfgStack_.append(state))
-        return ControlStatus_Error;
-
-    pc = current->pc();
-    return ControlStatus_Jumped;
+    pc = header->pc();
+
+    initLoopEntry();
+    return true;
 }
 
 bool
 IonBuilder::replaceTypeSet(MDefinition* subject, TemporaryTypeSet* type, MTest* test)
 {
     if (type->unknown())
         return true;
 
@@ -4041,580 +2792,175 @@ IonBuilder::improveTypesAtTest(MDefiniti
 
         type = TypeSet::intersectSets(&base, oldType, alloc_->lifoAlloc());
     }
 
     return type && replaceTypeSet(ins, type, test);
 }
 
 bool
-IonBuilder::jsop_label()
-{
-    MOZ_ASSERT(JSOp(*pc) == JSOP_LABEL);
-
-    jsbytecode* endpc = pc + GET_JUMP_OFFSET(pc);
-    MOZ_ASSERT(endpc > pc);
-
-    ControlFlowInfo label(cfgStack_.length(), endpc);
-    if (!labels_.append(label))
-        return false;
-
-    return cfgStack_.append(CFGState::Label(endpc));
-}
-
-bool
-IonBuilder::jsop_condswitch()
-{
-    // CondSwitch op looks as follows:
-    //   condswitch [length +exit_pc; first case offset +next-case ]
-    //   {
-    //     {
-    //       ... any code ...
-    //       case (+jump) [pcdelta offset +next-case]
-    //     }+
-    //     default (+jump)
-    //     ... jump targets ...
-    //   }
-    //
-    // The default case is always emitted even if there is no default case in
-    // the source.  The last case statement pcdelta source note might have a 0
-    // offset on the last case (not all the time).
-    //
-    // A conditional evaluate the condition of each case and compare it to the
-    // switch value with a strict equality.  Cases conditions are iterated
-    // linearly until one is matching. If one case succeeds, the flow jumps into
-    // the corresponding body block.  The body block might alias others and
-    // might continue in the next body block if the body is not terminated with
-    // a break.
-    //
-    // Algorithm:
-    //  1/ Loop over the case chain to reach the default target
-    //   & Estimate the number of uniq bodies.
-    //  2/ Generate code for all cases (see processCondSwitchCase).
-    //  3/ Generate code for all bodies (see processCondSwitchBody).
-
-    MOZ_ASSERT(JSOp(*pc) == JSOP_CONDSWITCH);
-    jssrcnote* sn = info().getNote(gsn, pc);
-    MOZ_ASSERT(SN_TYPE(sn) == SRC_CONDSWITCH);
-
-    // Get the exit pc
-    jsbytecode* exitpc = pc + GetSrcNoteOffset(sn, 0);
-    jsbytecode* firstCase = pc + GetSrcNoteOffset(sn, 1);
-
-    // Iterate all cases in the conditional switch.
-    // - Stop at the default case. (always emitted after the last case)
-    // - Estimate the number of uniq bodies. This estimation might be off by 1
-    //   if the default body alias a case body.
-    jsbytecode* curCase = firstCase;
-    jsbytecode* lastTarget = GetJumpOffset(curCase) + curCase;
-    size_t nbBodies = 2; // default target and the first body.
-
-    MOZ_ASSERT(pc < curCase && curCase <= exitpc);
-    while (JSOp(*curCase) == JSOP_CASE) {
-        // Fetch the next case.
-        jssrcnote* caseSn = info().getNote(gsn, curCase);
-        MOZ_ASSERT(caseSn && SN_TYPE(caseSn) == SRC_NEXTCASE);
-        ptrdiff_t off = GetSrcNoteOffset(caseSn, 0);
-        MOZ_ASSERT_IF(off == 0, JSOp(*GetNextPc(curCase)) == JSOP_JUMPTARGET);
-        curCase = off ? curCase + off : GetNextPc(GetNextPc(curCase));
-        MOZ_ASSERT(pc < curCase && curCase <= exitpc);
-
-        // Count non-aliased cases.
-        jsbytecode* curTarget = GetJumpOffset(curCase) + curCase;
-        if (lastTarget < curTarget)
-            nbBodies++;
-        lastTarget = curTarget;
-    }
-
-    // The current case now be the default case which jump to the body of the
-    // default case, which might be behind the last target.
-    MOZ_ASSERT(JSOp(*curCase) == JSOP_DEFAULT);
-    jsbytecode* defaultTarget = GetJumpOffset(curCase) + curCase;
-    MOZ_ASSERT(curCase < defaultTarget && defaultTarget <= exitpc);
-
-    // Allocate the current graph state.
-    CFGState state = CFGState::CondSwitch(this, exitpc, defaultTarget);
-    if (!state.condswitch.bodies || !state.condswitch.bodies->init(alloc(), nbBodies))
-        return ControlStatus_Error;
-
-    // We loop on case conditions with processCondSwitchCase.
-    MOZ_ASSERT(JSOp(*firstCase) == JSOP_CASE);
-    state.stopAt = firstCase;
-    state.state = CFGState::COND_SWITCH_CASE;
-
-    return cfgStack_.append(state);
-}
-
-IonBuilder::CFGState
-IonBuilder::CFGState::CondSwitch(IonBuilder* builder, jsbytecode* exitpc, jsbytecode* defaultTarget)
-{
-    CFGState state;
-    state.state = COND_SWITCH_CASE;
-    state.stopAt = nullptr;
-    state.condswitch.bodies = (FixedList<MBasicBlock*>*)builder->alloc_->allocate(
-        sizeof(FixedList<MBasicBlock*>));
-    state.condswitch.currentIdx = 0;
-    state.condswitch.defaultTarget = defaultTarget;
-    state.condswitch.defaultIdx = uint32_t(-1);
-    state.condswitch.exitpc = exitpc;
-    state.condswitch.breaks = nullptr;
-    return state;
-}
-
-IonBuilder::CFGState
-IonBuilder::CFGState::Label(jsbytecode* exitpc)
-{
-    CFGState state;
-    state.state = LABEL;
-    state.stopAt = exitpc;
-    state.label.breaks = nullptr;
-    return state;
-}
-
-IonBuilder::CFGState
-IonBuilder::CFGState::Try(jsbytecode* exitpc, MBasicBlock* successor)
-{
-    CFGState state;
-    state.state = TRY;
-    state.stopAt = exitpc;
-    state.try_.successor = successor;
-    return state;
-}
-
-IonBuilder::ControlStatus
-IonBuilder::processCondSwitchCase(CFGState& state)
-{
-    MOZ_ASSERT(state.state == CFGState::COND_SWITCH_CASE);
-    MOZ_ASSERT(!state.condswitch.breaks);
-    MOZ_ASSERT(current);
-    MOZ_ASSERT(JSOp(*pc) == JSOP_CASE);
-    FixedList<MBasicBlock*>& bodies = *state.condswitch.bodies;
-    jsbytecode* defaultTarget = state.condswitch.defaultTarget;
-    uint32_t& currentIdx = state.condswitch.currentIdx;
-    jsbytecode* lastTarget = currentIdx ? bodies[currentIdx - 1]->pc() : nullptr;
-
-    // Fetch the following case in which we will continue.
-    jssrcnote* sn = info().getNote(gsn, pc);
-    ptrdiff_t off = GetSrcNoteOffset(sn, 0);
-    MOZ_ASSERT_IF(off == 0, JSOp(*GetNextPc(pc)) == JSOP_JUMPTARGET);
-    jsbytecode* casePc = off ? pc + off : GetNextPc(GetNextPc(pc));
-    bool caseIsDefault = JSOp(*casePc) == JSOP_DEFAULT;
-    MOZ_ASSERT(JSOp(*casePc) == JSOP_CASE || caseIsDefault);
-
-    // Allocate the block of the matching case.
-    bool bodyIsNew = false;
-    MBasicBlock* bodyBlock = nullptr;
-    jsbytecode* bodyTarget = pc + GetJumpOffset(pc);
-    if (lastTarget < bodyTarget) {
-        // If the default body is in the middle or aliasing the current target.
-        if (lastTarget < defaultTarget && defaultTarget <= bodyTarget) {
-            MOZ_ASSERT(state.condswitch.defaultIdx == uint32_t(-1));
-            state.condswitch.defaultIdx = currentIdx;
-            bodies[currentIdx] = nullptr;
-            // If the default body does not alias any and it would be allocated
-            // later and stored in the defaultIdx location.
-            if (defaultTarget < bodyTarget)
-                currentIdx++;
-        }
-
-        bodyIsNew = true;
-        // Pop switch and case operands.
-        bodyBlock = newBlockPopN(current, bodyTarget, 2);
-        bodies[currentIdx++] = bodyBlock;
-    } else {
-        // This body alias the previous one.
-        MOZ_ASSERT(lastTarget == bodyTarget);
-        MOZ_ASSERT(currentIdx > 0);
-        bodyBlock = bodies[currentIdx - 1];
-    }
-
-    if (!bodyBlock)
-        return ControlStatus_Error;
-
-    lastTarget = bodyTarget;
-
-    // Allocate the block of the non-matching case.  This can either be a normal
-    // case or the default case.
-    bool caseIsNew = false;
-    MBasicBlock* caseBlock = nullptr;
-    if (!caseIsDefault) {
-        caseIsNew = true;
-        // Pop the case operand.
-        caseBlock = newBlockPopN(current, GetNextPc(pc), 1);
-    } else {
-        // The non-matching case is the default case, which jump directly to its
-        // body. Skip the creation of a default case block and directly create
-        // the default body if it does not alias any previous body.
-
-        if (state.condswitch.defaultIdx == uint32_t(-1)) {
-            // The default target is the last target.
-            MOZ_ASSERT(lastTarget < defaultTarget);
-            state.condswitch.defaultIdx = currentIdx++;
-            caseIsNew = true;
-        } else if (bodies[state.condswitch.defaultIdx] == nullptr) {
-            // The default target is in the middle and it does not alias any
-            // case target.
-            MOZ_ASSERT(defaultTarget < lastTarget);
-            caseIsNew = true;
-        } else {
-            // The default target is in the middle and it alias a case target.
-            MOZ_ASSERT(defaultTarget <= lastTarget);
-            caseBlock = bodies[state.condswitch.defaultIdx];
-        }
-
-        // Allocate and register the default body.
-        if (caseIsNew) {
-            // Pop the case & switch operands.
-            caseBlock = newBlockPopN(current, defaultTarget, 2);
-            bodies[state.condswitch.defaultIdx] = caseBlock;
-        }
-    }
-
-    if (!caseBlock)
-        return ControlStatus_Error;
-
-    // Terminate the last case condition block by emitting the code
-    // corresponding to JSOP_CASE bytecode.
-    if (bodyBlock != caseBlock) {
-        MDefinition* caseOperand = current->pop();
-        MDefinition* switchOperand = current->peek(-1);
-
-        if (!jsop_compare(JSOP_STRICTEQ, switchOperand, caseOperand))
-            return ControlStatus_Error;
-        MInstruction* cmpResult = current->pop()->toInstruction();
-        MOZ_ASSERT(!cmpResult->isEffectful());
-        current->end(newTest(cmpResult, bodyBlock, caseBlock));
-
-        // Add last case as predecessor of the body if the body is aliasing
-        // the previous case body.
-        if (!bodyIsNew && !bodyBlock->addPredecessorPopN(alloc(), current, 1))
-            return ControlStatus_Error;
-
-        // Add last case as predecessor of the non-matching case if the
-        // non-matching case is an aliased default case. We need to pop the
-        // switch operand as we skip the default case block and use the default
-        // body block directly.
-        MOZ_ASSERT_IF(!caseIsNew, caseIsDefault);
-        if (!caseIsNew && !caseBlock->addPredecessorPopN(alloc(), current, 1))
-            return ControlStatus_Error;
-    } else {
-        // The default case alias the last case body.
-        MOZ_ASSERT(caseIsDefault);
-        current->pop(); // Case operand
-        current->pop(); // Switch operand
-        current->end(MGoto::New(alloc(), bodyBlock));
-        if (!bodyIsNew && !bodyBlock->addPredecessor(alloc(), current))
-            return ControlStatus_Error;
-    }
-
-    if (caseIsDefault) {
-        // The last case condition is finished.  Loop in processCondSwitchBody,
-        // with potential stops in processSwitchBreak.  Check that the bodies
-        // fixed list is over-estimate by at most 1, and shrink the size such as
-        // length can be used as an upper bound while iterating bodies.
-        MOZ_ASSERT(currentIdx == bodies.length() || currentIdx + 1 == bodies.length());
-        bodies.shrink(bodies.length() - currentIdx);
-
-        // Handle break statements in processSwitchBreak while processing
-        // bodies.
-        ControlFlowInfo breakInfo(cfgStack_.length() - 1, state.condswitch.exitpc);
-        if (!switches_.append(breakInfo))
-            return ControlStatus_Error;
-
-        // Jump into the first body.
-        currentIdx = 0;
-        setCurrent(nullptr);
-        state.state = CFGState::COND_SWITCH_BODY;
-        return processCondSwitchBody(state);
-    }
-
-    // Continue until the case condition.
-    if (!setCurrentAndSpecializePhis(caseBlock))
-        return ControlStatus_Error;
-    pc = current->pc();
-    state.stopAt = casePc;
-    return ControlStatus_Jumped;
-}
-
-IonBuilder::ControlStatus
-IonBuilder::processCondSwitchBody(CFGState& state)
-{
-    MOZ_ASSERT(state.state == CFGState::COND_SWITCH_BODY);
-    MOZ_ASSERT(pc <= state.condswitch.exitpc);
-    FixedList<MBasicBlock*>& bodies = *state.condswitch.bodies;
-    uint32_t& currentIdx = state.condswitch.currentIdx;
-
-    MOZ_ASSERT(currentIdx <= bodies.length());
-    if (currentIdx == bodies.length()) {
-        MOZ_ASSERT_IF(current, pc == state.condswitch.exitpc);
-        return processSwitchEnd(state.condswitch.breaks, state.condswitch.exitpc);
-    }
-
-    // Get the next body
-    MBasicBlock* nextBody = bodies[currentIdx++];
-    MOZ_ASSERT_IF(current, pc == nextBody->pc());
-
-    // Fix the reverse post-order iteration.
-    graph().moveBlockToEnd(nextBody);
-
-    // The last body continue into the new one.
-    if (current) {
-        current->end(MGoto::New(alloc(), nextBody));
-        if (!nextBody->addPredecessor(alloc(), current))
-            return ControlStatus_Error;
-    }
-
-    // Continue in the next body.
-    if (!setCurrentAndSpecializePhis(nextBody))
-        return ControlStatus_Error;
-    pc = current->pc();
-
-    if (currentIdx < bodies.length())
-        state.stopAt = bodies[currentIdx]->pc();
-    else
-        state.stopAt = state.condswitch.exitpc;
-    return ControlStatus_Jumped;
-}
-
-bool
-IonBuilder::jsop_andor(JSOp op)
-{
-    MOZ_ASSERT(op == JSOP_AND || op == JSOP_OR);
-
-    jsbytecode* rhsStart = pc + CodeSpec[op].length;
-    jsbytecode* joinStart = pc + GetJumpOffset(pc);
-    MOZ_ASSERT(joinStart > pc);
-
-    // We have to leave the LHS on the stack.
-    MDefinition* lhs = current->peek(-1);
-
-    MBasicBlock* evalLhs = newBlock(current, joinStart);
-    MBasicBlock* evalRhs = newBlock(current, rhsStart);
-    if (!evalLhs || !evalRhs)
-        return false;
-
-    MTest* test = (op == JSOP_AND)
-                  ? newTest(lhs, evalRhs, evalLhs)
-                  : newTest(lhs, evalLhs, evalRhs);
-    current->end(test);
-
-    // Create the lhs block and specialize.
-    if (!setCurrentAndSpecializePhis(evalLhs))
-        return false;
-
-    if (!improveTypesAtTest(test->getOperand(0), test->ifTrue() == current, test))
-        return false;
-
-    // Create the rhs block.
-    if (!cfgStack_.append(CFGState::AndOr(joinStart, evalLhs)))
-        return false;
-
-    if (!setCurrentAndSpecializePhis(evalRhs))
-        return false;
-
-    if (!improveTypesAtTest(test->getOperand(0), test->ifTrue() == current, test))
-        return false;
-
-    return true;
-}
-
-bool
 IonBuilder::jsop_dup2()
 {
     uint32_t lhsSlot = current->stackDepth() - 2;
     uint32_t rhsSlot = current->stackDepth() - 1;
     current->pushSlot(lhsSlot);
     current->pushSlot(rhsSlot);
     return true;
 }
 
 bool
-IonBuilder::jsop_loophead(jsbytecode* pc)
-{
-    assertValidLoopHeadOp(pc);
-
-    current->add(MInterruptCheck::New(alloc()));
-    insertRecompileCheck();
-
-    return true;
-}
-
-bool
-IonBuilder::jsop_ifeq(JSOp op)
-{
-    // IFEQ always has a forward offset.
-    jsbytecode* trueStart = pc + CodeSpec[op].length;
-    jsbytecode* falseStart = pc + GetJumpOffset(pc);
-    MOZ_ASSERT(falseStart > pc);
-
-    // We only handle cases that emit source notes.
-    jssrcnote* sn = info().getNote(gsn, pc);
-    if (!sn)
-        return abort("expected sourcenote");
-
-    MDefinition* ins = current->pop();
+IonBuilder::visitTest(CFGTest* test)
+{
+    MDefinition* ins = test->mustKeepCondition() ? current->peek(-1) : current->pop();
 
     // Create true and false branches.
-    MBasicBlock* ifTrue = newBlock(current, trueStart);
-    MBasicBlock* ifFalse = newBlock(current, falseStart);
+    MBasicBlock* ifTrue = newBlock(current, test->trueBranch()->startPc());
+    MBasicBlock* ifFalse = newBlock(current, test->falseBranch()->startPc());
     if (!ifTrue || !ifFalse)
         return false;
 
-    MTest* test = newTest(ins, ifTrue, ifFalse);
-    current->end(test);
-
-    // The bytecode for if/ternary gets emitted either like this:
-    //
-    //    IFEQ X  ; src note (IF_ELSE, COND) points to the GOTO
-    //    ...
-    //    GOTO Z
-    // X: ...     ; else/else if
-    //    ...
-    // Z:         ; join
-    //
-    // Or like this:
-    //
-    //    IFEQ X  ; src note (IF) has no offset
-    //    ...
-    // Z: ...     ; join
-    //
-    // We want to parse the bytecode as if we were parsing the AST, so for the
-    // IF_ELSE/COND cases, we use the source note and follow the GOTO. For the
-    // IF case, the IFEQ offset is the join point.
-    switch (SN_TYPE(sn)) {
-      case SRC_IF:
-        if (!cfgStack_.append(CFGState::If(falseStart, test)))
-            return false;
-        break;
-
-      case SRC_IF_ELSE:
-      case SRC_COND:
-      {
-        // Infer the join point from the JSOP_GOTO[X] sitting here, then
-        // assert as we much we can that this is the right GOTO.
-        jsbytecode* trueEnd = pc + GetSrcNoteOffset(sn, 0);
-        MOZ_ASSERT(trueEnd > pc);
-        MOZ_ASSERT(trueEnd < falseStart);
-        MOZ_ASSERT(JSOp(*trueEnd) == JSOP_GOTO);
-        MOZ_ASSERT(!info().getNote(gsn, trueEnd));
-
-        jsbytecode* falseEnd = trueEnd + GetJumpOffset(trueEnd);
-        MOZ_ASSERT(falseEnd > trueEnd);
-        MOZ_ASSERT(falseEnd >= falseStart);
-
-        if (!cfgStack_.append(CFGState::IfElse(trueEnd, falseEnd, test)))
-            return false;
-        break;
-      }
-
-      default:
-        MOZ_CRASH("unexpected source note type");
-    }
-
-    // Switch to parsing the true branch. Note that no PC update is needed,
-    // it's the next instruction.
-    if (!setCurrentAndSpecializePhis(ifTrue))
-        return false;
+    MTest* mir = newTest(ins, ifTrue, ifFalse);
+    current->end(mir);
 
     // Filter the types in the true branch.
-    if (!improveTypesAtTest(test->getOperand(0), test->ifTrue() == current, test))
-        return false;
-
-    return true;
-}
-
-bool
-IonBuilder::jsop_try()
-{
-    MOZ_ASSERT(JSOp(*pc) == JSOP_TRY);
-
+    if (!setCurrentAndSpecializePhis(ifTrue))
+        return false;
+    if (!improveTypesAtTest(mir->getOperand(0), /* trueBranch = */ true, mir))
+        return false;
+
+    blockWorklist[test->trueBranch()->id()] = ifTrue;
+
+    // Filter the types in the false branch.
+    // Note: sometimes the false branch is used as merge point. As a result
+    // reuse the ifFalse block as a type improvement block and create a new
+    // ifFalse which we can use for the merge.
+    MBasicBlock* filterBlock = ifFalse;
+    ifFalse = nullptr;
+    graph().addBlock(filterBlock);
+
+    if (!setCurrentAndSpecializePhis(filterBlock))
+        return false;
+    if (!improveTypesAtTest(mir->getOperand(0), /* trueBranch = */ false, mir))
+        return false;
+
+    ifFalse = newBlock(filterBlock, test->falseBranch()->startPc());
+    if (!ifFalse)
+        return false;
+    filterBlock->end(MGoto::New(alloc(), ifFalse));
+
+    blockWorklist[test->falseBranch()->id()] = ifFalse;
+
+    current = nullptr;
+
+    return true;
+}
+
+bool
+IonBuilder::visitCompare(CFGCompare* compare)
+{
+    MDefinition* lhs = current->pop();
+    MDefinition* rhs = current->peek(-1);
+
+    if (!jsop_compare(JSOP_STRICTEQ, lhs, rhs))
+        return false;
+    MInstruction* cmpResult = current->pop()->toInstruction();
+    MOZ_ASSERT(!cmpResult->isEffectful());
+
+    // Create true and false branches.
+    MBasicBlock* ifTrue = newBlock(current, compare->trueBranch()->startPc());
+    MBasicBlock* ifFalse = newBlock(current, compare->falseBranch()->startPc());
+    if (!ifTrue || !ifFalse)
+        return false;
+
+    blockWorklist[compare->trueBranch()->id()] = ifTrue;
+    blockWorklist[compare->falseBranch()->id()] = ifFalse;
+
+    MTest* mir = newTest(cmpResult, ifTrue, ifFalse);
+    current->end(mir);
+
+    // Filter the types in the true branch.
+    if (!setCurrentAndSpecializePhis(ifTrue))
+        return false;
+    if (!improveTypesAtTest(mir->getOperand(0), /* trueBranch = */ true, mir))
+        return false;
+
+    // Filter the types in the false branch.
+    if (!setCurrentAndSpecializePhis(ifFalse))
+        return false;
+    if (!improveTypesAtTest(mir->getOperand(0), /* trueBranch = */ false, mir))
+        return false;
+
+    current = nullptr;
+
+    return true;
+}
+
+bool
+IonBuilder::visitTry(CFGTry* try_)
+{
     // Try-finally is not yet supported.
     if (analysis().hasTryFinally())
         return abort("Has try-finally");
 
     // Try-catch within inline frames is not yet supported.
     MOZ_ASSERT(!isInlineBuilder());
 
     // Try-catch during the arguments usage analysis is not yet supported. Code
     // accessing the arguments within the 'catch' block is not accounted for.
     if (info().analysisMode() == Analysis_ArgumentsUsage)
         return abort("Try-catch during arguments usage analysis");
 
     graph().setHasTryBlock();
 
-    jssrcnote* sn = info().getNote(gsn, pc);
-    MOZ_ASSERT(SN_TYPE(sn) == SRC_TRY);
-
-    // Get the pc of the last instruction in the try block. It's a JSOP_GOTO to
-    // jump over the catch block.
-    jsbytecode* endpc = pc + GetSrcNoteOffset(sn, 0);
-    MOZ_ASSERT(JSOp(*endpc) == JSOP_GOTO);
-    MOZ_ASSERT(GetJumpOffset(endpc) > 0);
-
-    jsbytecode* afterTry = endpc + GetJumpOffset(endpc);
-
-    // If controlflow in the try body is terminated (by a return or throw
-    // statement), the code after the try-statement may still be reachable
-    // via the catch block (which we don't compile) and OSR can enter it.
-    // For example:
-    //
-    //     try {
-    //         throw 3;
-    //     } catch(e) { }
-    //
-    //     for (var i=0; i<1000; i++) {}
-    //
-    // To handle this, we create two blocks: one for the try block and one
-    // for the code following the try-catch statement. Both blocks are
-    // connected to the graph with an MGotoWithFake instruction that always
-    // jumps to the try block. This ensures the successor block always has a
-    // predecessor.
-    //
-    // If the code after the try block is unreachable (control flow in both the
-    // try and catch blocks is terminated), only create the try block, to avoid
-    // parsing unreachable code.
-
-    MBasicBlock* tryBlock = newBlock(current, GetNextPc(pc));
+    MBasicBlock* tryBlock = newBlock(current, try_->tryBlock()->startPc());
     if (!tryBlock)
         return false;
 
-    MBasicBlock* successor;
-    if (analysis().maybeInfo(afterTry)) {
-        successor = newBlock(current, afterTry);
+    blockWorklist[try_->tryBlock()->id()] = tryBlock;
+
+    // If the code after the try catch is reachable we connected it to the
+    // graph with an MGotoWithFake instruction that always jumps to the try
+    // block. This ensures the successor block always has a predecessor.
+    if (try_->codeAfterTryCatchReachable()) {
+        MBasicBlock* successor = newBlock(current, try_->getSuccessor(1)->startPc());
         if (!successor)
             return false;
 
+        blockWorklist[try_->afterTryCatchBlock()->id()] = successor;
+
         current->end(MGotoWithFake::New(alloc(), tryBlock, successor));
+
+        // The baseline compiler should not attempt to enter the catch block
+        // via OSR.
+        MOZ_ASSERT(info().osrPc() < try_->catchStartPc() ||
+                   info().osrPc() >= try_->afterTryCatchBlock()->startPc());
+
     } else {
-        successor = nullptr;
         current->end(MGoto::New(alloc(), tryBlock));
-    }
-
-    if (!cfgStack_.append(CFGState::Try(endpc, successor)))
-        return false;
-
-    // The baseline compiler should not attempt to enter the catch block
-    // via OSR.
-    MOZ_ASSERT(info().osrPc() < endpc || info().osrPc() >= afterTry);
-
-    // Start parsing the try block.
-    return setCurrentAndSpecializePhis(tryBlock);
-}
-
-IonBuilder::ControlStatus
-IonBuilder::processReturn(JSOp op)
+
+        // The baseline compiler should not attempt to enter the catch block
+        // via OSR or the code after the catch block.
+        // TODO: pre-existing bug. OSR after the catch block. Shouldn't happen.
+        //MOZ_ASSERT(info().osrPc() < try_->catchStartPc());
+    }
+
+    return true;
+}
+
+bool
+IonBuilder::visitReturn(CFGControlInstruction* control)
 {
     MDefinition* def;
-    switch (op) {
-      case JSOP_RETURN:
+    switch (control->type()) {
+      case CFGControlInstruction::Type_Return:
         // Return the last instruction.
         def = current->pop();
         break;
 
-      case JSOP_RETRVAL:
+      case CFGControlInstruction::Type_RetRVal:
         // Return undefined eagerly if script doesn't use return value.
         if (script()->noScriptRval()) {
             MInstruction* ins = MConstant::New(alloc(), UndefinedValue());
             current->add(ins);
             def = ins;
             break;
         }
 
@@ -4625,25 +2971,25 @@ IonBuilder::processReturn(JSOp op)
         def = nullptr;
         MOZ_CRASH("unknown return op");
     }
 
     MReturn* ret = MReturn::New(alloc(), def);
     current->end(ret);
 
     if (!graph().addReturn(current))
-        return ControlStatus_Error;
+        return false;
 
     // Make sure no one tries to use this block now.
     setCurrent(nullptr);
-    return processControlEnd();
-}
-
-IonBuilder::ControlStatus
-IonBuilder::processThrow()
+    return true;
+}
+
+bool
+IonBuilder::visitThrow(CFGThrow *cfgIns)
 {
     MDefinition* def = current->pop();
 
     // MThrow is not marked as effectful. This means when it throws and we
     // are inside a try block, we could use an earlier resume point and this
     // resume point may not be up-to-date, for example:
     //
     // (function() {
@@ -4669,24 +3015,89 @@ IonBuilder::processThrow()
     // hook. When we need to handle the hook, we bail out to baseline right
     // after the throw and propagate the exception when debug mode is on. This
     // is opposed to the normal behavior of resuming directly in the
     // associated catch block.
     MNop* nop = MNop::New(alloc());
     current->add(nop);
 
     if (!resumeAfter(nop))
-        return ControlStatus_Error;
+        return false;
 
     MThrow* ins = MThrow::New(alloc(), def);
     current->end(ins);
 
-    // Make sure no one tries to use this block now.
-    setCurrent(nullptr);
-    return processControlEnd();
+    return true;
+}
+
+bool
+IonBuilder::visitTableSwitch(CFGTableSwitch* cfgIns)
+{
+    // Pop input.
+    MDefinition* ins = current->pop();
+
+    // Create MIR instruction
+    MTableSwitch* tableswitch = MTableSwitch::New(alloc(), ins, cfgIns->low(), cfgIns->high());
+
+#ifdef DEBUG
+    MOZ_ASSERT(cfgIns->defaultCase() == cfgIns->getSuccessor(0));
+    for (size_t i = 1; i < cfgIns->numSuccessors(); i++) {
+        MOZ_ASSERT(cfgIns->getCase(i-1) == cfgIns->getSuccessor(i));
+    }
+#endif
+
+    // Create the cases
+    for (size_t i = 0; i < cfgIns->numSuccessors(); i++) {
+        const CFGBlock* cfgblock = cfgIns->getSuccessor(i);
+
+        MBasicBlock* caseBlock = newBlock(current, cfgblock->startPc());
+        if (!caseBlock)
+            return false;
+
+        blockWorklist[cfgblock->id()] = caseBlock;
+
+        size_t index;
+        if (i == 0) {
+            if (!tableswitch->addDefault(caseBlock, &index))
+                return false;
+
+        } else {
+            if (!tableswitch->addSuccessor(caseBlock, &index))
+                return false;
+
+            if (!tableswitch->addCase(index))
+                return false;
+
+            // If this is an actual case statement, optimize by replacing the
+            // input to the switch case with the actual number of the case.
+            MConstant* constant = MConstant::New(alloc(), Int32Value(i - 1 + tableswitch->low()));
+            caseBlock->add(constant);
+            for (uint32_t j = 0; j < caseBlock->stackDepth(); j++) {
+                if (ins != caseBlock->getSlot(j))
+                    continue;
+
+                constant->setDependency(ins);
+                caseBlock->setSlot(j, constant);
+            }
+            graph().addBlock(caseBlock);
+
+            MBasicBlock* merge = newBlock(caseBlock, cfgblock->startPc());
+            if (!merge)
+                return false;
+
+            caseBlock->end(MGoto::New(alloc(), merge));
+            blockWorklist[cfgblock->id()] = merge;
+        }
+
+        MOZ_ASSERT(index == i);
+    }
+
+    // Save the MIR instruction as last instruction of this block.
+    current->end(tableswitch);
+    return true;
 }
 
 void
 IonBuilder::pushConstant(const Value& v)
 {
     current->push(constant(v));
 }
 
@@ -5257,16 +3668,20 @@ IonBuilder::inlineScriptedCall(CallInfo&
         }
 
         // Inlining the callee failed. Mark the callee as uninlineable only if
         // the inlining was aborted for a non-exception reason.
         if (inlineBuilder.abortReason_ == AbortReason_Disable) {
             calleeScript->setUninlineable();
             if (!JitOptions.disableInlineBacktracking) {
                 current = backup.restore();
+                if (!current) {
+                    abortReason_ = AbortReason_Alloc;
+                    return InliningStatus_Error;
+                }
                 return InliningStatus_NotInlined;
             }
             abortReason_ = AbortReason_Inlining;
         } else if (inlineBuilder.abortReason_ == AbortReason_Inlining) {
             abortReason_ = AbortReason_Inlining;
         } else if (inlineBuilder.abortReason_ == AbortReason_Alloc) {
             abortReason_ = AbortReason_Alloc;
         } else if (inlineBuilder.abortReason_ == AbortReason_PreliminaryObjects) {
@@ -5275,38 +3690,44 @@ IonBuilder::inlineScriptedCall(CallInfo&
             for (size_t i = 0; i < groups.length(); i++)
                 addAbortedPreliminaryGroup(groups[i]);
             abortReason_ = AbortReason_PreliminaryObjects;
         }
 
         return InliningStatus_Error;
     }
 
+    if (returns.empty()) {
+        // Inlining of functions that have no exit is not supported.
+        calleeScript->setUninlineable();
+        if (!JitOptions.disableInlineBacktracking) {
+            current = backup.restore();
+            if (!current) {
+                abortReason_ = AbortReason_Alloc;
+                return InliningStatus_Error;
+            }
+            return InliningStatus_NotInlined;
+        }
+        abortReason_ = AbortReason_Inlining;
+        return InliningStatus_Error;
+    }
+
     // Create return block.
     jsbytecode* postCall = GetNextPc(pc);
     MBasicBlock* returnBlock = newBlock(nullptr, postCall);
     if (!returnBlock)
         return InliningStatus_Error;
+    graph().addBlock(returnBlock);
     returnBlock->setCallerResumePoint(callerResumePoint_);
 
     // Inherit the slots from current and pop |fun|.
     returnBlock->inheritSlots(current);
     returnBlock->pop();
 
     // Accumulate return values.
-    if (returns.empty()) {
-        // Inlining of functions that have no exit is not supported.
-        calleeScript->setUninlineable();
-        if (!JitOptions.disableInlineBacktracking) {
-            current = backup.restore();
-            return InliningStatus_NotInlined;
-        }
-        abortReason_ = AbortReason_Inlining;
-        return InliningStatus_Error;
-    }
     MDefinition* retvalDefn = patchInlinedReturns(callInfo, returns, returnBlock);
     if (!retvalDefn)
         return InliningStatus_Error;
     returnBlock->push(retvalDefn);
 
     // Initialize entry slots now that the stack has been fixed up.
     if (!returnBlock->initEntrySlots(alloc()))
         return InliningStatus_Error;
@@ -5842,16 +4263,17 @@ IonBuilder::inlineCallsite(const ObjectV
 
 bool
 IonBuilder::inlineGenericFallback(JSFunction* target, CallInfo& callInfo, MBasicBlock* dispatchBlock)
 {
     // Generate a new block with all arguments on-stack.
     MBasicBlock* fallbackBlock = newBlock(dispatchBlock, pc);
     if (!fallbackBlock)
         return false;
+    graph().addBlock(fallbackBlock);
 
     // Create a new CallInfo to track modified state within this block.
     CallInfo fallbackInfo(alloc(), callInfo.constructing());
     if (!fallbackInfo.init(callInfo))
         return false;
     fallbackInfo.popFormals(fallbackBlock);
 
     // Generate an MCall, which uses stateful |current|.
@@ -5906,27 +4328,29 @@ IonBuilder::inlineObjectGroupFallback(Ca
     dispatchBlock->add(undefined);
     dispatchBlock->rewriteAtDepth(-int(callInfo.numFormals()), undefined);
 
     // Construct a block that does nothing but remove formals from the stack.
     // This is effectively changing the entry resume point of the later fallback block.
     MBasicBlock* prepBlock = newBlock(dispatchBlock, pc);
     if (!prepBlock)
         return false;
+    graph().addBlock(prepBlock);
     fallbackInfo.popFormals(prepBlock);
 
     // Construct a block into which the MGetPropertyCache can be moved.
     // This is subtle: the pc and resume point are those of the MGetPropertyCache!
     InlinePropertyTable* propTable = cache->propTable();
     MResumePoint* priorResumePoint = propTable->takePriorResumePoint();
     MOZ_ASSERT(propTable->pc() != nullptr);
     MOZ_ASSERT(priorResumePoint != nullptr);
     MBasicBlock* getPropBlock = newBlock(prepBlock, propTable->pc(), priorResumePoint);
     if (!getPropBlock)
         return false;
+    graph().addBlock(getPropBlock);
 
     prepBlock->end(MGoto::New(alloc(), getPropBlock));
 
     // Since the getPropBlock inherited the stack from right before the MGetPropertyCache,
     // the target of the MGetPropertyCache is still on the stack.
     DebugOnly<MDefinition*> checkObject = getPropBlock->pop();
     MOZ_ASSERT(checkObject == cache->object());
 
@@ -5945,16 +4369,17 @@ IonBuilder::inlineObjectGroupFallback(Ca
         getPropBlock->addFromElsewhere(barrier);
         getPropBlock->push(barrier);
     }
 
     // Construct an end block with the correct resume point.
     MBasicBlock* preCallBlock = newBlock(getPropBlock, pc, preCallResumePoint);
     if (!preCallBlock)
         return false;
+    graph().addBlock(preCallBlock);
     getPropBlock->end(MGoto::New(alloc(), preCallBlock));
 
     // Now inline the MCallGeneric, using preCallBlock as the dispatch point.
     if (!inlineGenericFallback(nullptr, fallbackInfo, preCallBlock))
         return false;
 
     // inlineGenericFallback() set the return block as |current|.
     preCallBlock->end(MGoto::New(alloc(), current));
@@ -5995,16 +4420,17 @@ IonBuilder::inlineCalls(CallInfo& callIn
         dispatch = MFunctionDispatch::New(alloc(), callInfo.fun());
     }
 
     // Generate a return block to host the rval-collecting MPhi.
     jsbytecode* postCall = GetNextPc(pc);
     MBasicBlock* returnBlock = newBlock(nullptr, postCall);
     if (!returnBlock)
         return false;
+    graph().addBlock(returnBlock);
     returnBlock->setCallerResumePoint(callerResumePoint_);
 
     // Set up stack, used to manually create a post-call resume point.
     returnBlock->inheritSlots(dispatchBlock);
     callInfo.popFormals(returnBlock);
 
     MPhi* retPhi = MPhi::New(alloc());
     returnBlock->addPhi(retPhi);
@@ -6040,16 +4466,17 @@ IonBuilder::inlineCalls(CallInfo& callIn
             choiceSet[i] = false;
             trackOptimizationOutcome(TrackedOutcome::CantInlineNotInDispatch);
             continue;
         }
 
         MBasicBlock* inlineBlock = newBlock(dispatchBlock, pc);
         if (!inlineBlock)
             return false;
+        graph().addBlock(inlineBlock);
 
         // Create a function MConstant to use in the entry ResumePoint. If we
         // can't use a constant, add a no-op MPolyInlineGuard, to prevent
         // hoisting env chain gets above the dispatch instruction.
         MInstruction* funcDef;
         if (target->isSingleton())
             funcDef = MConstant::New(alloc(), ObjectValue(*target), constraints());
         else
@@ -7751,85 +6178,89 @@ IonBuilder::jsop_initelem_getter_setter(
     MDefinition* obj = current->peek(-1);
 
     MInitElemGetterSetter* init = MInitElemGetterSetter::New(alloc(), obj, id, value);
     current->add(init);
     return resumeAfter(init);
 }
 
 MBasicBlock*
-IonBuilder::addBlock(MBasicBlock* block, uint32_t loopDepth)
-{
-    if (!block)
-        return nullptr;
-    if (block->pc() && script()->hasScriptCounts())
-        block->setHitCount(script()->getHitCount(block->pc()));
-    graph().addBlock(block);
-    block->setLoopDepth(loopDepth);
-    return block;
-}
-
-MBasicBlock*
 IonBuilder::newBlock(MBasicBlock* predecessor, jsbytecode* pc)
 {
     MBasicBlock* block = MBasicBlock::New(graph(), &analysis(), info(), predecessor,
                                           bytecodeSite(pc), MBasicBlock::NORMAL);
-    return addBlock(block, loopDepth_);
+    if (!block) {
+        abortReason_ = AbortReason_Alloc;
+        return nullptr;
+    }
+
+    block->setLoopDepth(loopDepth_);
+    return block;
 }
 
 MBasicBlock*
 IonBuilder::newBlock(MBasicBlock* predecessor, jsbytecode* pc, MResumePoint* priorResumePoint)
 {
     MBasicBlock* block = MBasicBlock::NewWithResumePoint(graph(), info(), predecessor,
                                                          bytecodeSite(pc), priorResumePoint);
-    return addBlock(block, loopDepth_);
+    if (!block) {
+        abortReason_ = AbortReason_Alloc;
+        return nullptr;
+    }
+
+    block->setLoopDepth(loopDepth_);
+    return block;
 }
 
 MBasicBlock*
 IonBuilder::newBlockPopN(MBasicBlock* predecessor, jsbytecode* pc, uint32_t popped)
 {
     MBasicBlock* block = MBasicBlock::NewPopN(graph(), info(), predecessor, bytecodeSite(pc),
                                               MBasicBlock::NORMAL, popped);
-    return addBlock(block, loopDepth_);
+    if (!block) {
+        abortReason_ = AbortReason_Alloc;
+        return nullptr;
+    }
+
+    block->setLoopDepth(loopDepth_);
+    return block;
 }
 
 MBasicBlock*
 IonBuilder::newBlockAfter(MBasicBlock* at, MBasicBlock* predecessor, jsbytecode* pc)
 {
     MBasicBlock* block = MBasicBlock::New(graph(), &analysis(), info(), predecessor,
                                           bytecodeSite(pc), MBasicBlock::NORMAL);
-    if (!block)
+    if (!block) {
+        abortReason_ = AbortReason_Alloc;
         return nullptr;
+    }
+
+    block->setLoopDepth(loopDepth_);
     block->setHitCount(0); // osr block
     graph().insertBlockAfter(at, block);
     return block;
 }
 
 MBasicBlock*
-IonBuilder::newBlock(MBasicBlock* predecessor, jsbytecode* pc, uint32_t loopDepth)
-{
-    MBasicBlock* block = MBasicBlock::New(graph(), &analysis(), info(), predecessor,
-                                          bytecodeSite(pc), MBasicBlock::NORMAL);
-    return addBlock(block, loopDepth);
-}
-
-MBasicBlock*
-IonBuilder::newOsrPreheader(MBasicBlock* predecessor, jsbytecode* loopEntry, jsbytecode* beforeLoopEntry)
-{
-    MOZ_ASSERT(LoopEntryCanIonOsr(loopEntry));
-    MOZ_ASSERT(loopEntry == info().osrPc());
+IonBuilder::newOsrPreheader(MBasicBlock* predecessor, jsbytecode* loopEntry,
+                            jsbytecode* beforeLoopEntry)
+{
+    MOZ_ASSERT(loopEntry == GetNextPc(info().osrPc()));
 
     // Create two blocks: one for the OSR entry with no predecessors, one for
     // the preheader, which has the OSR entry block as a predecessor. The
     // OSR block is always the second block (with id 1).
     MBasicBlock* osrBlock  = newBlockAfter(*graph().begin(), loopEntry);
     MBasicBlock* preheader = newBlock(predecessor, loopEntry);
     if (!osrBlock || !preheader)
         return nullptr;
 
+    graph().addBlock(preheader);
+
     // Give the pre-header the same hit count as the code before the loop.
     if (script()->hasScriptCounts())
         preheader->setHitCount(script()->getHitCount(beforeLoopEntry));
 
     MOsrEntry* entry = MOsrEntry::New(alloc());
     osrBlock->add(entry);
 
     // Initialize |envChain|.
@@ -7982,24 +6413,26 @@ IonBuilder::newOsrPreheader(MBasicBlock*
 
     return preheader;
 }
 
 MBasicBlock*
 IonBuilder::newPendingLoopHeader(MBasicBlock* predecessor, jsbytecode* pc, bool osr, bool canOsr,
                                  unsigned stackPhiCount)
 {
-    loopDepth_++;
     // If this site can OSR, all values on the expression stack are part of the loop.
     if (canOsr)
         stackPhiCount = predecessor->stackDepth() - info().firstStackSlot();
+
     MBasicBlock* block = MBasicBlock::NewPendingLoopHeader(graph(), info(), predecessor,
                                                            bytecodeSite(pc), stackPhiCount);
-    if (!addBlock(block, loopDepth_))
+    if (!block) {
+        abortReason_ = AbortReason_Alloc;
         return nullptr;
+    }
 
     if (osr) {
         // Incorporate type information from the OSR frame into the loop
         // header. The OSR frame may have unexpected types due to type changes
         // within the loop body or due to incomplete profiling information,
         // in which case this may avoid restarts of loop analysis or bailouts
         // during the OSR itself.
 
@@ -13403,17 +11836,17 @@ IonBuilder::jsop_setarg(uint32_t arg)
         current->add(MSetArgumentsObjectArg::New(alloc(), current->argumentsObject(),
                                                  GET_ARGNO(pc), val));
         return true;
     }
 
     // :TODO: if hasArguments() is true, and the script has a JSOP_SETARG, then
     // convert all arg accesses to go through the arguments object. (see Bug 957475)
     if (info().hasArguments())
-	return abort("NYI: arguments & setarg.");
+        return abort("NYI: arguments & setarg.");
 
     // Otherwise, if a magic arguments is in use, and it aliases formals, and there exist
     // arguments[...] GETELEM expressions in the script, then SetFrameArgument must be used.
     // If no arguments[...] GETELEM expressions are in the script, and an argsobj is not
     // required, then it means that any aliased argument set can never be observed, and
     // the frame does not actually need to be updated with the new arg value.
     if (info().argumentsAliasesFormals()) {
         // JSOP_SETARG with magic arguments within inline frames is not yet supported.
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -10,16 +10,17 @@
 // This file declares the data structures for building a MIRGraph from a
 // JSScript.
 
 #include "mozilla/LinkedList.h"
 
 #include "jit/BaselineInspector.h"
 #include "jit/BytecodeAnalysis.h"
 #include "jit/IonAnalysis.h"
+#include "jit/IonControlFlow.h"
 #include "jit/IonOptimizationLevels.h"
 #include "jit/MIR.h"
 #include "jit/MIRGenerator.h"
 #include "jit/MIRGraph.h"
 #include "jit/OptimizationTracking.h"
 
 namespace js {
 namespace jit {
@@ -34,183 +35,16 @@ enum class InlinableNative : uint16_t;
 // when later used off thread.
 BaselineFrameInspector*
 NewBaselineFrameInspector(TempAllocator* temp, BaselineFrame* frame, CompileInfo* info);
 
 class IonBuilder
   : public MIRGenerator,
     public mozilla::LinkedListElement<IonBuilder>
 {
-    enum ControlStatus {
-        ControlStatus_Error,
-        ControlStatus_Abort,
-        ControlStatus_Ended,        // There is no continuation/join point.
-        ControlStatus_Joined,       // Created a join node.
-        ControlStatus_Jumped,       // Parsing another branch at the same level.
-        ControlStatus_None          // No control flow.
-    };
-
-    struct DeferredEdge : public TempObject
-    {
-        MBasicBlock* block;
-        DeferredEdge* next;
-
-        DeferredEdge(MBasicBlock* block, DeferredEdge* next)
-          : block(block), next(next)
-        { }
-    };
-
-    struct ControlFlowInfo {
-        // Entry in the cfgStack.
-        uint32_t cfgEntry;
-
-        // Label that continues go to.
-        jsbytecode* continuepc;
-
-        ControlFlowInfo(uint32_t cfgEntry, jsbytecode* continuepc)
-          : cfgEntry(cfgEntry),
-            continuepc(continuepc)
-        { }
-    };
-
-    // To avoid recursion, the bytecode analyzer uses a stack where each entry
-    // is a small state machine. As we encounter branches or jumps in the
-    // bytecode, we push information about the edges on the stack so that the
-    // CFG can be built in a tree-like fashion.
-    struct CFGState {
-        enum State {
-            IF_TRUE,            // if() { }, no else.
-            IF_TRUE_EMPTY_ELSE, // if() { }, empty else
-            IF_ELSE_TRUE,       // if() { X } else { }
-            IF_ELSE_FALSE,      // if() { } else { X }
-            DO_WHILE_LOOP_BODY, // do { x } while ()
-            DO_WHILE_LOOP_COND, // do { } while (x)
-            WHILE_LOOP_COND,    // while (x) { }
-            WHILE_LOOP_BODY,    // while () { x }
-            FOR_LOOP_COND,      // for (; x;) { }
-            FOR_LOOP_BODY,      // for (; ;) { x }
-            FOR_LOOP_UPDATE,    // for (; ; x) { }
-            TABLE_SWITCH,       // switch() { x }
-            COND_SWITCH_CASE,   // switch() { case X: ... }
-            COND_SWITCH_BODY,   // switch() { case ...: X }
-            AND_OR,             // && x, || x
-            LABEL,              // label: x
-            TRY                 // try { x } catch(e) { }
-        };
-
-        State state;            // Current state of this control structure.
-        jsbytecode* stopAt;     // Bytecode at which to stop the processing loop.
-
-        // For if structures, this contains branch information.
-        union {
-            struct {
-                MBasicBlock* ifFalse;
-                jsbytecode* falseEnd;
-                MBasicBlock* ifTrue;    // Set when the end of the true path is reached.
-                MTest* test;
-            } branch;
-            struct {
-                // Common entry point.
-                MBasicBlock* entry;
-
-                // Whether OSR is being performed for this loop.
-                bool osr;
-
-                // Position of where the loop body starts and ends.
-                jsbytecode* bodyStart;
-                jsbytecode* bodyEnd;
-
-                // pc immediately after the loop exits.
-                jsbytecode* exitpc;
-
-                // pc for 'continue' jumps.
-                jsbytecode* continuepc;
-
-                // Common exit point. Created lazily, so it may be nullptr.
-                MBasicBlock* successor;
-
-                // Deferred break and continue targets.
-                DeferredEdge* breaks;
-                DeferredEdge* continues;
-
-                // Initial state, in case loop processing is restarted.
-                State initialState;
-                jsbytecode* initialPc;
-                jsbytecode* initialStopAt;
-                jsbytecode* loopHead;
-
-                // For-loops only.
-                jsbytecode* condpc;
-                jsbytecode* updatepc;
-                jsbytecode* updateEnd;
-            } loop;
-            struct {
-                // pc immediately after the switch.
-                jsbytecode* exitpc;
-
-                // Deferred break and continue targets.
-                DeferredEdge* breaks;
-
-                // MIR instruction
-                MTableSwitch* ins;
-
-                // The number of current successor that get mapped into a block.
-                uint32_t currentBlock;
-
-            } tableswitch;
-            struct {
-                // Vector of body blocks to process after the cases.
-                FixedList<MBasicBlock*>* bodies;
-
-                // When processing case statements, this counter points at the
-                // last uninitialized body.  When processing bodies, this
-                // counter targets the next body to process.
-                uint32_t currentIdx;
-
-                // Remember the block index of the default case.
-                jsbytecode* defaultTarget;
-                uint32_t defaultIdx;
-
-                // Block immediately after the switch.
-                jsbytecode* exitpc;
-                DeferredEdge* breaks;
-            } condswitch;
-            struct {
-                DeferredEdge* breaks;
-            } label;
-            struct {
-                MBasicBlock* successor;
-            } try_;
-        };
-
-        inline bool isLoop() const {
-            switch (state) {
-              case DO_WHILE_LOOP_COND:
-              case DO_WHILE_LOOP_BODY:
-              case WHILE_LOOP_COND:
-              case WHILE_LOOP_BODY:
-              case FOR_LOOP_COND:
-              case FOR_LOOP_BODY:
-              case FOR_LOOP_UPDATE:
-                return true;
-              default:
-                return false;
-            }
-        }
-
-        static CFGState If(jsbytecode* join, MTest* test);
-        static CFGState IfElse(jsbytecode* trueEnd, jsbytecode* falseEnd, MTest* test);
-        static CFGState AndOr(jsbytecode* join, MBasicBlock* lhs);
-        static CFGState TableSwitch(jsbytecode* exitpc, MTableSwitch* ins);
-        static CFGState CondSwitch(IonBuilder* builder, jsbytecode* exitpc, jsbytecode* defaultTarget);
-        static CFGState Label(jsbytecode* exitpc);
-        static CFGState Try(jsbytecode* exitpc, MBasicBlock* successor);
-    };
-
-    static int CmpSuccessors(const void* a, const void* b);
 
   public:
     IonBuilder(JSContext* analysisContext, CompileCompartment* comp,
                const JitCompileOptions& options, TempAllocator* temp,
                MIRGraph* graph, CompilerConstraintList* constraints,
                BaselineInspector* inspector, CompileInfo* info,
                const OptimizationInfo* optimizationInfo, BaselineFrameInspector* baselineFrame,
                size_t inliningDepth = 0, uint32_t loopDepth = 0);
@@ -219,126 +53,85 @@ class IonBuilder
     // call overrecursed, if false is returned.  Overrecursion is not
     // signaled as OOM and will not in general be caught by OOM paths.
     MOZ_MUST_USE bool build();
     MOZ_MUST_USE bool buildInline(IonBuilder* callerBuilder, MResumePoint* callerResumePoint,
                                   CallInfo& callInfo);
 
   private:
     MOZ_MUST_USE bool traverseBytecode();
-    ControlStatus snoopControlFlow(JSOp op);
     MOZ_MUST_USE bool processIterators();
     MOZ_MUST_USE bool inspectOpcode(JSOp op);
     uint32_t readIndex(jsbytecode* pc);
     JSAtom* readAtom(jsbytecode* pc);
     bool abort(const char* message, ...) MOZ_FORMAT_PRINTF(2, 3);
     void trackActionableAbort(const char* message);
     void spew(const char* message);
 
     JSFunction* getSingleCallTarget(TemporaryTypeSet* calleeTypes);
     MOZ_MUST_USE bool getPolyCallTargets(TemporaryTypeSet* calleeTypes, bool constructing,
                                          ObjectVector& targets, uint32_t maxTargets);
 
-    void popCfgStack();
-    DeferredEdge* filterDeadDeferredEdges(DeferredEdge* edge);
-    MOZ_MUST_USE bool processDeferredContinues(CFGState& state);
-    ControlStatus processControlEnd();
-    ControlStatus processCfgStack();
-    ControlStatus processCfgEntry(CFGState& state);
-    ControlStatus processIfEnd(CFGState& state);
-    ControlStatus processIfElseTrueEnd(CFGState& state);
-    ControlStatus processIfElseFalseEnd(CFGState& state);
-    ControlStatus processDoWhileBodyEnd(CFGState& state);
-    ControlStatus processDoWhileCondEnd(CFGState& state);
-    ControlStatus processWhileCondEnd(CFGState& state);
-    ControlStatus processWhileBodyEnd(CFGState& state);
-    ControlStatus processForCondEnd(CFGState& state);
-    ControlStatus processForBodyEnd(CFGState& state);
-    ControlStatus processForUpdateEnd(CFGState& state);
-    ControlStatus processNextTableSwitchCase(CFGState& state);
-    ControlStatus processCondSwitchCase(CFGState& state);
-    ControlStatus processCondSwitchBody(CFGState& state);
-    ControlStatus processSwitchBreak(JSOp op);
-    ControlStatus processSwitchEnd(DeferredEdge* breaks, jsbytecode* exitpc);
-    ControlStatus processAndOrEnd(CFGState& state);
-    ControlStatus processLabelEnd(CFGState& state);
-    ControlStatus processTryEnd(CFGState& state);
-    ControlStatus processReturn(JSOp op);
-    ControlStatus processThrow();
-    ControlStatus processContinue(JSOp op);
-    ControlStatus processBreak(JSOp op, jssrcnote* sn);
-    ControlStatus maybeLoop(JSOp op, jssrcnote* sn);
-    MOZ_MUST_USE bool pushLoop(CFGState::State state, jsbytecode* stopAt, MBasicBlock* entry,
-                               bool osr, jsbytecode* loopHead, jsbytecode* initialPc,
-                               jsbytecode* bodyStart, jsbytecode* bodyEnd,
-                               jsbytecode* exitpc, jsbytecode* continuepc);
-    MOZ_MUST_USE bool analyzeNewLoopTypes(MBasicBlock* entry, jsbytecode* start, jsbytecode* end);
+    MOZ_MUST_USE bool analyzeNewLoopTypes(const CFGBlock* loopEntryBlock);
 
-    MBasicBlock* addBlock(MBasicBlock* block, uint32_t loopDepth);
     MBasicBlock* newBlock(MBasicBlock* predecessor, jsbytecode* pc);
-    MBasicBlock* newBlock(MBasicBlock* predecessor, jsbytecode* pc, uint32_t loopDepth);
     MBasicBlock* newBlock(MBasicBlock* predecessor, jsbytecode* pc, MResumePoint* priorResumePoint);
     MBasicBlock* newBlockPopN(MBasicBlock* predecessor, jsbytecode* pc, uint32_t popped);
     MBasicBlock* newBlockAfter(MBasicBlock* at, MBasicBlock* predecessor, jsbytecode* pc);
     MBasicBlock* newOsrPreheader(MBasicBlock* header, jsbytecode* loopEntry,
                                  jsbytecode* beforeLoopEntry);
     MBasicBlock* newPendingLoopHeader(MBasicBlock* predecessor, jsbytecode* pc, bool osr, bool canOsr,
                                       unsigned stackPhiCount);
     MBasicBlock* newBlock(jsbytecode* pc) {
         return newBlock(nullptr, pc);
     }
     MBasicBlock* newBlockAfter(MBasicBlock* at, jsbytecode* pc) {
         return newBlockAfter(at, nullptr, pc);
     }
 
+    MOZ_MUST_USE bool visitBlock(const CFGBlock* hblock, MBasicBlock* mblock);
+    MOZ_MUST_USE bool visitControlInstruction(CFGControlInstruction* ins, bool* restarted);
+    MOZ_MUST_USE bool visitTest(CFGTest* test);
+    MOZ_MUST_USE bool visitCompare(CFGCompare* compare);
+    MOZ_MUST_USE bool visitLoopEntry(CFGLoopEntry* loopEntry);
+    MOZ_MUST_USE bool visitReturn(CFGControlInstruction* ins);
+    MOZ_MUST_USE bool visitGoto(CFGGoto* ins);
+    MOZ_MUST_USE bool visitBackEdge(CFGBackEdge* ins, bool* restarted);
+    MOZ_MUST_USE bool visitTry(CFGTry* test);
+    MOZ_MUST_USE bool visitThrow(CFGThrow* ins);
+    MOZ_MUST_USE bool visitTableSwitch(CFGTableSwitch* ins);
+
     // We want to make sure that our MTest instructions all check whether the
     // thing being tested might emulate undefined.  So we funnel their creation
     // through this method, to make sure that happens.  We don't want to just do
     // the check in MTest::New, because that can run on background compilation
     // threads, and we're not sure it's safe to touch that part of the typeset
     // from a background thread.
     MTest* newTest(MDefinition* ins, MBasicBlock* ifTrue, MBasicBlock* ifFalse);
 
-    // Given a list of pending breaks, creates a new block and inserts a Goto
-    // linking each break to the new block.
-    MBasicBlock* createBreakCatchBlock(DeferredEdge* edge, jsbytecode* pc);
-
-    // Finishes loops that do not actually loop, containing only breaks and
-    // returns or a do while loop with a condition that is constant false.
-    ControlStatus processBrokenLoop(CFGState& state);
-
-    // Computes loop phis, places them in all successors of a loop, then
-    // handles any pending breaks.
-    ControlStatus finishLoop(CFGState& state, MBasicBlock* successor);
-
     // Incorporates a type/typeSet into an OSR value for a loop, after the loop
     // body has been processed.
     MOZ_MUST_USE bool addOsrValueTypeBarrier(uint32_t slot, MInstruction** def,
                                              MIRType type, TemporaryTypeSet* typeSet);
     MOZ_MUST_USE bool maybeAddOsrTypeBarriers();
 
     // Restarts processing of a loop if the type information at its header was
     // incomplete.
-    ControlStatus restartLoop(const CFGState& state);
-
-    void assertValidLoopHeadOp(jsbytecode* pc);
-
-    ControlStatus forLoop(JSOp op, jssrcnote* sn);
-    ControlStatus whileOrForInLoop(jssrcnote* sn);
-    ControlStatus doWhileLoop(JSOp op, jssrcnote* sn);
-    ControlStatus tableSwitch(JSOp op, jssrcnote* sn);
-    ControlStatus condSwitch(JSOp op, jssrcnote* sn);
+    bool restartLoop(const CFGBlock* header);
+    bool initLoopEntry();
 
     // Please see the Big Honkin' Comment about how resume points work in
     // IonBuilder.cpp, near the definition for this function.
     MOZ_MUST_USE bool resume(MInstruction* ins, jsbytecode* pc, MResumePoint::Mode mode);
     MOZ_MUST_USE bool resumeAt(MInstruction* ins, jsbytecode* pc);
     MOZ_MUST_USE bool resumeAfter(MInstruction* ins);
     MOZ_MUST_USE bool maybeInsertResume();
 
+    bool blockIsOSREntry(const CFGBlock* block, const CFGBlock* predecessor);
+
     void insertRecompileCheck();
 
     MOZ_MUST_USE bool initParameters();
     void initLocals();
     void rewriteParameter(uint32_t slotIdx, MDefinition* param, int32_t argIndex);
     MOZ_MUST_USE bool rewriteParameters();
     MOZ_MUST_USE bool initEnvironmentChain(MDefinition* callee = nullptr);
     MOZ_MUST_USE bool initArgumentsObject();
@@ -703,20 +496,17 @@ class IonBuilder
     MOZ_MUST_USE bool jsop_checklexical();
     MOZ_MUST_USE bool jsop_checkaliasedlexical(EnvironmentCoordinate ec);
     MOZ_MUST_USE bool jsop_funcall(uint32_t argc);
     MOZ_MUST_USE bool jsop_funapply(uint32_t argc);
     MOZ_MUST_USE bool jsop_funapplyarguments(uint32_t argc);
     MOZ_MUST_USE bool jsop_funapplyarray(uint32_t argc);
     MOZ_MUST_USE bool jsop_call(uint32_t argc, bool constructing);
     MOZ_MUST_USE bool jsop_eval(uint32_t argc);
-    MOZ_MUST_USE bool jsop_ifeq(JSOp op);
-    MOZ_MUST_USE bool jsop_try();
     MOZ_MUST_USE bool jsop_label();
-    MOZ_MUST_USE bool jsop_condswitch();
     MOZ_MUST_USE bool jsop_andor(JSOp op);
     MOZ_MUST_USE bool jsop_dup2();
     MOZ_MUST_USE bool jsop_loophead(jsbytecode* pc);
     MOZ_MUST_USE bool jsop_compare(JSOp op);
     MOZ_MUST_USE bool jsop_compare(JSOp op, MDefinition* left, MDefinition* right);
     MOZ_MUST_USE bool getStaticName(JSObject* staticObject, PropertyName* name, bool* psucceeded,
                                     MDefinition* lexicalCheck = nullptr);
     MOZ_MUST_USE bool loadStaticSlot(JSObject* staticObject, BarrierKind barrier,
@@ -1188,16 +978,19 @@ class IonBuilder
     uint32_t* bytecodeTypeMap;
 
     GSNCache gsn;
     EnvironmentCoordinateNameCache envCoordinateNameCache;
 
     jsbytecode* pc;
     MBasicBlock* current;
     uint32_t loopDepth_;
+    Vector<MBasicBlock*, 0, JitAllocPolicy> blockWorklist;
+    const CFGBlock* cfgCurrent;
+    const ControlFlowGraph* cfg;
 
     Vector<BytecodeSite*, 0, JitAllocPolicy> trackedOptimizationSites_;
 
     BytecodeSite* bytecodeSite(jsbytecode* pc) {
         MOZ_ASSERT(info().inlineScriptTree()->script()->containsPC(pc));
         // See comment in maybeTrackedOptimizationSite.
         if (isOptimizationTrackingEnabled()) {
             if (BytecodeSite* site = maybeTrackedOptimizationSite(pc))
@@ -1233,22 +1026,23 @@ class IonBuilder
         jsbytecode* pc;
         MBasicBlock* header;
 
         LoopHeader(jsbytecode* pc, MBasicBlock* header)
           : pc(pc), header(header)
         {}
     };
 
-    Vector<CFGState, 8, JitAllocPolicy> cfgStack_;
-    Vector<ControlFlowInfo, 4, JitAllocPolicy> loops_;
-    Vector<ControlFlowInfo, 0, JitAllocPolicy> switches_;
-    Vector<ControlFlowInfo, 2, JitAllocPolicy> labels_;
     Vector<MInstruction*, 2, JitAllocPolicy> iterators_;
     Vector<LoopHeader, 0, JitAllocPolicy> loopHeaders_;
+    Vector<MBasicBlock*, 0, JitAllocPolicy> loopHeaderStack_;
+#ifdef DEBUG
+    Vector<const CFGBlock*, 0, JitAllocPolicy> cfgLoopHeaderStack_;
+#endif
+
     BaselineInspector* inspector;
 
     size_t inliningDepth_;
 
     // Total bytecode length of all inlined scripts. Only tracked for the
     // outermost builder.
     size_t inlinedBytecodeLength_;
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit/IonControlFlow.cpp
@@ -0,0 +1,2138 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "jit/IonControlFlow.h"
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/SizePrintfMacros.h"
+
+using namespace js;
+using namespace js::jit;
+using mozilla::DebugOnly;
+
+ControlFlowGenerator::ControlFlowGenerator(TempAllocator& temp, JSScript* script)
+  : script(script),
+    current(nullptr),
+    alloc_(temp),
+    blocks_(temp),
+    cfgStack_(temp),
+    loops_(temp),
+    switches_(temp),
+    labels_(temp),
+    analysis_(temp, script),
+    aborted_(false)
+{ }
+
+bool
+ControlFlowGenerator::init()
+{
+    return analysis_.init(alloc(), gsn);
+}
+
+static inline int32_t
+GetJumpOffset(jsbytecode* pc)
+{
+    MOZ_ASSERT(CodeSpec[JSOp(*pc)].type() == JOF_JUMP);
+    return GET_JUMP_OFFSET(pc);
+}
+
+void
+ControlFlowGraph::dump(GenericPrinter& print, JSScript* script)
+{
+    if (blocks_.length() == 0) {
+        print.printf("Didn't run yet.\n");
+        return;
+    }
+
+    fprintf(stderr, "Dumping cfg:\n\n");
+    for (size_t i = 0; i < blocks_.length(); i++) {
+        print.printf(" Block %" PRIuSIZE ", %" PRIuSIZE ":%" PRIuSIZE "\n",
+                     blocks_[i].id(),
+                     script->pcToOffset(blocks_[i].startPc()),
+                     script->pcToOffset(blocks_[i].stopPc()));
+
+        jsbytecode* pc = blocks_[i].startPc();
+        for ( ; pc < blocks_[i].stopPc(); pc += CodeSpec[JSOp(*pc)].length) {
+            MOZ_ASSERT(pc < script->codeEnd());
+            print.printf("  %" PRIuSIZE ": %s\n", script->pcToOffset(pc),
+                                                  CodeName[JSOp(*pc)]);
+        }
+
+        if (blocks_[i].stopIns()->isGoto()) {
+            print.printf("  %s (popping:%" PRIuSIZE ") [",
+                         blocks_[i].stopIns()->Name(),
+                         blocks_[i].stopIns()->toGoto()->popAmount());
+        } else {
+            print.printf("  %s [", blocks_[i].stopIns()->Name());
+        }
+        for (size_t j=0; j<blocks_[i].stopIns()->numSuccessors(); j++) {
+            if (j!=0)
+                print.printf(", ");
+            print.printf("%" PRIuSIZE, blocks_[i].stopIns()->getSuccessor(j)->id());
+        }
+        print.printf("]\n\n");
+    }
+}
+
+bool
+ControlFlowGraph::init(TempAllocator& alloc, const CFGBlockVector& blocks)
+{
+    if (!blocks_.reserve(blocks.length()))
+        return false;
+
+    for (size_t i = 0; i < blocks.length(); i++) {
+        MOZ_ASSERT(blocks[i]->id() == i);
+        CFGBlock block(blocks[i]->startPc());
+
+        block.setStopPc(blocks[i]->stopPc());
+        block.setId(i);
+        blocks_.infallibleAppend(mozilla::Move(block));
+    }
+
+    for (size_t i = 0; i < blocks.length(); i++) {
+        if (!alloc.ensureBallast())
+            return false;
+
+        CFGControlInstruction* copy = nullptr;
+        CFGControlInstruction* ins = blocks[i]->stopIns();
+        switch (ins->type()) {
+          case CFGControlInstruction::Type_Goto: {
+            CFGBlock* successor = &blocks_[ins->getSuccessor(0)->id()];
+            copy = CFGGoto::New(alloc, successor, ins->toGoto()->popAmount());
+            break;
+          }
+          case CFGControlInstruction::Type_BackEdge: {
+            CFGBlock* successor = &blocks_[ins->getSuccessor(0)->id()];
+            copy = CFGBackEdge::New(alloc, successor);
+            break;
+          }
+          case CFGControlInstruction::Type_LoopEntry: {
+            CFGLoopEntry* old = ins->toLoopEntry();
+            CFGBlock* successor = &blocks_[ins->getSuccessor(0)->id()];
+            copy = CFGLoopEntry::New(alloc, successor, old->canOsr(), old->stackPhiCount(),
+                                     old->loopStopPc());
+            break;
+          }
+          case CFGControlInstruction::Type_Throw: {
+            copy = CFGThrow::New(alloc);
+            break;
+          }
+          case CFGControlInstruction::Type_Test: {
+            CFGTest* old = ins->toTest();
+            CFGBlock* trueBranch = &blocks_[old->trueBranch()->id()];
+            CFGBlock* falseBranch = &blocks_[old->falseBranch()->id()];
+            copy = CFGTest::New(alloc, trueBranch, falseBranch, old->mustKeepCondition());
+            break;
+          }
+          case CFGControlInstruction::Type_Compare: {
+            CFGCompare* old = ins->toCompare();
+            CFGBlock* trueBranch = &blocks_[old->trueBranch()->id()];
+            CFGBlock* falseBranch = &blocks_[old->falseBranch()->id()];
+            copy = CFGCompare::New(alloc, trueBranch, falseBranch);
+            break;
+          }
+          case CFGControlInstruction::Type_Return: {
+            copy = CFGReturn::New(alloc);
+            break;
+          }
+          case CFGControlInstruction::Type_RetRVal: {
+            copy = CFGRetRVal::New(alloc);
+            break;
+          }
+          case CFGControlInstruction::Type_Try: {
+            CFGTry* old = ins->toTry();
+            CFGBlock* tryBlock = &blocks_[old->tryBlock()->id()];
+            CFGBlock* merge = nullptr;
+            if (old->numSuccessors() == 2)
+                merge = &blocks_[old->afterTryCatchBlock()->id()];
+            copy = CFGTry::New(alloc, tryBlock, old->catchStartPc(), merge);
+            break;
+          }
+          case CFGControlInstruction::Type_TableSwitch: {
+            CFGTableSwitch* old = ins->toTableSwitch();
+            CFGTableSwitch* tableSwitch =
+                CFGTableSwitch::New(alloc, old->low(), old->high());
+            if (!tableSwitch->addDefault(&blocks_[old->defaultCase()->id()]))
+                return false;
+            for (size_t i = 0; i < ins->numSuccessors() - 1; i++) {
+                if (!tableSwitch->addCase(&blocks_[old->getCase(i)->id()]))
+                    return false;
+            }
+            copy = tableSwitch;
+            break;
+          }
+        }
+        MOZ_ASSERT(copy);
+        blocks_[i].setStopIns(copy);
+    }
+    return true;
+}
+
+bool
+ControlFlowGenerator::addBlock(CFGBlock* block)
+{
+    block->setId(blocks_.length());
+    return blocks_.append(block);
+}
+
+// We try to build a control-flow graph in the order that it would be built as
+// if traversing the AST. This leads to a nice ordering and lets us build SSA
+// in one pass, since the bytecode is structured.
+//
+// Things get interesting when we encounter a control structure. This can be
+// either an IFEQ, downward GOTO, or a decompiler hint stashed away in source
+// notes. Once we encounter such an opcode, we recover the structure of the
+// control flow (its branches and bounds), and push it on a stack.
+//
+// As we continue traversing the bytecode, we look for points that would
+// terminate the topmost control flow path pushed on the stack. These are:
+//  (1) The bounds of the current structure (end of a loop or join/edge of a
+//      branch).
+//  (2) A "return", "break", or "continue" statement.
+//
+// For (1), we expect that there is a current block in the progress of being
+// built, and we complete the necessary edges in the CFG. For (2), we expect
+// that there is no active block.
+bool
+ControlFlowGenerator::traverseBytecode()
+{
+    blocks_.clear();
+
+    current = CFGBlock::New(alloc(), script->code());
+    pc = current->startPc();
+
+    if (!addBlock(current))
+        return false;
+
+    for (;;) {
+        MOZ_ASSERT(pc < script->codeEnd());
+
+        for (;;) {
+            if (!alloc().ensureBallast())
+                return false;
+
+            // Check if we've hit an expected join point or edge in the bytecode.
+            // Leaving one control structure could place us at the edge of another,
+            // thus |while| instead of |if| so we don't skip any opcodes.
+            MOZ_ASSERT_IF(!cfgStack_.empty(), cfgStack_.back().stopAt >= pc);
+            if (!cfgStack_.empty() && cfgStack_.back().stopAt == pc) {
+                ControlStatus status = processCfgStack();
+                if (status == ControlStatus::Error)
+                    return false;
+                if (status == ControlStatus::Abort) {
+                    aborted_ = true;
+                    return false;
+                }
+                if (!current)
+                    return true;
+                continue;
+            }
+
+            // Some opcodes need to be handled early because they affect control
+            // flow, terminating the current basic block and/or instructing the
+            // traversal algorithm to continue from a new pc.
+            //
+            //   (1) If the opcode does not affect control flow, then the opcode
+            //       is inspected and transformed to IR. This is the process_opcode
+            //       label.
+            //   (2) A loop could be detected via a forward GOTO. In this case,
+            //       we don't want to process the GOTO, but the following
+            //       instruction.
+            //   (3) A RETURN, STOP, BREAK, or CONTINUE may require processing the
+            //       CFG stack to terminate open branches.
+            //
+            // Similar to above, snooping control flow could land us at another
+            // control flow point, so we iterate until it's time to inspect a real
+            // opcode.
+            ControlStatus status;
+            if ((status = snoopControlFlow(JSOp(*pc))) == ControlStatus::None)
+                break;
+            if (status == ControlStatus::Error)
+                return false;
+            if (status == ControlStatus::Abort) {
+                aborted_ = true;
+                return false;
+            }
+            if (!current)
+                return true;
+        }
+
+        JSOp op = JSOp(*pc);
+        pc += CodeSpec[op].length;
+    }
+
+    return true;
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::snoopControlFlow(JSOp op)
+{
+    switch (op) {
+      case JSOP_POP:
+      case JSOP_NOP: {
+        jssrcnote* sn = GetSrcNote(gsn, script, pc);
+        return maybeLoop(op, sn);
+      }
+
+      case JSOP_RETURN:
+      case JSOP_RETRVAL:
+        return processReturn(op);
+
+      case JSOP_THROW:
+        return processThrow();
+
+      case JSOP_GOTO:
+      {
+        jssrcnote* sn = GetSrcNote(gsn, script, pc);
+        switch (sn ? SN_TYPE(sn) : SRC_NULL) {
+          case SRC_BREAK:
+          case SRC_BREAK2LABEL:
+            return processBreak(op, sn);
+
+          case SRC_CONTINUE:
+            return processContinue(op);
+
+          case SRC_SWITCHBREAK:
+            return processSwitchBreak(op);
+
+          case SRC_WHILE:
+          case SRC_FOR_IN:
+          case SRC_FOR_OF:
+            // while (cond) { }
+            return processWhileOrForInLoop(sn);
+
+          default:
+            // Hard assert for now - make an error later.
+            MOZ_CRASH("unknown goto case");
+        }
+        break;
+      }
+
+      case JSOP_TABLESWITCH: {
+        jssrcnote* sn = GetSrcNote(gsn, script, pc);
+        return processTableSwitch(op, sn);
+      }
+
+      case JSOP_CONDSWITCH:
+        return processCondSwitch();
+
+      case JSOP_IFNE:
+        // We should never reach an IFNE, it's a stopAt point, which will
+        // trigger closing the loop.
+        MOZ_CRASH("we should never reach an ifne!");
+
+      case JSOP_IFEQ:
+        return processIfStart(JSOP_IFEQ);
+
+      case JSOP_AND:
+      case JSOP_OR:
+        return processAndOr(op);
+
+      case JSOP_LABEL:
+        return processLabel();
+
+      case JSOP_TRY:
+        return processTry();
+
+      case JSOP_OPTIMIZE_SPREADCALL:
+        // Not implemented yet.
+        return ControlStatus::Abort;
+
+      default:
+        break;
+    }
+    return ControlStatus::None;
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processReturn(JSOp op)
+{
+    MOZ_ASSERT(op == JSOP_RETURN || op == JSOP_RETRVAL);
+
+    CFGControlInstruction* ins;
+    if (op == JSOP_RETURN)
+        ins = CFGReturn::New(alloc());
+    else
+        ins = CFGRetRVal::New(alloc());
+    endCurrentBlock(ins);
+
+    return processControlEnd();
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processThrow()
+{
+    CFGThrow* ins = CFGThrow::New(alloc());
+    endCurrentBlock(ins);
+
+    return processControlEnd();
+}
+
+void
+ControlFlowGenerator::endCurrentBlock(CFGControlInstruction* ins)
+{
+    current->setStopPc(pc);
+    current->setStopIns(ins);
+
+    // Make sure no one tries to use this block now.
+    current = nullptr;
+}
+
+// Processes the top of the CFG stack. This is used from two places:
+// (1) processControlEnd(), whereby a break, continue, or return may interrupt
+//     an in-progress CFG structure before reaching its actual termination
+//     point in the bytecode.
+// (2) traverseBytecode(), whereby we reach the last instruction in a CFG
+//     structure.
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processCfgStack()
+{
+    ControlStatus status = processCfgEntry(cfgStack_.back());
+
+    // If this terminated a CFG structure, act like processControlEnd() and
+    // keep propagating upward.
+    while (status == ControlStatus::Ended) {
+        popCfgStack();
+        if (cfgStack_.empty())
+            return status;
+        status = processCfgEntry(cfgStack_.back());
+    }
+
+    // If some join took place, the current structure is finished.
+    if (status == ControlStatus::Joined)
+        popCfgStack();
+
+    return status;
+}
+
+// Given that the current control flow structure has ended forcefully,
+// via a return, break, or continue (rather than joining), propagate the
+// termination up. For example, a return nested 5 loops deep may terminate
+// every outer loop at once, if there are no intervening conditionals:
+//
+// for (...) {
+//   for (...) {
+//     return x;
+//   }
+// }
+//
+// If |current| is nullptr when this function returns, then there is no more
+// control flow to be processed.
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processControlEnd()
+{
+    MOZ_ASSERT(!current);
+
+    if (cfgStack_.empty()) {
+        // If there is no more control flow to process, then this is the
+        // last return in the function.
+        return ControlStatus::Ended;
+    }
+
+    return processCfgStack();
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processCfgEntry(CFGState& state)
+{
+    switch (state.state) {
+      case CFGState::IF_TRUE:
+      case CFGState::IF_TRUE_EMPTY_ELSE:
+        return processIfEnd(state);
+
+      case CFGState::IF_ELSE_TRUE:
+        return processIfElseTrueEnd(state);
+
+      case CFGState::IF_ELSE_FALSE:
+        return processIfElseFalseEnd(state);
+
+      case CFGState::DO_WHILE_LOOP_BODY:
+        return processDoWhileBodyEnd(state);
+
+      case CFGState::DO_WHILE_LOOP_COND:
+        return processDoWhileCondEnd(state);
+
+      case CFGState::WHILE_LOOP_COND:
+        return processWhileCondEnd(state);
+
+      case CFGState::WHILE_LOOP_BODY:
+        return processWhileBodyEnd(state);
+
+      case CFGState::FOR_LOOP_COND:
+        return processForCondEnd(state);
+
+      case CFGState::FOR_LOOP_BODY:
+        return processForBodyEnd(state);
+
+      case CFGState::FOR_LOOP_UPDATE:
+        return processForUpdateEnd(state);
+
+      case CFGState::TABLE_SWITCH:
+        return processNextTableSwitchCase(state);
+
+      case CFGState::COND_SWITCH_CASE:
+        return processCondSwitchCase(state);
+
+      case CFGState::COND_SWITCH_BODY:
+        return processCondSwitchBody(state);
+
+      case CFGState::AND_OR:
+        return processAndOrEnd(state);
+
+      case CFGState::LABEL:
+        return processLabelEnd(state);
+
+      case CFGState::TRY:
+        return processTryEnd(state);
+
+      default:
+        MOZ_CRASH("unknown cfgstate");
+    }
+}
+
+void
+ControlFlowGenerator::popCfgStack()
+{
+    if (cfgStack_.back().isLoop())
+        loops_.popBack();
+    if (cfgStack_.back().state == CFGState::LABEL)
+        labels_.popBack();
+    cfgStack_.popBack();
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processLabelEnd(CFGState& state)
+{
+    MOZ_ASSERT(state.state == CFGState::LABEL);
+
+    // If there are no breaks and no current, controlflow is terminated.
+    if (!state.label.breaks && !current)
+        return ControlStatus::Ended;
+
+    // If there are no breaks to this label, there's nothing to do.
+    if (!state.label.breaks)
+        return ControlStatus::Joined;
+
+    CFGBlock* successor = createBreakCatchBlock(state.label.breaks, state.stopAt);
+    if (!successor)
+        return ControlStatus::Error;
+
+    if (current) {
+        current->setStopIns(CFGGoto::New(alloc(), successor));
+        current->setStopPc(pc);
+    }
+
+    current = successor;
+    pc = successor->startPc();
+
+    if (!addBlock(successor))
+        return ControlStatus::Error;
+
+    return ControlStatus::Joined;
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processTry()
+{
+    MOZ_ASSERT(JSOp(*pc) == JSOP_TRY);
+
+    // Try-finally is not yet supported.
+    if (analysis_.hasTryFinally())
+        return ControlStatus::Abort;
+
+    jssrcnote* sn = GetSrcNote(gsn, script, pc);
+    MOZ_ASSERT(SN_TYPE(sn) == SRC_TRY);
+
+    // Get the pc of the last instruction in the try block. It's a JSOP_GOTO to
+    // jump over the catch block.
+    jsbytecode* endpc = pc + GetSrcNoteOffset(sn, 0);
+    MOZ_ASSERT(JSOp(*endpc) == JSOP_GOTO);
+    MOZ_ASSERT(GetJumpOffset(endpc) > 0);
+
+    jsbytecode* afterTry = endpc + GetJumpOffset(endpc);
+
+    // If controlflow in the try body is terminated (by a return or throw
+    // statement), the code after the try-statement may still be reachable
+    // via the catch block (which we don't compile) and OSR can enter it.
+    // For example:
+    //
+    //     try {
+    //         throw 3;
+    //     } catch(e) { }
+    //
+    //     for (var i=0; i<1000; i++) {}
+    //
+    // To handle this, we create two blocks: one for the try block and one
+    // for the code following the try-catch statement.
+    //
+    // If the code after the try block is unreachable (control flow in both the
+    // try and catch blocks is terminated), only create the try block, to avoid
+    // parsing unreachable code.
+
+    CFGBlock* tryBlock = CFGBlock::New(alloc(), GetNextPc(pc));
+
+    CFGBlock* successor;
+    if (analysis_.maybeInfo(afterTry)) {
+        successor = CFGBlock::New(alloc(), afterTry);
+        current->setStopIns(CFGTry::New(alloc(), tryBlock, endpc, successor));
+    } else {
+        successor = nullptr;
+        current->setStopIns(CFGTry::New(alloc(), tryBlock, endpc));
+    }
+    current->setStopPc(pc);
+
+    if (!cfgStack_.append(CFGState::Try(endpc, successor)))
+        return ControlStatus::Error;
+
+    current = tryBlock;
+    pc = current->startPc();
+
+    if (!addBlock(current))
+        return ControlStatus::Error;
+
+    return ControlStatus::Jumped;
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processTryEnd(CFGState& state)
+{
+    MOZ_ASSERT(state.state == CFGState::TRY);
+
+    if (!state.try_.successor) {
+        MOZ_ASSERT(!current);
+        return ControlStatus::Ended;
+    }
+
+    if (current) {
+        current->setStopIns(CFGGoto::New(alloc(), state.try_.successor));
+        current->setStopPc(pc);
+    }
+
+    // Start parsing the code after this try-catch statement.
+    current = state.try_.successor;
+    pc = current->startPc();
+
+    if (!addBlock(current))
+        return ControlStatus::Error;
+
+    return ControlStatus::Joined;
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processIfEnd(CFGState& state)
+{
+    if (current) {
+        // Here, the false block is the join point. Create an edge from the
+        // current block to the false block. Note that a RETURN opcode
+        // could have already ended the block.
+        current->setStopIns(CFGGoto::New(alloc(), state.branch.ifFalse));
+        current->setStopPc(pc);
+    }
+
+    current = state.branch.ifFalse;
+    pc = current->startPc();
+
+    if (!addBlock(current))
+        return ControlStatus::Error;
+
+    return ControlStatus::Joined;
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processIfElseTrueEnd(CFGState& state)
+{
+    // We've reached the end of the true branch of an if-else. Don't
+    // create an edge yet, just transition to parsing the false branch.
+    state.state = CFGState::IF_ELSE_FALSE;
+    state.branch.ifTrue = current;
+    state.stopAt = state.branch.falseEnd;
+
+    if (current)
+        current->setStopPc(pc);
+
+    current = state.branch.ifFalse;
+    pc = current->startPc();
+
+    if (!addBlock(current))
+        return ControlStatus::Error;
+
+    return ControlStatus::Jumped;
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processIfElseFalseEnd(CFGState& state)
+{
+    // Update the state to have the latest block from the false path.
+    state.branch.ifFalse = current;
+    if (current)
+        current->setStopPc(pc);
+
+    // To create the join node, we need an incoming edge that has not been
+    // terminated yet.
+    CFGBlock* pred = state.branch.ifTrue
+                        ? state.branch.ifTrue
+                        : state.branch.ifFalse;
+    CFGBlock* other = (pred == state.branch.ifTrue)
+                        ? state.branch.ifFalse
+                        : state.branch.ifTrue;
+
+    if (!pred)
+        return ControlStatus::Ended;
+
+    // Create a new block to represent the join.
+    CFGBlock* join = CFGBlock::New(alloc(), state.branch.falseEnd);
+
+    // Create edges from the true and false blocks as needed.
+    pred->setStopIns(CFGGoto::New(alloc(), join));
+
+    if (other)
+        other->setStopIns(CFGGoto::New(alloc(), join));
+
+    // Ignore unreachable remainder of false block if existent.
+    current = join;
+    pc = current->startPc();
+
+    if (!addBlock(current))
+        return ControlStatus::Error;
+
+    return ControlStatus::Joined;
+}
+
+CFGBlock*
+ControlFlowGenerator::createBreakCatchBlock(DeferredEdge* edge, jsbytecode* pc)
+{
+    // Create block, using the first break statement as predecessor
+    CFGBlock* successor = CFGBlock::New(alloc(), pc);
+
+    // Finish up remaining breaks.
+    while (edge) {
+        CFGGoto* brk = CFGGoto::New(alloc(), successor);
+        edge->block->setStopIns(brk);
+        edge = edge->next;
+    }
+
+    return successor;
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processDoWhileBodyEnd(CFGState& state)
+{
+    if (!processDeferredContinues(state))
+        return ControlStatus::Error;
+
+    // No current means control flow cannot reach the condition, so this will
+    // never loop.
+    if (!current)
+        return processBrokenLoop(state);
+
+    CFGBlock* header = CFGBlock::New(alloc(), state.loop.updatepc);
+    current->setStopIns(CFGGoto::New(alloc(), header));
+    current->setStopPc(pc);
+
+    state.state = CFGState::DO_WHILE_LOOP_COND;
+    state.stopAt = state.loop.updateEnd;
+
+    current = header;
+    pc = header->startPc();
+
+    if (!addBlock(current))
+        return ControlStatus::Error;
+
+    return ControlStatus::Jumped;
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processDoWhileCondEnd(CFGState& state)
+{
+    MOZ_ASSERT(JSOp(*pc) == JSOP_IFNE);
+
+    // We're guaranteed a |current|, it's impossible to break or return from
+    // inside the conditional expression.
+    MOZ_ASSERT(current);
+
+    // Create the successor block.
+    CFGBlock* successor = CFGBlock::New(alloc(), GetNextPc(pc));
+
+    CFGLoopEntry* entry = state.loop.entry->stopIns()->toLoopEntry();
+    entry->setLoopStopPc(pc);
+
+    CFGBlock* backEdge = CFGBlock::New(alloc(), pc);
+    backEdge->setStopIns(CFGBackEdge::New(alloc(), entry->successor()));
+    backEdge->setStopPc(pc);
+
+    if (!addBlock(backEdge))
+        return ControlStatus::Error;
+
+    // Create the test instruction and end the current block.
+    CFGTest* test = CFGTest::New(alloc(), backEdge, successor);
+    current->setStopIns(test);
+    current->setStopPc(pc);
+    return finishLoop(state, successor);
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processWhileCondEnd(CFGState& state)
+{
+    MOZ_ASSERT(JSOp(*pc) == JSOP_IFNE || JSOp(*pc) == JSOP_IFEQ);
+
+    // Create the body and successor blocks.
+    CFGBlock* body = CFGBlock::New(alloc(), state.loop.bodyStart);
+    state.loop.successor = CFGBlock::New(alloc(), state.loop.exitpc);
+    if (!body || !state.loop.successor)
+        return ControlStatus::Error;
+
+    CFGTest* test;
+    if (JSOp(*pc) == JSOP_IFNE)
+        test = CFGTest::New(alloc(), body, state.loop.successor);
+    else
+        test = CFGTest::New(alloc(), state.loop.successor, body);
+    current->setStopIns(test);
+    current->setStopPc(pc);
+
+    state.state = CFGState::WHILE_LOOP_BODY;
+    state.stopAt = state.loop.bodyEnd;
+
+    current = body;
+    pc = body->startPc();
+
+    if (!addBlock(current))
+        return ControlStatus::Error;
+
+    return ControlStatus::Jumped;
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processWhileBodyEnd(CFGState& state)
+{
+    if (!processDeferredContinues(state))
+        return ControlStatus::Error;
+
+    if (!current)
+        return processBrokenLoop(state);
+
+    CFGLoopEntry* entry = state.loop.entry->stopIns()->toLoopEntry();
+    entry->setLoopStopPc(pc);
+
+    current->setStopIns(CFGBackEdge::New(alloc(), entry->successor()));
+    current->setStopPc(pc);
+    return finishLoop(state, state.loop.successor);
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processForCondEnd(CFGState& state)
+{
+    MOZ_ASSERT(JSOp(*pc) == JSOP_IFNE);
+
+    // Create the body and successor blocks.
+    CFGBlock* body = CFGBlock::New(alloc(), state.loop.bodyStart);
+    state.loop.successor = CFGBlock::New(alloc(), state.loop.exitpc);
+
+    CFGTest* test = CFGTest::New(alloc(), body, state.loop.successor);
+    current->setStopIns(test);
+    current->setStopPc(pc);
+
+    state.state = CFGState::FOR_LOOP_BODY;
+    state.stopAt = state.loop.bodyEnd;
+
+    current = body;
+    pc = body->startPc();
+
+    if (!addBlock(current))
+        return ControlStatus::Error;
+
+    return ControlStatus::Jumped;
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processForBodyEnd(CFGState& state)
+{
+    if (!processDeferredContinues(state))
+        return ControlStatus::Error;
+
+    // If there is no updatepc, just go right to processing what would be the
+    // end of the update clause. Otherwise, |current| might be nullptr; if this is
+    // the case, the update is unreachable anyway.
+    if (!state.loop.updatepc || !current)
+        return processForUpdateEnd(state);
+
+    //MOZ_ASSERT(pc == state.loop.updatepc);
+
+    if (state.loop.updatepc != pc) {
+        CFGBlock* next = CFGBlock::New(alloc(), state.loop.updatepc);
+        current->setStopIns(CFGGoto::New(alloc(), next));
+        current->setStopPc(pc);
+        current = next;
+
+        if (!addBlock(current))
+            return ControlStatus::Error;
+    }
+
+    pc = state.loop.updatepc;
+
+    state.state = CFGState::FOR_LOOP_UPDATE;
+    state.stopAt = state.loop.updateEnd;
+    return ControlStatus::Jumped;
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processForUpdateEnd(CFGState& state)
+{
+    // If there is no current, we couldn't reach the loop edge and there was no
+    // update clause.
+    if (!current)
+        return processBrokenLoop(state);
+
+    CFGLoopEntry* entry = state.loop.entry->stopIns()->toLoopEntry();
+    entry->setLoopStopPc(pc);
+
+    current->setStopIns(CFGBackEdge::New(alloc(), entry->successor()));
+    current->setStopPc(pc);
+    return finishLoop(state, state.loop.successor);
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processWhileOrForInLoop(jssrcnote* sn)
+{
+    // while (cond) { } loops have the following structure:
+    //    GOTO cond   ; SRC_WHILE (offset to IFNE)
+    //    LOOPHEAD
+    //    ...
+    //  cond:
+    //    LOOPENTRY
+    //    ...
+    //    IFNE        ; goes to LOOPHEAD
+    // for (x in y) { } loops are similar; the cond will be a MOREITER.
+    MOZ_ASSERT(SN_TYPE(sn) == SRC_FOR_OF || SN_TYPE(sn) == SRC_FOR_IN || SN_TYPE(sn) == SRC_WHILE);
+    int ifneOffset = GetSrcNoteOffset(sn, 0);
+    jsbytecode* ifne = pc + ifneOffset;
+    MOZ_ASSERT(ifne > pc);
+
+    // Verify that the IFNE goes back to a loophead op.
+    MOZ_ASSERT(JSOp(*GetNextPc(pc)) == JSOP_LOOPHEAD);
+    MOZ_ASSERT(GetNextPc(pc) == ifne + GetJumpOffset(ifne));
+
+    jsbytecode* loopEntry = pc + GetJumpOffset(pc);
+
+    size_t stackPhiCount;
+    if (SN_TYPE(sn) == SRC_FOR_OF)
+        stackPhiCount = 2;
+    else if (SN_TYPE(sn) == SRC_FOR_IN)
+        stackPhiCount = 1;
+    else
+        stackPhiCount = 0;
+
+    // Skip past the JSOP_LOOPHEAD for the body start.
+    jsbytecode* loopHead = GetNextPc(pc);
+    jsbytecode* bodyStart = GetNextPc(loopHead);
+    jsbytecode* bodyEnd = pc + GetJumpOffset(pc);
+    jsbytecode* exitpc = GetNextPc(ifne);
+    jsbytecode* continuepc = pc;
+
+    CFGBlock* header = CFGBlock::New(alloc(), GetNextPc(loopEntry));
+
+    CFGLoopEntry* ins = CFGLoopEntry::New(alloc(), header, stackPhiCount);
+    if (LoopEntryCanIonOsr(loopEntry))
+        ins->setCanOsr();
+
+    current->setStopIns(ins);
+    current->setStopPc(pc);
+
+    if (!pushLoop(CFGState::WHILE_LOOP_COND, ifne, current,
+                  loopHead, bodyEnd, bodyStart, bodyEnd, exitpc, continuepc))
+    {
+        return ControlStatus::Error;
+    }
+
+    // Parse the condition first.
+    current = header;
+    pc = header->startPc();
+
+    if (!addBlock(current))
+        return ControlStatus::Error;
+
+    return ControlStatus::Jumped;
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processBrokenLoop(CFGState& state)
+{
+    MOZ_ASSERT(!current);
+
+    {
+        state.loop.entry->setStopIns(
+            CFGGoto::New(alloc(), state.loop.entry->stopIns()->toLoopEntry()->successor()));
+    }
+
+    // If the loop started with a condition (while/for) then even if the
+    // structure never actually loops, the condition itself can still fail and
+    // thus we must resume at the successor, if one exists.
+    current = state.loop.successor;
+    if (current) {
+        if (!addBlock(current))
+            return ControlStatus::Error;
+    }
+
+    // Join the breaks together and continue parsing.
+    if (state.loop.breaks) {
+        CFGBlock* block = createBreakCatchBlock(state.loop.breaks, state.loop.exitpc);
+        if (!block)
+            return ControlStatus::Error;
+
+        if (current) {
+            current->setStopIns(CFGGoto::New(alloc(), block));
+            current->setStopPc(current->startPc());
+        }
+
+        current = block;
+
+        if (!addBlock(current))
+            return ControlStatus::Error;
+    }
+
+    // If the loop is not gated on a condition, and has only returns, we'll
+    // reach this case. For example:
+    // do { ... return; } while ();
+    if (!current)
+        return ControlStatus::Ended;
+
+    // Otherwise, the loop is gated on a condition and/or has breaks so keep
+    // parsing at the successor.
+    pc = current->startPc();
+    return ControlStatus::Joined;
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::finishLoop(CFGState& state, CFGBlock* successor)
+{
+    MOZ_ASSERT(current);
+
+    if (state.loop.breaks) {
+        if (successor) {
+            if (!addBlock(successor))
+                return ControlStatus::Error;
+        }
+
+        // Create a catch block to join all break exits.
+        CFGBlock* block = createBreakCatchBlock(state.loop.breaks, state.loop.exitpc);
+        if (!block)
+            return ControlStatus::Error;
+
+        if (successor) {
+            // Finally, create an unconditional edge from the successor to the
+            // catch block.
+            successor->setStopIns(CFGGoto::New(alloc(), block));
+            successor->setStopPc(successor->startPc());
+        }
+        successor = block;
+    }
+
+    // An infinite loop (for (;;) { }) will not have a successor.
+    if (!successor) {
+        current = nullptr;
+        return ControlStatus::Ended;
+    }
+
+    current = successor;
+    pc = current->startPc();
+
+    if (!addBlock(current))
+        return ControlStatus::Error;
+
+    return ControlStatus::Joined;
+}
+
+bool
+ControlFlowGenerator::processDeferredContinues(CFGState& state)
+{
+    // If there are any continues for this loop, and there is an update block,
+    // then we need to create a new basic block to house the update.
+    if (state.loop.continues) {
+        DeferredEdge* edge = state.loop.continues;
+
+        CFGBlock* update = CFGBlock::New(alloc(), pc);
+
+        if (current) {
+            current->setStopIns(CFGGoto::New(alloc(), update));
+            current->setStopPc(pc);
+        }
+
+        // Remaining edges
+        while (edge) {
+            edge->block->setStopIns(CFGGoto::New(alloc(), update));
+            edge = edge->next;
+        }
+        state.loop.continues = nullptr;
+
+        current = update;
+        if (!addBlock(current))
+            return false;
+    }
+
+    return true;
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processCondSwitch()
+{
+    // CondSwitch op looks as follows:
+    //   condswitch [length +exit_pc; first case offset +next-case ]
+    //   {
+    //     {
+    //       ... any code ...
+    //       case (+jump) [pcdelta offset +next-case]
+    //     }+
+    //     default (+jump)
+    //     ... jump targets ...
+    //   }
+    //
+    // The default case is always emitted even if there is no default case in
+    // the source.  The last case statement pcdelta source note might have a 0
+    // offset on the last case (not all the time).
+    //
+    // A conditional evaluate the condition of each case and compare it to the
+    // switch value with a strict equality.  Cases conditions are iterated
+    // linearly until one is matching. If one case succeeds, the flow jumps into
+    // the corresponding body block.  The body block might alias others and
+    // might continue in the next body block if the body is not terminated with
+    // a break.
+    //
+    // Algorithm:
+    //  1/ Loop over the case chain to reach the default target
+    //   & Estimate the number of uniq bodies.
+    //  2/ Generate code for all cases (see processCondSwitchCase).
+    //  3/ Generate code for all bodies (see processCondSwitchBody).
+
+    MOZ_ASSERT(JSOp(*pc) == JSOP_CONDSWITCH);
+    jssrcnote* sn = GetSrcNote(gsn, script, pc);
+    MOZ_ASSERT(SN_TYPE(sn) == SRC_CONDSWITCH);
+
+    // Get the exit pc
+    jsbytecode* exitpc = pc + GetSrcNoteOffset(sn, 0);
+    jsbytecode* firstCase = pc + GetSrcNoteOffset(sn, 1);
+
+    // Iterate all cases in the conditional switch.
+    // - Stop at the default case. (always emitted after the last case)
+    // - Estimate the number of uniq bodies. This estimation might be off by 1
+    //   if the default body alias a case body.
+    jsbytecode* curCase = firstCase;
+    jsbytecode* lastTarget = GetJumpOffset(curCase) + curCase;
+    size_t nbBodies = 1; // default target and the first body.
+
+    MOZ_ASSERT(pc < curCase && curCase <= exitpc);
+    while (JSOp(*curCase) == JSOP_CASE) {
+        // Fetch the next case.
+        jssrcnote* caseSn = GetSrcNote(gsn, script, curCase);
+        MOZ_ASSERT(caseSn && SN_TYPE(caseSn) == SRC_NEXTCASE);
+        ptrdiff_t off = GetSrcNoteOffset(caseSn, 0);
+        MOZ_ASSERT_IF(off == 0, JSOp(*GetNextPc(curCase)) == JSOP_JUMPTARGET);
+        curCase = off ? curCase + off : GetNextPc(GetNextPc(curCase));
+        MOZ_ASSERT(pc < curCase && curCase <= exitpc);
+
+        // Count non-aliased cases.
+        jsbytecode* curTarget = GetJumpOffset(curCase) + curCase;
+        if (lastTarget < curTarget)
+            nbBodies++;
+        lastTarget = curTarget;
+    }
+
+    // The current case now be the default case which jump to the body of the
+    // default case, which might be behind the last target.
+    MOZ_ASSERT(JSOp(*curCase) == JSOP_DEFAULT);
+    jsbytecode* defaultTarget = GetJumpOffset(curCase) + curCase;
+    MOZ_ASSERT(curCase < defaultTarget && defaultTarget <= exitpc);
+
+    // Iterate over all cases again to find the position of the default block.
+    curCase = firstCase;
+    lastTarget = nullptr;
+    size_t defaultIdx = 0;
+    while (JSOp(*curCase) == JSOP_CASE) {
+        jsbytecode* curTarget = GetJumpOffset(curCase) + curCase;
+        if (lastTarget < defaultTarget && defaultTarget <= curTarget) {
+            if (defaultTarget < curTarget)
+                nbBodies++;
+            break;
+        }
+        if (lastTarget < curTarget)
+            defaultIdx++;
+
+        jssrcnote* caseSn = GetSrcNote(gsn, script, curCase);
+        ptrdiff_t off = GetSrcNoteOffset(caseSn, 0);
+        curCase = off ? curCase + off : GetNextPc(GetNextPc(curCase));
+        lastTarget = curTarget;
+    }
+
+    // Allocate the current graph state.
+    CFGState state = CFGState::CondSwitch(alloc(), exitpc, defaultTarget);
+    if (!state.switch_.bodies || !state.switch_.bodies->init(alloc(), nbBodies))
+        return ControlStatus::Error;
+    state.switch_.defaultIdx = defaultIdx;
+
+    // Create the default case already.
+    FixedList<CFGBlock*>& bodies = *state.switch_.bodies;
+    bodies[state.switch_.defaultIdx] = CFGBlock::New(alloc(), defaultTarget);
+
+    // Skip default case.
+    if (state.switch_.defaultIdx == 0)
+        state.switch_.currentIdx++;
+
+    // We loop on case conditions with processCondSwitchCase.
+    MOZ_ASSERT(JSOp(*firstCase) == JSOP_CASE);
+    state.stopAt = firstCase;
+    state.state = CFGState::COND_SWITCH_CASE;
+
+    if (!cfgStack_.append(state))
+        return ControlStatus::Error;
+
+    jsbytecode* nextPc = GetNextPc(pc);
+    CFGBlock* next = CFGBlock::New(alloc(), nextPc);
+
+    current->setStopIns(CFGGoto::New(alloc(), next));
+    current->setStopPc(pc);
+
+    current = next;
+    pc = current->startPc();
+
+    if (!addBlock(current))
+        return ControlStatus::Error;
+
+    return ControlStatus::Jumped;
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processCondSwitchCase(CFGState& state)
+{
+    MOZ_ASSERT(state.state == CFGState::COND_SWITCH_CASE);
+    MOZ_ASSERT(!state.switch_.breaks);
+    MOZ_ASSERT(current);
+    MOZ_ASSERT(JSOp(*pc) == JSOP_CASE);
+    FixedList<CFGBlock*>& bodies = *state.switch_.bodies;
+    uint32_t& currentIdx = state.switch_.currentIdx;
+
+    jsbytecode* lastTarget = currentIdx ? bodies[currentIdx - 1]->startPc() : nullptr;
+
+    // Fetch the following case in which we will continue.
+    jssrcnote* sn = GetSrcNote(gsn, script, pc);
+    ptrdiff_t off = GetSrcNoteOffset(sn, 0);
+    MOZ_ASSERT_IF(off == 0, JSOp(*GetNextPc(pc)) == JSOP_JUMPTARGET);
+    jsbytecode* casePc = off ? pc + off : GetNextPc(GetNextPc(pc));
+    bool nextIsDefault = JSOp(*casePc) == JSOP_DEFAULT;
+    MOZ_ASSERT(JSOp(*casePc) == JSOP_CASE || nextIsDefault);
+
+    // Allocate the block of the matching case.
+    jsbytecode* bodyTarget = pc + GetJumpOffset(pc);
+    CFGBlock* bodyBlock = nullptr;
+    if (lastTarget < bodyTarget) {
+
+        // Skip default case.
+        if (currentIdx == state.switch_.defaultIdx) {
+            currentIdx++;
+            lastTarget = bodies[currentIdx - 1]->startPc();
+            if (lastTarget < bodyTarget) {
+                bodyBlock = CFGBlock::New(alloc(), bodyTarget);
+                bodies[currentIdx++] = bodyBlock;
+            } else {
+                // This body alias the previous one.
+                MOZ_ASSERT(lastTarget == bodyTarget);
+                MOZ_ASSERT(currentIdx > 0);
+                bodyBlock = bodies[currentIdx - 1];
+            }
+        } else {
+            bodyBlock = CFGBlock::New(alloc(), bodyTarget);
+            bodies[currentIdx++] = bodyBlock;
+        }
+
+    } else {
+        // This body alias the previous one.
+        MOZ_ASSERT(lastTarget == bodyTarget);
+        MOZ_ASSERT(currentIdx > 0);
+        bodyBlock = bodies[currentIdx - 1];
+    }
+
+    CFGBlock* nextBlock = CFGBlock::New(alloc(), GetNextPc(pc));
+    CFGBlock* popBlock = CFGBlock::New(alloc(), pc);
+    current->setStopIns(CFGCompare::New(alloc(), popBlock, nextBlock));
+    current->setStopPc(pc);
+
+    if (!addBlock(popBlock))
+        return ControlStatus::Error;
+
+    popBlock->setStopIns(CFGGoto::New(alloc(), bodyBlock, 1));
+    popBlock->setStopPc(pc);
+
+    // Continue until the case condition.
+    current = nextBlock;
+    pc = current->startPc();
+    state.stopAt = casePc;
+
+    if (!addBlock(current))
+        return ControlStatus::Error;
+
+    if (!nextIsDefault)
+        return ControlStatus::Jumped;
+
+    return processCondSwitchDefault(state);
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processCondSwitchDefault(CFGState& state)
+{
+    FixedList<CFGBlock*>& bodies = *state.switch_.bodies;
+    uint32_t& currentIdx = state.switch_.currentIdx;
+    CFGBlock* bodyBlock = bodies[state.switch_.defaultIdx];
+
+    current->setStopIns(CFGGoto::New(alloc(), bodyBlock, 1));
+    current->setStopPc(pc);
+
+    // The last case condition is finished.  Loop in processCondSwitchBody,
+    // with potential stops in processSwitchBreak.  Check that the bodies
+    // fixed list is over-estimate by at most 1, and shrink the size such as
+    // length can be used as an upper bound while iterating bodies.
+
+    // Test that we calculated the number of bodies correctly.
+    // Or currentIdx is
+    MOZ_ASSERT(state.switch_.currentIdx == bodies.length() ||
+               state.switch_.defaultIdx + 1 == bodies.length());
+
+    // Handle break statements in processSwitchBreak while processing
+    // bodies.
+    ControlFlowInfo breakInfo(cfgStack_.length() - 1, state.switch_.exitpc);
+    if (!switches_.append(breakInfo))
+        return ControlStatus::Error;
+
+    // Jump into the first body.
+    currentIdx = 0;
+    current = nullptr;
+    state.state = CFGState::COND_SWITCH_BODY;
+
+    return processCondSwitchBody(state);
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processCondSwitchBody(CFGState& state)
+{
+    MOZ_ASSERT(state.state == CFGState::COND_SWITCH_BODY);
+    MOZ_ASSERT(pc <= state.switch_.exitpc);
+    FixedList<CFGBlock*>& bodies = *state.switch_.bodies;
+    uint32_t& currentIdx = state.switch_.currentIdx;
+
+    MOZ_ASSERT(currentIdx <= bodies.length());
+    if (currentIdx == bodies.length()) {
+        MOZ_ASSERT_IF(current, pc == state.switch_.exitpc);
+        return processSwitchEnd(state.switch_.breaks, state.switch_.exitpc);
+    }
+
+    // Get the next body
+    CFGBlock* nextBody = bodies[currentIdx++];
+    MOZ_ASSERT_IF(current, pc == nextBody->startPc());
+
+    // The last body continue into the new one.
+    if (current) {
+        current->setStopIns(CFGGoto::New(alloc(), nextBody));
+        current->setStopPc(pc);
+    }
+
+    // Continue in the next body.
+    current = nextBody;
+    pc = current->startPc();
+
+    if (!addBlock(current))
+        return ControlStatus::Error;
+
+    if (currentIdx < bodies.length())
+        state.stopAt = bodies[currentIdx]->startPc();
+    else
+        state.stopAt = state.switch_.exitpc;
+    return ControlStatus::Jumped;
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processAndOrEnd(CFGState& state)
+{
+    MOZ_ASSERT(current);
+    CFGBlock* lhs = state.branch.ifFalse;
+
+    // Create a new block to represent the join.
+    CFGBlock* join = CFGBlock::New(alloc(), state.stopAt);
+
+    // End the rhs.
+    current->setStopIns(CFGGoto::New(alloc(), join));
+    current->setStopPc(pc);
+
+    // End the lhs.
+    lhs->setStopIns(CFGGoto::New(alloc(), join));
+    lhs->setStopPc(pc);
+
+    // Set the join path as current path.
+    current = join;
+    pc = current->startPc();
+
+    if (!addBlock(current))
+        return ControlStatus::Error;
+
+    return ControlStatus::Joined;
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::maybeLoop(JSOp op, jssrcnote* sn)
+{
+    // This function looks at the opcode and source note and tries to
+    // determine the structure of the loop. For some opcodes, like
+    // POP/NOP which are not explicitly control flow, this source note is
+    // optional. For opcodes with control flow, like GOTO, an unrecognized
+    // or not-present source note is a compilation failure.
+    switch (op) {
+      case JSOP_POP:
+        // for (init; ; update?) ...
+        if (sn && SN_TYPE(sn) == SRC_FOR) {
+            MOZ_CRASH("Not supported anymore?");
+            return processForLoop(op, sn);
+        }
+        break;
+
+      case JSOP_NOP:
+        if (sn) {
+            // do { } while (cond)
+            if (SN_TYPE(sn) == SRC_WHILE)
+                return processDoWhileLoop(op, sn);
+            // Build a mapping such that given a basic block, whose successor
+            // has a phi
+
+            // for (; ; update?)
+            if (SN_TYPE(sn) == SRC_FOR)
+                return processForLoop(op, sn);
+        }
+        break;
+
+      default:
+        MOZ_CRASH("unexpected opcode");
+    }
+
+    return ControlStatus::None;
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processForLoop(JSOp op, jssrcnote* sn)
+{
+    // Skip the NOP.
+    MOZ_ASSERT(op == JSOP_NOP);
+    pc = GetNextPc(pc);
+
+    jsbytecode* condpc = pc + GetSrcNoteOffset(sn, 0);
+    jsbytecode* updatepc = pc + GetSrcNoteOffset(sn, 1);
+    jsbytecode* ifne = pc + GetSrcNoteOffset(sn, 2);
+    jsbytecode* exitpc = GetNextPc(ifne);
+
+    // for loops have the following structures:
+    //
+    //   NOP or POP
+    //   [GOTO cond | NOP]
+    //   LOOPHEAD
+    // body:
+    //    ; [body]
+    // [increment:]
+    //   [FRESHENBLOCKSCOPE, if needed by a cloned block]
+    //    ; [increment]
+    // [cond:]
+    //   LOOPENTRY
+    //   GOTO body
+    //
+    // If there is a condition (condpc != ifne), this acts similar to a while
+    // loop otherwise, it acts like a do-while loop.
+    //
+    // Note that currently Ion does not compile pushblockscope/popblockscope as
+    // necessary prerequisites to freshenblockscope.  So the code below doesn't
+    // and needn't consider the implications of freshenblockscope.
+    jsbytecode* bodyStart = pc;
+    jsbytecode* bodyEnd = updatepc;
+    jsbytecode* loopEntry = condpc;
+    if (condpc != ifne) {
+        MOZ_ASSERT(JSOp(*bodyStart) == JSOP_GOTO);
+        MOZ_ASSERT(bodyStart + GetJumpOffset(bodyStart) == condpc);
+        bodyStart = GetNextPc(bodyStart);
+    } else {
+        // No loop condition, such as for(j = 0; ; j++)
+        if (op != JSOP_NOP) {
+            // If the loop starts with POP, we have to skip a NOP.
+            MOZ_ASSERT(JSOp(*bodyStart) == JSOP_NOP);
+            bodyStart = GetNextPc(bodyStart);
+        }
+        loopEntry = GetNextPc(bodyStart);
+    }
+    jsbytecode* loopHead = bodyStart;
+    MOZ_ASSERT(JSOp(*bodyStart) == JSOP_LOOPHEAD);
+    MOZ_ASSERT(ifne + GetJumpOffset(ifne) == bodyStart);
+    bodyStart = GetNextPc(bodyStart);
+
+    MOZ_ASSERT(JSOp(*loopEntry) == JSOP_LOOPENTRY);
+
+    CFGBlock* header = CFGBlock::New(alloc(), GetNextPc(loopEntry));
+
+    CFGLoopEntry* ins = CFGLoopEntry::New(alloc(), header, 0);
+    if (LoopEntryCanIonOsr(loopEntry))
+        ins->setCanOsr();
+
+    current->setStopIns(ins);
+    current->setStopPc(pc);
+
+    // If there is no condition, we immediately parse the body. Otherwise, we
+    // parse the condition.
+    jsbytecode* stopAt;
+    CFGState::State initial;
+    if (condpc != ifne) {
+        pc = condpc;
+        stopAt = ifne;
+        initial = CFGState::FOR_LOOP_COND;
+    } else {
+        pc = bodyStart;
+        stopAt = bodyEnd;
+        initial = CFGState::FOR_LOOP_BODY;
+    }
+
+    if (!pushLoop(initial, stopAt, current,
+                  loopHead, pc, bodyStart, bodyEnd, exitpc, updatepc))
+    {
+        return ControlStatus::Error;
+    }
+
+    CFGState& state = cfgStack_.back();
+    state.loop.condpc = (condpc != ifne) ? condpc : nullptr;
+    state.loop.updatepc = (updatepc != condpc) ? updatepc : nullptr;
+    if (state.loop.updatepc)
+        state.loop.updateEnd = condpc;
+
+    current = header;
+    if (!addBlock(current))
+        return ControlStatus::Error;
+    return ControlStatus::Jumped;
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processDoWhileLoop(JSOp op, jssrcnote* sn)
+{
+    // do { } while() loops have the following structure:
+    //    NOP         ; SRC_WHILE (offset to COND)
+    //    LOOPHEAD    ; SRC_WHILE (offset to IFNE)
+    //    LOOPENTRY
+    //    ...         ; body
+    //    ...
+    //    COND        ; start of condition
+    //    ...
+    //    IFNE ->     ; goes to LOOPHEAD
+    int condition_offset = GetSrcNoteOffset(sn, 0);
+    jsbytecode* conditionpc = pc + condition_offset;
+
+    jssrcnote* sn2 = GetSrcNote(gsn, script, pc + 1);
+    int offset = GetSrcNoteOffset(sn2, 0);
+    jsbytecode* ifne = pc + offset + 1;
+    MOZ_ASSERT(ifne > pc);
+
+    // Verify that the IFNE goes back to a loophead op.
+    jsbytecode* loopHead = GetNextPc(pc);
+    MOZ_ASSERT(JSOp(*loopHead) == JSOP_LOOPHEAD);
+    MOZ_ASSERT(loopHead == ifne + GetJumpOffset(ifne));
+
+    jsbytecode* loopEntry = GetNextPc(loopHead);
+
+    CFGBlock* header = CFGBlock::New(alloc(), GetNextPc(loopEntry));
+
+    CFGLoopEntry* ins = CFGLoopEntry::New(alloc(), header, 0);
+    if (LoopEntryCanIonOsr(loopEntry))
+        ins->setCanOsr();
+
+    current->setStopIns(ins);
+    current->setStopPc(pc);
+
+    jsbytecode* bodyEnd = conditionpc;
+    jsbytecode* exitpc = GetNextPc(ifne);
+    if (!pushLoop(CFGState::DO_WHILE_LOOP_BODY, conditionpc, current,
+                  loopHead, loopEntry, loopEntry, bodyEnd, exitpc, conditionpc))
+    {
+        return ControlStatus::Error;
+    }
+
+    CFGState& state = cfgStack_.back();
+    state.loop.updatepc = conditionpc;
+    state.loop.updateEnd = ifne;
+
+    current = header;
+    pc = loopEntry;
+
+    if (!addBlock(current))
+        return ControlStatus::Error;
+
+    return ControlStatus::Jumped;
+}
+
+bool
+ControlFlowGenerator::pushLoop(CFGState::State initial, jsbytecode* stopAt, CFGBlock* entry,
+                               jsbytecode* loopHead, jsbytecode* initialPc,
+                               jsbytecode* bodyStart, jsbytecode* bodyEnd,
+                               jsbytecode* exitpc, jsbytecode* continuepc)
+{
+    ControlFlowInfo loop(cfgStack_.length(), continuepc);
+    if (!loops_.append(loop))
+        return false;
+
+    CFGState state;
+    state.state = initial;
+    state.stopAt = stopAt;
+    state.loop.bodyStart = bodyStart;
+    state.loop.bodyEnd = bodyEnd;
+    state.loop.exitpc = exitpc;
+    state.loop.entry = entry;
+    state.loop.successor = nullptr;
+    state.loop.breaks = nullptr;
+    state.loop.continues = nullptr;
+    state.loop.initialState = initial;
+    state.loop.initialPc = initialPc;
+    state.loop.initialStopAt = stopAt;
+    state.loop.loopHead = loopHead;
+    return cfgStack_.append(state);
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processBreak(JSOp op, jssrcnote* sn)
+{
+    MOZ_ASSERT(op == JSOP_GOTO);
+
+    MOZ_ASSERT(SN_TYPE(sn) == SRC_BREAK ||
+               SN_TYPE(sn) == SRC_BREAK2LABEL);
+
+    // Find the break target.
+    jsbytecode* target = pc + GetJumpOffset(pc);
+    DebugOnly<bool> found = false;
+
+    if (SN_TYPE(sn) == SRC_BREAK2LABEL) {
+        for (size_t i = labels_.length() - 1; i < labels_.length(); i--) {
+            CFGState& cfg = cfgStack_[labels_[i].cfgEntry];
+            MOZ_ASSERT(cfg.state == CFGState::LABEL);
+            if (cfg.stopAt == target) {
+                cfg.label.breaks = new(alloc()) DeferredEdge(current, cfg.label.breaks);
+                found = true;
+                break;
+            }
+        }
+    } else {
+        for (size_t i = loops_.length() - 1; i < loops_.length(); i--) {
+            CFGState& cfg = cfgStack_[loops_[i].cfgEntry];
+            MOZ_ASSERT(cfg.isLoop());
+            if (cfg.loop.exitpc == target) {
+                cfg.loop.breaks = new(alloc()) DeferredEdge(current, cfg.loop.breaks);
+                found = true;
+                break;
+            }
+        }
+    }
+
+    current->setStopPc(pc);
+
+    MOZ_ASSERT(found);
+
+    current = nullptr;
+    pc += CodeSpec[op].length;
+    return processControlEnd();
+}
+
+static inline jsbytecode*
+EffectiveContinue(jsbytecode* pc)
+{
+    if (JSOp(*pc) == JSOP_GOTO)
+        return pc + GetJumpOffset(pc);
+    return pc;
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processContinue(JSOp op)
+{
+    MOZ_ASSERT(op == JSOP_GOTO);
+
+    // Find the target loop.
+    CFGState* found = nullptr;
+    jsbytecode* target = pc + GetJumpOffset(pc);
+    for (size_t i = loops_.length() - 1; i < loops_.length(); i--) {
+        // +1 to skip JSOP_JUMPTARGET.
+        if (loops_[i].continuepc == target + 1 ||
+            EffectiveContinue(loops_[i].continuepc) == target)
+        {
+            found = &cfgStack_[loops_[i].cfgEntry];
+            break;
+        }
+    }
+
+    // There must always be a valid target loop structure. If not, there's
+    // probably an off-by-something error in which pc we track.
+    MOZ_ASSERT(found);
+    CFGState& state = *found;
+
+    state.loop.continues = new(alloc()) DeferredEdge(current, state.loop.continues);
+    if (!state.loop.continues)
+        return ControlStatus::Error;
+    current->setStopPc(pc);
+
+    current = nullptr;
+    pc += CodeSpec[op].length;
+    return processControlEnd();
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processSwitchBreak(JSOp op)
+{
+    MOZ_ASSERT(op == JSOP_GOTO);
+
+    // Find the target switch.
+    CFGState* found = nullptr;
+    jsbytecode* target = pc + GetJumpOffset(pc);
+    for (size_t i = switches_.length() - 1; i < switches_.length(); i--) {
+        if (switches_[i].continuepc == target) {
+            found = &cfgStack_[switches_[i].cfgEntry];
+            break;
+        }
+    }
+
+    // There must always be a valid target loop structure. If not, there's
+    // probably an off-by-something error in which pc we track.
+    MOZ_ASSERT(found);
+    CFGState& state = *found;
+
+    DeferredEdge** breaks = nullptr;
+    switch (state.state) {
+      case CFGState::TABLE_SWITCH:
+        breaks = &state.switch_.breaks;
+        break;
+      case CFGState::COND_SWITCH_BODY:
+        breaks = &state.switch_.breaks;
+        break;
+      default:
+        MOZ_CRASH("Unexpected switch state.");
+    }
+
+    *breaks = new(alloc()) DeferredEdge(current, *breaks);
+
+    current->setStopPc(pc);
+
+    current = nullptr;
+    pc += CodeSpec[op].length;
+    return processControlEnd();
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processIfStart(JSOp op)
+{
+    // IFEQ always has a forward offset.
+    jsbytecode* trueStart = pc + CodeSpec[op].length;
+    jsbytecode* falseStart = pc + GetJumpOffset(pc);
+    MOZ_ASSERT(falseStart > pc);
+
+    // We only handle cases that emit source notes.
+    jssrcnote* sn = GetSrcNote(gsn, script, pc);
+    if (!sn)
+        return ControlStatus::Error;
+
+    // Create true and false branches.
+    CFGBlock* ifTrue = CFGBlock::New(alloc(), trueStart);
+    CFGBlock* ifFalse = CFGBlock::New(alloc(), falseStart);
+
+    CFGTest* test = CFGTest::New(alloc(), ifTrue, ifFalse);
+    current->setStopIns(test);
+    current->setStopPc(pc);
+
+    // The bytecode for if/ternary gets emitted either like this:
+    //
+    //    IFEQ X  ; src note (IF_ELSE, COND) points to the GOTO
+    //    ...
+    //    GOTO Z
+    // X: ...     ; else/else if
+    //    ...
+    // Z:         ; join
+    //
+    // Or like this:
+    //
+    //    IFEQ X  ; src note (IF) has no offset
+    //    ...
+    // Z: ...     ; join
+    //
+    // We want to parse the bytecode as if we were parsing the AST, so for the
+    // IF_ELSE/COND cases, we use the source note and follow the GOTO. For the
+    // IF case, the IFEQ offset is the join point.
+    switch (SN_TYPE(sn)) {
+      case SRC_IF:
+        if (!cfgStack_.append(CFGState::If(falseStart, test)))
+            return ControlStatus::Error;
+        break;
+
+      case SRC_IF_ELSE:
+      case SRC_COND:
+      {
+        // Infer the join point from the JSOP_GOTO[X] sitting here, then
+        // assert as we much we can that this is the right GOTO.
+        jsbytecode* trueEnd = pc + GetSrcNoteOffset(sn, 0);
+        MOZ_ASSERT(trueEnd > pc);
+        MOZ_ASSERT(trueEnd < falseStart);
+        MOZ_ASSERT(JSOp(*trueEnd) == JSOP_GOTO);
+        MOZ_ASSERT(!GetSrcNote(gsn, script, trueEnd));
+
+        jsbytecode* falseEnd = trueEnd + GetJumpOffset(trueEnd);
+        MOZ_ASSERT(falseEnd > trueEnd);
+        MOZ_ASSERT(falseEnd >= falseStart);
+
+        if (!cfgStack_.append(CFGState::IfElse(trueEnd, falseEnd, test)))
+            return ControlStatus::Error;
+        break;
+      }
+
+      default:
+        MOZ_CRASH("unexpected source note type");
+    }
+
+    // Switch to parsing the true branch. Note that no PC update is needed,
+    // it's the next instruction.
+    current = ifTrue;
+    pc = ifTrue->startPc();
+
+    if (!addBlock(current))
+        return ControlStatus::Error;
+
+    return ControlStatus::Jumped;
+}
+
+int
+ControlFlowGenerator::CmpSuccessors(const void* a, const void* b)
+{
+    const CFGBlock* a0 = * (CFGBlock * const*)a;
+    const CFGBlock* b0 = * (CFGBlock * const*)b;
+    if (a0->startPc() == b0->startPc())
+        return 0;
+
+    return (a0->startPc() > b0->startPc()) ? 1 : -1;
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processTableSwitch(JSOp op, jssrcnote* sn)
+{
+    // TableSwitch op contains the following data
+    // (length between data is JUMP_OFFSET_LEN)
+    //
+    // 0: Offset of default case
+    // 1: Lowest number in tableswitch
+    // 2: Highest number in tableswitch
+    // 3: Offset of case low
+    // 4: Offset of case low+1
+    // .: ...
+    // .: Offset of case high
+
+    MOZ_ASSERT(op == JSOP_TABLESWITCH);
+    MOZ_ASSERT(SN_TYPE(sn) == SRC_TABLESWITCH);
+
+    // Get the default and exit pc
+    jsbytecode* exitpc = pc + GetSrcNoteOffset(sn, 0);
+    jsbytecode* defaultpc = pc + GET_JUMP_OFFSET(pc);
+
+    MOZ_ASSERT(defaultpc > pc && defaultpc <= exitpc);
+
+    // Get the low and high from the tableswitch
+    jsbytecode* pc2 = pc;
+    pc2 += JUMP_OFFSET_LEN;
+    int low = GET_JUMP_OFFSET(pc2);
+    pc2 += JUMP_OFFSET_LEN;
+    int high = GET_JUMP_OFFSET(pc2);
+    pc2 += JUMP_OFFSET_LEN;
+
+    // Create MIR instruction
+    CFGTableSwitch* tableswitch = CFGTableSwitch::New(alloc(), low, high);
+
+    // Create default case
+    CFGBlock* defaultcase = CFGBlock::New(alloc(), defaultpc);
+
+    if (!tableswitch->addDefault(defaultcase))
+        return ControlStatus::Error;
+
+    // Create cases
+    jsbytecode* casepc = nullptr;
+    for (int i = 0; i < high-low+1; i++) {
+        casepc = pc + GET_JUMP_OFFSET(pc2);
+
+        MOZ_ASSERT(casepc >= pc && casepc <= exitpc);
+        CFGBlock* caseBlock;
+
+        if (casepc == pc) {
+            // If the casepc equals the current pc, it is not a written case,
+            // but a filled gap. That way we can use a tableswitch instead of
+            // condswitch, even if not all numbers are consecutive.
+            // In that case this block goes to the default case
+            caseBlock = CFGBlock::New(alloc(), defaultpc);
+            caseBlock->setStopIns(CFGGoto::New(alloc(), defaultcase));
+        } else {
+            // If this is an actual case (not filled gap),
+            // add this block to the list that still needs to get processed.
+            caseBlock = CFGBlock::New(alloc(), casepc);
+        }
+
+        if (!tableswitch->addCase(caseBlock))
+            return ControlStatus::Error;
+
+        pc2 += JUMP_OFFSET_LEN;
+    }
+
+    // Create info
+    ControlFlowInfo switchinfo(cfgStack_.length(), exitpc);
+    if (!switches_.append(switchinfo))
+        return ControlStatus::Error;
+
+    // Use a state to retrieve some information
+    CFGState state = CFGState::TableSwitch(alloc(), exitpc);
+    if (!state.switch_.bodies ||
+        !state.switch_.bodies->init(alloc(), tableswitch->numSuccessors()))
+    {
+        return ControlStatus::Error;
+    }
+
+    FixedList<CFGBlock*>& bodies = *state.switch_.bodies;
+    for (size_t i = 0; i < tableswitch->numSuccessors(); i++)
+        bodies[i] = tableswitch->getSuccessor(i);
+
+    qsort(bodies.begin(), state.switch_.bodies->length(),
+          sizeof(CFGBlock*), CmpSuccessors);
+
+    // Save the MIR instruction as last instruction of this block.
+    current->setStopIns(tableswitch);
+    current->setStopPc(pc);
+
+    // If there is only one successor the block should stop at the end of the switch
+    // Else it should stop at the start of the next successor
+    if (bodies.length() > 1)
+        state.stopAt = bodies[1]->startPc();
+    else
+        state.stopAt = exitpc;
+
+    if (!cfgStack_.append(state))
+        return ControlStatus::Error;
+
+    current = bodies[0];
+    pc = current->startPc();
+
+    if (!addBlock(current))
+        return ControlStatus::Error;
+
+    return ControlStatus::Jumped;
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processNextTableSwitchCase(CFGState& state)
+{
+    MOZ_ASSERT(state.state == CFGState::TABLE_SWITCH);
+    FixedList<CFGBlock*>& bodies = *state.switch_.bodies;
+
+    state.switch_.currentIdx++;
+
+    // Test if there are still unprocessed successors (cases/default)
+    if (state.switch_.currentIdx >= bodies.length())
+        return processSwitchEnd(state.switch_.breaks, state.switch_.exitpc);
+
+    // Get the next successor
+    CFGBlock* successor = bodies[state.switch_.currentIdx];
+
+    // Add current block as predecessor if available.
+    // This means the previous case didn't have a break statement.
+    // So flow will continue in this block.
+    if (current) {
+        current->setStopIns(CFGGoto::New(alloc(), successor));
+        current->setStopPc(pc);
+    }
+
+    // If this is the last successor the block should stop at the end of the tableswitch
+    // Else it should stop at the start of the next successor
+    if (state.switch_.currentIdx + 1 < bodies.length())
+        state.stopAt = bodies[state.switch_.currentIdx + 1]->startPc();
+    else
+        state.stopAt = state.switch_.exitpc;
+
+    current = bodies[state.switch_.currentIdx];
+    pc = current->startPc();
+
+    if (!addBlock(current))
+        return ControlStatus::Error;
+
+    return ControlStatus::Jumped;
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processSwitchEnd(DeferredEdge* breaks, jsbytecode* exitpc)
+{
+    // No break statements, no current.
+    // This means that control flow is cut-off from this point
+    // (e.g. all cases have return statements).
+    if (!breaks && !current)
+        return ControlStatus::Ended;
+
+    // Create successor block.
+    // If there are breaks, create block with breaks as predecessor
+    // Else create a block with current as predecessor
+    CFGBlock* successor = nullptr;
+    if (breaks)
+        successor = createBreakCatchBlock(breaks, exitpc);
+    else
+        successor = CFGBlock::New(alloc(), exitpc);
+
+    // If there is current, the current block flows into this one.
+    // So current is also a predecessor to this block
+    if (current) {
+        current->setStopIns(CFGGoto::New(alloc(), successor));
+        current->setStopPc(pc);
+    }
+
+    current = successor;
+    pc = successor->startPc();
+
+    if (!addBlock(successor))
+        return ControlStatus::Error;
+
+    return ControlStatus::Joined;
+}
+
+ControlFlowGenerator::CFGState
+ControlFlowGenerator::CFGState::If(jsbytecode* join, CFGTest* test)
+{
+    CFGState state;
+    state.state = IF_TRUE;
+    state.stopAt = join;
+    state.branch.ifFalse = test->getSuccessor(1);
+    state.branch.test = test;
+    return state;
+}
+
+ControlFlowGenerator::CFGState
+ControlFlowGenerator::CFGState::IfElse(jsbytecode* trueEnd, jsbytecode* falseEnd,
+                                       CFGTest* test)
+{
+    CFGBlock* ifFalse = test->getSuccessor(1);
+
+    CFGState state;
+    // If the end of the false path is the same as the start of the
+    // false path, then the "else" block is empty and we can devolve
+    // this to the IF_TRUE case. We handle this here because there is
+    // still an extra GOTO on the true path and we want stopAt to point
+    // there, whereas the IF_TRUE case does not have the GOTO.
+    state.state = (falseEnd == ifFalse->startPc())
+                  ? IF_TRUE_EMPTY_ELSE
+                  : IF_ELSE_TRUE;
+    state.stopAt = trueEnd;
+    state.branch.falseEnd = falseEnd;
+    state.branch.ifFalse = ifFalse;
+    state.branch.test = test;
+    return state;
+}
+
+ControlFlowGenerator::CFGState
+ControlFlowGenerator::CFGState::AndOr(jsbytecode* join, CFGBlock* lhs)
+{
+    CFGState state;
+    state.state = AND_OR;
+    state.stopAt = join;
+    state.branch.ifFalse = lhs;
+    state.branch.test = nullptr;
+    return state;
+}
+
+ControlFlowGenerator::CFGState
+ControlFlowGenerator::CFGState::TableSwitch(TempAllocator& alloc, jsbytecode* exitpc)
+{
+    CFGState state;
+    state.state = TABLE_SWITCH;
+    state.stopAt = exitpc;
+    state.switch_.bodies = (FixedList<CFGBlock*>*)alloc.allocate(sizeof(FixedList<CFGBlock*>));
+    state.switch_.currentIdx = 0;
+    state.switch_.exitpc = exitpc;
+    state.switch_.breaks = nullptr;
+    return state;
+}
+
+ControlFlowGenerator::CFGState
+ControlFlowGenerator::CFGState::CondSwitch(TempAllocator& alloc, jsbytecode* exitpc,
+                                           jsbytecode* defaultTarget)
+{
+    CFGState state;
+    state.state = COND_SWITCH_CASE;
+    state.stopAt = nullptr;
+    state.switch_.bodies = (FixedList<CFGBlock*>*)alloc.allocate(sizeof(FixedList<CFGBlock*>));
+    state.switch_.currentIdx = 0;
+    state.switch_.defaultTarget = defaultTarget;
+    state.switch_.defaultIdx = uint32_t(-1);
+    state.switch_.exitpc = exitpc;
+    state.switch_.breaks = nullptr;
+    return state;
+}
+ControlFlowGenerator::CFGState
+ControlFlowGenerator::CFGState::Label(jsbytecode* exitpc)
+{
+    CFGState state;
+    state.state = LABEL;
+    state.stopAt = exitpc;
+    state.label.breaks = nullptr;
+    return state;
+}
+
+ControlFlowGenerator::CFGState
+ControlFlowGenerator::CFGState::Try(jsbytecode* exitpc, CFGBlock* successor)
+{
+    CFGState state;
+    state.state = TRY;
+    state.stopAt = exitpc;
+    state.try_.successor = successor;
+    return state;
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processAndOr(JSOp op)
+{
+    MOZ_ASSERT(op == JSOP_AND || op == JSOP_OR);
+
+    jsbytecode* rhsStart = pc + CodeSpec[op].length;
+    jsbytecode* joinStart = pc + GetJumpOffset(pc);
+    MOZ_ASSERT(joinStart > pc);
+
+    CFGBlock* evalLhs = CFGBlock::New(alloc(), joinStart);
+    CFGBlock* evalRhs = CFGBlock::New(alloc(), rhsStart);
+
+    CFGTest* test = (op == JSOP_AND)
+                  ? CFGTest::New(alloc(), evalRhs, evalLhs)
+                  : CFGTest::New(alloc(), evalLhs, evalRhs);
+    test->keepCondition();
+    current->setStopIns(test);
+    current->setStopPc(pc);
+
+    // Create the rhs block.
+    if (!cfgStack_.append(CFGState::AndOr(joinStart, evalLhs)))
+        return ControlStatus::Error;
+
+    if (!addBlock(evalLhs))
+        return ControlStatus::Error;
+
+    current = evalRhs;
+    pc = current->startPc();
+
+    if (!addBlock(current))
+        return ControlStatus::Error;
+
+    return ControlStatus::Jumped;
+}
+
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processLabel()
+{
+    MOZ_ASSERT(JSOp(*pc) == JSOP_LABEL);
+
+    jsbytecode* endpc = pc + GET_JUMP_OFFSET(pc);
+    MOZ_ASSERT(endpc > pc);
+
+    ControlFlowInfo label(cfgStack_.length(), endpc);
+    if (!labels_.append(label))
+        return ControlStatus::Error;
+
+    if (!cfgStack_.append(CFGState::Label(endpc)))
+        return ControlStatus::Error;
+
+    return ControlStatus::None;
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit/IonControlFlow.h
@@ -0,0 +1,820 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef jit_IonControlFlow_h
+#define jit_IonControlFlow_h
+
+#include "mozilla/Array.h"
+
+#include "jsbytecode.h"
+
+#include "jit/BytecodeAnalysis.h"
+#include "jit/FixedList.h"
+#include "jit/JitAllocPolicy.h"
+#include "js/TypeDecls.h"
+
+namespace js {
+namespace jit {
+
+class CFGControlInstruction;
+
+// Adds MFoo::New functions which are mirroring the arguments of the
+// constructors. Opcodes which are using this macro can be called with a
+// TempAllocator, or the fallible version of the TempAllocator.
+#define TRIVIAL_CFG_NEW_WRAPPERS                                              \
+    template <typename... Args>                                               \
+    static CFGThisOpcode* New(TempAllocator& alloc, Args&&... args) {         \
+        return new(alloc) CFGThisOpcode(mozilla::Forward<Args>(args)...);     \
+    }                                                                         \
+    template <typename... Args>                                               \
+    static CFGThisOpcode* New(TempAllocator::Fallible alloc, Args&&... args)  \
+    {                                                                         \
+        return new(alloc) CFGThisOpcode(mozilla::Forward<Args>(args)...);     \
+    }
+
+class CFGSpace
+{
+    static const size_t DEFAULT_CHUNK_SIZE = 4096;
+
+  protected:
+    LifoAlloc allocator_;
+  public:
+
+    explicit CFGSpace()
+      : allocator_(DEFAULT_CHUNK_SIZE)
+    {}
+
+    LifoAlloc& lifoAlloc() {
+        return allocator_;
+    }
+
+    size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+        return allocator_.sizeOfExcludingThis(mallocSizeOf);
+    }
+};
+
+class CFGBlock : public TempObject
+{
+    size_t id_;
+    jsbytecode* start;
+    jsbytecode* stop;
+    CFGControlInstruction* end;
+    bool inWorkList;
+
+  public:
+    explicit CFGBlock(jsbytecode* start)
+      : id_(-1),
+        start(start),
+        stop(nullptr),
+        end(nullptr),
+        inWorkList(false)
+    {}
+
+    static CFGBlock* New(TempAllocator& alloc, jsbytecode* start) {
+        return new(alloc) CFGBlock(start);
+    }
+
+    void operator=(const CFGBlock&) = delete;
+
+    jsbytecode* startPc() const {
+        return start;
+    }
+    jsbytecode* stopPc() const {
+        MOZ_ASSERT(stop);
+        return stop;
+    }
+    void setStopPc(jsbytecode* stopPc) {
+        stop = stopPc;
+    }
+    CFGControlInstruction* stopIns() const {
+        MOZ_ASSERT(end);
+        return end;
+    }
+    void setStopIns(CFGControlInstruction* stopIns) {
+        end = stopIns;
+    }
+    bool isInWorkList() const {
+        return inWorkList;
+    }
+    void setInWorklist() {
+        MOZ_ASSERT(!inWorkList);
+        inWorkList = true;
+    }
+    void clearInWorkList() {
+        MOZ_ASSERT(inWorkList);
+        inWorkList = false;
+    }
+    size_t id() const {
+        return id_;
+    }
+    void setId(size_t id) {
+        id_ = id;
+    }
+};
+
+#define CFG_CONTROL_OPCODE_LIST(_)                                          \
+    _(Test)                                                                 \
+    _(Compare)                                                              \
+    _(Goto)                                                                 \
+    _(Return)                                                               \
+    _(RetRVal)                                                              \
+    _(LoopEntry)                                                            \
+    _(BackEdge)                                                             \
+    _(TableSwitch)                                                          \
+    _(Try)                                                                  \
+    _(Throw)
+
+// Forward declarations of MIR types.
+#define FORWARD_DECLARE(type) class CFG##type;
+ CFG_CONTROL_OPCODE_LIST(FORWARD_DECLARE)
+#undef FORWARD_DECLARE
+
+#define CFG_CONTROL_HEADER(type_name)                                        \
+    static const Type classOpcode = CFGControlInstruction::Type_##type_name; \
+    using CFGThisOpcode = CFG##type_name;                                    \
+    Type type() const override {                                             \
+        return classOpcode;                                                  \
+    }                                                                        \
+    const char* Name() const override {                                      \
+        return #type_name;                                                   \
+    }                                                                        \
+
+
+class CFGControlInstruction : public TempObject
+{
+  public:
+    enum Type {
+#   define DEFINE_TYPES(type) Type_##type,
+        CFG_CONTROL_OPCODE_LIST(DEFINE_TYPES)
+#   undef DEFINE_TYPES
+    };
+
+    virtual size_t numSuccessors() const = 0;
+    virtual CFGBlock* getSuccessor(size_t i) const = 0;
+    virtual void replaceSuccessor(size_t i, CFGBlock* successor) = 0;
+    virtual Type type() const = 0;
+    virtual const char* Name() const = 0;
+
+    template<typename CFGType> bool is() const {
+        return type() == CFGType::classOpcode;
+    }
+    template<typename CFGType> CFGType* to() {
+        MOZ_ASSERT(this->is<CFGType>());
+        return static_cast<CFGType*>(this);
+    }
+    template<typename CFGType> const CFGType* to() const {
+        MOZ_ASSERT(this->is<CFGType>());
+        return static_cast<const CFGType*>(this);
+    }
+#   define TYPE_CASTS(type)             \
+    bool is##type() const {             \
+        return this->is<CFG##type>();   \
+    }                                   \
+    CFG##type* to##type() {             \
+        return this->to<CFG##type>();   \
+    }                                   \
+    const CFG##type* to##type() const { \
+        return this->to<CFG##type>();   \
+    }
+    CFG_CONTROL_OPCODE_LIST(TYPE_CASTS)
+#   undef TYPE_CASTS
+};
+
+template <size_t Successors>
+class CFGAryControlInstruction : public CFGControlInstruction
+{
+    mozilla::Array<CFGBlock*, Successors> successors_;
+
+  public:
+    size_t numSuccessors() const final override {
+        return Successors;
+    }
+    CFGBlock* getSuccessor(size_t i) const final override {
+        return successors_[i];
+    }
+    void replaceSuccessor(size_t i, CFGBlock* succ) final override {
+        successors_[i] = succ;
+    }
+};
+
+class CFGTry : public CFGControlInstruction
+{
+    CFGBlock* tryBlock_;
+    jsbytecode* catchStartPc_;
+    CFGBlock* mergePoint_;
+
+    CFGTry(CFGBlock* successor, jsbytecode* catchStartPc, CFGBlock* mergePoint = nullptr)
+      : tryBlock_(successor),
+        catchStartPc_(catchStartPc),
+        mergePoint_(mergePoint)
+    { }
+
+  public:
+    CFG_CONTROL_HEADER(Try)
+    TRIVIAL_CFG_NEW_WRAPPERS
+
+    size_t numSuccessors() const final override {
+        return mergePoint_ ? 2 : 1;
+    }
+    CFGBlock* getSuccessor(size_t i) const final override {
+        MOZ_ASSERT(i < numSuccessors());
+        return (i == 0) ? tryBlock_ : mergePoint_;
+    }
+    void replaceSuccessor(size_t i, CFGBlock* succ) final override {
+        MOZ_ASSERT(i < numSuccessors());
+        if (i == 0)
+            tryBlock_ = succ;
+        else
+            mergePoint_ = succ;
+    }
+
+    CFGBlock* tryBlock() const {
+        return getSuccessor(0);
+    }
+
+    jsbytecode* catchStartPc() const {
+        return catchStartPc_;
+    }
+
+    CFGBlock* afterTryCatchBlock() const {
+        return getSuccessor(1);
+    }
+
+    bool codeAfterTryCatchReachable() const {
+        return !!mergePoint_;
+    }
+};
+
+class CFGTableSwitch : public CFGControlInstruction
+{
+    Vector<CFGBlock*, 4, JitAllocPolicy> successors_;
+    size_t low_;
+    size_t high_;
+
+    CFGTableSwitch(TempAllocator& alloc, size_t low, size_t high)
+      : successors_(alloc),
+        low_(low),
+        high_(high)
+    {}
+
+  public:
+    CFG_CONTROL_HEADER(TableSwitch);
+
+    static CFGTableSwitch* New(TempAllocator& alloc, size_t low, size_t high) {
+        return new(alloc) CFGTableSwitch(alloc, low, high);
+    }
+
+    size_t numSuccessors() const final override {
+        return successors_.length();
+    }
+    CFGBlock* getSuccessor(size_t i) const final override {
+        MOZ_ASSERT(i < numSuccessors());
+        return successors_[i];
+    }
+    void replaceSuccessor(size_t i, CFGBlock* succ) final override {
+        MOZ_ASSERT(i < numSuccessors());
+        successors_[i] = succ;
+    }
+
+    bool addDefault(CFGBlock* defaultCase) {
+        MOZ_ASSERT(successors_.length() == 0);
+        return successors_.append(defaultCase);
+    }
+
+    bool addCase(CFGBlock* caseBlock) {
+        MOZ_ASSERT(successors_.length() > 0);
+        return successors_.append(caseBlock);
+    }
+
+    CFGBlock* defaultCase() const {
+        return getSuccessor(0);
+    }
+
+    CFGBlock* getCase(size_t i) const {
+        return getSuccessor(i + 1);
+    }
+
+    size_t high() const {
+        return high_;
+    }
+
+    size_t low() const {
+        return low_;
+    }
+};
+
+/**
+ * CFGCompare
+ *
+ * POP
+ * PEEK
+ * STRICTEQ JUMP succ1
+ * STRICTNEQ JUMP succ2
+ */
+class CFGCompare : public CFGAryControlInstruction<2>
+{
+    CFGCompare(CFGBlock* succ1, CFGBlock* succ2)
+    {
+        replaceSuccessor(0, succ1);
+        replaceSuccessor(1, succ2);
+    }
+
+  public:
+    CFG_CONTROL_HEADER(Compare);
+    TRIVIAL_CFG_NEW_WRAPPERS
+
+    CFGBlock* trueBranch() const {
+        return getSuccessor(0);
+    }
+    CFGBlock* falseBranch() const {
+        return getSuccessor(1);
+    }
+};
+
+/**
+ * CFGTest
+ *
+ * POP / PEEK (depending on keepCondition_)
+ * IFEQ JUMP succ1
+ * IFNEQ JUMP succ2
+ *
+ */
+class CFGTest : public CFGAryControlInstruction<2>
+{
+    // By default the condition gets popped. This variable
+    // keeps track if we want to keep the condition.
+    bool keepCondition_;
+
+    CFGTest(CFGBlock* succ1, CFGBlock* succ2)
+      : keepCondition_(false)
+    {
+        replaceSuccessor(0, succ1);
+        replaceSuccessor(1, succ2);
+    }
+    CFGTest(CFGBlock* succ1, CFGBlock* succ2, bool keepCondition)
+      : keepCondition_(keepCondition)
+    {
+        replaceSuccessor(0, succ1);
+        replaceSuccessor(1, succ2);
+    }
+
+  public:
+    CFG_CONTROL_HEADER(Test);
+    TRIVIAL_CFG_NEW_WRAPPERS
+
+    CFGBlock* trueBranch() const {
+        return getSuccessor(0);
+    }
+    CFGBlock* falseBranch() const {
+        return getSuccessor(1);
+    }
+    void keepCondition() {
+        keepCondition_ = true;
+    }
+    bool mustKeepCondition() const {
+        return keepCondition_;
+    }
+};
+
+/**
+ * CFGReturn
+ *
+ * POP
+ * RETURN popped value
+ *
+ */
+class CFGReturn : public CFGAryControlInstruction<0>
+{
+  public:
+    CFG_CONTROL_HEADER(Return);
+    TRIVIAL_CFG_NEW_WRAPPERS
+};
+
+/**
+ * CFGRetRVal
+ *
+ * RETURN the value in the return value slot
+ *
+ */
+class CFGRetRVal : public CFGAryControlInstruction<0>
+{
+  public:
+    CFG_CONTROL_HEADER(RetRVal);
+    TRIVIAL_CFG_NEW_WRAPPERS
+};
+
+/**
+ * CFGThrow
+ *
+ * POP
+ * THROW popped value
+ *
+ */
+class CFGThrow : public CFGAryControlInstruction<0>
+{
+  public:
+    CFG_CONTROL_HEADER(Throw);
+    TRIVIAL_CFG_NEW_WRAPPERS
+};
+
+class CFGUnaryControlInstruction : public CFGAryControlInstruction<1>
+{
+  public:
+    explicit CFGUnaryControlInstruction(CFGBlock* block) {
+        MOZ_ASSERT(block);
+        replace