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 232449 f72b6d7ece75f3824d6322dc011a41ef5c049c2d
parent 232448 09bd9d93d3e2574abdc5db3f608737a309eb8d59
child 232450 6525c7ee1f50addf145723c976dc353e4be9a00c
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbbouvier
bugs965880
milestone35.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 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