Bug 965880 - OdinMonkey: allow asm.js to change (resize) heap (r=bbouvier)
authorLuke Wagner <luke@mozilla.com>
Tue, 07 Oct 2014 14:07:52 -0500
changeset 209234 f72b6d7ece75f3824d6322dc011a41ef5c049c2d
parent 209233 09bd9d93d3e2574abdc5db3f608737a309eb8d59
child 209235 6525c7ee1f50addf145723c976dc353e4be9a00c
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersbbouvier
bugs965880
milestone35.0a1
Bug 965880 - OdinMonkey: allow asm.js to change (resize) heap (r=bbouvier)
js/src/asmjs/AsmJSLink.cpp
js/src/asmjs/AsmJSModule.cpp
js/src/asmjs/AsmJSModule.h
js/src/asmjs/AsmJSValidate.cpp
js/src/jit-test/lib/asm.js
js/src/jit-test/tests/asm.js/testResize.js
js/src/jit-test/tests/asm.js/testTimeout7.js
js/src/jit/arm/MacroAssembler-arm.h
js/src/jit/mips/MacroAssembler-mips.h
js/src/jit/shared/Assembler-shared.h
js/src/jit/x64/Assembler-x64.h
js/src/jit/x64/CodeGenerator-x64.cpp
js/src/jit/x86/Assembler-x86.h
--- a/js/src/asmjs/AsmJSLink.cpp
+++ b/js/src/asmjs/AsmJSLink.cpp
@@ -234,16 +234,40 @@ ValidateArrayView(JSContext *cx, AsmJSMo
     {
         return LinkFail(cx, "bad typed array constructor");
     }
 
     return true;
 }
 
 static bool
+ValidateByteLength(JSContext *cx, HandleValue globalVal)
+{
+    RootedPropertyName field(cx, cx->names().byteLength);
+    RootedValue v(cx);
+    if (!GetDataProperty(cx, globalVal, field, &v))
+        return false;
+
+    if (!v.isObject() || !v.toObject().isBoundFunction())
+        return LinkFail(cx, "byteLength must be a bound function object");
+
+    RootedFunction fun(cx, &v.toObject().as<JSFunction>());
+
+    RootedValue boundTarget(cx, ObjectValue(*fun->getBoundFunctionTarget()));
+    if (!IsNativeFunction(boundTarget, js_fun_call))
+        return LinkFail(cx, "bound target of byteLength must be Function.prototype.call");
+
+    RootedValue boundThis(cx, fun->getBoundFunctionThis());
+    if (!IsNativeFunction(boundThis, ArrayBufferObject::byteLengthGetter))
+        return LinkFail(cx, "bound this value must be ArrayBuffer.protototype.byteLength accessor");
+
+    return true;
+}
+
+static bool
 ValidateMathBuiltinFunction(JSContext *cx, AsmJSModule::Global &global, HandleValue globalVal)
 {
     RootedValue v(cx);
     if (!GetDataProperty(cx, globalVal, cx->names().Math, &v))
         return false;
 
     RootedPropertyName field(cx, global.mathName());
     if (!GetDataProperty(cx, v, field, &v))
@@ -436,19 +460,18 @@ LinkModuleToHeap(JSContext *cx, AsmJSMod
         return LinkFail(cx, msg.get());
     }
 
     // This check is sufficient without considering the size of the loaded datum because heap
     // loads and stores start on an aligned boundary and the heap byteLength has larger alignment.
     MOZ_ASSERT((module.minHeapLength() - 1) <= INT32_MAX);
     if (heapLength < module.minHeapLength()) {
         ScopedJSFreePtr<char> msg(
-            JS_smprintf("ArrayBuffer byteLength of 0x%x is less than 0x%x (which is the "
-                        "largest constant heap access offset rounded up to the next valid "
-                        "heap size).",
+            JS_smprintf("ArrayBuffer byteLength of 0x%x is less than 0x%x (the size implied "
+                        "by const heap accesses and/or change-heap minimum-length requirements).",
                         heapLength,
                         module.minHeapLength()));
         return LinkFail(cx, msg.get());
     }
 
     // If we've generated the code with signal handlers in mind (for bounds
     // checks on x64 and for interrupt callback requesting on all platforms),
     // we need to be able to use signals at runtime. In particular, a module
@@ -467,27 +490,19 @@ LinkModuleToHeap(JSContext *cx, AsmJSMod
     return true;
 }
 
 static bool
 DynamicallyLinkModule(JSContext *cx, CallArgs args, AsmJSModule &module)
 {
     module.setIsDynamicallyLinked();
 
-    RootedValue globalVal(cx);
-    if (args.length() > 0)
-        globalVal = args[0];
-
-    RootedValue importVal(cx);
-    if (args.length() > 1)
-        importVal = args[1];
-
-    RootedValue bufferVal(cx);
-    if (args.length() > 2)
-        bufferVal = args[2];
+    HandleValue globalVal = args.get(0);
+    HandleValue importVal = args.get(1);
+    HandleValue bufferVal = args.get(2);
 
     Rooted<ArrayBufferObjectMaybeShared *> heap(cx);
     if (module.hasArrayView()) {
         if (IsArrayBuffer(bufferVal) || IsSharedArrayBuffer(bufferVal))
             heap = &AsAnyArrayBuffer(bufferVal);
         else
             return LinkFail(cx, "bad ArrayBuffer argument");
         if (!LinkModuleToHeap(cx, module, heap))
@@ -509,16 +524,20 @@ DynamicallyLinkModule(JSContext *cx, Cal
             if (!ValidateFFI(cx, global, importVal, &ffis))
                 return false;
             break;
           case AsmJSModule::Global::ArrayView:
           case AsmJSModule::Global::ArrayViewCtor:
             if (!ValidateArrayView(cx, global, globalVal))
                 return false;
             break;
+          case AsmJSModule::Global::ByteLength:
+            if (!ValidateByteLength(cx, globalVal))
+                return false;
+            break;
           case AsmJSModule::Global::MathBuiltinFunction:
             if (!ValidateMathBuiltinFunction(cx, global, globalVal))
                 return false;
             break;
           case AsmJSModule::Global::Constant:
             if (!ValidateConstant(cx, global, globalVal))
                 return false;
             break;
@@ -536,22 +555,42 @@ DynamicallyLinkModule(JSContext *cx, Cal
     for (unsigned i = 0; i < module.numExits(); i++)
         module.exitIndexToGlobalDatum(i).fun = &ffis[module.exit(i).ffiIndex()]->as<JSFunction>();
 
     module.initGlobalNaN();
 
     return true;
 }
 
+static bool
+ChangeHeap(JSContext *cx, AsmJSModule &module, CallArgs args)
+{
+    HandleValue bufferArg = args.get(0);
+    if (!IsArrayBuffer(bufferArg)) {
+        ReportIncompatible(cx, args);
+        return false;
+    }
+
+    Rooted<ArrayBufferObject*> newBuffer(cx, &bufferArg.toObject().as<ArrayBufferObject>());
+    bool rval = module.changeHeap(newBuffer, cx);
+
+    args.rval().set(BooleanValue(rval));
+    return true;
+}
+
+// An asm.js function stores, in its extended slots:
+//  - a pointer to the module from which it was returned
+//  - its index in the ordered list of exported functions
 static const unsigned ASM_MODULE_SLOT = 0;
 static const unsigned ASM_EXPORT_INDEX_SLOT = 1;
 
 static unsigned
 FunctionToExportedFunctionIndex(HandleFunction fun)
 {
+    MOZ_ASSERT(IsAsmJSFunction(fun));
     Value v = fun->getExtendedSlot(ASM_EXPORT_INDEX_SLOT);
     return v.toInt32();
 }
 
 static const AsmJSModule::ExportedFunction &
 FunctionToExportedFunction(HandleFunction fun, AsmJSModule &module)
 {
     unsigned funIndex = FunctionToExportedFunctionIndex(fun);
@@ -559,41 +598,36 @@ FunctionToExportedFunction(HandleFunctio
 }
 
 static AsmJSModule &
 FunctionToEnclosingModule(HandleFunction fun)
 {
     return fun->getExtendedSlot(ASM_MODULE_SLOT).toObject().as<AsmJSModuleObject>().module();
 }
 
-// The JSNative for the functions nested in an asm.js module. Calling this
-// native will trampoline into generated code.
+// This is the js::Native for functions exported by an asm.js module.
 static bool
 CallAsmJS(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs callArgs = CallArgsFromVp(argc, vp);
     RootedFunction callee(cx, &callArgs.callee().as<JSFunction>());
+    AsmJSModule &module = FunctionToEnclosingModule(callee);
+    const AsmJSModule::ExportedFunction &func = FunctionToExportedFunction(callee, module);
 
-    // An asm.js function stores, in its extended slots:
-    //  - a pointer to the module from which it was returned
-    //  - its index in the ordered list of exported functions
-    AsmJSModule &module = FunctionToEnclosingModule(callee);
+    // The heap-changing function is a special-case and is implemented by C++.
+    if (func.isChangeHeap())
+        return ChangeHeap(cx, module, callArgs);
 
     // Enable/disable profiling in the asm.js module to match the current global
     // profiling state. Don't do this if the module is already active on the
     // stack since this would leave the module in a state where profiling is
     // enabled but the stack isn't unwindable.
     if (module.profilingEnabled() != cx->runtime()->spsProfiler.enabled() && !module.active())
         module.setProfilingEnabled(cx->runtime()->spsProfiler.enabled(), cx);
 
-    // An exported function points to the code as well as the exported
-    // function's signature, which implies the dynamic coercions performed on
-    // the arguments.
-    const AsmJSModule::ExportedFunction &func = FunctionToExportedFunction(callee, module);
-
     // The calling convention for an external call into asm.js is to pass an
     // array of 16-byte values where each value contains either a coerced int32
     // (in the low word), a double value (in the low dword) or a SIMD vector
     // value, with the coercions specified by the asm.js signature. The
     // external entry point unpacks this array into the system-ABI-specified
     // registers and stack memory and then calls into the internal entry point.
     // The return value is stored in the first element of the array (which,
     // therefore, must have length >= 1).
@@ -699,19 +733,19 @@ CallAsmJS(JSContext *cx, unsigned argc, 
     return true;
 }
 
 static JSFunction *
 NewExportedFunction(JSContext *cx, const AsmJSModule::ExportedFunction &func,
                     HandleObject moduleObj, unsigned exportIndex)
 {
     RootedPropertyName name(cx, func.name());
-    JSFunction *fun = NewFunction(cx, NullPtr(), CallAsmJS, func.numArgs(),
-                                  JSFunction::ASMJS_CTOR, cx->global(), name,
-                                  JSFunction::ExtendedFinalizeKind);
+    unsigned numArgs = func.isChangeHeap() ? 1 : func.numArgs();
+    JSFunction *fun = NewFunction(cx, NullPtr(), CallAsmJS, numArgs, JSFunction::ASMJS_CTOR,
+                                  cx->global(), name, JSFunction::ExtendedFinalizeKind);
     if (!fun)
         return nullptr;
 
     fun->setExtendedSlot(ASM_MODULE_SLOT, ObjectValue(*moduleObj));
     fun->setExtendedSlot(ASM_EXPORT_INDEX_SLOT, Int32Value(exportIndex));
     return fun;
 }
 
--- a/js/src/asmjs/AsmJSModule.cpp
+++ b/js/src/asmjs/AsmJSModule.cpp
@@ -332,18 +332,20 @@ AsmJSModule::finish(ExclusiveContext *cx
 
 #if defined(JS_CODEGEN_ARM)
     // ARM requires the offsets to be updated.
     pod.functionBytes_ = masm.actualOffset(pod.functionBytes_);
     for (size_t i = 0; i < heapAccesses_.length(); i++) {
         AsmJSHeapAccess &a = heapAccesses_[i];
         a.setOffset(masm.actualOffset(a.offset()));
     }
-    for (unsigned i = 0; i < numExportedFunctions(); i++)
-        exportedFunction(i).updateCodeOffset(masm);
+    for (unsigned i = 0; i < numExportedFunctions(); i++) {
+        if (!exportedFunction(i).isChangeHeap())
+            exportedFunction(i).updateCodeOffset(masm);
+    }
     for (unsigned i = 0; i < numExits(); i++)
         exit(i).updateOffsets(masm);
     for (size_t i = 0; i < callSites_.length(); i++) {
         CallSite &c = callSites_[i];
         c.setReturnAddressOffset(masm.actualOffset(c.returnAddressOffset()));
     }
     for (size_t i = 0; i < codeRanges_.length(); i++) {
         codeRanges_[i].updateOffsets(masm);
@@ -760,40 +762,61 @@ AsmJSModule::initHeap(Handle<ArrayBuffer
         if (access.hasLengthCheck())
             X86Assembler::setPointer(access.patchLengthAt(code_), heapLength);
         void *addr = access.patchOffsetAt(code_);
         uint32_t disp = reinterpret_cast<uint32_t>(X86Assembler::getPointer(addr));
         MOZ_ASSERT(disp <= INT32_MAX);
         X86Assembler::setPointer(addr, (void *)(heapOffset + disp));
     }
 #elif defined(JS_CODEGEN_X64)
-    int32_t heapLength = int32_t(intptr_t(heap->byteLength()));
     if (usesSignalHandlersForOOB())
         return;
     // If we cannot use the signal handlers, we need to patch the heap length
     // checks at the right places. All accesses that have been recorded are the
     // only ones that need bound checks (see also
     // CodeGeneratorX64::visitAsmJS{Load,Store}Heap)
+    int32_t heapLength = int32_t(intptr_t(heap->byteLength()));
     for (size_t i = 0; i < heapAccesses_.length(); i++) {
         const jit::AsmJSHeapAccess &access = heapAccesses_[i];
         if (access.hasLengthCheck())
             X86Assembler::setInt32(access.patchLengthAt(code_), heapLength);
     }
 #elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
     uint32_t heapLength = heap->byteLength();
     for (unsigned i = 0; i < heapAccesses_.length(); i++) {
         jit::Assembler::UpdateBoundsCheck(heapLength,
                                           (jit::Instruction*)(heapAccesses_[i].offset() + code_));
     }
 #endif
 }
 
 void
-AsmJSModule::restoreToInitialState(uint8_t *prevCode,
-                                   ArrayBufferObjectMaybeShared *maybePrevBuffer,
+AsmJSModule::restoreHeapToInitialState(ArrayBufferObjectMaybeShared *maybePrevBuffer)
+{
+#if defined(JS_CODEGEN_X86)
+    if (maybePrevBuffer) {
+        // Subtract out the base-pointer added by AsmJSModule::initHeap.
+        uint8_t *ptrBase = maybePrevBuffer->dataPointer();
+        for (unsigned i = 0; i < heapAccesses_.length(); i++) {
+            const jit::AsmJSHeapAccess &access = heapAccesses_[i];
+            void *addr = access.patchOffsetAt(code_);
+            uint8_t *ptr = reinterpret_cast<uint8_t*>(X86Assembler::getPointer(addr));
+            MOZ_ASSERT(ptr >= ptrBase);
+            X86Assembler::setPointer(addr, (void *)(ptr - ptrBase));
+        }
+    }
+#endif
+
+    maybeHeap_ = nullptr;
+    heapDatum() = nullptr;
+}
+
+void
+AsmJSModule::restoreToInitialState(ArrayBufferObjectMaybeShared *maybePrevBuffer,
+                                   uint8_t *prevCode,
                                    ExclusiveContext *cx)
 {
 #ifdef DEBUG
     // Put the absolute links back to -1 so PatchDataWithValueCheck assertions
     // in staticallyLink are valid.
     for (size_t imm = 0; imm < AsmJSImm_Limit; imm++) {
         void *callee = AddressOf(AsmJSImmKind(imm), cx);
 
@@ -812,29 +835,17 @@ AsmJSModule::restoreToInitialState(uint8
                                   : callee;
             Assembler::PatchDataWithValueCheck(CodeLocationLabel(caller),
                                                PatchedImmPtr((void*)-1),
                                                PatchedImmPtr(originalValue));
         }
     }
 #endif
 
-    if (maybePrevBuffer) {
-#if defined(JS_CODEGEN_X86)
-        // Subtract out the base-pointer added by AsmJSModule::initHeap.
-        uint8_t *ptrBase = maybePrevBuffer->dataPointer();
-        for (unsigned i = 0; i < heapAccesses_.length(); i++) {
-            const jit::AsmJSHeapAccess &access = heapAccesses_[i];
-            void *addr = access.patchOffsetAt(code_);
-            uint8_t *ptr = reinterpret_cast<uint8_t*>(X86Assembler::getPointer(addr));
-            MOZ_ASSERT(ptr >= ptrBase);
-            X86Assembler::setPointer(addr, (void *)(ptr - ptrBase));
-        }
-#endif
-    }
+    restoreHeapToInitialState(maybePrevBuffer);
 }
 
 static void
 AsmJSModuleObject_finalize(FreeOp *fop, JSObject *obj)
 {
     fop->delete_(&obj->as<AsmJSModuleObject>().module());
 }
 
@@ -1545,17 +1556,32 @@ AsmJSModule::clone(JSContext *cx, Scoped
 
     out.loadedFromCache_ = loadedFromCache_;
     out.profilingEnabled_ = profilingEnabled_;
 
     // We already know the exact extent of areas that need to be patched, just make sure we
     // flush all of them at once.
     out.setAutoFlushICacheRange();
 
-    out.restoreToInitialState(code_, maybeHeap_, cx);
+    out.restoreToInitialState(maybeHeap_, code_, cx);
+    return true;
+}
+
+bool
+AsmJSModule::changeHeap(Handle<ArrayBufferObject*> newBuffer, JSContext *cx)
+{
+    uint32_t heapLength = newBuffer->byteLength();
+    if (heapLength & pod.heapLengthMask_ || heapLength < pod.minHeapLength_)
+        return false;
+
+    MOZ_ASSERT(IsValidAsmJSHeapLength(heapLength));
+    MOZ_ASSERT(!IsDeprecatedAsmJSHeapLength(heapLength));
+
+    restoreHeapToInitialState(maybeHeap_);
+    initHeap(newBuffer, cx);
     return true;
 }
 
 void
 AsmJSModule::setProfilingEnabled(bool enabled, JSContext *cx)
 {
     MOZ_ASSERT(isDynamicallyLinked());
 
--- a/js/src/asmjs/AsmJSModule.h
+++ b/js/src/asmjs/AsmJSModule.h
@@ -196,17 +196,17 @@ class AsmJSNumLit
 // NB: this means that AsmJSModule must be GC-safe.
 class AsmJSModule
 {
   public:
     class Global
     {
       public:
         enum Which { Variable, FFI, ArrayView, ArrayViewCtor, MathBuiltinFunction, Constant,
-                     SimdCtor, SimdOperation};
+                     SimdCtor, SimdOperation, ByteLength };
         enum VarInitKind { InitConstant, InitImport };
         enum ConstantKind { GlobalConstant, MathConstant };
 
       private:
         struct Pod {
             Which which_;
             union {
                 struct {
@@ -405,41 +405,54 @@ class AsmJSModule
     enum ReturnType { Return_Int32, Return_Double, Return_Int32x4, Return_Float32x4, Return_Void };
 
     class ExportedFunction
     {
         PropertyName *name_;
         PropertyName *maybeFieldName_;
         ArgCoercionVector argCoercions_;
         struct Pod {
+            bool isChangeHeap_;
             ReturnType returnType_;
             uint32_t codeOffset_;
-            // These two fields are offsets to the beginning of the ScriptSource
-            // of the module, and thus invariant under serialization (unlike
-            // absolute offsets into ScriptSource).
-            uint32_t startOffsetInModule_;
-            uint32_t endOffsetInModule_;
+            uint32_t startOffsetInModule_;  // Store module-start-relative offsets
+            uint32_t endOffsetInModule_;    // so preserved by serialization.
         } pod;
 
         friend class AsmJSModule;
 
         ExportedFunction(PropertyName *name,
                          uint32_t startOffsetInModule, uint32_t endOffsetInModule,
                          PropertyName *maybeFieldName,
                          ArgCoercionVector &&argCoercions,
                          ReturnType returnType)
         {
+            MOZ_ASSERT(name->isTenured());
+            MOZ_ASSERT_IF(maybeFieldName, maybeFieldName->isTenured());
             name_ = name;
             maybeFieldName_ = maybeFieldName;
             argCoercions_ = mozilla::Move(argCoercions);
+            pod.isChangeHeap_ = false;
             pod.returnType_ = returnType;
             pod.codeOffset_ = UINT32_MAX;
             pod.startOffsetInModule_ = startOffsetInModule;
             pod.endOffsetInModule_ = endOffsetInModule;
-            MOZ_ASSERT_IF(maybeFieldName_, name_->isTenured());
+        }
+
+        ExportedFunction(PropertyName *name,
+                         uint32_t startOffsetInModule, uint32_t endOffsetInModule,
+                         PropertyName *maybeFieldName)
+        {
+            MOZ_ASSERT(name->isTenured());
+            MOZ_ASSERT_IF(maybeFieldName, maybeFieldName->isTenured());
+            name_ = name;
+            maybeFieldName_ = maybeFieldName;
+            pod.isChangeHeap_ = true;
+            pod.startOffsetInModule_ = startOffsetInModule;
+            pod.endOffsetInModule_ = endOffsetInModule;
         }
 
         void trace(JSTracer *trc) {
             MarkStringUnbarriered(trc, &name_, "asm.js export name");
             if (maybeFieldName_)
                 MarkStringUnbarriered(trc, &maybeFieldName_, "asm.js export field");
         }
 
@@ -447,44 +460,53 @@ class AsmJSModule
         ExportedFunction() {}
         ExportedFunction(ExportedFunction &&rhs) {
             name_ = rhs.name_;
             maybeFieldName_ = rhs.maybeFieldName_;
             argCoercions_ = mozilla::Move(rhs.argCoercions_);
             pod = rhs.pod;
         }
 
-        void initCodeOffset(unsigned off) {
-            MOZ_ASSERT(pod.codeOffset_ == UINT32_MAX);
-            pod.codeOffset_ = off;
-        }
-        void updateCodeOffset(jit::MacroAssembler &masm) {
-            pod.codeOffset_ = masm.actualOffset(pod.codeOffset_);
-        }
-
-
         PropertyName *name() const {
             return name_;
         }
+        PropertyName *maybeFieldName() const {
+            return maybeFieldName_;
+        }
         uint32_t startOffsetInModule() const {
             return pod.startOffsetInModule_;
         }
         uint32_t endOffsetInModule() const {
             return pod.endOffsetInModule_;
         }
-        PropertyName *maybeFieldName() const {
-            return maybeFieldName_;
+
+        bool isChangeHeap() const {
+            return pod.isChangeHeap_;
         }
+
+        void initCodeOffset(unsigned off) {
+            MOZ_ASSERT(!isChangeHeap());
+            MOZ_ASSERT(pod.codeOffset_ == UINT32_MAX);
+            pod.codeOffset_ = off;
+        }
+        void updateCodeOffset(jit::MacroAssembler &masm) {
+            MOZ_ASSERT(!isChangeHeap());
+            pod.codeOffset_ = masm.actualOffset(pod.codeOffset_);
+        }
+
         unsigned numArgs() const {
+            MOZ_ASSERT(!isChangeHeap());
             return argCoercions_.length();
         }
         AsmJSCoercion argCoercion(unsigned i) const {
+            MOZ_ASSERT(!isChangeHeap());
             return argCoercions_[i];
         }
         ReturnType returnType() const {
+            MOZ_ASSERT(!isChangeHeap());
             return pod.returnType_;
         }
 
         size_t serializedSize() const;
         uint8_t *serialize(uint8_t *cursor) const;
         const uint8_t *deserialize(ExclusiveContext *cx, const uint8_t *cursor);
         bool clone(ExclusiveContext *cx, ExportedFunction *out) const;
     };
@@ -747,23 +769,25 @@ class AsmJSModule
 
   private:
     struct Pod {
         size_t                            funcPtrTableAndExitBytes_;
         size_t                            functionBytes_; // just the function bodies, no stubs
         size_t                            codeBytes_;     // function bodies and stubs
         size_t                            totalBytes_;    // function bodies, stubs, and global data
         uint32_t                          minHeapLength_;
+        uint32_t                          heapLengthMask_;
         uint32_t                          numGlobalScalarVars_;
         uint32_t                          numGlobalSimdVars_;
         uint32_t                          numFFIs_;
         uint32_t                          srcLength_;
         uint32_t                          srcLengthWithRightBrace_;
         bool                              strict_;
         bool                              hasArrayView_;
+        bool                              hasFixedMinHeapLength_;
         bool                              usesSignalHandlers_;
     } pod;
 
     // These two fields need to be kept out pod as they depend on the position
     // of the module within the ScriptSource and thus aren't invariant with
     // respect to caching.
     const uint32_t                        srcStart_;
     const uint32_t                        srcBodyStart_;
@@ -956,16 +980,21 @@ class AsmJSModule
     }
     bool addArrayViewCtor(Scalar::Type vt, PropertyName *field) {
         MOZ_ASSERT(!isFinishedWithModulePrologue());
         MOZ_ASSERT(field);
         Global g(Global::ArrayViewCtor, field);
         g.pod.u.viewType_ = vt;
         return globals_.append(g);
     }
+    bool addByteLength() {
+        MOZ_ASSERT(!isFinishedWithModulePrologue());
+        Global g(Global::ByteLength, nullptr);
+        return globals_.append(g);
+    }
     bool addMathBuiltinFunction(AsmJSMathBuiltinFunction func, PropertyName *field) {
         MOZ_ASSERT(!isFinishedWithModulePrologue());
         Global g(Global::MathBuiltinFunction, field);
         g.pod.u.mathBuiltinFunc_ = func;
         return globals_.append(g);
     }
     bool addMathBuiltinConstant(double value, PropertyName *field) {
         MOZ_ASSERT(!isFinishedWithModulePrologue());
@@ -1005,21 +1034,33 @@ class AsmJSModule
         MOZ_ASSERT(!isFinishedWithModulePrologue());
         pod.funcPtrTableAndExitBytes_ = 0;
         MOZ_ASSERT(isFinishedWithModulePrologue());
     }
 
     /*************************************************************************/
     // These functions are called while parsing/compiling function bodies:
 
-    void requireHeapLengthToBeAtLeast(uint32_t len) {
+    void addChangeHeap(uint32_t mask, uint32_t min) {
+        MOZ_ASSERT(isFinishedWithModulePrologue());
+        MOZ_ASSERT(!pod.hasFixedMinHeapLength_);
+        MOZ_ASSERT(IsValidAsmJSHeapLength(mask + 1));
+        MOZ_ASSERT(min >= RoundUpToNextValidAsmJSHeapLength(0));
+        pod.heapLengthMask_ = mask;
+        pod.minHeapLength_ = min;
+        pod.hasFixedMinHeapLength_ = true;
+    }
+    bool tryRequireHeapLengthToBeAtLeast(uint32_t len) {
         MOZ_ASSERT(isFinishedWithModulePrologue() && !isFinishedWithFunctionBodies());
+        if (pod.hasFixedMinHeapLength_ && len > pod.minHeapLength_)
+            return false;
         len = RoundUpToNextValidAsmJSHeapLength(len);
         if (len > pod.minHeapLength_)
             pod.minHeapLength_ = len;
+        return true;
     }
     bool addCodeRange(CodeRange::Kind kind, uint32_t begin, uint32_t end) {
         return codeRanges_.append(CodeRange(kind, begin, end));
     }
     bool addCodeRange(CodeRange::Kind kind, uint32_t begin, uint32_t pret, uint32_t end) {
         return codeRanges_.append(CodeRange(kind, begin, pret, end));
     }
     bool addFunctionCodeRange(PropertyName *name, uint32_t lineNumber,
@@ -1137,16 +1178,29 @@ class AsmJSModule
         // the beginning of the module (so that they are caching-invariant).
         MOZ_ASSERT(isFinishedWithFunctionBodies() && !isFinished());
         MOZ_ASSERT(srcStart_ < funcSrcBegin);
         MOZ_ASSERT(funcSrcBegin < funcSrcEnd);
         ExportedFunction func(name, funcSrcBegin - srcStart_, funcSrcEnd - srcStart_,
                               maybeFieldName, mozilla::Move(argCoercions), returnType);
         return exports_.length() < UINT32_MAX && exports_.append(mozilla::Move(func));
     }
+    bool addExportedChangeHeap(PropertyName *name,
+                               uint32_t funcSrcBegin,
+                               uint32_t funcSrcEnd,
+                               PropertyName *maybeFieldName)
+    {
+        // See addExportedFunction.
+        MOZ_ASSERT(isFinishedWithFunctionBodies() && !isFinished());
+        MOZ_ASSERT(srcStart_ < funcSrcBegin);
+        MOZ_ASSERT(funcSrcBegin < funcSrcEnd);
+        ExportedFunction func(name, funcSrcBegin - srcStart_, funcSrcEnd - srcStart_,
+                              maybeFieldName);
+        return exports_.length() < UINT32_MAX && exports_.append(mozilla::Move(func));
+    }
     unsigned numExportedFunctions() const {
         MOZ_ASSERT(isFinishedWithFunctionBodies());
         return exports_.length();
     }
     const ExportedFunction &exportedFunction(unsigned i) const {
         MOZ_ASSERT(isFinishedWithFunctionBodies());
         return exports_[i];
     }
@@ -1268,16 +1322,17 @@ class AsmJSModule
     }
     AsmJSActivation *&activation() const {
         return *(AsmJSActivation**)(globalData() + activationGlobalDataOffset());
     }
     bool active() const {
         return activation() != nullptr;
     }
     static unsigned heapGlobalDataOffset() {
+        JS_STATIC_ASSERT(jit::AsmJSHeapGlobalDataOffset == sizeof(void*));
         return sizeof(void*);
     }
     uint8_t *&heapDatum() const {
         MOZ_ASSERT(isFinished());
         return *(uint8_t**)(globalData() + heapGlobalDataOffset());
     }
     static unsigned nan64GlobalDataOffset() {
         static_assert(jit::AsmJSNaN64GlobalDataOffset % sizeof(double) == 0,
@@ -1382,26 +1437,29 @@ class AsmJSModule
     // variables. A given asm.js module cannot be dynamically linked more than
     // once so, if JS tries, the module is cloned.
     void setIsDynamicallyLinked() {
         MOZ_ASSERT(!isDynamicallyLinked());
         dynamicallyLinked_ = true;
         MOZ_ASSERT(isDynamicallyLinked());
     }
     void initHeap(Handle<ArrayBufferObjectMaybeShared*> heap, JSContext *cx);
+    void restoreHeapToInitialState(ArrayBufferObjectMaybeShared *maybePrevBuffer);
+    void restoreToInitialState(ArrayBufferObjectMaybeShared *maybePrevBuffer,
+                               uint8_t *prevCode,
+                               ExclusiveContext *cx);
     bool clone(JSContext *cx, ScopedJSDeletePtr<AsmJSModule> *moduleOut) const;
-    void restoreToInitialState(uint8_t *prevCode,
-                               ArrayBufferObjectMaybeShared *maybePrevBuffer,
-                               ExclusiveContext *cx);
+    bool changeHeap(Handle<ArrayBufferObject*> newHeap, JSContext *cx);
 
     /*************************************************************************/
     // Functions that can be called after dynamic linking succeeds:
 
     CodePtr entryTrampoline(const ExportedFunction &func) const {
         MOZ_ASSERT(isDynamicallyLinked());
+        MOZ_ASSERT(!func.isChangeHeap());
         return JS_DATA_TO_FUNC_PTR(CodePtr, code_ + func.pod.codeOffset_);
     }
     uint8_t *interruptExit() const {
         MOZ_ASSERT(isDynamicallyLinked());
         return interruptExit_;
     }
     uint8_t *maybeHeap() const {
         MOZ_ASSERT(isDynamicallyLinked());
--- a/js/src/asmjs/AsmJSValidate.cpp
+++ b/js/src/asmjs/AsmJSValidate.cpp
@@ -1084,17 +1084,19 @@ class MOZ_STACK_CLASS ModuleCompiler
             ConstantImport,
             Function,
             FuncPtrTable,
             FFI,
             ArrayView,
             ArrayViewCtor,
             MathBuiltinFunction,
             SimdCtor,
-            SimdOperation
+            SimdOperation,
+            ByteLength,
+            ChangeHeap
         };
 
       private:
         Which which_;
         union {
             struct {
                 VarType::Which type_;
                 uint32_t index_;
@@ -1105,16 +1107,20 @@ class MOZ_STACK_CLASS ModuleCompiler
             uint32_t ffiIndex_;
             Scalar::Type viewType_;
             AsmJSMathBuiltinFunction mathBuiltinFunc_;
             AsmJSSimdType simdCtorType_;
             struct {
                 AsmJSSimdType type_;
                 AsmJSSimdOperation which_;
             } simdOp;
+            struct {
+                uint32_t srcBegin_;
+                uint32_t srcEnd_;
+            } changeHeap;
         } u;
 
         friend class ModuleCompiler;
         friend class js::LifoAlloc;
 
         explicit Global(Which which) : which_(which) {}
 
       public:
@@ -1172,16 +1178,24 @@ class MOZ_STACK_CLASS ModuleCompiler
         AsmJSSimdOperation simdOperation() const {
             MOZ_ASSERT(which_ == SimdOperation);
             return u.simdOp.which_;
         }
         AsmJSSimdType simdOperationType() const {
             MOZ_ASSERT(which_ == SimdOperation);
             return u.simdOp.type_;
         }
+        uint32_t changeHeapSrcBegin() const {
+            MOZ_ASSERT(which_ == ChangeHeap);
+            return u.changeHeap.srcBegin_;
+        }
+        uint32_t changeHeapSrcEnd() const {
+            MOZ_ASSERT(which_ == ChangeHeap);
+            return u.changeHeap.srcEnd_;
+        }
     };
 
     typedef Vector<const Func*> FuncPtrVector;
 
     class FuncPtrTable
     {
         Signature sig_;
         uint32_t mask_;
@@ -1259,16 +1273,26 @@ class MOZ_STACK_CLASS ModuleCompiler
         explicit MathBuiltin(double cst) : kind(Constant) {
             u.cst = cst;
         }
         explicit MathBuiltin(AsmJSMathBuiltinFunction func) : kind(Function) {
             u.func = func;
         }
     };
 
+    struct ArrayView
+    {
+        ArrayView(PropertyName *name, Scalar::Type type)
+          : name(name), type(type)
+        {}
+
+        PropertyName *name;
+        Scalar::Type type;
+    };
+
   private:
     struct SlowFunction
     {
         SlowFunction(PropertyName *name, unsigned ms, unsigned line, unsigned column)
          : name(name), ms(ms), line(line), column(column)
         {}
 
         PropertyName *name;
@@ -1278,46 +1302,50 @@ class MOZ_STACK_CLASS ModuleCompiler
     };
 
     typedef HashMap<PropertyName*, MathBuiltin> MathNameMap;
     typedef HashMap<PropertyName*, AsmJSSimdOperation> SimdOperationNameMap;
     typedef HashMap<PropertyName*, Global*> GlobalMap;
     typedef Vector<Func*> FuncVector;
     typedef Vector<AsmJSGlobalAccess> GlobalAccessVector;
     typedef Vector<SlowFunction> SlowFunctionVector;
+    typedef Vector<ArrayView> ArrayViewVector;
 
     ExclusiveContext *             cx_;
     AsmJSParser &                  parser_;
 
     MacroAssembler                 masm_;
 
     ScopedJSDeletePtr<AsmJSModule> module_;
     LifoAlloc                      moduleLifo_;
     ParseNode *                    moduleFunctionNode_;
     PropertyName *                 moduleFunctionName_;
 
     GlobalMap                      globals_;
     FuncVector                     functions_;
     FuncPtrTableVector             funcPtrTables_;
+    ArrayViewVector                arrayViews_;
     ExitMap                        exits_;
     MathNameMap                    standardLibraryMathNames_;
     SimdOperationNameMap           standardLibrarySimdOpNames_;
     NonAssertingLabel              stackOverflowLabel_;
     NonAssertingLabel              asyncInterruptLabel_;
     NonAssertingLabel              syncInterruptLabel_;
 
     UniquePtr<char[], JS::FreePolicy> errorString_;
     uint32_t                       errorOffset_;
     bool                           errorOverRecursed_;
 
     int64_t                        usecBefore_;
     SlowFunctionVector             slowFunctions_;
 
     DebugOnly<bool>                finishedFunctionBodies_;
     bool                           supportsSimd_;
+    bool                           canValidateChangeHeap_;
+    bool                           hasChangeHeap_;
 
     bool addStandardLibraryMathName(const char *name, AsmJSMathBuiltinFunction func) {
         JSAtom *atom = Atomize(cx_, name, strlen(name));
         if (!atom)
             return false;
         MathBuiltin builtin(func);
         return standardLibraryMathNames_.putNew(atom->asPropertyName(), builtin);
     }
@@ -1341,26 +1369,29 @@ class MOZ_STACK_CLASS ModuleCompiler
         parser_(parser),
         masm_(MacroAssembler::AsmJSToken()),
         moduleLifo_(LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
         moduleFunctionNode_(parser.pc->maybeFunction),
         moduleFunctionName_(nullptr),
         globals_(cx),
         functions_(cx),
         funcPtrTables_(cx),
+        arrayViews_(cx),
         exits_(cx),
         standardLibraryMathNames_(cx),
         standardLibrarySimdOpNames_(cx),
         errorString_(nullptr),
         errorOffset_(UINT32_MAX),
         errorOverRecursed_(false),
         usecBefore_(PRMJ_Now()),
         slowFunctions_(cx),
         finishedFunctionBodies_(false),
-        supportsSimd_(cx->jitSupportsSimd())
+        supportsSimd_(cx->jitSupportsSimd()),
+        canValidateChangeHeap_(false),
+        hasChangeHeap_(false)
     {
         MOZ_ASSERT(moduleFunctionNode_->pn_funbox == parser.pc->sc->asFunctionBox());
     }
 
     ~ModuleCompiler() {
         if (errorString_) {
             MOZ_ASSERT(errorOffset_ != UINT32_MAX);
             tokenStream().reportAsmJSError(errorOffset_,
@@ -1537,16 +1568,22 @@ class MOZ_STACK_CLASS ModuleCompiler
             *op = p->value();
             return true;
         }
         return false;
     }
     ExitMap::Range allExits() const {
         return exits_.all();
     }
+    unsigned numArrayViews() const {
+        return arrayViews_.length();
+    }
+    const ArrayView &arrayView(unsigned i) const {
+        return arrayViews_[i];
+    }
 
     /***************************************************** Mutable interface */
 
     void initModuleFunctionName(PropertyName *name) { moduleFunctionName_ = name; }
 
     void initGlobalArgumentName(PropertyName *n) { module_->initGlobalArgumentName(n); }
     void initImportArgumentName(PropertyName *n) { module_->initImportArgumentName(n); }
     void initBufferArgumentName(PropertyName *n) { module_->initBufferArgumentName(n); }
@@ -1609,27 +1646,46 @@ class MOZ_STACK_CLASS ModuleCompiler
         if (!module_->addFuncPtrTable(/* numElems = */ mask + 1, &globalDataOffset))
             return false;
         FuncPtrTable tmpTable(cx_, Move(sig), mask, globalDataOffset);
         if (!funcPtrTables_.append(Move(tmpTable)))
             return false;
         *table = &funcPtrTables_.back();
         return true;
     }
+    bool addByteLength(PropertyName *name) {
+        canValidateChangeHeap_ = true;
+        if (!module_->addByteLength())
+            return false;
+        Global *global = moduleLifo_.new_<Global>(Global::ByteLength);
+        return global && globals_.putNew(name, global);
+    }
+    bool addChangeHeap(PropertyName *name, ParseNode *fn, uint32_t mask, uint32_t min) {
+        hasChangeHeap_ = true;
+        module_->addChangeHeap(mask, min);
+        Global *global = moduleLifo_.new_<Global>(Global::ChangeHeap);
+        if (!global)
+            return false;
+        global->u.changeHeap.srcBegin_ = fn->pn_pos.begin;
+        global->u.changeHeap.srcEnd_ = fn->pn_pos.end;
+        return globals_.putNew(name, global);
+    }
     bool addFFI(PropertyName *varName, PropertyName *field) {
         Global *global = moduleLifo_.new_<Global>(Global::FFI);
         if (!global)
             return false;
         uint32_t index;
         if (!module_->addFFI(field, &index))
             return false;
         global->u.ffiIndex_ = index;
         return globals_.putNew(varName, global);
     }
     bool addArrayView(PropertyName *varName, Scalar::Type vt, PropertyName *maybeField) {
+        if (!arrayViews_.append(ArrayView(varName, vt)))
+            return false;
         Global *global = moduleLifo_.new_<Global>(Global::ArrayView);
         if (!global)
             return false;
         if (!module_->addArrayView(vt, maybeField))
             return false;
         global->u.viewType_ = vt;
         return globals_.putNew(varName, global);
     }
@@ -1688,52 +1744,64 @@ class MOZ_STACK_CLASS ModuleCompiler
             return false;
         return addGlobalDoubleConstant(varName, constant);
     }
     bool addGlobalConstant(PropertyName *varName, double constant, PropertyName *fieldName) {
         if (!module_->addGlobalConstant(constant, fieldName))
             return false;
         return addGlobalDoubleConstant(varName, constant);
     }
-    bool addExportedFunction(const Func *func, PropertyName *maybeFieldName) {
+    bool addExportedFunction(const Func &func, PropertyName *maybeFieldName) {
         AsmJSModule::ArgCoercionVector argCoercions;
-        const VarTypeVector &args = func->sig().args();
+        const VarTypeVector &args = func.sig().args();
         if (!argCoercions.resize(args.length()))
             return false;
         for (unsigned i = 0; i < args.length(); i++)
             argCoercions[i] = args[i].toCoercion();
-        AsmJSModule::ReturnType retType = func->sig().retType().toModuleReturnType();
-        return module_->addExportedFunction(func->name(), func->srcBegin(), func->srcEnd(),
+        AsmJSModule::ReturnType retType = func.sig().retType().toModuleReturnType();
+        return module_->addExportedFunction(func.name(), func.srcBegin(), func.srcEnd(),
                                             maybeFieldName, Move(argCoercions), retType);
     }
+    bool addExportedChangeHeap(PropertyName *name, const Global &g, PropertyName *maybeFieldName) {
+        return module_->addExportedChangeHeap(name, g.changeHeapSrcBegin(), g.changeHeapSrcEnd(),
+                                              maybeFieldName);
+    }
     bool addExit(unsigned ffiIndex, PropertyName *name, Signature &&sig, unsigned *exitIndex) {
         ExitDescriptor exitDescriptor(name, Move(sig));
         ExitMap::AddPtr p = exits_.lookupForAdd(exitDescriptor);
         if (p) {
             *exitIndex = p->value();
             return true;
         }
         if (!module_->addExit(ffiIndex, exitIndex))
             return false;
         return exits_.add(p, Move(exitDescriptor), *exitIndex);
     }
 
-    void requireHeapLengthToBeAtLeast(uint32_t len) {
-        module_->requireHeapLengthToBeAtLeast(len);
+    bool tryRequireHeapLengthToBeAtLeast(uint32_t len) {
+        return module_->tryRequireHeapLengthToBeAtLeast(len);
     }
     uint32_t minHeapLength() const {
         return module_->minHeapLength();
     }
     LifoAlloc &lifo() {
         return moduleLifo_;
     }
 
     void startFunctionBodies() {
         module_->startFunctionBodies();
     }
+    bool tryOnceToValidateChangeHeap() {
+        bool ret = canValidateChangeHeap_;
+        canValidateChangeHeap_ = false;
+        return ret;
+    }
+    bool hasChangeHeap() {
+        return hasChangeHeap_;
+    }
     bool finishGeneratingFunction(Func &func, CodeGenerator &codegen,
                                   const AsmJSFunctionLabels &labels)
     {
         uint32_t line, column;
         tokenStream().srcCoords.lineNumAndColumnIndex(func.srcBegin(), &line, &column);
 
         if (!module_->addFunctionCodeRange(func.name(), line, labels))
             return false;
@@ -1971,18 +2039,20 @@ IsSimdTuple(ModuleCompiler &m, ParseNode
         return false;
 
     *type = global->simdCtorType();
     return true;
 }
 
 static bool
 IsNumericLiteral(ModuleCompiler &m, ParseNode *pn);
+
 static AsmJSNumLit
 ExtractNumericLiteral(ModuleCompiler &m, ParseNode *pn);
+
 static inline bool
 IsLiteralInt(ModuleCompiler &m, ParseNode *pn, uint32_t *u32);
 
 static bool
 IsSimdLiteral(ModuleCompiler &m, ParseNode *pn)
 {
     AsmJSSimdType type;
     if (!IsSimdTuple(m, pn, &type))
@@ -2187,16 +2257,18 @@ class FunctionCompiler
 
     NodeStack              loopStack_;
     NodeStack              breakableStack_;
     UnlabeledBlockMap      unlabeledBreaks_;
     UnlabeledBlockMap      unlabeledContinues_;
     LabeledBlockMap        labeledBreaks_;
     LabeledBlockMap        labeledContinues_;
 
+    unsigned               heapExpressionDepth_;
+
   public:
     FunctionCompiler(ModuleCompiler &m, ParseNode *fn, LifoAlloc &lifo)
       : m_(m),
         lifo_(lifo),
         fn_(fn),
         locals_(m.cx()),
         varInitializers_(m.cx()),
         alloc_(nullptr),
@@ -2204,17 +2276,18 @@ class FunctionCompiler
         info_(nullptr),
         mirGen_(nullptr),
         curBlock_(nullptr),
         loopStack_(m.cx()),
         breakableStack_(m.cx()),
         unlabeledBreaks_(m.cx()),
         unlabeledContinues_(m.cx()),
         labeledBreaks_(m.cx()),
-        labeledContinues_(m.cx())
+        labeledContinues_(m.cx()),
+        heapExpressionDepth_(0)
     {}
 
     ModuleCompiler &    m() const      { return m_; }
     TempAllocator &     alloc() const  { return *alloc_; }
     LifoAlloc &         lifo() const   { return lifo_; }
     ParseNode *         fn() const     { return fn_; }
     ExclusiveContext *  cx() const     { return m_.cx(); }
     const AsmJSModule & module() const { return m_.module(); }
@@ -2366,16 +2439,29 @@ class FunctionCompiler
             return nullptr;
         return m_.lookupGlobal(name);
     }
 
     bool supportsSimd() const {
         return m_.supportsSimd();
     }
 
+    /*************************************************************************/
+
+    void enterHeapExpression() {
+        heapExpressionDepth_++;
+    }
+    void leaveHeapExpression() {
+        MOZ_ASSERT(heapExpressionDepth_ > 0);
+        heapExpressionDepth_--;
+    }
+    bool canCall() const {
+        return heapExpressionDepth_ == 0 || !m_.hasChangeHeap();
+    }
+
     /***************************** Code generation (after local scope setup) */
 
     MDefinition *constant(const AsmJSNumLit &lit)
     {
         if (inDeadCode())
             return nullptr;
 
         MInstruction *constant;
@@ -3530,16 +3616,29 @@ IsArrayViewCtorName(ModuleCompiler &m, P
     else if (name == names.Float64Array || name == names.SharedFloat64Array)
         *type = Scalar::Float64;
     else
         return false;
     return true;
 }
 
 static bool
+CheckNewArrayViewArgs(ModuleCompiler &m, ParseNode *ctorExpr, PropertyName *bufferName)
+{
+    ParseNode *bufArg = NextNode(ctorExpr);
+    if (!bufArg || NextNode(bufArg) != nullptr)
+        return m.fail(ctorExpr, "array view constructor takes exactly one argument");
+
+    if (!IsUseOfName(bufArg, bufferName))
+        return m.failName(bufArg, "argument to array view constructor must be '%s'", bufferName);
+
+    return true;
+}
+
+static bool
 CheckNewArrayView(ModuleCompiler &m, PropertyName *varName, ParseNode *newExpr)
 {
     PropertyName *globalName = m.module().globalArgumentName();
     if (!globalName)
         return m.fail(newExpr, "cannot create array view without an asm.js global parameter");
 
     PropertyName *bufferName = m.module().bufferArgumentName();
     if (!bufferName)
@@ -3569,22 +3668,18 @@ CheckNewArrayView(ModuleCompiler &m, Pro
 
         if (global->which() != ModuleCompiler::Global::ArrayViewCtor)
             return m.failName(ctorExpr, "%s must be an imported array view constructor", globalName);
 
         field = nullptr;
         type = global->viewType();
     }
 
-    ParseNode *bufArg = NextNode(ctorExpr);
-    if (!bufArg || NextNode(bufArg) != nullptr)
-        return m.fail(ctorExpr, "array view constructor takes exactly one argument");
-
-    if (!IsUseOfName(bufArg, bufferName))
-        return m.failName(bufArg, "argument to array view constructor must be '%s'", bufferName);
+    if (!CheckNewArrayViewArgs(m, ctorExpr, bufferName))
+        return false;
 
     return m.addArrayView(varName, type, field);
 }
 
 static bool
 IsSimdTypeName(ModuleCompiler &m, PropertyName *name, AsmJSSimdType *type)
 {
     if (name == m.cx()->names().int32x4) {
@@ -3694,16 +3789,18 @@ CheckGlobalDotImport(ModuleCompiler &m, 
     if (!base->isKind(PNK_NAME))
         return m.fail(base, "expected name of variable or parameter");
 
     if (base->name() == m.module().globalArgumentName()) {
         if (field == m.cx()->names().NaN)
             return m.addGlobalConstant(varName, GenericNaN(), field);
         if (field == m.cx()->names().Infinity)
             return m.addGlobalConstant(varName, PositiveInfinity<double>(), field);
+        if (field == m.cx()->names().byteLength)
+            return m.addByteLength(varName);
 
         Scalar::Type type;
         if (IsArrayViewCtorName(m, field, &type))
             return m.addArrayViewCtor(varName, type, field);
 
         return m.failName(initNode, "'%s' is not a standard constant or typed array name", field);
     }
 
@@ -3968,16 +4065,18 @@ CheckVarRef(FunctionCompiler &f, ParseNo
           case ModuleCompiler::Global::Function:
           case ModuleCompiler::Global::FFI:
           case ModuleCompiler::Global::MathBuiltinFunction:
           case ModuleCompiler::Global::FuncPtrTable:
           case ModuleCompiler::Global::ArrayView:
           case ModuleCompiler::Global::ArrayViewCtor:
           case ModuleCompiler::Global::SimdCtor:
           case ModuleCompiler::Global::SimdOperation:
+          case ModuleCompiler::Global::ByteLength:
+          case ModuleCompiler::Global::ChangeHeap:
             return f.failName(varRef, "'%s' may not be accessed by ordinary expressions", name);
         }
         return true;
     }
 
     return f.failName(varRef, "'%s' not found in local or asm.js module scope", name);
 }
 
@@ -4045,17 +4144,20 @@ CheckArrayAccess(FunctionCompiler &f, Pa
 
     uint32_t index;
     if (IsLiteralOrConstInt(f, indexExpr, &index)) {
         uint64_t byteOffset = uint64_t(index) << TypedArrayShift(*viewType);
         if (byteOffset > INT32_MAX)
             return f.fail(indexExpr, "constant index out of range");
 
         unsigned elementSize = 1 << TypedArrayShift(*viewType);
-        f.m().requireHeapLengthToBeAtLeast(byteOffset + elementSize);
+        if (!f.m().tryRequireHeapLengthToBeAtLeast(byteOffset + elementSize)) {
+            return f.failf(indexExpr, "constant index outside heap size declared by the "
+                                      "change-heap function (0x%x)", f.m().minHeapLength());
+        }
 
         *needsBoundsCheck = NO_BOUNDS_CHECK;
         *def = f.constant(Int32Value(byteOffset), Type::Int);
         return true;
     }
 
     // Mask off the low bits to account for the clearing effect of a right shift
     // followed by the left shift implicit in the array access. E.g., H32[i>>2]
@@ -4073,36 +4175,44 @@ CheckArrayAccess(FunctionCompiler &f, Pa
 
         unsigned requiredShift = TypedArrayShift(*viewType);
         if (shift != requiredShift)
             return f.failf(shiftNode, "shift amount must be %u", requiredShift);
 
         if (pointerNode->isKind(PNK_BITAND))
             FoldMaskedArrayIndex(f, &pointerNode, &mask, needsBoundsCheck);
 
+        f.enterHeapExpression();
+
         Type pointerType;
         if (!CheckExpr(f, pointerNode, &pointerDef, &pointerType))
             return false;
 
+        f.leaveHeapExpression();
+
         if (!pointerType.isIntish())
             return f.failf(indexExpr, "%s is not a subtype of int", pointerType.toChars());
     } else {
         if (TypedArrayShift(*viewType) != 0)
             return f.fail(indexExpr, "index expression isn't shifted; must be an Int8/Uint8 access");
 
         MOZ_ASSERT(mask == -1);
         bool folded = false;
 
         if (indexExpr->isKind(PNK_BITAND))
             folded = FoldMaskedArrayIndex(f, &indexExpr, &mask, needsBoundsCheck);
 
+        f.enterHeapExpression();
+
         Type pointerType;
         if (!CheckExpr(f, indexExpr, &pointerDef, &pointerType))
             return false;
 
+        f.leaveHeapExpression();
+
         if (folded) {
             if (!pointerType.isIntish())
                 return f.failf(indexExpr, "%s is not a subtype of intish", pointerType.toChars());
         } else {
             if (!pointerType.isInt())
                 return f.failf(indexExpr, "%s is not a subtype of int", pointerType.toChars());
         }
     }
@@ -4176,21 +4286,25 @@ static bool
 CheckStoreArray(FunctionCompiler &f, ParseNode *lhs, ParseNode *rhs, MDefinition **def, Type *type)
 {
     Scalar::Type viewType;
     MDefinition *pointerDef;
     NeedsBoundsCheck needsBoundsCheck;
     if (!CheckArrayAccess(f, lhs, &viewType, &pointerDef, &needsBoundsCheck))
         return false;
 
+    f.enterHeapExpression();
+
     MDefinition *rhsDef;
     Type rhsType;
     if (!CheckExpr(f, rhs, &rhsDef, &rhsType))
         return false;
 
+    f.leaveHeapExpression();
+
     switch (viewType) {
       case Scalar::Int8:
       case Scalar::Int16:
       case Scalar::Int32:
       case Scalar::Uint8:
       case Scalar::Uint16:
       case Scalar::Uint32:
         if (!rhsType.isIntish())
@@ -5195,16 +5309,21 @@ CheckCoercedSimdCall(FunctionCompiler &f
     return CoerceResult(f, call, retType, *def, *type, def, type);
 }
 
 static bool
 CheckCoercedCall(FunctionCompiler &f, ParseNode *call, RetType retType, MDefinition **def, Type *type)
 {
     JS_CHECK_RECURSION_DONT_REPORT(f.cx(), return f.m().failOverRecursed());
 
+    if (!f.canCall()) {
+        return f.fail(call, "call expressions may not be nested inside heap expressions when "
+                            "the module contains a change-heap function");
+    }
+
     if (IsNumericLiteral(f.m(), call)) {
         AsmJSNumLit literal = ExtractNumericLiteral(f.m(), call);
         MDefinition *result = f.constant(literal);
         return CoerceResult(f, call, retType, result, Type::Of(literal), def, type);
     }
 
     ParseNode *callee = CallCallee(call);
 
@@ -5223,16 +5342,18 @@ CheckCoercedCall(FunctionCompiler &f, Pa
           case ModuleCompiler::Global::MathBuiltinFunction:
             return CheckCoercedMathBuiltinCall(f, call, global->mathBuiltinFunction(), retType, def, type);
           case ModuleCompiler::Global::ConstantLiteral:
           case ModuleCompiler::Global::ConstantImport:
           case ModuleCompiler::Global::Variable:
           case ModuleCompiler::Global::FuncPtrTable:
           case ModuleCompiler::Global::ArrayView:
           case ModuleCompiler::Global::ArrayViewCtor:
+          case ModuleCompiler::Global::ByteLength:
+          case ModuleCompiler::Global::ChangeHeap:
             return f.failName(callee, "'%s' is not callable function", callee->name());
           case ModuleCompiler::Global::SimdCtor:
           case ModuleCompiler::Global::SimdOperation:
             return CheckCoercedSimdCall(f, call, global, retType, def, type);
           case ModuleCompiler::Global::Function:
             break;
         }
     }
@@ -6363,16 +6484,205 @@ CheckStatement(FunctionCompiler &f, Pars
       case PNK_CONTINUE:      return f.addContinue(LoopControlMaybeLabel(stmt));
       default:;
     }
 
     return f.fail(stmt, "unexpected statement kind");
 }
 
 static bool
+CheckByteLengthCall(ModuleCompiler &m, ParseNode *pn, PropertyName *newBufferName)
+{
+    if (!pn->isKind(PNK_CALL) || !CallCallee(pn)->isKind(PNK_NAME))
+        return m.fail(pn, "expecting call to imported byteLength");
+
+    const ModuleCompiler::Global *global = m.lookupGlobal(CallCallee(pn)->name());
+    if (!global || global->which() != ModuleCompiler::Global::ByteLength)
+        return m.fail(pn, "expecting call to imported byteLength");
+
+    if (CallArgListLength(pn) != 1 || !IsUseOfName(CallArgList(pn), newBufferName))
+        return m.failName(pn, "expecting %s as argument to byteLength call", newBufferName);
+
+    return true;
+}
+
+static bool
+CheckHeapLengthCondition(ModuleCompiler &m, ParseNode *cond, PropertyName *newBufferName,
+                         uint32_t *mask, uint32_t *minimumLength)
+{
+    if (!cond->isKind(PNK_OR))
+        return m.fail(cond, "expecting byteLength & K || byteLength <= L");
+
+    ParseNode *leftCond = BinaryLeft(cond);
+    ParseNode *rightCond = BinaryRight(cond);
+
+    if (!leftCond->isKind(PNK_BITAND))
+        return m.fail(leftCond, "expecting byteLength & K");
+
+    if (!CheckByteLengthCall(m, BinaryLeft(leftCond), newBufferName))
+        return false;
+
+    ParseNode *maskNode = BinaryRight(leftCond);
+    if (!IsLiteralInt(m, maskNode, mask))
+        return m.fail(maskNode, "expecting integer literal mask");
+    if ((*mask & 0xffffff) != 0xffffff)
+        return m.fail(maskNode, "mask value must have the bits 0xffffff set");
+
+    if (!rightCond->isKind(PNK_LE))
+        return m.fail(rightCond, "expecting byteLength <= L");
+
+    if (!CheckByteLengthCall(m, BinaryLeft(rightCond), newBufferName))
+        return false;
+
+    ParseNode *minLengthNode = BinaryRight(rightCond);
+    uint32_t minLengthExclusive;
+    if (!IsLiteralInt(m, minLengthNode, &minLengthExclusive))
+        return m.fail(minLengthNode, "expecting integer limit literal");
+    if (minLengthExclusive < 0xffffff)
+        return m.fail(minLengthNode, "limit value must be >= 0xffffff");
+
+    // Add one to convert from exclusive (the branch rejects if ==) to inclusive.
+    *minimumLength = minLengthExclusive + 1;
+    return true;
+}
+
+static bool
+CheckReturnBoolLiteral(ModuleCompiler &m, ParseNode *stmt, bool retval)
+{
+    if (!stmt)
+        return m.fail(stmt, "expected return statement");
+
+    if (stmt->isKind(PNK_STATEMENTLIST)) {
+        stmt = SkipEmptyStatements(ListHead(stmt));
+        if (!stmt || NextNonEmptyStatement(stmt))
+            return m.fail(stmt, "expected single return statement");
+    }
+
+    if (!stmt->isKind(PNK_RETURN))
+        return m.fail(stmt, "expected return statement");
+
+    ParseNode *returnExpr = ReturnExpr(stmt);
+    if (!returnExpr || !returnExpr->isKind(retval ? PNK_TRUE : PNK_FALSE))
+        return m.failf(stmt, "expected 'return %s;'", retval ? "true" : "false");
+
+    return true;
+}
+
+static bool
+CheckReassignmentTo(ModuleCompiler &m, ParseNode *stmt, PropertyName *lhsName, ParseNode **rhs)
+{
+    if (!stmt || !stmt->isKind(PNK_SEMI))
+        return m.fail(stmt, "missing reassignment");
+
+    ParseNode *assign = UnaryKid(stmt);
+    if (!assign || !assign->isKind(PNK_ASSIGN))
+        return m.fail(stmt, "missing reassignment");
+
+    ParseNode *lhs = BinaryLeft(assign);
+    if (!IsUseOfName(lhs, lhsName))
+        return m.failName(lhs, "expecting reassignment of %s", lhsName);
+
+    *rhs = BinaryRight(assign);
+    return true;
+}
+
+static bool
+CheckChangeHeap(ModuleCompiler &m, ParseNode *fn, bool *validated)
+{
+    MOZ_ASSERT(fn->isKind(PNK_FUNCTION));
+
+    // We don't yet know whether this is a change-heap function.
+    // The point at which we know we have a change-heap function is once we see
+    // whether the argument is coerced according to the normal asm.js rules. If
+    // it is coerced, it's not change-heap and must validate according to normal
+    // rules; otherwise it must validate as a change-heap function.
+    *validated = false;
+
+    PropertyName *changeHeapName = FunctionName(fn);
+    if (!CheckModuleLevelName(m, fn, changeHeapName))
+        return false;
+
+    unsigned numFormals;
+    ParseNode *arg = FunctionArgsList(fn, &numFormals);
+    if (numFormals != 1)
+        return true;
+
+    PropertyName *newBufferName;
+    if (!CheckArgument(m, arg, &newBufferName))
+        return false;
+
+    ParseNode *stmtIter = SkipEmptyStatements(ListHead(FunctionStatementList(fn)));
+    if (!stmtIter || !stmtIter->isKind(PNK_IF))
+        return true;
+
+    // We can now issue validation failures if we see something that isn't a
+    // valid change-heap function.
+    *validated = true;
+
+    PropertyName *bufferName = m.module().bufferArgumentName();
+    if (!bufferName)
+        return m.fail(fn, "to change heaps, the module must have a buffer argument");
+
+    ParseNode *cond = TernaryKid1(stmtIter);
+    ParseNode *thenStmt = TernaryKid2(stmtIter);
+    if (ParseNode *elseStmt = TernaryKid3(stmtIter))
+        return m.fail(elseStmt, "unexpected else statement");
+
+    uint32_t mask, min;
+    if (!CheckHeapLengthCondition(m, cond, newBufferName, &mask, &min))
+        return false;
+
+    if (!CheckReturnBoolLiteral(m, thenStmt, false))
+        return false;
+
+    stmtIter = NextNonEmptyStatement(stmtIter);
+
+    for (unsigned i = 0; i < m.numArrayViews(); i++, stmtIter = NextNonEmptyStatement(stmtIter)) {
+        const ModuleCompiler::ArrayView &view = m.arrayView(i);
+
+        ParseNode *rhs;
+        if (!CheckReassignmentTo(m, stmtIter, view.name, &rhs))
+            return false;
+
+        if (!rhs->isKind(PNK_NEW))
+            return m.failName(rhs, "expecting assignment of new array view to %s", view.name);
+
+        ParseNode *ctorExpr = ListHead(rhs);
+        if (!ctorExpr->isKind(PNK_NAME))
+            return m.fail(rhs, "expecting name of imported typed array constructor");
+
+        const ModuleCompiler::Global *global = m.lookupGlobal(ctorExpr->name());
+        if (!global || global->which() != ModuleCompiler::Global::ArrayViewCtor)
+            return m.fail(rhs, "expecting name of imported typed array constructor");
+        if (global->viewType() != view.type)
+            return m.fail(rhs, "can't change the type of a global view variable");
+
+        if (!CheckNewArrayViewArgs(m, ctorExpr, newBufferName))
+            return false;
+    }
+
+    ParseNode *rhs;
+    if (!CheckReassignmentTo(m, stmtIter, bufferName, &rhs))
+        return false;
+    if (!IsUseOfName(rhs, newBufferName))
+        return m.failName(stmtIter, "expecting assignment of new buffer to %s", bufferName);
+
+    stmtIter = NextNonEmptyStatement(stmtIter);
+
+    if (!CheckReturnBoolLiteral(m, stmtIter, true))
+        return false;
+
+    stmtIter = NextNonEmptyStatement(stmtIter);
+    if (stmtIter)
+        return m.fail(stmtIter, "expecting end of function");
+
+    return m.addChangeHeap(changeHeapName, fn, mask, min);
+}
+
+static bool
 ParseFunction(ModuleCompiler &m, ParseNode **fnOut)
 {
     TokenStream &tokenStream = m.tokenStream();
 
     DebugOnly<TokenKind> tk = tokenStream.getToken();
     MOZ_ASSERT(tk == TOK_FUNCTION);
 
     RootedPropertyName name(m.cx());
@@ -6437,16 +6747,26 @@ CheckFunction(ModuleCompiler &m, LifoAll
 
     ParseNode *fn;
     if (!ParseFunction(m, &fn))
         return false;
 
     if (!CheckFunctionHead(m, fn))
         return false;
 
+    if (m.tryOnceToValidateChangeHeap()) {
+        bool validated;
+        if (!CheckChangeHeap(m, fn, &validated))
+            return false;
+        if (validated) {
+            *mir = nullptr;
+            return true;
+        }
+    }
+
     FunctionCompiler f(m, fn, lifo);
     if (!f.init())
         return false;
 
     ParseNode *stmtIter = ListHead(FunctionStatementList(fn));
 
     VarTypeVector argTypes(m.lifo());
     if (!CheckArguments(f, &stmtIter, &argTypes))
@@ -6546,16 +6866,20 @@ CheckFunctionsSequential(ModuleCompiler 
     while (PeekToken(m.parser()) == TOK_FUNCTION) {
         LifoAllocScope scope(&lifo);
 
         MIRGenerator *mir;
         ModuleCompiler::Func *func;
         if (!CheckFunction(m, lifo, &mir, &func))
             return false;
 
+        // In the case of the change-heap function, no MIR is produced.
+        if (!mir)
+            continue;
+
         int64_t before = PRMJ_Now();
 
         IonContext icx(m.cx(), &mir->alloc());
 
         IonSpewNewFunction(&mir->graph(), NullPtr());
 
         if (!OptimizeMIR(mir))
             return m.failOffset(func->srcBegin(), "internal compiler failure (probably out of memory)");
@@ -6643,17 +6967,17 @@ GetFinishedCompilation(ModuleCompiler &m
         }
         HelperThreadState().wait(GlobalHelperThreadState::CONSUMER);
     }
 
     return nullptr;
 }
 
 static bool
-GenerateCodeForFinishedJob(ModuleCompiler &m, ParallelGroupState &group, AsmJSParallelTask **outTask)
+GetUsedTask(ModuleCompiler &m, ParallelGroupState &group, AsmJSParallelTask **outTask)
 {
     // Block until a used LifoAlloc becomes available.
     AsmJSParallelTask *task = GetFinishedCompilation(m, group);
     if (!task)
         return false;
 
     ModuleCompiler::Func &func = *reinterpret_cast<ModuleCompiler::Func *>(task->func);
     func.accumulateCompileTime(task->compileTime);
@@ -6695,40 +7019,44 @@ CheckFunctionsParallel(ModuleCompiler &m
     {
         AutoLockHelperThreadState lock;
         MOZ_ASSERT(HelperThreadState().asmJSWorklist().empty());
         MOZ_ASSERT(HelperThreadState().asmJSFinishedList().empty());
     }
 #endif
     HelperThreadState().resetAsmJSFailureState();
 
+    AsmJSParallelTask *task = nullptr;
     for (unsigned i = 0; PeekToken(m.parser()) == TOK_FUNCTION; i++) {
-        // Get exclusive access to an empty LifoAlloc from the thread group's pool.
-        AsmJSParallelTask *task = nullptr;
-        if (!GetUnusedTask(group, i, &task) && !GenerateCodeForFinishedJob(m, group, &task))
+        if (!task && !GetUnusedTask(group, i, &task) && !GetUsedTask(m, group, &task))
             return false;
 
         // Generate MIR into the LifoAlloc on the main thread.
         MIRGenerator *mir;
         ModuleCompiler::Func *func;
         if (!CheckFunction(m, task->lifo, &mir, &func))
             return false;
 
+        // In the case of the change-heap function, no MIR is produced.
+        if (!mir)
+            continue;
+
         // Perform optimizations and LIR generation on a helper thread.
         task->init(m.cx()->compartment()->runtimeFromAnyThread(), func, mir);
         if (!StartOffThreadAsmJSCompile(m.cx(), task))
             return false;
 
         group.outstandingJobs++;
+        task = nullptr;
     }
 
     // Block for all outstanding helpers to complete.
     while (group.outstandingJobs > 0) {
         AsmJSParallelTask *ignored = nullptr;
-        if (!GenerateCodeForFinishedJob(m, group, &ignored))
+        if (!GetUsedTask(m, group, &ignored))
             return false;
     }
 
     if (!CheckAllFunctionsDefined(m))
         return false;
 
     MOZ_ASSERT(group.outstandingJobs == 0);
     MOZ_ASSERT(group.compiledJobs == m.numFunctions());
@@ -6900,28 +7228,33 @@ CheckFuncPtrTables(ModuleCompiler &m)
         if (!m.funcPtrTable(i).initialized())
             return m.fail(nullptr, "expecting function-pointer table");
     }
 
     return true;
 }
 
 static bool
-CheckModuleExportFunction(ModuleCompiler &m, ParseNode *returnExpr)
-{
-    if (!returnExpr->isKind(PNK_NAME))
-        return m.fail(returnExpr, "export statement must be of the form 'return name'");
-
-    PropertyName *funcName = returnExpr->name();
-
-    const ModuleCompiler::Func *func = m.lookupFunction(funcName);
-    if (!func)
-        return m.failName(returnExpr, "exported function name '%s' not found", funcName);
-
-    return m.addExportedFunction(func, /* maybeFieldName = */ nullptr);
+CheckModuleExportFunction(ModuleCompiler &m, ParseNode *pn, PropertyName *maybeFieldName = nullptr)
+{
+    if (!pn->isKind(PNK_NAME))
+        return m.fail(pn, "expected name of exported function");
+
+    PropertyName *funcName = pn->name();
+    const ModuleCompiler::Global *global = m.lookupGlobal(funcName);
+    if (!global)
+        return m.failName(pn, "exported function name '%s' not found", funcName);
+
+    if (global->which() == ModuleCompiler::Global::Function)
+        return m.addExportedFunction(m.function(global->funcIndex()), maybeFieldName);
+
+    if (global->which() == ModuleCompiler::Global::ChangeHeap)
+        return m.addExportedChangeHeap(funcName, *global, maybeFieldName);
+
+    return m.failName(pn, "'%s' is not a function", funcName);
 }
 
 static bool
 CheckModuleExportObject(ModuleCompiler &m, ParseNode *object)
 {
     MOZ_ASSERT(object->isKind(PNK_OBJECT));
 
     for (ParseNode *pn = ListHead(object); pn; pn = NextNode(pn)) {
@@ -6929,23 +7262,17 @@ CheckModuleExportObject(ModuleCompiler &
             return m.fail(pn, "only normal object properties may be used in the export object literal");
 
         PropertyName *fieldName = ObjectNormalFieldName(m.cx(), pn);
 
         ParseNode *initNode = ObjectNormalFieldInitializer(m.cx(), pn);
         if (!initNode->isKind(PNK_NAME))
             return m.fail(initNode, "initializer of exported object literal must be name of function");
 
-        PropertyName *funcName = initNode->name();
-
-        const ModuleCompiler::Func *func = m.lookupFunction(funcName);
-        if (!func)
-            return m.failName(initNode, "exported function name '%s' not found", funcName);
-
-        if (!m.addExportedFunction(func, fieldName))
+        if (!CheckModuleExportFunction(m, initNode, fieldName))
             return false;
     }
 
     return true;
 }
 
 static bool
 CheckModuleReturn(ModuleCompiler &m)
@@ -7070,20 +7397,19 @@ GenerateEntry(ModuleCompiler &m, unsigne
     // addressing, x86 uses immediates in effective addresses). For the
     // AsmJSGlobalRegBias addition, see Assembler-(mips,arm).h.
 #if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
     masm.movePtr(IntArgReg1, GlobalReg);
     masm.addPtr(Imm32(AsmJSGlobalRegBias), GlobalReg);
 #endif
 
     // ARM, MIPS and x64 have a globally-pinned HeapReg (x86 uses immediates in
-    // effective addresses).
-#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
-    masm.loadPtr(Address(IntArgReg1, AsmJSModule::heapGlobalDataOffset()), HeapReg);
-#endif
+    // effective addresses). Loading the heap register depends on the global
+    // register already having been loaded.
+    masm.loadAsmJSHeapRegisterFromGlobalData();
 
     // Put the 'argv' argument into a non-argument/return register so that we
     // can use 'argv' while we fill in the arguments for the asm.js callee.
     // Also, save 'argv' on the stack so that we can recover it after the call.
     // Use a second non-argument/return register as temporary scratch.
     Register argv = ABIArgGenerator::NonArgReturnReg0;
     Register scratch = ABIArgGenerator::NonArgReturnReg1;
 #if defined(JS_CODEGEN_X86)
@@ -7333,67 +7659,63 @@ GenerateFFIInterpExit(ModuleCompiler &m,
         break;
       case RetType::Float:
         MOZ_CRASH("Float32 shouldn't be returned from a FFI");
       case RetType::Int32x4:
       case RetType::Float32x4:
         MOZ_CRASH("SIMD types shouldn't be returned from a FFI");
     }
 
+    // The heap pointer may have changed during the FFI, so reload it.
+    masm.loadAsmJSHeapRegisterFromGlobalData();
+
     Label profilingReturn;
     GenerateAsmJSExitEpilogue(masm, framePushed, AsmJSExit::SlowFFI, &profilingReturn);
     return m.finishGeneratingInterpExit(exitIndex, &begin, &profilingReturn) && !masm.oom();
 }
 
 // On ARM/MIPS, we need to include an extra word of space at the top of the
 // stack so we can explicitly store the return address before making the call
-// to C++ or Ion. On x86/x64, this isn't necessary since the call instruction
-// pushes the return address.
+// to C++ or Ion and an extra word to store the pinned global-data register. On
+// x86/x64, neither is necessary since the call instruction pushes the return
+// address and global data is reachable via immediate or rip-relative
+// addressing.
 #if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
 static const unsigned MaybeRetAddr = sizeof(void*);
+static const unsigned MaybeSavedGlobalReg = sizeof(void*);
 #else
 static const unsigned MaybeRetAddr = 0;
+static const unsigned MaybeSavedGlobalReg = 0;
 #endif
 
 static bool
 GenerateFFIIonExit(ModuleCompiler &m, const ModuleCompiler::ExitDescriptor &exit,
                    unsigned exitIndex, Label *throwLabel)
 {
     MacroAssembler &masm = m.masm();
 
-    // Even though the caller has saved volatile registers, we still need to
-    // save/restore globally-pinned asm.js registers at Ion calls since Ion does
-    // not preserve non-volatile registers.
-#if defined(JS_CODEGEN_X64)
-    unsigned savedRegBytes = 1 * sizeof(void*);  // HeapReg
-#elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
-    unsigned savedRegBytes = 2 * sizeof(void*);  // HeapReg, GlobalReg
-#else
-    unsigned savedRegBytes = 0;
-#endif
-
     // The same stack frame is used for the call into Ion and (possibly) a call
     // into C++ to coerce the return type. To do this, we compute the amount of
     // space required for both calls and take the maximum. In both cases,
-    // include space for savedRegBytes, since these go below the Ion/coerce.
+    // include space for MaybeSavedGlobalReg, since this goes below the Ion/coerce.
 
     // Ion calls use the following stack layout (sp grows to the left):
     //   | return address | descriptor | callee | argc | this | arg1 | arg2 | ...
     unsigned offsetToIonArgs = MaybeRetAddr;
     unsigned ionArgBytes = 3 * sizeof(size_t) + (1 + exit.sig().args().length()) * sizeof(Value);
-    unsigned totalIonBytes = offsetToIonArgs + ionArgBytes + savedRegBytes;
+    unsigned totalIonBytes = offsetToIonArgs + ionArgBytes + MaybeSavedGlobalReg;
     unsigned ionFrameSize = StackDecrementForCall(masm, AsmJSStackAlignment, totalIonBytes);
 
     // Coercion calls use the following stack layout (sp grows to the left):
     //   | stack args | padding | Value argv[1] | ...
     // The padding between args and argv ensures that argv is aligned.
     MIRTypeVector coerceArgTypes(m.cx());
     coerceArgTypes.infallibleAppend(MIRType_Pointer); // argv
     unsigned offsetToCoerceArgv = AlignBytes(StackArgBytes(coerceArgTypes), sizeof(double));
-    unsigned totalCoerceBytes = offsetToCoerceArgv + sizeof(Value) + savedRegBytes;
+    unsigned totalCoerceBytes = offsetToCoerceArgv + sizeof(Value) + MaybeSavedGlobalReg;
     unsigned coerceFrameSize = StackDecrementForCall(masm, AsmJSStackAlignment, totalCoerceBytes);
 
     unsigned framePushed = Max(ionFrameSize, coerceFrameSize);
 
     Label begin;
     GenerateAsmJSExitPrologue(masm, framePushed, AsmJSExit::IonFFI, &begin);
 
     // 1. Descriptor
@@ -7437,25 +7759,27 @@ GenerateFFIIonExit(ModuleCompiler &m, co
     argOffset += sizeof(Value);
 
     // 5. Fill the arguments
     unsigned offsetToCallerStackArgs = framePushed + sizeof(AsmJSFrame);
     FillArgumentArray(m, exit.sig().args(), argOffset, offsetToCallerStackArgs, scratch);
     argOffset += exit.sig().args().length() * sizeof(Value);
     MOZ_ASSERT(argOffset == offsetToIonArgs + ionArgBytes);
 
-    // 6. Store asm.js pinned registers
-#if defined(JS_CODEGEN_X64)
-    unsigned savedHeapOffset = framePushed - sizeof(void*);
-    masm.storePtr(HeapReg, Address(StackPointer, savedHeapOffset));
-#elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
-    unsigned savedHeapOffset = framePushed - 1 * sizeof(void*);
-    unsigned savedGlobalOffset = framePushed - 2 * sizeof(void*);
-    masm.storePtr(HeapReg, Address(StackPointer, savedHeapOffset));
-    masm.storePtr(GlobalReg, Address(StackPointer, savedGlobalOffset));
+    // 6. Ion will clobber all registers, even non-volatiles. GlobalReg and
+    //    HeapReg are removed from the general register set for asm.js code, so
+    //    these will not have been saved by the caller like all other registers,
+    //    so they must be explicitly preserved. Only save GlobalReg since
+    //    HeapReg must be reloaded (from global data) after the call since the
+    //    heap may change during the FFI call.
+#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
+    JS_STATIC_ASSERT(MaybeSavedGlobalReg > 0);
+    unsigned savedGlobalOffset = framePushed - MaybeSavedGlobalReg;
+#else
+    JS_STATIC_ASSERT(MaybeSavedGlobalReg == 0);
 #endif
 
     {
         // Enable Activation.
         //
         // This sequence requires four registers, and needs to preserve the 'callee'
         // register, so there are five live registers.
         MOZ_ASSERT(callee == AsmJSIonExitRegCallee);
@@ -7519,24 +7843,16 @@ GenerateFFIIonExit(ModuleCompiler &m, co
         masm.loadPtr(Address(reg0, offsetOfActivation), reg1);
         masm.store8(Imm32(0), Address(reg1, JitActivation::offsetOfActiveUint8()));
         masm.loadPtr(Address(reg1, JitActivation::offsetOfPrevJitTop()), reg2);
         masm.storePtr(reg2, Address(reg0, offsetOfJitTop));
         masm.loadPtr(Address(reg1, JitActivation::offsetOfPrevJitJSContext()), reg2);
         masm.storePtr(reg2, Address(reg0, offsetOfJitJSContext));
     }
 
-    MOZ_ASSERT(masm.framePushed() == framePushed);
-#if defined(JS_CODEGEN_X64)
-    masm.loadPtr(Address(StackPointer, savedHeapOffset), HeapReg);
-#elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
-    masm.loadPtr(Address(StackPointer, savedHeapOffset), HeapReg);
-    masm.loadPtr(Address(StackPointer, savedGlobalOffset), GlobalReg);
-#endif
-
     masm.branchTestMagic(Assembler::Equal, JSReturnOperand, throwLabel);
 
     Label oolConvert;
     switch (exit.sig().retType().which()) {
       case RetType::Void:
         break;
       case RetType::Signed:
         masm.convertValueToInt32(JSReturnOperand, ReturnDoubleReg, ReturnReg, &oolConvert,
@@ -7550,16 +7866,27 @@ GenerateFFIIonExit(ModuleCompiler &m, co
       case RetType::Int32x4:
       case RetType::Float32x4:
         MOZ_CRASH("SIMD types shouldn't be returned from a FFI");
     }
 
     Label done;
     masm.bind(&done);
 
+    MOZ_ASSERT(masm.framePushed() == framePushed);
+
+    // Reload pinned registers after all calls into arbitrary JS.
+#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
+    JS_STATIC_ASSERT(MaybeSavedGlobalReg > 0);
+    masm.loadPtr(Address(StackPointer, savedGlobalOffset), GlobalReg);
+#else
+    JS_STATIC_ASSERT(MaybeSavedGlobalReg == 0);
+#endif
+    masm.loadAsmJSHeapRegisterFromGlobalData();
+
     Label profilingReturn;
     GenerateAsmJSExitEpilogue(masm, framePushed, AsmJSExit::IonFFI, &profilingReturn);
 
     if (oolConvert.used()) {
         masm.bind(&oolConvert);
         masm.setFramePushed(framePushed);
 
         // Store return value into argv[0]
@@ -7762,16 +8089,17 @@ GenerateAsyncInterruptExit(ModuleCompile
 
     masm.branchIfFalseBool(ReturnReg, throwLabel);
 
     // Restore the StackPointer to it's position before the call.
     masm.mov(ABIArgGenerator::NonVolatileReg, StackPointer);
 
     // Restore the machine state to before the interrupt.
     masm.PopRegsInMask(AllRegsExceptSP, AllRegsExceptSP.fpus()); // restore all GP/FP registers (except SP)
+    masm.loadAsmJSHeapRegisterFromGlobalData();  // In case there was a changeHeap
     masm.popFlags();              // after this, nothing that sets conditions
     masm.ret();                   // pop resumePC into PC
 #elif defined(JS_CODEGEN_MIPS)
     // Reserve space to store resumePC.
     masm.subPtr(Imm32(sizeof(intptr_t)), StackPointer);
     // set to zero so we can use masm.framePushed() below.
     masm.setFramePushed(0);
     // When this platform supports SIMD extensions, we'll need to push high lanes
@@ -7801,21 +8129,19 @@ GenerateAsyncInterruptExit(ModuleCompile
     masm.branchIfFalseBool(ReturnReg, throwLabel);
 
     // This will restore stack to the address before the call.
     masm.movePtr(s0, StackPointer);
     masm.PopRegsInMask(AllRegsExceptSP);
 
     // Pop resumePC into PC. Clobber HeapReg to make the jump and restore it
     // during jump delay slot.
-    MOZ_ASSERT(Imm16::IsInSignedRange(AsmJSModule::heapGlobalDataOffset() - AsmJSGlobalRegBias));
     masm.pop(HeapReg);
     masm.as_jr(HeapReg);
-    masm.loadPtr(Address(GlobalReg, AsmJSModule::heapGlobalDataOffset() - AsmJSGlobalRegBias),
-                 HeapReg);
+    masm.loadAsmJSHeapRegisterFromGlobalData();  // In case there was a changeHeap
 #elif defined(JS_CODEGEN_ARM)
     masm.setFramePushed(0);         // set to zero so we can use masm.framePushed() below
     masm.PushRegsInMask(RegisterSet(GeneralRegisterSet(Registers::AllMask & ~(1<<Registers::sp)), FloatRegisterSet(uint32_t(0))));   // save all GP registers,excep sp
 
     // Save both the APSR and FPSCR in non-volatile registers.
     masm.as_mrs(r4);
     masm.as_vmrs(r5);
     // Save the stack pointer in a non-volatile register.
@@ -7855,16 +8181,17 @@ GenerateAsyncInterruptExit(ModuleCompile
     masm.transferReg(r7);
     masm.transferReg(r8);
     masm.transferReg(r9);
     masm.transferReg(r10);
     masm.transferReg(r11);
     masm.transferReg(r12);
     masm.transferReg(lr);
     masm.finishDataTransfer();
+    masm.loadAsmJSHeapRegisterFromGlobalData();  // In case there was a changeHeap
     masm.ret();
 
 #elif defined (JS_CODEGEN_NONE)
     MOZ_CRASH();
 #else
 # error "Unknown architecture!"
 #endif
 
@@ -7880,16 +8207,19 @@ GenerateSyncInterruptExit(ModuleCompiler
     unsigned framePushed = StackDecrementForCall(masm, ABIStackAlignment, ShadowStackSpace);
 
     GenerateAsmJSExitPrologue(masm, framePushed, AsmJSExit::Interrupt, &m.syncInterruptLabel());
 
     AssertStackAlignment(masm, ABIStackAlignment);
     masm.call(AsmJSImmPtr(AsmJSImm_HandleExecutionInterrupt));
     masm.branchIfFalseBool(ReturnReg, throwLabel);
 
+    // Reload the heap register in case the callback changed heaps.
+    masm.loadAsmJSHeapRegisterFromGlobalData();
+
     Label profilingReturn;
     GenerateAsmJSExitEpilogue(masm, framePushed, AsmJSExit::Interrupt, &profilingReturn);
     return m.finishGeneratingInterrupt(&m.syncInterruptLabel(), &profilingReturn) && !masm.oom();
 }
 
 // If an exception is thrown, simply pop all frames (since asm.js does not
 // contain try/catch). To do this:
 //  1. Restore 'sp' to it's value right after the PushRegsInMask in GenerateEntry.
@@ -7920,16 +8250,18 @@ GenerateThrowStub(ModuleCompiler &m, Lab
 
     return m.finishGeneratingInlineStub(throwLabel) && !masm.oom();
 }
 
 static bool
 GenerateStubs(ModuleCompiler &m)
 {
     for (unsigned i = 0; i < m.module().numExportedFunctions(); i++) {
+        if (m.module().exportedFunction(i).isChangeHeap())
+            continue;
         if (!GenerateEntry(m, i))
            return false;
     }
 
     Label throwLabel;
 
     for (ModuleCompiler::ExitMap::Range r = m.allExits(); !r.empty(); r.popFront()) {
         if (!GenerateFFIExits(m, r.front().key(), r.front().value(), &throwLabel))
--- a/js/src/jit-test/lib/asm.js
+++ b/js/src/jit-test/lib/asm.js
@@ -7,16 +7,17 @@ const ASM_TYPE_FAIL_STRING = "asm.js typ
 const ASM_DIRECTIVE_FAIL_STRING = "\"use asm\" is only meaningful in the Directive Prologue of a function body";
 
 const USE_ASM = '"use asm";';
 const HEAP_IMPORTS = "const i8=new glob.Int8Array(b);var u8=new glob.Uint8Array(b);"+
                      "const i16=new glob.Int16Array(b);var u16=new glob.Uint16Array(b);"+
                      "const i32=new glob.Int32Array(b);var u32=new glob.Uint32Array(b);"+
                      "const f32=new glob.Float32Array(b);var f64=new glob.Float64Array(b);";
 const BUF_MIN = 64 * 1024;
+const BUF_CHANGE_MIN = 16 * 1024 * 1024;
 const BUF_64KB = new ArrayBuffer(BUF_MIN);
 
 function asmCompile()
 {
     var f = Function.apply(null, arguments);
     assertEq(!isAsmJSCompilationAvailable() || isAsmJSModule(f), true);
     return f;
 }
--- a/js/src/jit-test/tests/asm.js/testResize.js
+++ b/js/src/jit-test/tests/asm.js/testResize.js
@@ -1,9 +1,12 @@
 load(libdir + "asm.js");
+load(libdir + "asserts.js");
+
+// Tests for importing typed array view constructors
 
 assertAsmTypeFail('glob', USE_ASM + "var I32=glob.Int32Arra; function f() {} return f");
 var m = asmCompile('glob', USE_ASM + "var I32=glob.Int32Array; function f() {} return f");
 assertAsmLinkFail(m, {});
 assertAsmLinkFail(m, {Int32Array:null});
 assertAsmLinkFail(m, {Int32Array:{}});
 assertAsmLinkFail(m, {Int32Array:Uint32Array});
 assertEq(asmLink(m, {Int32Array:Int32Array})(), undefined);
@@ -25,8 +28,246 @@ assertAsmLinkAlwaysFail(m, this, null, n
 assertAsmLinkFail(m, this, null, new ArrayBuffer(100));
 assertEq(asmLink(m, this, null, BUF_64KB)(), undefined);
 
 var m = asmCompile('glob', 'ffis', 'buf', USE_ASM + 'var F32=glob.Float32Array; var i32=new glob.Int32Array(buf); function f() {} return f');
 assertAsmLinkFail(m, this, null, {});
 assertAsmLinkAlwaysFail(m, this, null, null);
 assertAsmLinkFail(m, this, null, new ArrayBuffer(100));
 assertEq(asmLink(m, this, null, BUF_64KB)(), undefined);
+
+// Tests for link-time validation of byteLength import
+
+assertAsmTypeFail('glob', 'ffis', 'buf', USE_ASM + 'var byteLength=glob.byteLength; function f() { return byteLength(1)|0 } return f');
+
+var m = asmCompile('glob', 'ffis', 'buf', USE_ASM + 'var byteLength=glob.byteLength; function f() { return 42 } return f');
+assertEq('byteLength' in this, false);
+assertAsmLinkFail(m, this);
+this['byteLength'] = null;
+assertAsmLinkFail(m, this);
+this['byteLength'] = {};
+assertAsmLinkFail(m, this);
+this['byteLength'] = function(){}
+assertAsmLinkFail(m, this);
+this['byteLength'] = (function(){}).bind(null);
+assertAsmLinkFail(m, this);
+this['byteLength'] = Function.prototype.call.bind();
+assertAsmLinkFail(m, this);
+this['byteLength'] = Function.prototype.call.bind({});
+assertAsmLinkFail(m, this);
+this['byteLength'] = Function.prototype.call.bind(function f() {});
+assertAsmLinkFail(m, this);
+this['byteLength'] = Function.prototype.call.bind(Math.sin);
+assertAsmLinkFail(m, this);
+this['byteLength'] =
+  Function.prototype.call.bind(Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, 'byteLength').get);
+assertEq(asmLink(m, this)(), 42);
+
+var m = asmCompile('glob', 'ffis', 'buf', USE_ASM + 'var b1=glob.byteLength, b2=glob.byteLength; function f() { return 43 } return f');
+assertEq(asmLink(m, this)(), 43);
+
+// Tests for validation of change-heap function
+
+const BYTELENGTH_IMPORT = "var len = glob.byteLength; ";
+const IMPORT0 = BYTELENGTH_IMPORT;
+const IMPORT1 = "var I8=glob.Int8Array; var i8=new I8(b); " + BYTELENGTH_IMPORT;
+const IMPORT2 = "var I8=glob.Int8Array; var i8=new I8(b); var I32=glob.Int32Array; var i32=new I32(b); var II32=glob.Int32Array; " + BYTELENGTH_IMPORT;
+
+       asmCompile('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function f() { return 42 } function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function b(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function f(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2=1) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2,xyz) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(...r) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2,...r) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch({b2}) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+       asmCompile('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+       asmCompile('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { ;if((len((b2))) & (0xffffff) || (len((b2)) <= (0xffffff))) {;;return false;;} ; i8=new I8(b2);; b=b2;; return true;; } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function ch2(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { 3; if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { b2=b2|0; if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(1) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(1 || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(1 & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || 1) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(i8(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(xyz) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff && len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) | 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) == 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xfffffe || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+       asmCompile('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0x1ffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) < 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xfffffe) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+       asmCompile('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0x1000000) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) ; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) {} i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+       asmCompile('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) {return false} i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return true; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+       asmCompile('glob', 'ffis', 'b', USE_ASM + IMPORT0 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i7=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; b=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=1; b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new 1; b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I7(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new b(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8; b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(1); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2,1); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); xyz=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=1; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; 1; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return 1 } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return false } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true; 1 } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT2 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT2 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i32=new I32(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT2 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i32=new I32(b2); i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+       asmCompile('glob', 'ffis', 'b', USE_ASM + IMPORT2 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); i32=new I32(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT2 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I32(b2); i32=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+       asmCompile('glob', 'ffis', 'b', USE_ASM + IMPORT2 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); i32=new II32(b2); b=b2; return true } function f() { return 42 } return f');
+
+// Tests for no calls in heap index expressions
+
+const SETUP = USE_ASM + IMPORT2 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); i32=new I32(b2); b=b2; return true }';
+
+       asmCompile('glob', 'ffis', 'b', SETUP + 'function f() { i32[0] } return f');
+       asmCompile('glob', 'ffis', 'b', SETUP + 'function f() { i32[0] = 0 } return f');
+       asmCompile('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[i >> 2] } return f');
+       asmCompile('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[i >> 2] = 0 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[(g()|0) >> 2] } function g() { return 0 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[(g()|0) >> 2] = 0 } function g() { return 0 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[i >> 2] = g()|0 } function g() { return 0 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[i32[(g()|0)>>2] >> 2] } function g() { return 0 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[i32[(g()|0)>>2] >> 2] = 0 } function g() { return 0 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[i >> 2] = i32[(g()|0)>>2] } function g() { return 0 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[((i32[i>>2]|0) + (g()|0)) >> 2] } function g() { return 0 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[((i32[i>>2]|0) + (g()|0)) >> 2] = 0 } function g() { return 0 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[i >> 2] = (i32[i>>2]|0) + (g()|0) } function g() { return 0 } return f');
+
+// Tests for constant heap accesses when change-heap is used
+
+const HEADER = USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & MASK || len(b2) <= MIN) return false; i8=new I8(b2); b=b2; return true } ';
+assertAsmTypeFail('glob', 'ffis', 'b', HEADER.replace('MASK', '0xffffff').replace('MIN', '0xffffff') + 'function f() { i8[0x1000000] = 0 } return f');
+       asmCompile('glob', 'ffis', 'b', HEADER.replace('MASK', '0xffffff').replace('MIN', '0xffffff') + 'function f() { i8[0xffffff] = 0 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', HEADER.replace('MASK', '0xffffff').replace('MIN', '0x1000000') + 'function f() { i8[0x1000001] = 0 } return f');
+       asmCompile('glob', 'ffis', 'b', HEADER.replace('MASK', '0xffffff').replace('MIN', '0x1000000') + 'function f() { i8[0x1000000] = 0 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', HEADER.replace('MASK', '0xffffff').replace('MIN', '0xffffff') + 'function f() { return i8[0x1000000]|0 } return f');
+       asmCompile('glob', 'ffis', 'b', HEADER.replace('MASK', '0xffffff').replace('MIN', '0xffffff') + 'function f() { return i8[0xffffff]|0 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', HEADER.replace('MASK', '0xffffff').replace('MIN', '0x1000000') + 'function f() { return i8[0x1000001]|0 } return f');
+       asmCompile('glob', 'ffis', 'b', HEADER.replace('MASK', '0xffffff').replace('MIN', '0x1000000') + 'function f() { return i8[0x1000000]|0 } return f');
+
+// Tests for validation of heap length
+
+var body = USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & MASK || len(b2) <= MIN) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return ch';
+var m = asmCompile('glob', 'ffis', 'b', body.replace('MASK', '0xffffff').replace('MIN', '0x1ffffff'));
+assertAsmLinkFail(m, this, null, new ArrayBuffer(BUF_CHANGE_MIN));
+assertAsmLinkFail(m, this, null, new ArrayBuffer(0x1000000));
+var changeHeap = asmLink(m, this, null, new ArrayBuffer(0x2000000));
+assertEq(changeHeap(new ArrayBuffer(0x1000000)), false);
+assertEq(changeHeap(new ArrayBuffer(0x2000000)), true);
+assertEq(changeHeap(new ArrayBuffer(0x2000001)), false);
+assertThrowsInstanceOf(() => changeHeap(null), TypeError);
+assertThrowsInstanceOf(() => changeHeap({}), TypeError);
+assertThrowsInstanceOf(() => changeHeap(new Int32Array(100)), TypeError);
+
+var detached = new ArrayBuffer(BUF_CHANGE_MIN);
+neuter(detached, "change-data");
+assertEq(changeHeap(detached), false);
+
+// Tests for runtime changing heap
+
+const CHANGE_HEAP = 'var changeHeap = glob.byteLength;';
+
+var changeHeapSource = `function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i32=new I32(b2); b=b2; return true }`;
+var body = `var I32=glob.Int32Array; var i32=new I32(b);
+            var len=glob.byteLength;` +
+            changeHeapSource +
+           `function get(i) { i=i|0; return i32[i>>2]|0 }
+            function set(i, v) { i=i|0; v=v|0; i32[i>>2] = v }
+            return {get:get, set:set, changeHeap:ch}`;
+
+var m = asmCompile('glob', 'ffis', 'b', USE_ASM + body);
+var buf1 = new ArrayBuffer(BUF_CHANGE_MIN);
+var {get, set, changeHeap} = asmLink(m, this, null, buf1);
+
+assertEq(m.toString(), "function anonymous(glob, ffis, b) {\n" + USE_ASM + body + "\n}");
+assertEq(m.toSource(), "(function anonymous(glob, ffis, b) {\n" + USE_ASM + body + "\n})");
+assertEq(changeHeap.toString(), changeHeapSource);
+assertEq(changeHeap.toSource(), changeHeapSource);
+
+set(0, 42);
+set(4, 13);
+assertEq(get(0), 42);
+assertEq(get(4), 13);
+set(BUF_CHANGE_MIN, 262);
+assertEq(get(BUF_CHANGE_MIN), 0);
+var buf2 = new ArrayBuffer(2*BUF_CHANGE_MIN);
+assertEq(changeHeap(buf2), true);
+assertEq(get(0), 0);
+assertEq(get(4), 0);
+set(BUF_CHANGE_MIN, 262);
+assertEq(get(BUF_CHANGE_MIN), 262);
+changeHeap(buf1);
+assertEq(get(0), 42);
+assertEq(get(4), 13);
+set(BUF_CHANGE_MIN, 262);
+assertEq(get(BUF_CHANGE_MIN), 0);
+
+var buf1 = new ArrayBuffer(BUF_CHANGE_MIN);
+new Int32Array(buf1)[0] = 13;
+var buf2 = new ArrayBuffer(BUF_CHANGE_MIN);
+new Int32Array(buf2)[0] = 42;
+
+var m = asmCompile('glob', 'ffis', 'b', USE_ASM +
+                   `var len=glob.byteLength;
+                    function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; b=b2; return true }
+                    return ch`);
+var changeHeap = asmLink(m, this, null, buf1);
+changeHeap(buf2);
+
+// Tests for changing heap during an FFI:
+
+// Set the warmup to '2' so we can hit both interp and ion FFI exits
+setJitCompilerOption("ion.warmup.trigger", 2);
+setJitCompilerOption("baseline.warmup.trigger", 0);
+setJitCompilerOption("offthread-compilation.enable", 0);
+
+var changeToBuf = null;
+var m = asmCompile('glob', 'ffis', 'b', USE_ASM +
+                   `var ffi=ffis.ffi;
+                    var I32=glob.Int32Array; var i32=new I32(b);
+                    var len=glob.byteLength;
+                    function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i32=new I32(b2); b=b2; return true }
+                    function test(i) { i=i|0; var sum=0; sum = i32[i>>2]|0; sum = (sum + (ffi()|0))|0; sum = (sum + (i32[i>>2]|0))|0; return sum|0 }
+                    return {test:test, changeHeap:ch}`);
+var ffi = function() { changeHeap(changeToBuf); return 1 }
+var {test, changeHeap} = asmLink(m, this, {ffi:ffi}, buf1);
+changeToBuf = buf1;
+assertEq(test(0), 27);
+changeToBuf = buf2;
+assertEq(test(0), 56);
+changeToBuf = buf2;
+assertEq(test(0), 85);
+changeToBuf = buf1;
+assertEq(test(0), 56);
+changeToBuf = buf1;
+assertEq(test(0), 27);
+
+var ffi = function() { return { valueOf:function() { changeHeap(changeToBuf); return 100 } } };
+var {test, changeHeap} = asmLink(m, this, {ffi:ffi}, buf1);
+changeToBuf = buf1;
+assertEq(test(0), 126);
+changeToBuf = buf2;
+assertEq(test(0), 155);
+changeToBuf = buf2;
+assertEq(test(0), 184);
+changeToBuf = buf1;
+assertEq(test(0), 155);
+changeToBuf = buf1;
+assertEq(test(0), 126);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/asm.js/testTimeout7.js
@@ -0,0 +1,41 @@
+load(libdir + "asm.js");
+
+// This test may iloop for valid reasons if not compiled with asm.js (namely,
+// inlining may allow the heap load to be hoisted out of the loop).
+if (!isAsmJSCompilationAvailable())
+    quit();
+
+var byteLength =
+  Function.prototype.call.bind(Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, 'byteLength').get);
+
+var buf1 = new ArrayBuffer(BUF_CHANGE_MIN);
+new Int32Array(buf1)[0] = 13;
+var buf2 = new ArrayBuffer(BUF_CHANGE_MIN);
+new Int32Array(buf2)[0] = 42;
+
+// Test changeHeap from interrupt (as if that could ever happen...)
+var m = asmCompile('glob', 'ffis', 'b', USE_ASM +
+                   `var I32=glob.Int32Array; var i32=new I32(b);
+                    var len=glob.byteLength;
+                    function changeHeap(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i32=new I32(b2); b=b2; return true }
+                    function f() {}
+                    function loop(i) { i=i|0; while((i32[i>>2]|0) == 13) { f() } }
+                    return {loop:loop, changeHeap:changeHeap}`);
+var { loop, changeHeap } = asmLink(m, this, null, buf1);
+timeout(1, function() { changeHeap(buf2); return true });
+loop(0);
+timeout(-1);
+
+// Try again, but this time with signals disabled
+setJitCompilerOption("signals.enable", 0);
+var m = asmCompile('glob', 'ffis', 'b', USE_ASM +
+                   `var I32=glob.Int32Array; var i32=new I32(b);
+                    var len=glob.byteLength;
+                    function changeHeap(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i32=new I32(b2); b=b2; return true }
+                    function f() {}
+                    function loop(i) { i=i|0; while((i32[i>>2]|0) == 13) { f() } }
+                    return {loop:loop, changeHeap:changeHeap}`);
+var { loop, changeHeap } = asmLink(m, this, null, buf1);
+timeout(1, function() { changeHeap(buf2); return true });
+loop(0);
+timeout(-1);
--- a/js/src/jit/arm/MacroAssembler-arm.h
+++ b/js/src/jit/arm/MacroAssembler-arm.h
@@ -1640,16 +1640,19 @@ class MacroAssemblerARMCompat : public M
 #ifdef JSGC_GENERATIONAL
     void branchPtrInNurseryRange(Condition cond, Register ptr, Register temp, Label *label);
     void branchValueIsNurseryObject(Condition cond, ValueOperand value, Register temp, Label *label);
 #endif
 
     void loadAsmJSActivation(Register dest) {
         loadPtr(Address(GlobalReg, AsmJSActivationGlobalDataOffset - AsmJSGlobalRegBias), dest);
     }
+    void loadAsmJSHeapRegisterFromGlobalData() {
+        loadPtr(Address(GlobalReg, AsmJSHeapGlobalDataOffset - AsmJSGlobalRegBias), HeapReg);
+    }
 };
 
 typedef MacroAssemblerARMCompat MacroAssemblerSpecific;
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_arm_MacroAssembler_arm_h */
--- a/js/src/jit/mips/MacroAssembler-mips.h
+++ b/js/src/jit/mips/MacroAssembler-mips.h
@@ -1299,16 +1299,20 @@ public:
     void branchPtrInNurseryRange(Condition cond, Register ptr, Register temp, Label *label);
     void branchValueIsNurseryObject(Condition cond, ValueOperand value, Register temp,
                                     Label *label);
 #endif
 
     void loadAsmJSActivation(Register dest) {
         loadPtr(Address(GlobalReg, AsmJSActivationGlobalDataOffset - AsmJSGlobalRegBias), dest);
     }
+    void loadAsmJSHeapRegisterFromGlobalData() {
+        MOZ_ASSERT(Imm16::IsInSignedRange(AsmJSHeapGlobalDataOffset - AsmJSGlobalRegBias));
+        loadPtr(Address(GlobalReg, AsmJSHeapGlobalDataOffset - AsmJSGlobalRegBias), HeapReg);
+    }
 };
 
 typedef MacroAssemblerMIPSCompat MacroAssemblerSpecific;
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_mips_MacroAssembler_mips_h */
--- a/js/src/jit/shared/Assembler-shared.h
+++ b/js/src/jit/shared/Assembler-shared.h
@@ -657,16 +657,17 @@ struct AsmJSFrame
     void *returnAddress;
 };
 static_assert(sizeof(AsmJSFrame) == 2 * sizeof(void*), "?!");
 static const uint32_t AsmJSFrameBytesAfterReturnAddress = sizeof(void*);
 
 // A hoisting of constants that would otherwise require #including AsmJSModule.h
 // everywhere. Values are asserted in AsmJSModule.h.
 static const unsigned AsmJSActivationGlobalDataOffset = 0;
+static const unsigned AsmJSHeapGlobalDataOffset = sizeof(void*);
 static const unsigned AsmJSNaN64GlobalDataOffset = 2 * sizeof(void*);
 static const unsigned AsmJSNaN32GlobalDataOffset = 2 * sizeof(void*) + sizeof(double);
 
 // Summarizes a heap access made by asm.js code that needs to be patched later
 // and/or looked up by the asm.js signal handlers. Different architectures need
 // to know different things (x64: offset and length, ARM: where to patch in
 // heap length, x86: where to patch in heap length and base) hence the massive
 // #ifdefery.
--- a/js/src/jit/x64/Assembler-x64.h
+++ b/js/src/jit/x64/Assembler-x64.h
@@ -629,16 +629,20 @@ class Assembler : public AssemblerX86Sha
     CodeOffsetLabel leaRipRelative(Register dest) {
         return CodeOffsetLabel(masm.leaq_rip(dest.code()).offset());
     }
 
     void loadAsmJSActivation(Register dest) {
         CodeOffsetLabel label = loadRipRelativeInt64(dest);
         append(AsmJSGlobalAccess(label, AsmJSActivationGlobalDataOffset));
     }
+    void loadAsmJSHeapRegisterFromGlobalData() {
+        CodeOffsetLabel label = loadRipRelativeInt64(HeapReg);
+        append(AsmJSGlobalAccess(label, AsmJSHeapGlobalDataOffset));
+    }
 
     // The below cmpq methods switch the lhs and rhs when it invokes the
     // macroassembler to conform with intel standard.  When calling this
     // function put the left operand on the left as you would expect.
     void cmpq(const Operand &lhs, Register rhs) {
         switch (lhs.kind()) {
           case Operand::REG:
             masm.cmpq_rr(rhs.code(), lhs.reg());
--- a/js/src/jit/x64/CodeGenerator-x64.cpp
+++ b/js/src/jit/x64/CodeGenerator-x64.cpp
@@ -239,16 +239,27 @@ CodeGeneratorX64::visitStoreTypedArrayEl
 {
     MOZ_CRASH("NYI");
 }
 
 bool
 CodeGeneratorX64::visitAsmJSCall(LAsmJSCall *ins)
 {
     emitAsmJSCall(ins);
+
+#ifdef DEBUG
+    Register scratch = ABIArgGenerator::NonReturn_VolatileReg0;
+    masm.movePtr(HeapReg, scratch);
+    masm.loadAsmJSHeapRegisterFromGlobalData();
+    Label ok;
+    masm.branchPtr(Assembler::Equal, HeapReg, scratch, &ok);
+    masm.breakpoint();
+    masm.bind(&ok);
+#endif
+
     return true;
 }
 
 bool
 CodeGeneratorX64::visitAsmJSLoadHeap(LAsmJSLoadHeap *ins)
 {
     MAsmJSLoadHeap *mir = ins->mir();
     Scalar::Type vt = mir->viewType();
--- a/js/src/jit/x86/Assembler-x86.h
+++ b/js/src/jit/x86/Assembler-x86.h
@@ -567,16 +567,19 @@ class Assembler : public AssemblerX86Sha
         masm.movaps_rm(src.code(), dest.addr);
         return CodeOffsetLabel(masm.currentOffset());
     }
 
     void loadAsmJSActivation(Register dest) {
         CodeOffsetLabel label = movlWithPatch(PatchedAbsoluteAddress(), dest);
         append(AsmJSGlobalAccess(label, AsmJSActivationGlobalDataOffset));
     }
+    void loadAsmJSHeapRegisterFromGlobalData() {
+        // x86 doesn't have a pinned heap register.
+    }
 
     static bool canUseInSingleByteInstruction(Register reg) {
         return !ByteRegRequiresRex(reg.code());
     }
 };
 
 // Get a register in which we plan to put a quantity that will be used as an
 // integer argument.  This differs from GetIntArgReg in that if we have no more