Bug 1030446 - Store and use exit-fp instead of exit-sp for asm.js stack unwinding (r=dougc)
☠☠ backed out by 5cc5f26b237e ☠ ☠
authorLuke Wagner <luke@mozilla.com>
Wed, 25 Jun 2014 15:15:46 -0500
changeset 212127 c11c8733e76acc844811583349a63a96ce639b2e
parent 212126 addc1a7459a7c550fcf38ce1584d05490069eddd
child 212128 4bc102bd4067dc76c99b480fb89965b9a25faeb7
push id3857
push userraliiev@mozilla.com
push dateTue, 02 Sep 2014 16:39:23 +0000
treeherdermozilla-beta@5638b907b505 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdougc
bugs1030446
milestone33.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 1030446 - Store and use exit-fp instead of exit-sp for asm.js stack unwinding (r=dougc)
js/src/jit/AsmJS.cpp
js/src/jit/AsmJSFrameIterator.cpp
js/src/jit/AsmJSFrameIterator.h
js/src/jit/AsmJSSignalHandlers.cpp
js/src/jit/arm/MacroAssembler-arm.cpp
js/src/jit/arm/MacroAssembler-arm.h
js/src/jit/arm/Simulator-arm.cpp
js/src/jit/mips/MacroAssembler-mips.cpp
js/src/jit/mips/MacroAssembler-mips.h
js/src/jit/x64/MacroAssembler-x64.h
js/src/jit/x86/MacroAssembler-x86.h
js/src/vm/Stack.cpp
js/src/vm/Stack.h
--- a/js/src/jit/AsmJS.cpp
+++ b/js/src/jit/AsmJS.cpp
@@ -6212,21 +6212,25 @@ GenerateFFIInterpreterExit(ModuleCompile
     MacroAssembler &masm = m.masm();
     masm.align(CodeAlignment);
     m.setInterpExitOffset(exitIndex);
     masm.setFramePushed(0);
 
     // See AsmJSFrameSize comment in Assembler-*.h.
 #if defined(JS_CODEGEN_ARM)
     masm.push(lr);
-#endif
-#if defined(JS_CODEGEN_MIPS)
+#elif defined(JS_CODEGEN_MIPS)
     masm.push(ra);
 #endif
 
+    // Store the frame pointer in AsmJSActivation::exitFP for stack unwinding.
+    Register activation = ABIArgGenerator::NonArgReturnVolatileReg0;
+    LoadAsmJSActivationIntoRegister(masm, activation);
+    masm.storePtr(StackPointer, Address(activation, AsmJSActivation::offsetOfExitFP()));
+
     MIRType typeArray[] = { MIRType_Pointer,   // cx
                             MIRType_Pointer,   // exitDatum
                             MIRType_Int32,     // argc
                             MIRType_Pointer }; // argv
     MIRTypeVector invokeArgTypes(m.cx());
     invokeArgTypes.infallibleAppend(typeArray, ArrayLength(typeArray));
 
     // At the point of the call, the stack layout shall be (sp grows to the left):
@@ -6235,26 +6239,21 @@ GenerateFFIInterpreterExit(ModuleCompile
     // sp is aligned.
     unsigned offsetToArgv = AlignBytes(StackArgBytes(invokeArgTypes) + MaybeRetAddr, StackAlignment);
     unsigned argvBytes = Max<size_t>(1, exit.sig().args().length()) * sizeof(Value);
     unsigned stackDec = StackDecrementForCall(masm, offsetToArgv + argvBytes);
     masm.reserveStack(stackDec);
 
     // Fill the argument array.
     unsigned offsetToCallerStackArgs = AsmJSFrameSize + masm.framePushed();
-    Register scratch = ABIArgGenerator::NonArgReturnVolatileReg0;
+    Register scratch = ABIArgGenerator::NonArgReturnVolatileReg1;
     FillArgumentArray(m, exit.sig().args(), offsetToArgv, offsetToCallerStackArgs, scratch);
 
     // Prepare the arguments for the call to InvokeFromAsmJS_*.
     ABIArgMIRTypeIter i(invokeArgTypes);
-    Register activation = ABIArgGenerator::NonArgReturnVolatileReg1;
-    LoadAsmJSActivationIntoRegister(masm, activation);
-
-    // Record sp in the AsmJSActivation for stack-walking.
-    masm.storePtr(StackPointer, Address(activation, AsmJSActivation::offsetOfExitSP()));
 
     // argument 0: cx
     if (i->kind() == ABIArg::GPR) {
         LoadJSContextFromActivation(masm, activation, i->gpr());
     } else {
         LoadJSContextFromActivation(masm, activation, scratch);
         masm.storePtr(scratch, Address(StackPointer, i->offsetFromArgBase()));
     }
@@ -6285,37 +6284,42 @@ GenerateFFIInterpreterExit(ModuleCompile
     }
     i++;
     JS_ASSERT(i.done());
 
     // Make the call, test whether it succeeded, and extract the return value.
     AssertStackAlignment(masm);
     switch (exit.sig().retType().which()) {
       case RetType::Void:
-        masm.callExit(AsmJSImmPtr(AsmJSImm_InvokeFromAsmJS_Ignore), i.stackBytesConsumedSoFar());
+        masm.call(AsmJSImmPtr(AsmJSImm_InvokeFromAsmJS_Ignore));
         masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
         break;
       case RetType::Signed:
-        masm.callExit(AsmJSImmPtr(AsmJSImm_InvokeFromAsmJS_ToInt32), i.stackBytesConsumedSoFar());
+        masm.call(AsmJSImmPtr(AsmJSImm_InvokeFromAsmJS_ToInt32));
         masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
         masm.unboxInt32(argv, ReturnReg);
         break;
       case RetType::Double:
-        masm.callExit(AsmJSImmPtr(AsmJSImm_InvokeFromAsmJS_ToNumber), i.stackBytesConsumedSoFar());
+        masm.call(AsmJSImmPtr(AsmJSImm_InvokeFromAsmJS_ToNumber));
         masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
         masm.loadDouble(argv, ReturnDoubleReg);
         break;
       case RetType::Float:
         MOZ_ASSUME_UNREACHABLE("Float32 shouldn't be returned from a FFI");
         break;
     }
 
     // Note: the caller is IonMonkey code which means there are no non-volatile
     // registers to restore.
     masm.freeStack(stackDec);
+
+    // Clear exitFP before the frame is destroyed.
+    LoadAsmJSActivationIntoRegister(masm, activation);
+    masm.storePtr(ImmWord(0), Address(activation, AsmJSActivation::offsetOfExitFP()));
+
     masm.ret();
 }
 
 static void
 GenerateOOLConvert(ModuleCompiler &m, RetType retType, Label *throwLabel)
 {
     MacroAssembler &masm = m.masm();
 
@@ -6330,19 +6334,16 @@ GenerateOOLConvert(ModuleCompiler &m, Re
     // Store value
     unsigned offsetToArgv = StackArgBytes(callArgTypes) + MaybeRetAddr;
     masm.storeValue(JSReturnOperand, Address(StackPointer, offsetToArgv));
 
     Register scratch = ABIArgGenerator::NonArgReturnVolatileReg0;
     Register activation = ABIArgGenerator::NonArgReturnVolatileReg1;
     LoadAsmJSActivationIntoRegister(masm, activation);
 
-    // Record sp in the AsmJSActivation for stack-walking.
-    masm.storePtr(StackPointer, Address(activation, AsmJSActivation::offsetOfExitSP()));
-
     // Store real arguments
     ABIArgMIRTypeIter i(callArgTypes);
 
     // argument 0: cx
     if (i->kind() == ABIArg::GPR) {
         LoadJSContextFromActivation(masm, activation, i->gpr());
     } else {
         LoadJSContextFromActivation(masm, activation, scratch);
@@ -6360,22 +6361,22 @@ GenerateOOLConvert(ModuleCompiler &m, Re
     }
     i++;
     JS_ASSERT(i.done());
 
     // Call
     AssertStackAlignment(masm);
     switch (retType.which()) {
       case RetType::Signed:
-        masm.callExit(AsmJSImmPtr(AsmJSImm_CoerceInPlace_ToInt32), i.stackBytesConsumedSoFar());
+        masm.call(AsmJSImmPtr(AsmJSImm_CoerceInPlace_ToInt32));
         masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
         masm.unboxInt32(Address(StackPointer, offsetToArgv), ReturnReg);
         break;
       case RetType::Double:
-        masm.callExit(AsmJSImmPtr(AsmJSImm_CoerceInPlace_ToNumber), i.stackBytesConsumedSoFar());
+        masm.call(AsmJSImmPtr(AsmJSImm_CoerceInPlace_ToNumber));
         masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
         masm.loadDouble(Address(StackPointer, offsetToArgv), ReturnDoubleReg);
         break;
       default:
         MOZ_ASSUME_UNREACHABLE("Unsupported convert type");
     }
 }
 
@@ -6384,28 +6385,31 @@ GenerateFFIIonExit(ModuleCompiler &m, co
                          unsigned exitIndex, Label *throwLabel)
 {
     MacroAssembler &masm = m.masm();
     masm.align(CodeAlignment);
     m.setIonExitOffset(exitIndex);
     masm.setFramePushed(0);
 
     // See AsmJSFrameSize comment in Assembler-*.h.
+#if defined(JS_CODEGEN_ARM)
+    masm.push(lr);
+#elif defined(JS_CODEGEN_MIPS)
+    masm.push(ra);
+#endif
+
+    // Store the frame pointer in AsmJSActivation::exitFP for stack unwinding.
+    Register activation = ABIArgGenerator::NonArgReturnVolatileReg0;
+    LoadAsmJSActivationIntoRegister(masm, activation);
+    masm.storePtr(StackPointer, Address(activation, AsmJSActivation::offsetOfExitFP()));
+
+    // Ion does not preserve nonvolatile registers, so we have to preserve them.
 #if defined(JS_CODEGEN_X64)
     masm.Push(HeapReg);
-#elif defined(JS_CODEGEN_ARM)
-    masm.push(lr);
-
-    // The GlobalReg (r10) and HeapReg (r11) also need to be restored before
-    // returning to asm.js code.
-    // The NANReg also needs to be restored, but is a constant and is reloaded before
-    // returning to asm.js code.
-    masm.PushRegsInMask(GeneralRegisterSet((1<<GlobalReg.code()) | (1<<HeapReg.code())));
-#elif defined(JS_CODEGEN_MIPS)
-    masm.push(ra);
+#elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
     masm.PushRegsInMask(GeneralRegisterSet((1<<GlobalReg.code()) | (1<<HeapReg.code())));
 #endif
 
     // The stack frame is used for the call into Ion and also for calls into C for OOL
     // conversion of the result.  A frame large enough for both is allocated.
     //
     // Arguments to the Ion function are in the following order on the stack:
     // | return address | descriptor | callee | argc | this | arg1 | arg2 | ...
@@ -6493,42 +6497,30 @@ GenerateFFIIonExit(ModuleCompiler &m, co
         // This sequence requires four registers, and needs to preserve the 'callee'
         // register, so there are five live registers.
         JS_ASSERT(callee == AsmJSIonExitRegCallee);
         Register reg0 = AsmJSIonExitRegE0;
         Register reg1 = AsmJSIonExitRegE1;
         Register reg2 = AsmJSIonExitRegE2;
         Register reg3 = AsmJSIonExitRegE3;
 
-        LoadAsmJSActivationIntoRegister(masm, reg0);
-
-        // Record sp in the AsmJSActivation for stack-walking.
-#if defined(JS_CODEGEN_MIPS)
-        // Add a flag to indicate to AsmJSFrameIterator that we are calling
-        // into Ion, since the offset from SP to the return address is
-        // different when calling Ion vs. the native ABI.
-        masm.ma_or(reg1, StackPointer, Imm32(0x1));
-        masm.storePtr(reg1, Address(reg0, AsmJSActivation::offsetOfExitSP()));
-#else
-        masm.storePtr(StackPointer, Address(reg0, AsmJSActivation::offsetOfExitSP()));
-#endif
-
         // The following is inlined:
         //   JSContext *cx = activation->cx();
         //   Activation *act = cx->mainThread().activation();
         //   act.active_ = true;
         //   act.prevJitTop_ = cx->mainThread().jitTop;
         //   act.prevJitJSContext_ = cx->mainThread().jitJSContext;
         //   cx->mainThread().jitJSContext = cx;
         // On the ARM store8() uses the secondScratchReg (lr) as a temp.
         size_t offsetOfActivation = offsetof(JSRuntime, mainThread) +
                                     PerThreadData::offsetOfActivation();
         size_t offsetOfJitTop = offsetof(JSRuntime, mainThread) + offsetof(PerThreadData, jitTop);
         size_t offsetOfJitJSContext = offsetof(JSRuntime, mainThread) +
                                       offsetof(PerThreadData, jitJSContext);
+        LoadAsmJSActivationIntoRegister(masm, reg0);
         masm.loadPtr(Address(reg0, AsmJSActivation::offsetOfContext()), reg3);
         masm.loadPtr(Address(reg3, JSContext::offsetOfRuntime()), reg0);
         masm.loadPtr(Address(reg0, offsetOfActivation), reg1);
         masm.store8(Imm32(1), Address(reg1, JitActivation::offsetOfActiveUint8()));
         masm.loadPtr(Address(reg0, offsetOfJitTop), reg2);
         masm.storePtr(reg2, Address(reg1, JitActivation::offsetOfPrevJitTop()));
         masm.loadPtr(Address(reg0, offsetOfJitJSContext), reg2);
         masm.storePtr(reg2, Address(reg1, JitActivation::offsetOfPrevJitJSContext()));
@@ -6590,23 +6582,29 @@ GenerateFFIIonExit(ModuleCompiler &m, co
         break;
       case RetType::Float:
         MOZ_ASSUME_UNREACHABLE("Float shouldn't be returned from a FFI");
         break;
     }
 
     masm.bind(&done);
     masm.freeStack(stackDec);
+
+    // Restore non-volatile registers saved in the prologue.
 #if defined(JS_CODEGEN_X64)
     masm.Pop(HeapReg);
-#endif
-#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
+#elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
     masm.loadConstantDouble(GenericNaN(), NANReg);
     masm.PopRegsInMask(GeneralRegisterSet((1<<GlobalReg.code()) | (1<<HeapReg.code())));
 #endif
+
+    // Clear exitFP before the frame is destroyed.
+    LoadAsmJSActivationIntoRegister(masm, activation);
+    masm.storePtr(ImmWord(0), Address(activation, AsmJSActivation::offsetOfExitFP()));
+
     masm.ret();
     JS_ASSERT(masm.framePushed() == 0);
 
     // oolConvert
     if (oolConvert.used()) {
         masm.bind(&oolConvert);
         masm.setFramePushed(oolConvertFramePushed);
         GenerateOOLConvert(m, exit.sig().retType(), throwLabel);
@@ -6637,43 +6635,49 @@ GenerateFFIExit(ModuleCompiler &m, const
 // all the frames.
 static bool
 GenerateStackOverflowExit(ModuleCompiler &m, Label *throwLabel)
 {
     MacroAssembler &masm = m.masm();
     masm.align(CodeAlignment);
     masm.bind(&m.stackOverflowLabel());
 
+    // The stack-overflow is checked before bumping the stack.
+    masm.setFramePushed(0);
+
+    // Store the frame pointer in AsmJSActivation::exitFP for stack unwinding.
+    Register activation = ABIArgGenerator::NonArgReturnVolatileReg0;
+    LoadAsmJSActivationIntoRegister(masm, activation);
+    masm.storePtr(StackPointer, Address(activation, AsmJSActivation::offsetOfExitFP()));
+
     MIRTypeVector argTypes(m.cx());
     argTypes.infallibleAppend(MIRType_Pointer); // cx
 
     unsigned stackDec = StackDecrementForCall(masm, argTypes, MaybeRetAddr);
     masm.reserveStack(stackDec);
 
-    Register activation = ABIArgGenerator::NonArgReturnVolatileReg0;
-    LoadAsmJSActivationIntoRegister(masm, activation);
-
-    // Record sp in the AsmJSActivation for stack-walking.
-    masm.storePtr(StackPointer, Address(activation, AsmJSActivation::offsetOfExitSP()));
-
     ABIArgMIRTypeIter i(argTypes);
 
     // argument 0: cx
     if (i->kind() == ABIArg::GPR) {
         LoadJSContextFromActivation(masm, activation, i->gpr());
     } else {
         LoadJSContextFromActivation(masm, activation, activation);
         masm.storePtr(activation, Address(StackPointer, i->offsetFromArgBase()));
     }
     i++;
 
     JS_ASSERT(i.done());
 
     AssertStackAlignment(masm);
-    masm.callExit(AsmJSImmPtr(AsmJSImm_ReportOverRecursed), i.stackBytesConsumedSoFar());
+    masm.call(AsmJSImmPtr(AsmJSImm_ReportOverRecursed));
+
+    // Clear exitFP before the frame is destroyed.
+    LoadAsmJSActivationIntoRegister(masm, activation);
+    masm.storePtr(ImmWord(0), Address(activation, AsmJSActivation::offsetOfExitFP()));
 
     // Don't worry about restoring the stack; throwLabel will pop everything.
     masm.jump(throwLabel);
     return !masm.oom();
 }
 
 static const RegisterSet AllRegsExceptSP =
     RegisterSet(GeneralRegisterSet(Registers::AllMask &
--- a/js/src/jit/AsmJSFrameIterator.cpp
+++ b/js/src/jit/AsmJSFrameIterator.cpp
@@ -8,91 +8,53 @@
 
 #include "jit/AsmJS.h"
 #include "jit/AsmJSModule.h"
 
 using namespace js;
 using namespace js::jit;
 
 static uint8_t *
-ReturnAddressForExitCall(uint8_t **psp)
+ReturnAddressFromFP(uint8_t *fp)
 {
-    uint8_t *sp = *psp;
-#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
-    // For calls to Ion/C++ on x86/x64, the exitSP is the SP right before the call
-    // to C++. Since the call instruction pushes the return address, we know
-    // that the return address is 1 word below exitSP.
-    return *(uint8_t**)(sp - sizeof(void*));
-#elif defined(JS_CODEGEN_ARM)
-    // For calls to Ion/C++ on ARM, the *caller* pushes the return address on
-    // the stack. For Ion, this is just part of the ABI. For C++, the return
-    // address is explicitly pushed before the call since we cannot expect the
-    // callee to immediately push lr. This means that exitSP points to the
-    // return address.
-    return *(uint8_t**)sp;
-#elif defined(JS_CODEGEN_MIPS)
-    // On MIPS we have two cases: an exit to C++ will store the return address
-    // at ShadowStackSpace above sp; an exit to Ion will store the return
-    // address at sp. To distinguish the two cases, the low bit of sp (which is
-    // aligned and therefore zero) is set for Ion exits.
-    if (uintptr_t(sp) & 0x1) {
-        sp = *psp -= 0x1;  // Clear the low bit
-        return *(uint8_t**)sp;
-    }
-    return *(uint8_t**)(sp + ShadowStackSpace);
-#else
-# error "Unknown architecture!"
-#endif
+    // In asm.js code, the "frame" consists of a single word: the saved
+    // return address of the caller.
+    static_assert(AsmJSFrameSize == sizeof(void*), "Frame size mismatch");
+    return *(uint8_t**)fp;
 }
 
-static uint8_t *
-ReturnAddressForJitCall(uint8_t *sp)
+AsmJSFrameIterator::AsmJSFrameIterator(const AsmJSActivation &activation)
+  : module_(&activation.module()),
+    fp_(activation.exitFP())
 {
-    // Once inside JIT code, sp always points to the word before the return
-    // address.
-    return *(uint8_t**)(sp - sizeof(void*));
-}
-
-AsmJSFrameIterator::AsmJSFrameIterator(const AsmJSActivation *activation)
-  : module_(nullptr)
-{
-    if (!activation || activation->isInterruptedSP())
+    if (!fp_)
         return;
-
-    module_ = &activation->module();
-    sp_ = activation->exitSP();
-
-    settle(ReturnAddressForExitCall(&sp_));
+    settle(ReturnAddressFromFP(fp_));
 }
 
 void
 AsmJSFrameIterator::operator++()
 {
-    settle(ReturnAddressForJitCall(sp_));
+    JS_ASSERT(!done());
+    fp_ += callsite_->stackDepth();
+    settle(ReturnAddressFromFP(fp_));
 }
 
 void
 AsmJSFrameIterator::settle(uint8_t *returnAddress)
 {
     callsite_ = module_->lookupCallSite(returnAddress);
-    if (!callsite_ || callsite_->isEntry()) {
-        module_ = nullptr;
-        return;
-    }
+    JS_ASSERT(callsite_);
 
     if (callsite_->isEntry()) {
-        module_ = nullptr;
+        fp_ = nullptr;
+        JS_ASSERT(done());
         return;
     }
 
-    sp_ += callsite_->stackDepth();
-
-    if (callsite_->isExit())
-        return settle(ReturnAddressForJitCall(sp_));
-
     JS_ASSERT(callsite_->isNormal());
 }
 
 JSAtom *
 AsmJSFrameIterator::functionDisplayAtom() const
 {
     JS_ASSERT(!done());
     return module_->functionName(callsite_->functionNameIndex());
--- a/js/src/jit/AsmJSFrameIterator.h
+++ b/js/src/jit/AsmJSFrameIterator.h
@@ -19,23 +19,24 @@ class AsmJSActivation;
 class AsmJSModule;
 namespace jit { struct CallSite; }
 
 // Iterates over the frames of a single AsmJSActivation.
 class AsmJSFrameIterator
 {
     const AsmJSModule *module_;
     const jit::CallSite *callsite_;
-    uint8_t *sp_;
+    uint8_t *fp_;
 
     void settle(uint8_t *returnAddress);
 
   public:
-    explicit AsmJSFrameIterator(const AsmJSActivation *activation);
+    explicit AsmJSFrameIterator() : module_(nullptr) {}
+    explicit AsmJSFrameIterator(const AsmJSActivation &activation);
     void operator++();
-    bool done() const { return !module_; }
+    bool done() const { return !fp_; }
     JSAtom *functionDisplayAtom() const;
     unsigned computeLine(uint32_t *column) const;
 };
 
 } // namespace js
 
 #endif // jit_AsmJSFrameIterator_h
--- a/js/src/jit/AsmJSSignalHandlers.cpp
+++ b/js/src/jit/AsmJSSignalHandlers.cpp
@@ -351,17 +351,17 @@ HandleSimulatorInterrupt(JSRuntime *rt, 
     // simulator could be in the middle of an instruction. On ARM, the signal
     // handlers are currently only used for Odin code, see bug 964258.
 
 #if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR)
     const AsmJSModule &module = activation->module();
     if (module.containsPC((void *)rt->mainThread.simulator()->get_pc()) &&
         module.containsPC(faultingAddress))
     {
-        activation->setInterrupted(nullptr);
+        activation->setResumePC(nullptr);
         int32_t nextpc = int32_t(module.interruptExit());
         rt->mainThread.simulator()->set_resume_pc(nextpc);
         return true;
     }
 #endif
     return false;
 }
 
@@ -460,17 +460,17 @@ HandleException(PEXCEPTION_POINTERS exce
         return false;
 
     // If we faulted trying to execute code in 'module', this must be an
     // interrupt callback (see RequestInterruptForAsmJSCode). Redirect
     // execution to a trampoline which will call js::HandleExecutionInterrupt.
     // The trampoline will jump to activation->resumePC if execution isn't
     // interrupted.
     if (module.containsPC(faultingAddress)) {
-        activation->setInterrupted(pc);
+        activation->setResumePC(pc);
         *ppc = module.interruptExit();
 
         JSRuntime::AutoLockForInterrupt lock(rt);
         module.unprotectCode(rt);
         return true;
     }
 
 # if defined(JS_CODEGEN_X64)
@@ -663,17 +663,17 @@ HandleMachException(JSRuntime *rt, const
         return false;
 
     // If we faulted trying to execute code in 'module', this must be an
     // interrupt callback (see RequestInterruptForAsmJSCode). Redirect
     // execution to a trampoline which will call js::HandleExecutionInterrupt.
     // The trampoline will jump to activation->resumePC if execution isn't
     // interrupted.
     if (module.containsPC(faultingAddress)) {
-        activation->setInterrupted(pc);
+        activation->setResumePC(pc);
         *ppc = module.interruptExit();
 
         JSRuntime::AutoLockForInterrupt lock(rt);
         module.unprotectCode(rt);
 
         // Update the thread state with the new pc.
         kret = thread_set_state(rtThread, x86_THREAD_STATE, (thread_state_t)&state, x86_THREAD_STATE_COUNT);
         return kret == KERN_SUCCESS;
@@ -913,17 +913,17 @@ HandleSignal(int signum, siginfo_t *info
         return false;
 
     // If we faulted trying to execute code in 'module', this must be an
     // interrupt callback (see RequestInterruptForAsmJSCode). Redirect
     // execution to a trampoline which will call js::HandleExecutionInterrupt.
     // The trampoline will jump to activation->resumePC if execution isn't
     // interrupted.
     if (module.containsPC(faultingAddress)) {
-        activation->setInterrupted(pc);
+        activation->setResumePC(pc);
         *ppc = module.interruptExit();
 
         JSRuntime::AutoLockForInterrupt lock(rt);
         module.unprotectCode(rt);
         return true;
     }
 
 # if defined(JS_CODEGEN_X64)
--- a/js/src/jit/arm/MacroAssembler-arm.cpp
+++ b/js/src/jit/arm/MacroAssembler-arm.cpp
@@ -3620,29 +3620,16 @@ MacroAssemblerARM::ma_call(ImmPtr dest)
     else
         rs = L_LDR;
 
     ma_movPatchable(dest, CallReg, Always, rs);
     as_blx(CallReg);
 }
 
 void
-MacroAssemblerARM::ma_callAndStoreRet(const Register r, uint32_t stackArgBytes)
-{
-    // Note: this function stores the return address to sp[0]. The caller must
-    // anticipate this by pushing additional space on the stack. The ABI does
-    // not provide space for a return address so this function may only be
-    // called if no argument are passed.
-    JS_ASSERT(stackArgBytes == 0);
-    AutoForbidPools afp(this);
-    as_dtr(IsStore, 32, Offset, pc, DTRAddr(sp, DtrOffImm(0)));
-    as_blx(r);
-}
-
-void
 MacroAssemblerARMCompat::breakpoint()
 {
     as_bkpt();
 }
 
 void
 MacroAssemblerARMCompat::ensureDouble(const ValueOperand &source, FloatRegister dest, Label *failure)
 {
--- a/js/src/jit/arm/MacroAssembler-arm.h
+++ b/js/src/jit/arm/MacroAssembler-arm.h
@@ -392,19 +392,16 @@ class MacroAssemblerARM : public Assembl
     void ma_callIon(const Register reg);
     // callso an Ion function, assuming that sp has already been decremented
     void ma_callIonNoPush(const Register reg);
     // calls an ion function, assuming that the stack is currently not 8 byte aligned
     void ma_callIonHalfPush(const Register reg);
 
     void ma_call(ImmPtr dest);
 
-    // calls reg, storing the return address into sp[0]
-    void ma_callAndStoreRet(const Register reg, uint32_t stackArgBytes);
-
     // Float registers can only be loaded/stored in continuous runs
     // when using vstm/vldm.
     // This function breaks set into continuous runs and loads/stores
     // them at [rm]. rm will be modified and left in a state logically
     // suitable for the next load/store.
     // Returns the offset from [dm] for the logical next load/store.
     int32_t transferMultipleByRuns(FloatRegisterSet set, LoadStore ls,
                                    Register rm, DTMMode mode)
@@ -580,21 +577,16 @@ class MacroAssemblerARMCompat : public M
     void call(const CallSiteDesc &desc, Label *label) {
         call(label);
         appendCallSite(desc);
     }
     void call(const CallSiteDesc &desc, AsmJSImmPtr imm) {
         call(imm);
         appendCallSite(desc);
     }
-    void callExit(AsmJSImmPtr imm, uint32_t stackArgBytes) {
-        movePtr(imm, CallReg);
-        ma_callAndStoreRet(CallReg, stackArgBytes);
-        appendCallSite(CallSiteDesc::Exit());
-    }
     void callIonFromAsmJS(const Register reg) {
         ma_callIonNoPush(reg);
         appendCallSite(CallSiteDesc::Exit());
 
         // The Ion ABI has the callee pop the return address off the stack.
         // The asm.js caller assumes that the call leaves sp unchanged, so bump
         // the stack.
         subPtr(Imm32(sizeof(void*)), sp);
--- a/js/src/jit/arm/Simulator-arm.cpp
+++ b/js/src/jit/arm/Simulator-arm.cpp
@@ -4065,17 +4065,17 @@ Simulator::execute()
         } else {
             SimInstruction *instr = reinterpret_cast<SimInstruction *>(program_counter);
             instructionDecode(instr);
             icount_++;
 
             int32_t rpc = resume_pc_;
             if (MOZ_UNLIKELY(rpc != 0)) {
                 // AsmJS signal handler ran and we have to adjust the pc.
-                activation->setInterrupted((void *)get_pc());
+                activation->setResumePC((void *)get_pc());
                 set_pc(rpc);
                 resume_pc_ = 0;
             }
         }
         program_counter = get_pc();
     }
 }
 
--- a/js/src/jit/mips/MacroAssembler-mips.cpp
+++ b/js/src/jit/mips/MacroAssembler-mips.cpp
@@ -2956,31 +2956,16 @@ MacroAssemblerMIPS::ma_callIonHalfPush(c
 {
     // This is a MIPS hack to push return address during jalr delay slot.
     as_addiu(StackPointer, StackPointer, -sizeof(intptr_t));
     as_jalr(r);
     as_sw(ra, StackPointer, 0);
 }
 
 void
-MacroAssemblerMIPS::ma_callAndStoreRet(const Register r, uint32_t stackArgBytes)
-{
-    // Note: this function stores the return address to sp[16]. The caller
-    // must anticipate this by reserving additional space on the stack.
-    // The ABI does not provide space for a return address so this function
-    // stores 'ra' before any ABI arguments.
-    // This function may only be called if there are 4 or less arguments.
-    JS_ASSERT(stackArgBytes == 4 * sizeof(uintptr_t));
-
-    // This is a MIPS hack to push return address during jalr delay slot.
-    as_jalr(r);
-    as_sw(ra, StackPointer, 4 * sizeof(uintptr_t));
-}
-
-void
 MacroAssemblerMIPS::ma_call(ImmPtr dest)
 {
     ma_liPatchable(CallReg, dest);
     as_jalr(CallReg);
     as_nop();
 }
 
 void
--- a/js/src/jit/mips/MacroAssembler-mips.h
+++ b/js/src/jit/mips/MacroAssembler-mips.h
@@ -296,19 +296,16 @@ class MacroAssemblerMIPS : public Assemb
   public:
     // calls an Ion function, assumes that the stack is untouched (8 byte alinged)
     void ma_callIon(const Register reg);
     // callso an Ion function, assuming that sp has already been decremented
     void ma_callIonNoPush(const Register reg);
     // calls an ion function, assuming that the stack is currently not 8 byte aligned
     void ma_callIonHalfPush(const Register reg);
 
-    // calls reg, storing the return address into sp[stackArgBytes]
-    void ma_callAndStoreRet(const Register reg, uint32_t stackArgBytes);
-
     void ma_call(ImmPtr dest);
 
     void ma_jump(ImmPtr dest);
 
     void ma_cmp_set(Register dst, Register lhs, Register rhs, Condition c);
     void ma_cmp_set(Register dst, Register lhs, Imm32 imm, Condition c);
     void ma_cmp_set(Register dst, Register lhs, ImmPtr imm, Condition c) {
         ma_cmp_set(dst, lhs, Imm32(uint32_t(imm.value)), c);
@@ -429,21 +426,16 @@ class MacroAssemblerMIPSCompat : public 
     void call(const CallSiteDesc &desc, Label *label) {
         call(label);
         appendCallSite(desc);
     }
     void call(const CallSiteDesc &desc, AsmJSImmPtr imm) {
         call(imm);
         appendCallSite(desc);
     }
-    void callExit(AsmJSImmPtr imm, uint32_t stackArgBytes) {
-        movePtr(imm, CallReg);
-        ma_callAndStoreRet(CallReg, stackArgBytes);
-        appendCallSite(CallSiteDesc::Exit());
-    }
     void callIonFromAsmJS(const Register reg) {
         ma_callIonNoPush(reg);
         appendCallSite(CallSiteDesc::Exit());
 
         // The Ion ABI has the callee pop the return address off the stack.
         // The asm.js caller assumes that the call leaves sp unchanged, so bump
         // the stack.
         subPtr(Imm32(sizeof(void*)), StackPointer);
--- a/js/src/jit/x64/MacroAssembler-x64.h
+++ b/js/src/jit/x64/MacroAssembler-x64.h
@@ -104,19 +104,16 @@ class MacroAssemblerX64 : public MacroAs
         mov(target, rax);
         call(rax);
     }
 
     void call(const CallSiteDesc &desc, AsmJSImmPtr target) {
         call(target);
         appendCallSite(desc);
     }
-    void callExit(AsmJSImmPtr target, uint32_t stackArgBytes) {
-        call(CallSiteDesc::Exit(), target);
-    }
 
     // Refers to the upper 32 bits of a 64-bit Value operand.
     // On x86_64, the upper 32 bits do not necessarily only contain the type.
     Operand ToUpper32(Operand base) {
         switch (base.kind()) {
           case Operand::MEM_REG_DISP:
             return Operand(Register::FromCode(base.base()), base.disp() + 4);
 
--- a/js/src/jit/x86/MacroAssembler-x86.h
+++ b/js/src/jit/x86/MacroAssembler-x86.h
@@ -1113,19 +1113,16 @@ class MacroAssemblerX86 : public MacroAs
         makeFrameDescriptor(dynStack, JitFrame_IonJS);
         Push(dynStack);
         call(target);
     }
     void call(const CallSiteDesc &desc, AsmJSImmPtr target) {
         call(target);
         appendCallSite(desc);
     }
-    void callExit(AsmJSImmPtr target, uint32_t stackArgBytes) {
-        call(CallSiteDesc::Exit(), target);
-    }
 
 #ifdef JSGC_GENERATIONAL
     void branchPtrInNurseryRange(Condition cond, Register ptr, Register temp, Label *label);
     void branchValueIsNurseryObject(Condition cond, ValueOperand value, Register temp, Label *label);
 #endif
 };
 
 typedef MacroAssemblerX86 MacroAssemblerSpecific;
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -581,17 +581,17 @@ FrameIter::settleOnActivation()
             }
 
             nextJitFrame();
             data_.state_ = JIT;
             return;
         }
 
         if (activation->isAsmJS()) {
-            data_.asmJSFrames_ = AsmJSFrameIterator(data_.activations_->asAsmJS());
+            data_.asmJSFrames_ = AsmJSFrameIterator(*data_.activations_->asAsmJS());
 
             if (data_.asmJSFrames_.done()) {
                 ++data_.activations_;
                 continue;
             }
 
             data_.state_ = ASMJS;
             return;
@@ -633,17 +633,17 @@ FrameIter::Data::Data(ThreadSafeContext 
     contextOption_(contextOption),
     principals_(principals),
     pc_(nullptr),
     interpFrames_(nullptr),
     activations_(cx->perThreadData)
 #ifdef JS_ION
   , jitFrames_((uint8_t *)nullptr, SequentialExecution)
   , ionInlineFrameNo_(0)
-  , asmJSFrames_(nullptr)
+  , asmJSFrames_()
 #endif
 {
 }
 
 FrameIter::Data::Data(const FrameIter::Data &other)
   : cx_(other.cx_),
     savedOption_(other.savedOption_),
     contextOption_(other.contextOption_),
@@ -1678,17 +1678,17 @@ jit::JitActivation::markRematerializedFr
 #endif // JS_ION
 
 AsmJSActivation::AsmJSActivation(JSContext *cx, AsmJSModule &module)
   : Activation(cx, AsmJS),
     module_(module),
     errorRejoinSP_(nullptr),
     profiler_(nullptr),
     resumePC_(nullptr),
-    exitSP_(nullptr)
+    exitFP_(nullptr)
 {
     if (cx->runtime()->spsProfiler.enabled()) {
         // Use a profiler string that matches jsMatch regex in
         // browser/devtools/profiler/cleopatra/js/parserWorker.js.
         // (For now use a single static string to avoid further slowing down
         // calls into asm.js.)
         profiler_ = &cx->runtime()->spsProfiler;
         profiler_->enterNative("asm.js code :0", this);
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -1506,44 +1506,44 @@ class InterpreterFrameIterator
 // all kinds of jit code.
 class AsmJSActivation : public Activation
 {
     AsmJSModule &module_;
     AsmJSActivation *prevAsmJS_;
     void *errorRejoinSP_;
     SPSProfiler *profiler_;
     void *resumePC_;
-    uint8_t *exitSP_;
-
-    static const intptr_t InterruptedSP = -1;
+    uint8_t *exitFP_;
 
   public:
     AsmJSActivation(JSContext *cx, AsmJSModule &module);
     ~AsmJSActivation();
 
     inline JSContext *cx();
     AsmJSModule &module() const { return module_; }
     AsmJSActivation *prevAsmJS() const { return prevAsmJS_; }
 
     // Read by JIT code:
     static unsigned offsetOfContext() { return offsetof(AsmJSActivation, cx_); }
     static unsigned offsetOfResumePC() { return offsetof(AsmJSActivation, resumePC_); }
 
     // Initialized by JIT code:
     static unsigned offsetOfErrorRejoinSP() { return offsetof(AsmJSActivation, errorRejoinSP_); }
-    static unsigned offsetOfExitSP() { return offsetof(AsmJSActivation, exitSP_); }
+    static unsigned offsetOfExitFP() { return offsetof(AsmJSActivation, exitFP_); }
 
     // Set from SIGSEGV handler:
-    void setInterrupted(void *pc) { resumePC_ = pc; exitSP_ = (uint8_t*)InterruptedSP; }
-    bool isInterruptedSP() const { return exitSP_ == (uint8_t*)InterruptedSP; }
+    void setResumePC(void *pc) { resumePC_ = pc; }
 
-    // Note: exitSP is the sp right before the call instruction. On x86, this
-    // means before the return address is pushed on the stack, on ARM, this
-    // means after.
-    uint8_t *exitSP() const { JS_ASSERT(!isInterruptedSP()); return exitSP_; }
+    // If pc is in C++/Ion code, exitFP points to the innermost asm.js frame
+    // (the one that called into C++). While in asm.js code, exitFP is either
+    // null or points to the innermost asm.js frame. Thus, it is always valid to
+    // unwind a non-null exitFP. The only way C++ can observe a null exitFP is
+    // asychronous interruption of asm.js execution (viz., via the profiler,
+    // a signal handler, or the interrupt exit).
+    uint8_t *exitFP() const { return exitFP_; }
 };
 
 // A FrameIter walks over the runtime's stack of JS script activations,
 // abstracting over whether the JS scripts were running in the interpreter or
 // different modes of compiled code.
 //
 // FrameIter is parameterized by what it includes in the stack iteration:
 //  - The SavedOption controls whether FrameIter stops when it finds an