Bug 1027885 - OdinMonkey: maintain AsmJSActivation::fp in all frames in profiling mode (r=dougc)
authorLuke Wagner <luke@mozilla.com>
Mon, 21 Jul 2014 10:58:12 -0500
changeset 195350 a90a7709ab2d5508dc1d05c59bc7ab2b4563491e
parent 195349 ddfd02b154601910b6598fd0ed68019c39765b42
child 195351 a15e60a850ccbad8e0fe76e7614390a1eb800da6
push id27177
push userkwierso@gmail.com
push dateTue, 22 Jul 2014 01:00:35 +0000
treeherdermozilla-central@3b3ba206fc05 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdougc
bugs1027885
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 1027885 - OdinMonkey: maintain AsmJSActivation::fp in all frames in profiling mode (r=dougc)
js/public/ProfilingFrameIterator.h
js/src/assembler/assembler/X86Assembler.h
js/src/jit-test/tests/asm.js/testProfiling.js
js/src/jit-test/tests/asm.js/testTimeout5.js
js/src/jit-test/tests/asm.js/testTimeout6.js
js/src/jit/AsmJS.cpp
js/src/jit/AsmJSFrameIterator.cpp
js/src/jit/AsmJSFrameIterator.h
js/src/jit/AsmJSLink.cpp
js/src/jit/AsmJSModule.cpp
js/src/jit/AsmJSModule.h
js/src/jit/AsmJSSignalHandlers.cpp
js/src/jit/CodeGenerator.cpp
js/src/jit/CodeGenerator.h
js/src/jit/arm/Assembler-arm.cpp
js/src/jit/arm/Assembler-arm.h
js/src/jit/arm/MacroAssembler-arm.h
js/src/jit/arm/Simulator-arm.cpp
js/src/jit/arm/Simulator-arm.h
js/src/jit/shared/Assembler-shared.h
js/src/jit/shared/Assembler-x86-shared.h
js/src/moz.build
js/src/shell/js.cpp
js/src/vm/Runtime.h
js/src/vm/Stack.cpp
js/src/vm/Stack.h
new file mode 100644
--- /dev/null
+++ b/js/public/ProfilingFrameIterator.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef js_ProfilingFrameIterator_h
+#define js_ProfilingFrameIterator_h
+
+#include "mozilla/Alignment.h"
+
+#include <stdint.h>
+
+#include "js/Utility.h"
+
+class JSAtom;
+struct JSRuntime;
+namespace js { class AsmJSActivation; class AsmJSProfilingFrameIterator; }
+
+namespace JS {
+
+// This iterator can be used to walk the stack of a thread suspended at an
+// arbitrary pc. To provide acurate results, profiling must have been enabled
+// (via EnableRuntimeProfilingStack) before executing the callstack being
+// unwound.
+class JS_PUBLIC_API(ProfilingFrameIterator)
+{
+    js::AsmJSActivation *activation_;
+
+    static const unsigned StorageSpace = 5 * sizeof(void*);
+    mozilla::AlignedStorage<StorageSpace> storage_;
+    js::AsmJSProfilingFrameIterator &iter() {
+        JS_ASSERT(!done());
+        return *reinterpret_cast<js::AsmJSProfilingFrameIterator*>(storage_.addr());
+    }
+    const js::AsmJSProfilingFrameIterator &iter() const {
+        JS_ASSERT(!done());
+        return *reinterpret_cast<const js::AsmJSProfilingFrameIterator*>(storage_.addr());
+    }
+
+    void settle();
+
+  public:
+    struct RegisterState
+    {
+        void *pc;
+        void *sp;
+#if defined(JS_CODEGEN_ARM)
+        void *lr;
+#endif
+    };
+
+    ProfilingFrameIterator(JSRuntime *rt, const RegisterState &state);
+    ~ProfilingFrameIterator();
+    void operator++();
+    bool done() const { return !activation_; }
+
+    enum Kind {
+        Function,
+        AsmJSTrampoline
+    };
+    Kind kind() const;
+
+    // Methods available if kind() == Function:
+    JSAtom *functionDisplayAtom() const;
+    const char *functionFilename() const;
+
+    // Methods available if kind() != Function
+    const char *nonFunctionDescription() const;
+};
+
+} // namespace JS
+
+#endif  /* js_ProfilingFrameIterator_h */
--- a/js/src/assembler/assembler/X86Assembler.h
+++ b/js/src/assembler/assembler/X86Assembler.h
@@ -446,16 +446,23 @@ public:
     bool oom() const { return m_formatter.oom(); }
 
     void nop()
     {
         spew("nop");
         m_formatter.oneByteOp(OP_NOP);
     }
 
+    void twoByteNop()
+    {
+        spew("nop (2 byte)");
+        m_formatter.prefix(PRE_OPERAND_SIZE);
+        m_formatter.oneByteOp(OP_NOP);
+    }
+
     // Stack operations:
 
     void push_r(RegisterID reg)
     {
         spew("push       %s", nameIReg(reg));
         m_formatter.oneByteOp(OP_PUSH_EAX, reg);
     }
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/asm.js/testProfiling.js
@@ -0,0 +1,110 @@
+load(libdir + "asm.js");
+
+// Single-step profiling currently only works in the ARM simulator
+if (!getBuildConfiguration()["arm-simulator"])
+    quit();
+
+// Test profiling enablement while asm.js is running.
+var stacks;
+var ffi = function(enable) {
+    if (enable == +1)
+        enableSPSProfiling();
+    if (enable == -1)
+        disableSPSProfiling();
+    enableSingleStepProfiling();
+    stacks = disableSingleStepProfiling();
+}
+var f = asmLink(asmCompile('global','ffis',USE_ASM + "var ffi=ffis.ffi; function g(i) { i=i|0; ffi(i|0) } function f(i) { i=i|0; g(i|0) } return f"), null, {ffi});
+f(0);
+assertEq(String(stacks), "");
+f(+1);
+assertEq(String(stacks), "");
+f(0);
+assertEq(String(stacks), "*gf*");
+f(-1);
+assertEq(String(stacks), "*gf*");
+f(0);
+assertEq(String(stacks), "");
+
+// Enable profiling for the rest of the tests.
+enableSPSProfiling();
+
+var f = asmLink(asmCompile(USE_ASM + "function f() { return 42 } return f"));
+enableSingleStepProfiling();
+assertEq(f(), 42);
+var stacks = disableSingleStepProfiling();
+assertEq(String(stacks), ",*,f*,*,");
+
+var f = asmLink(asmCompile(USE_ASM + "function g(i) { i=i|0; return (i+1)|0 } function f() { return g(42)|0 } return f"));
+enableSingleStepProfiling();
+assertEq(f(), 43);
+var stacks = disableSingleStepProfiling();
+assertEq(String(stacks), ",*,f*,gf*,f*,*,");
+
+var f = asmLink(asmCompile(USE_ASM + "function g1() { return 1 } function g2() { return 2 } function f(i) { i=i|0; return TBL[i&1]()|0 } var TBL=[g1,g2]; return f"));
+enableSingleStepProfiling();
+assertEq(f(0), 1);
+assertEq(f(1), 2);
+var stacks = disableSingleStepProfiling();
+assertEq(String(stacks), ",*,f*,g1f*,f*,*,,*,f*,g2f*,f*,*,");
+
+//TODO: next patch
+//var f = asmLink(asmCompile('g', USE_ASM + "var sin=g.Math.sin; function f(d) { d=+d; return +sin(d) } return f"), this);
+//enableSingleStepProfiling();
+//assertEq(f(.5), Math.sin(.5));
+//var stacks = disableSingleStepProfiling();
+//assertEq(String(stacks), ",*,f*,Math.sinf*,f*,*,");
+
+// FFI tests:
+setJitCompilerOption("ion.usecount.trigger", 10);
+setJitCompilerOption("baseline.usecount.trigger", 0);
+setJitCompilerOption("offthread-compilation.enable", 0);
+
+var ffi1 = function() { return 10 }
+var ffi2 = function() { return 73 }
+var f = asmLink(asmCompile('g','ffis', USE_ASM + "var ffi1=ffis.ffi1, ffi2=ffis.ffi2; function f() { return ((ffi1()|0) + (ffi2()|0))|0 } return f"), null, {ffi1,ffi2});
+// Interpreter FFI exit
+enableSingleStepProfiling();
+assertEq(f(), 83);
+var stacks = disableSingleStepProfiling();
+assertEq(String(stacks), ",*,f*,*f*,f*,*f*,f*,*,");
+// Ion FFI exit
+for (var i = 0; i < 20; i++)
+    assertEq(f(), 83);
+enableSingleStepProfiling();
+assertEq(f(), 83);
+var stacks = disableSingleStepProfiling();
+assertEq(String(stacks), ",*,f*,*f*,f*,*f*,f*,*,");
+
+var ffi1 = function() { return 15 }
+var ffi2 = function() { return f2() + 17 }
+var {f1,f2} = asmLink(asmCompile('g','ffis', USE_ASM + "var ffi1=ffis.ffi1, ffi2=ffis.ffi2; function f2() { return ffi1()|0 } function f1() { return ffi2()|0 } return {f1:f1, f2:f2}"), null, {ffi1, ffi2});
+// Interpreter FFI exit
+enableSingleStepProfiling();
+assertEq(f1(), 32);
+var stacks = disableSingleStepProfiling();
+assertEq(String(stacks), ",*,f1*,*f1*,**f1*,f2**f1*,*f2**f1*,f2**f1*,**f1*,*f1*,f1*,*,");
+// Ion FFI exit
+for (var i = 0; i < 20; i++)
+    assertEq(f1(), 32);
+enableSingleStepProfiling();
+assertEq(f1(), 32);
+var stacks = disableSingleStepProfiling();
+assertEq(String(stacks), ",*,f1*,*f1*,**f1*,f2**f1*,*f2**f1*,f2**f1*,**f1*,*f1*,f1*,*,");
+
+// This takes forever to run.
+// Stack-overflow exit test
+//var limit = -1;
+//var maxct = 0;
+//function ffi(ct) { if (ct == limit) { enableSingleStepProfiling(); print("enabled"); } maxct = ct; }
+//var f = asmLink(asmCompile('g', 'ffis',USE_ASM + "var ffi=ffis.ffi; var ct=0; function rec(){ ct=(ct+1)|0; ffi(ct|0); rec() } function f() { ct=0; rec() } return f"), null, {ffi});
+//// First find the stack limit:
+//var caught = false;
+//try { f() } catch (e) { caught = true; assertEq(String(e).indexOf("too much recursion") >= 0, true) }
+//assertEq(caught, true);
+//limit = maxct;
+//print("Setting limit");
+//var caught = false;
+//try { f() } catch (e) { caught = true; print("caught"); assertEq(String(e).indexOf("too much recursion") >= 0, true) }
+//var stacks = disableSingleStepProfiling();
+//assertEq(String(stacks).indexOf("rec") >= 0, true);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/asm.js/testTimeout5.js
@@ -0,0 +1,12 @@
+// |jit-test| exitstatus: 6;
+
+load(libdir + "asm.js");
+
+enableSPSProfiling();
+
+var f = asmLink(asmCompile('glob', 'ffis', 'buf', USE_ASM + "function f() { var i=0; while (1) { i=(i+1)|0 } } return f"));
+timeout(1);
+if (getBuildConfiguration()["arm-simulator"])
+    enableSingleStepProfiling();
+f();
+assertEq(true, false);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/asm.js/testTimeout6.js
@@ -0,0 +1,12 @@
+// |jit-test| exitstatus: 6;
+
+load(libdir + "asm.js");
+
+enableSPSProfiling();
+
+var f = asmLink(asmCompile('glob', 'ffis', 'buf', USE_ASM + "function g() { var i=0; while (1) { i=(i+1)|0 } } function f() { g() } return f"));
+timeout(1);
+if (getBuildConfiguration()["arm-simulator"])
+    enableSingleStepProfiling();
+f();
+assertEq(true, false);
--- a/js/src/jit/AsmJS.cpp
+++ b/js/src/jit/AsmJS.cpp
@@ -812,25 +812,25 @@ typedef Vector<MBasicBlock*,8> BlockVect
 // non-JSAtom pointers, or this will break!
 class MOZ_STACK_CLASS ModuleCompiler
 {
   public:
     class Func
     {
         Signature sig_;
         PropertyName *name_;
-        Label *code_;
+        Label *entry_;
         uint32_t srcBegin_;
         uint32_t srcEnd_;
         uint32_t compileTime_;
         bool defined_;
 
       public:
-        Func(PropertyName *name, Signature &&sig, Label *code)
-          : sig_(Move(sig)), name_(name), code_(code), srcBegin_(0), srcEnd_(0),
+        Func(PropertyName *name, Signature &&sig, Label *entry)
+          : sig_(Move(sig)), name_(name), entry_(entry), srcBegin_(0), srcEnd_(0),
             compileTime_(0), defined_(false)
         {}
 
         PropertyName *name() const { return name_; }
         bool defined() const { return defined_; }
 
         void define(ModuleCompiler &m, ParseNode *fn) {
             JS_ASSERT(!defined_);
@@ -843,17 +843,17 @@ class MOZ_STACK_CLASS ModuleCompiler
             srcBegin_ = fn->pn_pos.begin - m.srcStart();
             srcEnd_ = fn->pn_pos.end - m.srcStart();
         }
 
         uint32_t srcBegin() const { JS_ASSERT(defined_); return srcBegin_; }
         uint32_t srcEnd() const { JS_ASSERT(defined_); return srcEnd_; }
         Signature &sig() { return sig_; }
         const Signature &sig() const { return sig_; }
-        Label *code() const { return code_; }
+        Label &entry() const { return *entry_; }
         uint32_t compileTime() const { return compileTime_; }
         void accumulateCompileTime(uint32_t ms) { compileTime_ += ms; }
     };
 
     class Global
     {
       public:
         enum Which {
@@ -966,16 +966,19 @@ class MOZ_STACK_CLASS ModuleCompiler
         Signature sig_;
 
       public:
         ExitDescriptor(PropertyName *name, Signature &&sig)
           : name_(name), sig_(Move(sig)) {}
         ExitDescriptor(ExitDescriptor &&rhs)
           : name_(rhs.name_), sig_(Move(rhs.sig_))
         {}
+        PropertyName *name() const {
+            return name_;
+        }
         const Signature &sig() const {
             return sig_;
         }
 
         // ExitDescriptor is a HashPolicy:
         typedef ExitDescriptor Lookup;
         static HashNumber hash(const ExitDescriptor &d) {
             HashNumber hn = HashGeneric(d.name_, d.sig_.retType().which());
@@ -1200,28 +1203,16 @@ class MOZ_STACK_CLASS ModuleCompiler
         return false;
     }
 
     bool failOverRecursed() {
         errorOverRecursed_ = true;
         return false;
     }
 
-    static const unsigned SLOW_FUNCTION_THRESHOLD_MS = 250;
-
-    bool maybeReportCompileTime(const Func &func) {
-        if (func.compileTime() < SLOW_FUNCTION_THRESHOLD_MS)
-            return true;
-        SlowFunction sf;
-        sf.name = func.name();
-        sf.ms = func.compileTime();
-        tokenStream().srcCoords.lineNumAndColumnIndex(func.srcBegin(), &sf.line, &sf.column);
-        return slowFunctions_.append(sf);
-    }
-
     /*************************************************** Read-only interface */
 
     ExclusiveContext *cx() const { return cx_; }
     AsmJSParser &parser() const { return parser_; }
     TokenStream &tokenStream() const { return parser_.tokenStream; }
     MacroAssembler &masm() { return masm_; }
     Label &stackOverflowLabel() { return stackOverflowLabel_; }
     Label &asyncInterruptLabel() { return asyncInterruptLabel_; }
@@ -1313,20 +1304,20 @@ class MOZ_STACK_CLASS ModuleCompiler
     bool addFunction(PropertyName *name, Signature &&sig, Func **func) {
         JS_ASSERT(!finishedFunctionBodies_);
         Global *global = moduleLifo_.new_<Global>(Global::Function);
         if (!global)
             return false;
         global->u.funcIndex_ = functions_.length();
         if (!globals_.putNew(name, global))
             return false;
-        Label *code = moduleLifo_.new_<Label>();
-        if (!code)
+        Label *entry = moduleLifo_.new_<Label>();
+        if (!entry)
             return false;
-        *func = moduleLifo_.new_<Func>(name, Move(sig), code);
+        *func = moduleLifo_.new_<Func>(name, Move(sig), entry);
         if (!*func)
             return false;
         return functions_.append(*func);
     }
     bool addFuncPtrTable(PropertyName *name, Signature &&sig, uint32_t mask, FuncPtrTable **table) {
         Global *global = moduleLifo_.new_<Global>(Global::FuncPtrTable);
         if (!global)
             return false;
@@ -1423,82 +1414,99 @@ class MOZ_STACK_CLASS ModuleCompiler
     }
     LifoAlloc &lifo() {
         return moduleLifo_;
     }
 
     void startFunctionBodies() {
         module_->startFunctionBodies();
     }
-
-    void startGeneratingFunction(Func &func, MIRGenerator &mir) {
-        // A single MacroAssembler is reused for all function compilations so
-        // that there is a single linear code segment for each module. To avoid
-        // spiking memory, a LifoAllocScope in the caller frees all MIR/LIR
-        // after each function is compiled. This method is responsible for cleaning
-        // out any dangling pointers that the MacroAssembler may have kept.
-        masm_.resetForNewCodeGenerator(mir.alloc());
-        masm_.align(CodeAlignment);
-        masm_.bind(func.code());
-    }
-
-    bool finishGeneratingFunction(Func &func, MIRGenerator &mir, CodeGenerator &codegen) {
-        JS_ASSERT(func.defined() && func.code()->bound());
-
-        PropertyName *name = func.name();
-        uint32_t codeBegin = func.code()->offset();
-        uint32_t codeEnd = masm_.currentOffset();
-        if (!module_->addFunctionCodeRange(name, codeBegin, codeEnd))
+    bool finishGeneratingFunction(Func &func, CodeGenerator &codegen,
+                                  const AsmJSFunctionLabels &labels)
+    {
+        if (!module_->addFunctionCodeRange(func.name(), labels))
             return false;
 
         jit::IonScriptCounts *counts = codegen.extractScriptCounts();
         if (counts && !module_->addFunctionCounts(counts)) {
             js_delete(counts);
             return false;
         }
 
+        if (func.compileTime() >= 250) {
+            SlowFunction sf;
+            sf.name = func.name();
+            sf.ms = func.compileTime();
+            tokenStream().srcCoords.lineNumAndColumnIndex(func.srcBegin(), &sf.line, &sf.column);
+            if (!slowFunctions_.append(sf))
+                return false;
+        }
+
 #if defined(MOZ_VTUNE) || defined(JS_ION_PERF)
-        unsigned line = 0, column = 0;
+        uint32_t line, column;
         tokenStream().srcCoords.lineNumAndColumnIndex(func.srcBegin(), &line, &column);
-        if (!module_->addProfiledFunction(name, codeBegin, codeEnd, line, column))
+        unsigned begin = labels.begin.offset();
+        unsigned end = labels.end.offset();
+        if (!module_->addProfiledFunction(func.name(), begin, end, line, column))
             return false;
 # ifdef JS_ION_PERF
         // Per-block profiling info uses significantly more memory so only store
         // this information if it is actively requested.
         if (PerfBlockEnabled()) {
-            AsmJSPerfSpewer &ps = mir.perfSpewer();
+            AsmJSPerfSpewer &ps = codegen.mirGen().perfSpewer();
             ps.noteBlocksOffsets();
             unsigned inlineEnd = ps.endInlineCode.offset();
-            if (!module_->addProfiledBlocks(name, codeBegin, inlineEnd, codeEnd, ps.basicBlocks()))
+            if (!module_->addProfiledBlocks(func.name(), begin, inlineEnd, end, ps.basicBlocks()))
                 return false;
         }
 # endif
 #endif
         return true;
     }
-
     void finishFunctionBodies() {
+        // When an interrupt is triggered, all function code is mprotected and,
+        // for sanity, stub code (particularly the interrupt stub) is not.
+        // Protection works at page granularity, so we need to ensure that no
+        // stub code gets into the function code pages.
         JS_ASSERT(!finishedFunctionBodies_);
         masm_.align(AsmJSPageSize);
+        module_->finishFunctionBodies(masm_.currentOffset());
         finishedFunctionBodies_ = true;
-        module_->finishFunctionBodies(masm_.currentOffset());
-    }
-
-    void startGeneratingEntry(unsigned exportIndex) {
-        module_->exportedFunction(exportIndex).initCodeOffset(masm_.currentOffset());
-    }
-    bool finishGeneratingEntry(unsigned exportIndex) {
-        return module_->addEntryCodeRange(exportIndex, masm_.currentOffset());
-    }
-
-    void setInterpExitOffset(unsigned exitIndex) {
-        module_->exit(exitIndex).initInterpOffset(masm_.currentOffset());
-    }
-    void setIonExitOffset(unsigned exitIndex) {
-        module_->exit(exitIndex).initIonOffset(masm_.currentOffset());
+    }
+
+    bool finishGeneratingEntry(unsigned exportIndex, Label *begin) {
+        JS_ASSERT(finishedFunctionBodies_);
+        module_->exportedFunction(exportIndex).initCodeOffset(begin->offset());
+        uint32_t end = masm_.currentOffset();
+        return module_->addEntryCodeRange(begin->offset(), end);
+    }
+    bool finishGeneratingFFI(Label *begin, Label *profilingReturn) {
+        JS_ASSERT(finishedFunctionBodies_);
+        uint32_t end = masm_.currentOffset();
+        return module_->addFFICodeRange(begin->offset(), profilingReturn->offset(), end);
+    }
+    bool finishGeneratingInterpExit(unsigned exitIndex, Label *begin, Label *profilingReturn) {
+        JS_ASSERT(finishedFunctionBodies_);
+        module_->exit(exitIndex).initInterpOffset(begin->offset());
+        return finishGeneratingFFI(begin, profilingReturn);
+    }
+    bool finishGeneratingIonExit(unsigned exitIndex, Label *begin, Label *profilingReturn) {
+        JS_ASSERT(finishedFunctionBodies_);
+        module_->exit(exitIndex).initIonOffset(begin->offset());
+        return finishGeneratingFFI(begin, profilingReturn);
+    }
+    bool finishGeneratingInterrupt(Label *begin, Label *profilingReturn) {
+        JS_ASSERT(finishedFunctionBodies_);
+        uint32_t end = masm_.currentOffset();
+        return module_->addInterruptCodeRange(begin->offset(), profilingReturn->offset(), end);
+    }
+    bool finishGeneratingInlineStub(Label *begin) {
+        JS_ASSERT(finishedFunctionBodies_);
+        uint32_t end = masm_.currentOffset();
+        return module_->addInlineCodeRange(begin->offset(), end);
     }
 
     void buildCompilationTimeReport(bool storedInCache, ScopedJSFreePtr<char> *out) {
         ScopedJSFreePtr<char> slowFuns;
 #ifndef JS_MORE_DETERMINISTIC
         int64_t usecAfter = PRMJ_Now();
         int msTotal = (usecAfter - usecBefore_) / PRMJ_USEC_PER_MSEC;
         if (!slowFunctions_.empty()) {
@@ -1536,17 +1544,17 @@ class MOZ_STACK_CLASS ModuleCompiler
         // Finally, convert all the function-pointer table elements into
         // RelativeLinks that will be patched by AsmJSModule::staticallyLink.
         for (unsigned tableIndex = 0; tableIndex < funcPtrTables_.length(); tableIndex++) {
             FuncPtrTable &table = funcPtrTables_[tableIndex];
             unsigned tableBaseOffset = module_->offsetOfGlobalData() + table.globalDataOffset();
             for (unsigned elemIndex = 0; elemIndex < table.numElems(); elemIndex++) {
                 AsmJSModule::RelativeLink link(AsmJSModule::RelativeLink::RawPointer);
                 link.patchAtOffset = tableBaseOffset + elemIndex * sizeof(uint8_t*);
-                link.targetOffset = masm_.actualOffset(table.elem(elemIndex).code()->offset());
+                link.targetOffset = masm_.actualOffset(table.elem(elemIndex).entry().offset());
                 if (!module_->addRelativeLink(link))
                     return false;
             }
         }
 
         *module = module_.forget();
         return true;
     }
@@ -2173,17 +2181,17 @@ class FunctionCompiler
         if (inDeadCode())
             return;
 
         if (m().usesSignalHandlersForInterrupt())
             return;
 
         unsigned lineno = 0, column = 0;
         m().tokenStream().srcCoords.lineNumAndColumnIndex(pn->pn_pos.begin, &lineno, &column);
-        CallSiteDesc callDesc(lineno, column);
+        CallSiteDesc callDesc(lineno, column, CallSiteDesc::Relative);
         curBlock_->add(MAsmJSInterruptCheck::New(alloc(), &m().syncInterruptLabel(), callDesc));
     }
 
     /***************************************************************** Calls */
 
     // The IonMonkey backend maintains a single stack offset (from the stack
     // pointer to the base of the frame) by adding the total amount of spill
     // space required plus the maximum stack required for argument passing.
@@ -2290,32 +2298,38 @@ class FunctionCompiler
         if (inDeadCode()) {
             *def = nullptr;
             return true;
         }
 
         uint32_t line, column;
         m_.tokenStream().srcCoords.lineNumAndColumnIndex(call.node_->pn_pos.begin, &line, &column);
 
-        CallSiteDesc desc(line, column);
-        MAsmJSCall *ins = MAsmJSCall::New(alloc(), desc, callee, call.regArgs_, returnType,
-                                          call.spIncrement_);
+        CallSiteDesc::Kind kind;
+        switch (callee.which()) {
+          case MAsmJSCall::Callee::Internal: kind = CallSiteDesc::Relative; break;
+          case MAsmJSCall::Callee::Dynamic:  kind = CallSiteDesc::Register; break;
+          case MAsmJSCall::Callee::Builtin:  kind = CallSiteDesc::Register; break;
+        }
+
+        MAsmJSCall *ins = MAsmJSCall::New(alloc(), CallSiteDesc(line, column, kind), callee,
+                                          call.regArgs_, returnType, call.spIncrement_);
         if (!ins)
             return false;
 
         curBlock_->add(ins);
         *def = ins;
         return true;
     }
 
   public:
     bool internalCall(const ModuleCompiler::Func &func, const Call &call, MDefinition **def)
     {
         MIRType returnType = func.sig().retType().toMIRType();
-        return callPrivate(MAsmJSCall::Callee(func.code()), call, returnType, def);
+        return callPrivate(MAsmJSCall::Callee(&func.entry()), call, returnType, def);
     }
 
     bool funcPtrCall(const ModuleCompiler::FuncPtrTable &table, MDefinition *index,
                      const Call &call, MDefinition **def)
     {
         if (inDeadCode()) {
             *def = nullptr;
             return true;
@@ -5381,41 +5395,48 @@ CheckFunction(ModuleCompiler &m, LifoAll
     return true;
 }
 
 static bool
 GenerateCode(ModuleCompiler &m, ModuleCompiler::Func &func, MIRGenerator &mir, LIRGraph &lir)
 {
     int64_t before = PRMJ_Now();
 
-    m.startGeneratingFunction(func, mir);
+    // A single MacroAssembler is reused for all function compilations so
+    // that there is a single linear code segment for each module. To avoid
+    // spiking memory, a LifoAllocScope in the caller frees all MIR/LIR
+    // after each function is compiled. This method is responsible for cleaning
+    // out any dangling pointers that the MacroAssembler may have kept.
+    m.masm().resetForNewCodeGenerator(mir.alloc());
 
     ScopedJSDeletePtr<CodeGenerator> codegen(js_new<CodeGenerator>(&mir, &lir, &m.masm()));
-    if (!codegen || !codegen->generateAsmJS(&m.stackOverflowLabel()))
-        return m.fail(nullptr, "internal codegen failure (probably out of memory)");
-
-    if (!m.finishGeneratingFunction(func, mir, *codegen))
+    if (!codegen)
+        return false;
+
+    AsmJSFunctionLabels labels(func.entry(), m.stackOverflowLabel());
+    if (!codegen->generateAsmJS(&labels))
         return false;
 
     func.accumulateCompileTime((PRMJ_Now() - before) / PRMJ_USEC_PER_MSEC);
-    if (!m.maybeReportCompileTime(func))
-        return false;
-
-    // Unlike regular IonMonkey which links and generates a new JitCode for
+
+    if (!m.finishGeneratingFunction(func, *codegen, labels))
+        return false;
+
+    // Unlike regular IonMonkey, which links and generates a new JitCode for
     // every function, we accumulate all the functions in the module in a
     // single MacroAssembler and link at end. Linking asm.js doesn't require a
     // CodeGenerator so we can destroy it now (via ScopedJSDeletePtr).
     return true;
 }
 
 static bool
 CheckAllFunctionsDefined(ModuleCompiler &m)
 {
     for (unsigned i = 0; i < m.numFunctions(); i++) {
-        if (!m.function(i).code()->bound())
+        if (!m.function(i).entry().bound())
             return m.failName(nullptr, "missing definition of function %s", m.function(i).name());
     }
 
     return true;
 }
 
 static bool
 CheckFunctionsSequential(ModuleCompiler &m)
@@ -5916,20 +5937,25 @@ static const unsigned FramePushedAfterSa
                                              NonVolatileRegs.fpus().size() * sizeof(double) +
                                              sizeof(double);
 #else
 static const unsigned FramePushedAfterSave = NonVolatileRegs.gprs().size() * sizeof(intptr_t) +
                                              NonVolatileRegs.fpus().getPushSizeInBytes();
 #endif
 
 static bool
-GenerateEntry(ModuleCompiler &m, const AsmJSModule::ExportedFunction &exportedFunc)
-{
+GenerateEntry(ModuleCompiler &m, unsigned exportIndex)
+{
+    PropertyName *funcName = m.module().exportedFunction(exportIndex).name();
+    const ModuleCompiler::Func &func = *m.lookupFunction(funcName);
+
     MacroAssembler &masm = m.masm();
-    GenerateAsmJSEntryPrologue(masm);
+
+    Label begin;
+    GenerateAsmJSEntryPrologue(masm, &begin);
 
     // In constrast to the system ABI, the Ion convention is that all registers
     // are clobbered by calls. Thus, we must save the caller's non-volatile
     // registers.
     masm.PushRegsInMask(NonVolatileRegs);
     JS_ASSERT(masm.framePushed() == FramePushedAfterSave);
 
     // ARM and MIPS have a globally-pinned GlobalReg (x64 uses RIP-relative
@@ -5960,17 +5986,16 @@ GenerateEntry(ModuleCompiler &m, const A
 #if defined(JS_CODEGEN_X86)
     masm.loadPtr(Address(StackPointer, sizeof(AsmJSFrame) + masm.framePushed()), argv);
 #else
     masm.movePtr(IntArgReg0, argv);
 #endif
     masm.Push(argv);
 
     // Bump the stack for the call.
-    const ModuleCompiler::Func &func = *m.lookupFunction(exportedFunc.name());
     unsigned stackDec = StackDecrementForCall(masm, func.sig().args());
     masm.reserveStack(stackDec);
 
     // Copy parameters out of argv and into the registers/stack-slots specified by
     // the system ABI.
     for (ABIArgTypeIter iter(func.sig().args()); !iter.done(); iter++) {
         unsigned argOffset = iter.index() * sizeof(uint64_t);
         Address src(argv, argOffset);
@@ -5991,17 +6016,17 @@ GenerateEntry(ModuleCompiler &m, const A
                 masm.storeDouble(ScratchDoubleReg, Address(StackPointer, iter->offsetFromArgBase()));
             }
             break;
         }
     }
 
     // Call into the real function.
     AssertStackAlignment(masm);
-    masm.call(func.code());
+    masm.call(CallSiteDesc(CallSiteDesc::Relative), &func.entry());
 
     // Pop the stack and recover the original 'argv' argument passed to the
     // trampoline (which was pushed on the stack).
     masm.freeStack(stackDec);
     masm.Pop(argv);
 
     // Store the return value in argv[0]
     switch (func.sig().retType().which()) {
@@ -6019,18 +6044,19 @@ GenerateEntry(ModuleCompiler &m, const A
         break;
     }
 
     // Restore clobbered non-volatile registers of the caller.
     masm.PopRegsInMask(NonVolatileRegs);
     JS_ASSERT(masm.framePushed() == 0);
 
     masm.move32(Imm32(true), ReturnReg);
+
     GenerateAsmJSEntryEpilogue(masm);
-    return true;
+    return m.finishGeneratingEntry(exportIndex, &begin) && !masm.oom();
 }
 
 static void
 FillArgumentArray(ModuleCompiler &m, const VarTypeVector &argTypes,
                   unsigned offsetToArgs, unsigned offsetToCallerStackArgs,
                   Register scratch)
 {
     MacroAssembler &masm = m.masm();
@@ -6061,19 +6087,19 @@ FillArgumentArray(ModuleCompiler &m, con
                 masm.canonicalizeDouble(ScratchDoubleReg);
                 masm.storeDouble(ScratchDoubleReg, dstAddr);
             }
             break;
         }
     }
 }
 
-static void
-GenerateFFIInterpreterExit(ModuleCompiler &m, const ModuleCompiler::ExitDescriptor &exit,
-                           unsigned exitIndex, Label *throwLabel)
+static bool
+GenerateFFIInterpExit(ModuleCompiler &m, const ModuleCompiler::ExitDescriptor &exit,
+                      unsigned exitIndex, Label *throwLabel)
 {
     MacroAssembler &masm = m.masm();
     JS_ASSERT(masm.framePushed() == 0);
 
     // Argument types for InvokeFromAsmJS_*:
     MIRType typeArray[] = { MIRType_Pointer,   // exitDatum
                             MIRType_Int32,     // argc
                             MIRType_Pointer }; // argv
@@ -6083,19 +6109,18 @@ GenerateFFIInterpreterExit(ModuleCompile
     // At the point of the call, the stack layout shall be (sp grows to the left):
     //   | stack args | padding | Value argv[] | padding | retaddr | caller stack args |
     // The padding between stack args and argv ensures that argv is aligned. The
     // padding between argv and retaddr ensures that sp is aligned.
     unsigned offsetToArgv = AlignBytes(StackArgBytes(invokeArgTypes), sizeof(double));
     unsigned argvBytes = Max<size_t>(1, exit.sig().args().length()) * sizeof(Value);
     unsigned framePushed = StackDecrementForCall(masm, offsetToArgv + argvBytes);
 
-    // Emit prologue code.
-    m.setInterpExitOffset(exitIndex);
-    GenerateAsmJSFFIExitPrologue(masm, framePushed);
+    Label begin;
+    GenerateAsmJSExitPrologue(masm, framePushed, AsmJSFFI, &begin);
 
     // Fill the argument array.
     unsigned offsetToCallerStackArgs = sizeof(AsmJSFrame) + masm.framePushed();
     Register scratch = ABIArgGenerator::NonArgReturnVolatileReg0;
     FillArgumentArray(m, exit.sig().args(), offsetToArgv, offsetToCallerStackArgs, scratch);
 
     // Prepare the arguments for the call to InvokeFromAsmJS_*.
     ABIArgMIRTypeIter i(invokeArgTypes);
@@ -6143,32 +6168,34 @@ GenerateFFIInterpreterExit(ModuleCompile
         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;
     }
 
-    GenerateAsmJSFFIExitEpilogue(masm, framePushed);
+    Label profilingReturn;
+    GenerateAsmJSExitEpilogue(masm, framePushed, AsmJSFFI, &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.
 #if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
 static const unsigned MaybeRetAddr = sizeof(void*);
 #else
 static const unsigned MaybeRetAddr = 0;
 #endif
 
-static void
+static bool
 GenerateFFIIonExit(ModuleCompiler &m, const ModuleCompiler::ExitDescriptor &exit,
-                         unsigned exitIndex, Label *throwLabel)
+                   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
@@ -6196,19 +6223,18 @@ GenerateFFIIonExit(ModuleCompiler &m, co
     MIRTypeVector coerceArgTypes(m.cx());
     coerceArgTypes.infallibleAppend(MIRType_Pointer); // argv
     unsigned offsetToCoerceArgv = AlignBytes(StackArgBytes(coerceArgTypes), sizeof(double));
     unsigned totalCoerceBytes = offsetToCoerceArgv + sizeof(Value) + savedRegBytes;
     unsigned coerceFrameSize = StackDecrementForCall(masm, totalCoerceBytes);
 
     unsigned framePushed = Max(ionFrameSize, coerceFrameSize);
 
-    // Emit prologue code.
-    m.setIonExitOffset(exitIndex);
-    GenerateAsmJSFFIExitPrologue(masm, framePushed);
+    Label begin;
+    GenerateAsmJSExitPrologue(masm, framePushed, AsmJSFFI, &begin);
 
     // 1. Descriptor
     size_t argOffset = offsetToIonArgs;
     uint32_t descriptor = MakeFrameDescriptor(framePushed, JitFrame_Entry);
     masm.storePtr(ImmWord(uintptr_t(descriptor)), Address(StackPointer, argOffset));
     argOffset += sizeof(size_t);
 
     // 2. Callee
@@ -6357,17 +6383,18 @@ GenerateFFIIonExit(ModuleCompiler &m, co
     JS_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
 
-    GenerateAsmJSFFIExitEpilogue(masm, framePushed);
+    Label profilingReturn;
+    GenerateAsmJSExitEpilogue(masm, framePushed, AsmJSFFI, &profilingReturn);
 
     if (oolConvert.used()) {
         masm.bind(&oolConvert);
         masm.setFramePushed(framePushed);
 
         // Store return value into argv[0]
         masm.storeValue(JSReturnOperand, Address(StackPointer, offsetToCoerceArgv));
 
@@ -6400,28 +6427,42 @@ GenerateFFIIonExit(ModuleCompiler &m, co
             MOZ_ASSUME_UNREACHABLE("Unsupported convert type");
         }
 
         masm.jump(&done);
         masm.setFramePushed(0);
     }
 
     JS_ASSERT(masm.framePushed() == 0);
+
+    return m.finishGeneratingIonExit(exitIndex, &begin, &profilingReturn) && !masm.oom();
 }
 
 // See "asm.js FFI calls" comment above.
-static void
-GenerateFFIExit(ModuleCompiler &m, const ModuleCompiler::ExitDescriptor &exit, unsigned exitIndex,
-                Label *throwLabel)
+static bool
+GenerateFFIExits(ModuleCompiler &m, const ModuleCompiler::ExitDescriptor &exit, unsigned exitIndex,
+                 Label *throwLabel)
 {
     // Generate the slow path through the interpreter
-    GenerateFFIInterpreterExit(m, exit, exitIndex, throwLabel);
+    if (!GenerateFFIInterpExit(m, exit, exitIndex, throwLabel))
+        return false;
 
     // Generate the fast path
-    GenerateFFIIonExit(m, exit, exitIndex, throwLabel);
+    if (!GenerateFFIIonExit(m, exit, exitIndex, throwLabel))
+        return false;
+
+    return true;
+}
+
+static bool
+GenerateStackOverflowExit(ModuleCompiler &m, Label *throwLabel)
+{
+    MacroAssembler &masm = m.masm();
+    GenerateAsmJSStackOverflowExit(masm, &m.stackOverflowLabel(), throwLabel);
+    return m.finishGeneratingInlineStub(&m.stackOverflowLabel()) && !masm.oom();
 }
 
 static const RegisterSet AllRegsExceptSP =
     RegisterSet(GeneralRegisterSet(Registers::AllMask &
                                    ~(uint32_t(1) << Registers::StackPointer)),
                 FloatRegisterSet(FloatRegisters::AllDoubleMask));
 
 // The async interrupt-callback exit is called from arbitrarily-interrupted asm.js
@@ -6559,47 +6600,36 @@ GenerateAsyncInterruptExit(ModuleCompile
     masm.transferReg(lr);
     masm.finishDataTransfer();
     masm.ret();
 
 #else
 # error "Unknown architecture!"
 #endif
 
-    return !masm.oom();
+    return m.finishGeneratingInlineStub(&m.asyncInterruptLabel()) && !masm.oom();
 }
 
 static bool
 GenerateSyncInterruptExit(ModuleCompiler &m, Label *throwLabel)
 {
     MacroAssembler &masm = m.masm();
-
     masm.setFramePushed(0);
-    masm.align(CodeAlignment);
-    masm.bind(&m.syncInterruptLabel());
-
-    // See AsmJSFrameSize comment in Assembler-shared.h.
-#if defined(JS_CODEGEN_ARM)
-    masm.push(lr);
-#elif defined(JS_CODEGEN_MIPS)
-    masm.push(ra);
-#endif
-
-    unsigned stackDec = StackDecrementForCall(masm, ShadowStackSpace);
-    masm.reserveStack(stackDec);
+
+    unsigned framePushed = StackDecrementForCall(masm, ShadowStackSpace);
+
+    GenerateAsmJSExitPrologue(masm, framePushed, AsmJSInterrupt, &m.syncInterruptLabel());
 
     AssertStackAlignment(masm);
     masm.call(AsmJSImmPtr(AsmJSImm_HandleExecutionInterrupt));
     masm.branchIfFalseBool(ReturnReg, throwLabel);
 
-    masm.freeStack(stackDec);
-
-    JS_ASSERT(masm.framePushed() == 0);
-    masm.ret();
-    return !masm.oom();
+    Label profilingReturn;
+    GenerateAsmJSExitEpilogue(masm, framePushed, AsmJSInterrupt, &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.
 //  2. PopRegsInMask to restore the caller's non-volatile registers.
 //  3. Return (to CallAsmJS).
 static bool
@@ -6620,42 +6650,38 @@ GenerateThrowStub(ModuleCompiler &m, Lab
     masm.loadPtr(Address(activation, AsmJSActivation::offsetOfErrorRejoinSP()), StackPointer);
     masm.PopRegsInMask(NonVolatileRegs);
     JS_ASSERT(masm.framePushed() == 0);
 
     masm.mov(ImmWord(0), ReturnReg);
     masm.addPtr(Imm32(AsmJSFrameBytesAfterReturnAddress), StackPointer);
     masm.ret();
 
-    return !masm.oom();
+    return m.finishGeneratingInlineStub(throwLabel) && !masm.oom();
 }
 
 static bool
 GenerateStubs(ModuleCompiler &m)
 {
     for (unsigned i = 0; i < m.module().numExportedFunctions(); i++) {
-        m.startGeneratingEntry(i);
-        if (!GenerateEntry(m, m.module().exportedFunction(i)))
-            return false;
-        if (m.masm().oom() || !m.finishGeneratingEntry(i))
-            return false;
+        if (!GenerateEntry(m, i))
+           return false;
     }
 
     Label throwLabel;
 
     // The order of the iterations here is non-deterministic, since
     // m.allExits() is a hash keyed by pointer values!
     for (ModuleCompiler::ExitMap::Range r = m.allExits(); !r.empty(); r.popFront()) {
-        GenerateFFIExit(m, r.front().key(), r.front().value(), &throwLabel);
-        if (m.masm().oom())
+        if (!GenerateFFIExits(m, r.front().key(), r.front().value(), &throwLabel))
             return false;
     }
 
-    if (m.stackOverflowLabel().used())
-        GenerateAsmJSStackOverflowExit(m.masm(), &m.stackOverflowLabel(), &throwLabel);
+    if (m.stackOverflowLabel().used() && !GenerateStackOverflowExit(m, &throwLabel))
+        return false;
 
     if (!GenerateAsyncInterruptExit(m, &throwLabel))
         return false;
     if (m.syncInterruptLabel().used() && !GenerateSyncInterruptExit(m, &throwLabel))
         return false;
 
     if (!GenerateThrowStub(m, &throwLabel))
         return false;
--- a/js/src/jit/AsmJSFrameIterator.cpp
+++ b/js/src/jit/AsmJSFrameIterator.cpp
@@ -8,60 +8,74 @@
 
 #include "jit/AsmJS.h"
 #include "jit/AsmJSModule.h"
 #include "jit/IonMacroAssembler.h"
 
 using namespace js;
 using namespace js::jit;
 
+using mozilla::DebugOnly;
+
 /*****************************************************************************/
 // AsmJSFrameIterator implementation
 
 static void *
-ReturnAddressFromFP(uint8_t *fp)
+ReturnAddressFromFP(void *fp)
 {
     return reinterpret_cast<AsmJSFrame*>(fp)->returnAddress;
 }
 
+static uint8_t *
+CallerFPFromFP(void *fp)
+{
+    return reinterpret_cast<AsmJSFrame*>(fp)->callerFP;
+}
+
 AsmJSFrameIterator::AsmJSFrameIterator(const AsmJSActivation &activation)
   : module_(&activation.module()),
     fp_(activation.fp())
 {
     if (!fp_)
         return;
     settle();
 }
 
 void
 AsmJSFrameIterator::operator++()
 {
     JS_ASSERT(!done());
+    DebugOnly<uint8_t*> oldfp = fp_;
     fp_ += callsite_->stackDepth();
+    JS_ASSERT_IF(module_->profilingEnabled(), fp_ == CallerFPFromFP(oldfp));
     settle();
 }
 
 void
 AsmJSFrameIterator::settle()
 {
     void *returnAddress = ReturnAddressFromFP(fp_);
 
     const AsmJSModule::CodeRange *codeRange = module_->lookupCodeRange(returnAddress);
     JS_ASSERT(codeRange);
     codeRange_ = codeRange;
 
     switch (codeRange->kind()) {
-      case AsmJSModule::CodeRange::Entry:
-        fp_ = nullptr;
-        JS_ASSERT(done());
-        return;
       case AsmJSModule::CodeRange::Function:
         callsite_ = module_->lookupCallSite(returnAddress);
         JS_ASSERT(callsite_);
         break;
+      case AsmJSModule::CodeRange::Entry:
+        fp_ = nullptr;
+        JS_ASSERT(done());
+        break;
+      case AsmJSModule::CodeRange::FFI:
+      case AsmJSModule::CodeRange::Interrupt:
+      case AsmJSModule::CodeRange::Inline:
+        MOZ_ASSUME_UNREACHABLE("Should not encounter an exit during iteration");
     }
 }
 
 JSAtom *
 AsmJSFrameIterator::functionDisplayAtom() const
 {
     JS_ASSERT(!done());
     return reinterpret_cast<const AsmJSModule::CodeRange*>(codeRange_)->functionName(*module_);
@@ -74,72 +88,217 @@ AsmJSFrameIterator::computeLine(uint32_t
     if (column)
         *column = callsite_->column();
     return callsite_->line();
 }
 
 /*****************************************************************************/
 // Prologue/epilogue code generation
 
+// These constants reflect statically-determined offsets in the profiling
+// prologue/epilogue. The offsets are dynamically asserted during code
+// generation.
+#if defined(JS_CODEGEN_X64)
+static const unsigned PushedRetAddr = 0;
+static const unsigned PushedFP = 11;
+static const unsigned StoredFP = 15;
+#elif defined(JS_CODEGEN_X86)
+static const unsigned PushedRetAddr = 0;
+static const unsigned PushedFP = 9;
+static const unsigned StoredFP = 12;
+#elif defined(JS_CODEGEN_ARM)
+static const unsigned PushedRetAddr = 4;
+static const unsigned PushedFP = 16;
+static const unsigned StoredFP = 20;
+#endif
+
 static void
 PushRetAddr(MacroAssembler &masm)
 {
 #if defined(JS_CODEGEN_ARM)
     masm.push(lr);
 #elif defined(JS_CODEGEN_MIPS)
     masm.push(ra);
 #else
     // The x86/x64 call instruction pushes the return address.
 #endif
 }
 
+// Generate a prologue that maintains AsmJSActivation::fp as the virtual frame
+// pointer so that AsmJSProfilingFrameIterator can walk the stack at any pc in
+// generated code.
+static void
+GenerateProfilingPrologue(MacroAssembler &masm, unsigned framePushed, AsmJSExitReason reason,
+                          Label *begin)
+{
+    Register act = ABIArgGenerator::NonArgReturnVolatileReg0;
+
+    // AsmJSProfilingFrameIterator needs to know the offsets of several key
+    // instructions from 'begin'. To save space, we make these offsets static
+    // constants and assert that they match the actual codegen below. On ARM,
+    // this requires AutoForbidPools to prevent a constant pool from being
+    // randomly inserted between two instructions.
+    {
+#if defined(JS_CODEGEN_ARM)
+        AutoForbidPools afp(&masm, /* number of instructions in scope = */ 5);
+#endif
+        DebugOnly<uint32_t> offsetAtBegin = masm.currentOffset();
+        masm.bind(begin);
+
+        PushRetAddr(masm);
+        JS_ASSERT(PushedRetAddr == masm.currentOffset() - offsetAtBegin);
+
+        masm.loadAsmJSActivation(act);
+        masm.push(Address(act, AsmJSActivation::offsetOfFP()));
+        JS_ASSERT(PushedFP == masm.currentOffset() - offsetAtBegin);
+
+        masm.storePtr(StackPointer, Address(act, AsmJSActivation::offsetOfFP()));
+        JS_ASSERT(StoredFP == masm.currentOffset() - offsetAtBegin);
+    }
+
+    if (reason != AsmJSNoExit)
+        masm.store32(Imm32(reason), Address(act, AsmJSActivation::offsetOfExitReason()));
+
+    if (framePushed)
+        masm.subPtr(Imm32(framePushed), StackPointer);
+}
+
+// Generate the inverse of GenerateProfilingPrologue.
+static void
+GenerateProfilingEpilogue(MacroAssembler &masm, unsigned framePushed, AsmJSExitReason reason,
+                          Label *profilingReturn)
+{
+    Register act = ABIArgGenerator::NonArgReturnVolatileReg0;
+
+    if (framePushed)
+        masm.addPtr(Imm32(framePushed), StackPointer);
+
+    masm.loadAsmJSActivation(act);
+
+    if (reason != AsmJSNoExit)
+        masm.store32(Imm32(AsmJSNoExit), Address(act, AsmJSActivation::offsetOfExitReason()));
+
+    // AsmJSProfilingFrameIterator assumes that there is only a single 'ret'
+    // instruction (whose offset is recorded by profilingReturn) after the store
+    // which sets AsmJSActivation::fp to the caller's fp. Use AutoForbidPools to
+    // ensure that a pool is not inserted before the return (a pool inserts a
+    // jump instruction).
+    {
+#if defined(JS_CODEGEN_ARM)
+        AutoForbidPools afp(&masm, /* number of instructions in scope = */ 3);
+#endif
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        masm.pop(Operand(act, AsmJSActivation::offsetOfFP()));
+#else
+        Register fp = ABIArgGenerator::NonArgReturnVolatileReg1;
+        masm.pop(fp);
+        masm.storePtr(fp, Address(act, AsmJSActivation::offsetOfFP()));
+#endif
+        masm.bind(profilingReturn);
+        masm.ret();
+    }
+}
+
+// In profiling mode, we need to maintain fp so that we can unwind the stack at
+// any pc. In non-profiling mode, the only way to observe AsmJSActivation::fp is
+// to call out to C++ so, as an optimization, we don't update fp. To avoid
+// recompilation when the profiling mode is toggled, we generate both prologues
+// a priori and switch between prologues when the profiling mode is toggled.
+// Specifically, AsmJSModule::setProfilingEnabled patches all callsites to
+// either call the profiling or non-profiling entry point.
 void
 js::GenerateAsmJSFunctionPrologue(MacroAssembler &masm, unsigned framePushed,
-                                  Label *maybeOverflowThunk, Label *overflowExit)
+                                  AsmJSFunctionLabels *labels)
 {
-    // When not in profiling mode, the only way to observe fp (i.e.,
-    // AsmJSActivation::fp) is to call out to C++ so, as an optimization, we
-    // don't update fp. Technically, the interrupt exit can observe fp at an
-    // arbitrary pc, but we don't care about providing an accurate stack in this
-    // case. We still need to reserve space for the saved frame pointer, though,
-    // to maintain the AsmJSFrame layout.
+#if defined(JS_CODEGEN_ARM)
+    // Flush pending pools so they do not get dumped between the 'begin' and
+    // 'entry' labels since the difference must be less than UINT8_MAX.
+    masm.flushBuffer();
+#endif
+
+    masm.align(CodeAlignment);
+
+    GenerateProfilingPrologue(masm, framePushed, AsmJSNoExit, &labels->begin);
+    Label body;
+    masm.jump(&body);
+
+    // Generate normal prologue:
+    masm.align(CodeAlignment);
+    masm.bind(&labels->entry);
     PushRetAddr(masm);
     masm.subPtr(Imm32(framePushed + AsmJSFrameBytesAfterReturnAddress), StackPointer);
+
+    // Prologue join point, body begin:
+    masm.bind(&body);
     masm.setFramePushed(framePushed);
 
     // Overflow checks are omitted by CodeGenerator in some cases (leaf
     // functions with small framePushed). Perform overflow-checking after
     // pushing framePushed to catch cases with really large frames.
-    if (maybeOverflowThunk) {
-        // If framePushed is zero, we don't need a thunk.
-        Label *target = framePushed ? maybeOverflowThunk : overflowExit;
+    if (!labels->overflowThunk.empty()) {
+        // If framePushed is zero, we don't need a thunk to adjust StackPointer.
+        Label *target = framePushed ? labels->overflowThunk.addr() : &labels->overflowExit;
         masm.branchPtr(Assembler::AboveOrEqual,
                        AsmJSAbsoluteAddress(AsmJSImm_StackLimit),
                        StackPointer,
                        target);
     }
 }
 
+// Similar to GenerateAsmJSFunctionPrologue (see comment), we generate both a
+// profiling and non-profiling epilogue a priori. When the profiling mode is
+// toggled, AsmJSModule::setProfilingEnabled patches the 'profiling jump' to
+// either be a nop (falling through to the normal prologue) or a jump (jumping
+// to the profiling epilogue).
 void
 js::GenerateAsmJSFunctionEpilogue(MacroAssembler &masm, unsigned framePushed,
-                                  Label *maybeOverflowThunk, Label *overflowExit)
+                                  AsmJSFunctionLabels *labels)
 {
-    // Inverse of GenerateAsmJSFunctionPrologue:
     JS_ASSERT(masm.framePushed() == framePushed);
+
+#if defined(JS_CODEGEN_ARM)
+    // Flush pending pools so they do not get dumped between the profilingReturn
+    // and profilingJump/profilingEpilogue labels since the difference must be
+    // less than UINT8_MAX.
+    masm.flushBuffer();
+#endif
+
+    {
+#if defined(JS_CODEGEN_ARM)
+        // Forbid pools from being inserted between the profilingJump label and
+        // the nop since we need the location of the actual nop to patch it.
+        AutoForbidPools afp(&masm, 1);
+#endif
+
+        // The exact form of this instruction must be kept consistent with the
+        // patching in AsmJSModule::setProfilingEnabled.
+        masm.bind(&labels->profilingJump);
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        masm.twoByteNop();
+#elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
+        masm.nop();
+#endif
+    }
+
+    // Normal epilogue:
     masm.addPtr(Imm32(framePushed + AsmJSFrameBytesAfterReturnAddress), StackPointer);
     masm.ret();
     masm.setFramePushed(0);
 
-    if (maybeOverflowThunk && maybeOverflowThunk->used()) {
+    // Profiling epilogue:
+    masm.bind(&labels->profilingEpilogue);
+    GenerateProfilingEpilogue(masm, framePushed, AsmJSNoExit, &labels->profilingReturn);
+
+    if (!labels->overflowThunk.empty() && labels->overflowThunk.ref().used()) {
         // The general throw stub assumes that only sizeof(AsmJSFrame) bytes
         // have been pushed. The overflow check occurs after incrementing by
         // framePushed, so pop that before jumping to the overflow exit.
-        masm.bind(maybeOverflowThunk);
+        masm.bind(labels->overflowThunk.addr());
         masm.addPtr(Imm32(framePushed), StackPointer);
-        masm.jump(overflowExit);
+        masm.jump(&labels->overflowExit);
     }
 }
 
 void
 js::GenerateAsmJSStackOverflowExit(MacroAssembler &masm, Label *overflowExit, Label *throwLabel)
 {
     masm.bind(overflowExit);
 
@@ -159,69 +318,344 @@ js::GenerateAsmJSStackOverflowExit(Macro
 
     // No need to restore the stack; the throw stub pops everything.
     masm.assertStackAlignment();
     masm.call(AsmJSImmPtr(AsmJSImm_ReportOverRecursed));
     masm.jump(throwLabel);
 }
 
 void
-js::GenerateAsmJSEntryPrologue(MacroAssembler &masm)
+js::GenerateAsmJSEntryPrologue(MacroAssembler &masm, Label *begin)
 {
     // Stack-unwinding stops at the entry prologue, so there is no need to
     // update AsmJSActivation::fp. Furthermore, on ARM/MIPS, GlobalReg is not
     // yet initialized, so we can't even if we wanted to.
+    masm.align(CodeAlignment);
+    masm.bind(begin);
     PushRetAddr(masm);
     masm.subPtr(Imm32(AsmJSFrameBytesAfterReturnAddress), StackPointer);
     masm.setFramePushed(0);
 }
 
 void
 js::GenerateAsmJSEntryEpilogue(MacroAssembler &masm)
 {
     // Inverse of GenerateAsmJSEntryPrologue:
     JS_ASSERT(masm.framePushed() == 0);
     masm.addPtr(Imm32(AsmJSFrameBytesAfterReturnAddress), StackPointer);
     masm.ret();
     masm.setFramePushed(0);
 }
 
 void
-js::GenerateAsmJSFFIExitPrologue(MacroAssembler &masm, unsigned framePushed)
+js::GenerateAsmJSExitPrologue(MacroAssembler &masm, unsigned framePushed, AsmJSExitReason reason,
+                              Label *begin)
 {
-    // Stack-unwinding from C++ starts unwinding depends on AsmJSActivation::fp.
-    PushRetAddr(masm);
-
-    Register activation = ABIArgGenerator::NonArgReturnVolatileReg0;
-    masm.loadAsmJSActivation(activation);
-    Address fp(activation, AsmJSActivation::offsetOfFP());
-    masm.push(fp);
-    masm.storePtr(StackPointer, fp);
-
-    if (framePushed)
-        masm.subPtr(Imm32(framePushed), StackPointer);
-
+    masm.align(CodeAlignment);
+    GenerateProfilingPrologue(masm, framePushed, reason, begin);
     masm.setFramePushed(framePushed);
 }
 
 void
-js::GenerateAsmJSFFIExitEpilogue(MacroAssembler &masm, unsigned framePushed)
+js::GenerateAsmJSExitEpilogue(MacroAssembler &masm, unsigned framePushed, AsmJSExitReason reason,
+                              Label *profilingReturn)
 {
-    // Inverse of GenerateAsmJSFFIExitPrologue:
+    // Inverse of GenerateAsmJSExitPrologue:
     JS_ASSERT(masm.framePushed() == framePushed);
-
-    if (framePushed)
-        masm.addPtr(Imm32(framePushed), StackPointer);
-
-    Register activation = ABIArgGenerator::NonArgReturnVolatileReg0;
-    masm.loadAsmJSActivation(activation);
-#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
-    masm.pop(Operand(activation, AsmJSActivation::offsetOfFP()));
-#else
-    Register fp = ABIArgGenerator::NonArgReturnVolatileReg1;
-    masm.pop(fp);
-    masm.storePtr(fp, Address(activation, AsmJSActivation::offsetOfFP()));
-#endif
-
-    masm.ret();
+    GenerateProfilingEpilogue(masm, framePushed, reason, profilingReturn);
     masm.setFramePushed(0);
 }
 
+/*****************************************************************************/
+// AsmJSProfilingFrameIterator
+
+AsmJSProfilingFrameIterator::AsmJSProfilingFrameIterator(const AsmJSActivation &activation)
+  : module_(&activation.module()),
+    callerFP_(nullptr),
+    callerPC_(nullptr),
+    exitReason_(AsmJSNoExit),
+    codeRange_(nullptr)
+{
+    initFromFP(activation);
+}
+
+static inline void
+AssertMatchesCallSite(const AsmJSModule &module, void *pc, void *newfp, void *oldfp)
+{
+#ifdef DEBUG
+    const AsmJSModule::CodeRange *codeRange = module.lookupCodeRange(pc);
+    JS_ASSERT(codeRange);
+    if (codeRange->isEntry()) {
+        JS_ASSERT(newfp == nullptr);
+        return;
+    }
+
+    const CallSite *callsite = module.lookupCallSite(pc);
+    JS_ASSERT(callsite);
+    JS_ASSERT(newfp == (uint8_t*)oldfp + callsite->stackDepth());
+#endif
+}
+
+void
+AsmJSProfilingFrameIterator::initFromFP(const AsmJSActivation &activation)
+{
+    uint8_t *fp = activation.fp();
+
+    // If a signal was handled while entering an activation, the frame will
+    // still be null.
+    if (!fp) {
+        JS_ASSERT(done());
+        return;
+    }
+
+    // Since we don't have the pc for fp, start unwinding at the caller of fp,
+    // whose pc we do have via fp->returnAddress. This means that the innermost
+    // frame is skipped but this is fine because:
+    //  - for FFI calls, the innermost frame is a thunk, so the first frame that
+    //    shows up is the function calling the FFI;
+    //  - for Math and other builtin calls, when profiling is activated, we
+    //    patch all call sites to instead call through a thunk; and
+    //  - for interrupts, we just accept that we'll lose the innermost frame.
+    // However, we do want FFI trampolines to show up in callstacks (so that
+    // they properly accumulate self-time) and for this we use the exitReason.
+
+    exitReason_ = activation.exitReason();
+
+    void *pc = ReturnAddressFromFP(fp);
+    const AsmJSModule::CodeRange *codeRange = module_->lookupCodeRange(pc);
+    JS_ASSERT(codeRange);
+    codeRange_ = codeRange;
+
+    switch (codeRange->kind()) {
+      case AsmJSModule::CodeRange::Entry:
+        callerPC_ = nullptr;
+        callerFP_ = nullptr;
+        break;
+      case AsmJSModule::CodeRange::Function:
+      case AsmJSModule::CodeRange::FFI:
+      case AsmJSModule::CodeRange::Interrupt:
+      case AsmJSModule::CodeRange::Inline:
+        AssertMatchesCallSite(*module_, pc, CallerFPFromFP(fp), fp);
+        fp = CallerFPFromFP(fp);
+        callerPC_ = ReturnAddressFromFP(fp);
+        callerFP_ = CallerFPFromFP(fp);
+        AssertMatchesCallSite(*module_, callerPC_, callerFP_, fp);
+        break;
+    }
+
+    JS_ASSERT(!done());
+}
+
+typedef JS::ProfilingFrameIterator::RegisterState RegisterState;
+
+AsmJSProfilingFrameIterator::AsmJSProfilingFrameIterator(const AsmJSActivation &activation,
+                                                         const RegisterState &state)
+  : module_(&activation.module()),
+    callerFP_(nullptr),
+    callerPC_(nullptr),
+    exitReason_(AsmJSNoExit),
+    codeRange_(nullptr)
+{
+    // If profiling hasn't been enabled for this module, then CallerFPFromFP
+    // will be trash, so ignore the entire activation. In practice, this only
+    // happens if profiling is enabled while module->active() (in this case,
+    // profiling will be enabled when the module becomes inactive and gets
+    // called again).
+    if (!module_->profilingEnabled()) {
+        JS_ASSERT(done());
+        return;
+    }
+
+    // If pc isn't in the module, we must have exited the asm.js module via an
+    // exit trampoline or signal handler.
+    if (!module_->containsCodePC(state.pc)) {
+        initFromFP(activation);
+        return;
+    }
+
+    // Note: fp may be null while entering and leaving the activation.
+    uint8_t *fp = activation.fp();
+
+    const AsmJSModule::CodeRange *codeRange = module_->lookupCodeRange(state.pc);
+    switch (codeRange->kind()) {
+      case AsmJSModule::CodeRange::Function:
+      case AsmJSModule::CodeRange::FFI:
+      case AsmJSModule::CodeRange::Interrupt: {
+        // While codeRange describes the *current* frame, the fp/pc state stored in
+        // the iterator is the *caller's* frame. The reason for this is that the
+        // activation.fp isn't always the AsmJSFrame for state.pc; during the
+        // prologue/epilogue, activation.fp will point to the caller's frame.
+        // Naively unwinding starting at activation.fp could thus lead to the
+        // second-to-innermost function being skipped in the callstack which will
+        // bork profiling stacks. Instead, we depend on the exact layout of the
+        // prologue/epilogue, as generated by GenerateProfiling(Prologue|Epilogue)
+        // below.
+        uint32_t offsetInModule = ((uint8_t*)state.pc) - module_->codeBase();
+        JS_ASSERT(offsetInModule < module_->codeBytes());
+        JS_ASSERT(offsetInModule >= codeRange->begin());
+        JS_ASSERT(offsetInModule < codeRange->end());
+        uint32_t offsetInCodeRange = offsetInModule - codeRange->begin();
+        void **sp = (void**)state.sp;
+#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
+        if (offsetInCodeRange < PushedRetAddr) {
+            callerPC_ = state.lr;
+            callerFP_ = fp;
+            AssertMatchesCallSite(*module_, callerPC_, callerFP_, sp - 2);
+        } else
+#endif
+        if (offsetInCodeRange < PushedFP || offsetInModule == codeRange->profilingReturn()) {
+            callerPC_ = *sp;
+            callerFP_ = fp;
+            AssertMatchesCallSite(*module_, callerPC_, callerFP_, sp - 1);
+        } else if (offsetInCodeRange < StoredFP) {
+            JS_ASSERT(fp == CallerFPFromFP(sp));
+            callerPC_ = ReturnAddressFromFP(sp);
+            callerFP_ = CallerFPFromFP(sp);
+            AssertMatchesCallSite(*module_, callerPC_, callerFP_, sp);
+        } else {
+            callerPC_ = ReturnAddressFromFP(fp);
+            callerFP_ = CallerFPFromFP(fp);
+            AssertMatchesCallSite(*module_, callerPC_, callerFP_, fp);
+        }
+        break;
+      }
+      case AsmJSModule::CodeRange::Entry: {
+        // The entry trampoline is the final frame in an AsmJSActivation. The entry
+        // trampoline also doesn't GenerateAsmJSPrologue/Epilogue so we can't use
+        // the general unwinding logic below.
+        JS_ASSERT(!fp);
+        callerPC_ = nullptr;
+        callerFP_ = nullptr;
+        break;
+      }
+      case AsmJSModule::CodeRange::Inline: {
+        // The throw stub clears AsmJSActivation::fp on it's way out.
+        if (!fp) {
+            JS_ASSERT(done());
+            return;
+        }
+
+        // Inline code ranges execute in the frame of the caller have no
+        // prologue/epilogue and thus don't require the general unwinding logic
+        // as below.
+        callerPC_ = ReturnAddressFromFP(fp);
+        callerFP_ = CallerFPFromFP(fp);
+        AssertMatchesCallSite(*module_, callerPC_, callerFP_, fp);
+        break;
+      }
+    }
+
+    codeRange_ = codeRange;
+    JS_ASSERT(!done());
+}
+
+void
+AsmJSProfilingFrameIterator::operator++()
+{
+    if (exitReason_ != AsmJSNoExit) {
+        JS_ASSERT(codeRange_);
+        exitReason_ = AsmJSNoExit;
+        JS_ASSERT(!done());
+        return;
+    }
+
+    if (!callerPC_) {
+        JS_ASSERT(!callerFP_);
+        codeRange_ = nullptr;
+        JS_ASSERT(done());
+        return;
+    }
+
+    JS_ASSERT(callerPC_);
+    const AsmJSModule::CodeRange *codeRange = module_->lookupCodeRange(callerPC_);
+    JS_ASSERT(codeRange);
+    codeRange_ = codeRange;
+
+    switch (codeRange->kind()) {
+      case AsmJSModule::CodeRange::Entry:
+        callerPC_ = nullptr;
+        callerFP_ = nullptr;
+        break;
+      case AsmJSModule::CodeRange::Function:
+      case AsmJSModule::CodeRange::FFI:
+      case AsmJSModule::CodeRange::Interrupt:
+      case AsmJSModule::CodeRange::Inline:
+        callerPC_ = ReturnAddressFromFP(callerFP_);
+        AssertMatchesCallSite(*module_, callerPC_, CallerFPFromFP(callerFP_), callerFP_);
+        callerFP_ = CallerFPFromFP(callerFP_);
+        break;
+    }
+
+    JS_ASSERT(!done());
+}
+
+AsmJSProfilingFrameIterator::Kind
+AsmJSProfilingFrameIterator::kind() const
+{
+    JS_ASSERT(!done());
+
+    switch (exitReason_) {
+      case AsmJSNoExit:
+        break;
+      case AsmJSInterrupt:
+      case AsmJSFFI:
+        return JS::ProfilingFrameIterator::AsmJSTrampoline;
+    }
+
+    auto codeRange = reinterpret_cast<const AsmJSModule::CodeRange*>(codeRange_);
+    switch (codeRange->kind()) {
+      case AsmJSModule::CodeRange::Function:
+        return JS::ProfilingFrameIterator::Function;
+      case AsmJSModule::CodeRange::Entry:
+      case AsmJSModule::CodeRange::FFI:
+      case AsmJSModule::CodeRange::Interrupt:
+      case AsmJSModule::CodeRange::Inline:
+        return JS::ProfilingFrameIterator::AsmJSTrampoline;
+    }
+
+    MOZ_ASSUME_UNREACHABLE("Bad kind");
+}
+
+JSAtom *
+AsmJSProfilingFrameIterator::functionDisplayAtom() const
+{
+    JS_ASSERT(kind() == JS::ProfilingFrameIterator::Function);
+    return reinterpret_cast<const AsmJSModule::CodeRange*>(codeRange_)->functionName(*module_);
+}
+
+const char *
+AsmJSProfilingFrameIterator::functionFilename() const
+{
+    JS_ASSERT(kind() == JS::ProfilingFrameIterator::Function);
+    return module_->scriptSource()->filename();
+}
+
+const char *
+AsmJSProfilingFrameIterator::nonFunctionDescription() const
+{
+    JS_ASSERT(!done());
+
+    // Use the same string for both time inside and under so that the two
+    // entries will be coalesced by the profiler.
+    const char *ffiDescription = "asm.js FFI trampoline";
+    const char *interruptDescription = "asm.js slow script interrupt";
+
+    switch (exitReason_) {
+      case AsmJSNoExit:
+        break;
+      case AsmJSFFI:
+        return ffiDescription;
+      case AsmJSInterrupt:
+        return interruptDescription;
+    }
+
+    auto codeRange = reinterpret_cast<const AsmJSModule::CodeRange*>(codeRange_);
+    switch (codeRange->kind()) {
+      case AsmJSModule::CodeRange::Function:  MOZ_ASSUME_UNREACHABLE("non-functions only");
+      case AsmJSModule::CodeRange::Entry:     return "asm.js entry trampoline";
+      case AsmJSModule::CodeRange::FFI:       return ffiDescription;
+      case AsmJSModule::CodeRange::Interrupt: return interruptDescription;
+      case AsmJSModule::CodeRange::Inline:    return "asm.js inline stub";
+    }
+
+    MOZ_ASSUME_UNREACHABLE("Bad kind");
+}
+
--- a/js/src/jit/AsmJSFrameIterator.h
+++ b/js/src/jit/AsmJSFrameIterator.h
@@ -6,26 +6,32 @@
 
 #ifndef jit_AsmJSFrameIterator_h
 #define jit_AsmJSFrameIterator_h
 
 #include "mozilla/NullPtr.h"
 
 #include <stdint.h>
 
+#include "js/ProfilingFrameIterator.h"
+
 class JSAtom;
 struct JSContext;
 
 namespace js {
 
 class AsmJSActivation;
 class AsmJSModule;
-namespace jit { struct CallSite; class MacroAssembler; class Label; }
+struct AsmJSFunctionLabels;
+namespace jit { class CallSite; class MacroAssembler; class Label; }
 
-// Iterates over the frames of a single AsmJSActivation.
+// Iterates over the frames of a single AsmJSActivation, called synchronously
+// from C++ in the thread of the asm.js. The one exception is that this iterator
+// may be called from the interrupt callback which may be called asynchronously
+// from asm.js code; in this case, the backtrace may not be correct.
 class AsmJSFrameIterator
 {
     const AsmJSModule *module_;
     const jit::CallSite *callsite_;
     uint8_t *fp_;
 
     // Really, a const AsmJSModule::CodeRange*, but no forward declarations of
     // nested classes, so use void* to avoid pulling in all of AsmJSModule.h.
@@ -37,34 +43,81 @@ class AsmJSFrameIterator
     explicit AsmJSFrameIterator() : module_(nullptr) {}
     explicit AsmJSFrameIterator(const AsmJSActivation &activation);
     void operator++();
     bool done() const { return !fp_; }
     JSAtom *functionDisplayAtom() const;
     unsigned computeLine(uint32_t *column) const;
 };
 
+// List of reasons for execution leaving asm.js-generated code, stored in
+// AsmJSActivation. The initial and default state is AsmJSNoExit. If AsmJSNoExit
+// is observed when the pc isn't in asm.js code, execution must have been
+// interrupted asynchronously (viz., by a exception/signal handler).
+enum AsmJSExitReason
+{
+    AsmJSNoExit,
+    AsmJSFFI,
+    AsmJSInterrupt
+};
+
+// Iterates over the frames of a single AsmJSActivation, given an
+// asynchrously-interrupted thread's state. If the activation's
+// module is not in profiling mode, the activation is skipped.
+class AsmJSProfilingFrameIterator
+{
+    const AsmJSModule *module_;
+    uint8_t *callerFP_;
+    void *callerPC_;
+    AsmJSExitReason exitReason_;
+
+    // Really, a const AsmJSModule::CodeRange*, but no forward declarations of
+    // nested classes, so use void* to avoid pulling in all of AsmJSModule.h.
+    const void *codeRange_;
+
+    void initFromFP(const AsmJSActivation &activation);
+
+  public:
+    AsmJSProfilingFrameIterator() : codeRange_(nullptr) {}
+    AsmJSProfilingFrameIterator(const AsmJSActivation &activation);
+    AsmJSProfilingFrameIterator(const AsmJSActivation &activation,
+                                const JS::ProfilingFrameIterator::RegisterState &state);
+    void operator++();
+    bool done() const { return !codeRange_; }
+
+    typedef JS::ProfilingFrameIterator::Kind Kind;
+    Kind kind() const;
+
+    JSAtom *functionDisplayAtom() const;
+    const char *functionFilename() const;
+    unsigned functionLine() const;
+
+    const char *nonFunctionDescription() const;
+};
+
 /******************************************************************************/
 // Prologue/epilogue code generation.
 
 void
 GenerateAsmJSFunctionPrologue(jit::MacroAssembler &masm, unsigned framePushed,
-                              jit::Label *maybeOverflowThunk, jit::Label *overflowExit);
+                              AsmJSFunctionLabels *labels);
 void
 GenerateAsmJSFunctionEpilogue(jit::MacroAssembler &masm, unsigned framePushed,
-                              jit::Label *maybeOverflowThunk, jit::Label *overflowExit);
+                              AsmJSFunctionLabels *labels);
 void
 GenerateAsmJSStackOverflowExit(jit::MacroAssembler &masm, jit::Label *overflowExit,
                                jit::Label *throwLabel);
 
 void
-GenerateAsmJSEntryPrologue(jit::MacroAssembler &masm);
+GenerateAsmJSEntryPrologue(jit::MacroAssembler &masm, jit::Label *begin);
 void
 GenerateAsmJSEntryEpilogue(jit::MacroAssembler &masm);
 
 void
-GenerateAsmJSFFIExitPrologue(jit::MacroAssembler &masm, unsigned framePushed);
+GenerateAsmJSExitPrologue(jit::MacroAssembler &masm, unsigned framePushed, AsmJSExitReason reason,
+                          jit::Label *begin);
 void
-GenerateAsmJSFFIExitEpilogue(jit::MacroAssembler &masm, unsigned framePushed);
+GenerateAsmJSExitEpilogue(jit::MacroAssembler &masm, unsigned framePushed, AsmJSExitReason reason,
+                          jit::Label *profilingReturn);
 
 } // namespace js
 
 #endif // jit_AsmJSFrameIterator_h
--- a/js/src/jit/AsmJSLink.cpp
+++ b/js/src/jit/AsmJSLink.cpp
@@ -364,16 +364,23 @@ CallAsmJS(JSContext *cx, unsigned argc, 
     CallArgs callArgs = CallArgsFromVp(argc, vp);
     RootedFunction callee(cx, &callArgs.callee().as<JSFunction>());
 
     // 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);
 
+    // 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());
+
     // 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 8-byte values where each value contains either a coerced int32
     // (in the low word) or double value, with the coercions specified by the
--- a/js/src/jit/AsmJSModule.cpp
+++ b/js/src/jit/AsmJSModule.cpp
@@ -83,16 +83,17 @@ AsmJSModule::AsmJSModule(ScriptSource *s
     scriptSource_(scriptSource),
     globalArgumentName_(nullptr),
     importArgumentName_(nullptr),
     bufferArgumentName_(nullptr),
     code_(nullptr),
     interruptExit_(nullptr),
     dynamicallyLinked_(false),
     loadedFromCache_(false),
+    profilingEnabled_(false),
     codeIsProtected_(false)
 {
     mozilla::PodZero(&pod);
     pod.funcPtrTableAndExitBytes_ = SIZE_MAX;
     pod.functionBytes_ = UINT32_MAX;
     pod.minHeapLength_ = AsmJSAllocationGranularity;
     pod.strict_ = strict;
     pod.usesSignalHandlers_ = canUseSignalHandlers;
@@ -134,18 +135,18 @@ AsmJSModule::trace(JSTracer *trc)
     for (unsigned i = 0; i < globals_.length(); i++)
         globals_[i].trace(trc);
     for (unsigned i = 0; i < exits_.length(); i++) {
         if (exitIndexToGlobalDatum(i).fun)
             MarkObject(trc, &exitIndexToGlobalDatum(i).fun, "asm.js imported function");
     }
     for (unsigned i = 0; i < exports_.length(); i++)
         exports_[i].trace(trc);
-    for (unsigned i = 0; i < functionNames_.length(); i++)
-        MarkStringUnbarriered(trc, &functionNames_[i].name(), "asm.js module function name");
+    for (unsigned i = 0; i < names_.length(); i++)
+        MarkStringUnbarriered(trc, &names_[i].name(), "asm.js module function name");
 #if defined(MOZ_VTUNE) || defined(JS_ION_PERF)
     for (unsigned i = 0; i < profiledFunctions_.length(); i++)
         profiledFunctions_[i].trace(trc);
 #endif
 #if defined(JS_ION_PERF)
     for (unsigned i = 0; i < perfProfiledBlocksFunctions_.length(); i++)
         perfProfiledBlocksFunctions_[i].trace(trc);
 #endif
@@ -165,17 +166,18 @@ AsmJSModule::addSizeOfMisc(mozilla::Mall
 {
     *asmJSModuleCode += pod.totalBytes_;
     *asmJSModuleData += mallocSizeOf(this) +
                         globals_.sizeOfExcludingThis(mallocSizeOf) +
                         exits_.sizeOfExcludingThis(mallocSizeOf) +
                         exports_.sizeOfExcludingThis(mallocSizeOf) +
                         callSites_.sizeOfExcludingThis(mallocSizeOf) +
                         codeRanges_.sizeOfExcludingThis(mallocSizeOf) +
-                        functionNames_.sizeOfExcludingThis(mallocSizeOf) +
+                        funcPtrTables_.sizeOfExcludingThis(mallocSizeOf) +
+                        names_.sizeOfExcludingThis(mallocSizeOf) +
                         heapAccesses_.sizeOfExcludingThis(mallocSizeOf) +
                         functionCounts_.sizeOfExcludingThis(mallocSizeOf) +
 #if defined(MOZ_VTUNE) || defined(JS_ION_PERF)
                         profiledFunctions_.sizeOfExcludingThis(mallocSizeOf) +
 #endif
 #if defined(JS_ION_PERF)
                         perfProfiledBlocksFunctions_.sizeOfExcludingThis(mallocSizeOf) +
 #endif
@@ -254,17 +256,17 @@ struct HeapAccessOffset
         return accesses[index].offset();
     }
 };
 
 const AsmJSHeapAccess *
 AsmJSModule::lookupHeapAccess(void *pc) const
 {
     JS_ASSERT(isFinished());
-    JS_ASSERT(containsPC(pc));
+    JS_ASSERT(containsFunctionPC(pc));
 
     uint32_t target = ((uint8_t*)pc) - code_;
     size_t lowerBound = 0;
     size_t upperBound = heapAccesses_.length();
 
     size_t match;
     if (!BinarySearch(HeapAccessOffset(heapAccesses_), lowerBound, upperBound, target, &match))
         return nullptr;
@@ -331,21 +333,18 @@ AsmJSModule::finish(ExclusiveContext *cx
         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++) {
-        CodeRange &c = codeRanges_[i];
-        c.begin_ = masm.actualOffset(c.begin_);
-        c.end_ = masm.actualOffset(c.end_);
-        JS_ASSERT(c.begin_ <= c.end_);
-        JS_ASSERT_IF(i > 0, codeRanges_[i - 1].end_ <= c.begin_);
+        codeRanges_[i].updateOffsets(masm);
+        JS_ASSERT_IF(i > 0, codeRanges_[i - 1].end() <= codeRanges_[i].begin());
     }
 #endif
     JS_ASSERT(pod.functionBytes_ % AsmJSPageSize == 0);
 
     // Absolute link metadata: absolute addresses that refer to some fixed
     // address in the address space.
     for (size_t i = 0; i < masm.numAsmJSAbsoluteLinks(); i++) {
         AsmJSAbsoluteLink src = masm.asmJSAbsoluteLink(i);
@@ -1161,16 +1160,85 @@ AsmJSModule::ExportedFunction::clone(Exc
 
     if (!ClonePodVector(cx, argCoercions_, &out->argCoercions_))
         return false;
 
     out->pod = pod;
     return true;
 }
 
+AsmJSModule::CodeRange::CodeRange(uint32_t nameIndex, const AsmJSFunctionLabels &l)
+  : nameIndex_(nameIndex),
+    begin_(l.begin.offset()),
+    profilingReturn_(l.profilingReturn.offset()),
+    end_(l.end.offset()),
+    kind_(Function)
+{
+    JS_ASSERT(l.begin.offset() < l.entry.offset());
+    JS_ASSERT(l.entry.offset() < l.profilingJump.offset());
+    JS_ASSERT(l.profilingJump.offset() < l.profilingEpilogue.offset());
+    JS_ASSERT(l.profilingEpilogue.offset() < l.profilingReturn.offset());
+    JS_ASSERT(l.profilingReturn.offset() < l.end.offset());
+
+    setDeltas(l.entry.offset(), l.profilingJump.offset(), l.profilingEpilogue.offset());
+}
+
+void
+AsmJSModule::CodeRange::setDeltas(uint32_t entry, uint32_t profilingJump, uint32_t profilingEpilogue)
+{
+    JS_ASSERT(entry - begin_ <= UINT8_MAX);
+    beginToEntry_ = entry - begin_;
+
+    JS_ASSERT(profilingReturn_ - profilingJump <= UINT8_MAX);
+    profilingJumpToProfilingReturn_ = profilingReturn_ - profilingJump;
+
+    JS_ASSERT(profilingReturn_ - profilingEpilogue <= UINT8_MAX);
+    profilingEpilogueToProfilingReturn_ = profilingReturn_ - profilingEpilogue;
+}
+
+AsmJSModule::CodeRange::CodeRange(Kind kind, uint32_t begin, uint32_t end)
+  : begin_(begin),
+    end_(end),
+    kind_(kind)
+{
+    JS_ASSERT(begin_ <= end_);
+    JS_ASSERT(kind_ == Entry || kind_ == Inline);
+}
+
+AsmJSModule::CodeRange::CodeRange(Kind kind, uint32_t begin, uint32_t profilingReturn, uint32_t end)
+  : begin_(begin),
+    profilingReturn_(profilingReturn),
+    end_(end),
+    kind_(kind)
+{
+    JS_ASSERT(begin_ < profilingReturn_);
+    JS_ASSERT(profilingReturn_ < end_);
+}
+
+void
+AsmJSModule::CodeRange::updateOffsets(jit::MacroAssembler &masm)
+{
+    uint32_t entryBefore, profilingJumpBefore, profilingEpilogueBefore;
+    if (isFunction()) {
+        entryBefore = entry();
+        profilingJumpBefore = profilingJump();
+        profilingEpilogueBefore = profilingEpilogue();
+    }
+
+    begin_ = masm.actualOffset(begin_);
+    profilingReturn_ = masm.actualOffset(profilingReturn_);
+    end_ = masm.actualOffset(end_);
+
+    if (isFunction()) {
+        setDeltas(masm.actualOffset(entryBefore),
+                  masm.actualOffset(profilingJumpBefore),
+                  masm.actualOffset(profilingEpilogueBefore));
+    }
+}
+
 size_t
 AsmJSModule::StaticLinkData::serializedSize() const
 {
     return sizeof(uint32_t) +
            SerializedPodVectorSize(relativeLinks) +
            SerializedPodVectorSize(absoluteLinks);
 }
 
@@ -1240,17 +1308,18 @@ AsmJSModule::serializedSize() const
            SerializedNameSize(globalArgumentName_) +
            SerializedNameSize(importArgumentName_) +
            SerializedNameSize(bufferArgumentName_) +
            SerializedVectorSize(globals_) +
            SerializedVectorSize(exits_) +
            SerializedVectorSize(exports_) +
            SerializedPodVectorSize(callSites_) +
            SerializedPodVectorSize(codeRanges_) +
-           SerializedVectorSize(functionNames_) +
+           SerializedPodVectorSize(funcPtrTables_) +
+           SerializedVectorSize(names_) +
            SerializedPodVectorSize(heapAccesses_) +
 #if defined(MOZ_VTUNE) || defined(JS_ION_PERF)
            SerializedVectorSize(profiledFunctions_) +
 #endif
            staticLinkData_.serializedSize();
 }
 
 uint8_t *
@@ -1261,17 +1330,18 @@ AsmJSModule::serialize(uint8_t *cursor) 
     cursor = SerializeName(cursor, globalArgumentName_);
     cursor = SerializeName(cursor, importArgumentName_);
     cursor = SerializeName(cursor, bufferArgumentName_);
     cursor = SerializeVector(cursor, globals_);
     cursor = SerializeVector(cursor, exits_);
     cursor = SerializeVector(cursor, exports_);
     cursor = SerializePodVector(cursor, callSites_);
     cursor = SerializePodVector(cursor, codeRanges_);
-    cursor = SerializeVector(cursor, functionNames_);
+    cursor = SerializePodVector(cursor, funcPtrTables_);
+    cursor = SerializeVector(cursor, names_);
     cursor = SerializePodVector(cursor, heapAccesses_);
 #if defined(MOZ_VTUNE) || defined(JS_ION_PERF)
     cursor = SerializeVector(cursor, profiledFunctions_);
 #endif
     cursor = staticLinkData_.serialize(cursor);
     return cursor;
 }
 
@@ -1288,17 +1358,18 @@ AsmJSModule::deserialize(ExclusiveContex
     (cursor = DeserializeName(cx, cursor, &globalArgumentName_)) &&
     (cursor = DeserializeName(cx, cursor, &importArgumentName_)) &&
     (cursor = DeserializeName(cx, cursor, &bufferArgumentName_)) &&
     (cursor = DeserializeVector(cx, cursor, &globals_)) &&
     (cursor = DeserializeVector(cx, cursor, &exits_)) &&
     (cursor = DeserializeVector(cx, cursor, &exports_)) &&
     (cursor = DeserializePodVector(cx, cursor, &callSites_)) &&
     (cursor = DeserializePodVector(cx, cursor, &codeRanges_)) &&
-    (cursor = DeserializeVector(cx, cursor, &functionNames_)) &&
+    (cursor = DeserializePodVector(cx, cursor, &funcPtrTables_)) &&
+    (cursor = DeserializeVector(cx, cursor, &names_)) &&
     (cursor = DeserializePodVector(cx, cursor, &heapAccesses_)) &&
 #if defined(MOZ_VTUNE) || defined(JS_ION_PERF)
     (cursor = DeserializeVector(cx, cursor, &profiledFunctions_)) &&
 #endif
     (cursor = staticLinkData_.deserialize(cx, cursor));
 
     loadedFromCache_ = true;
 
@@ -1360,34 +1431,149 @@ AsmJSModule::clone(JSContext *cx, Scoped
     out.importArgumentName_ = importArgumentName_;
     out.bufferArgumentName_ = bufferArgumentName_;
 
     if (!CloneVector(cx, globals_, &out.globals_) ||
         !CloneVector(cx, exits_, &out.exits_) ||
         !CloneVector(cx, exports_, &out.exports_) ||
         !ClonePodVector(cx, callSites_, &out.callSites_) ||
         !ClonePodVector(cx, codeRanges_, &out.codeRanges_) ||
-        !CloneVector(cx, functionNames_, &out.functionNames_) ||
+        !ClonePodVector(cx, funcPtrTables_, &out.funcPtrTables_) ||
+        !CloneVector(cx, names_, &out.names_) ||
         !ClonePodVector(cx, heapAccesses_, &out.heapAccesses_) ||
         !staticLinkData_.clone(cx, &out.staticLinkData_))
     {
         return false;
     }
 
     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(maybeHeap_, cx);
     return true;
 }
 
 void
+AsmJSModule::setProfilingEnabled(bool enabled)
+{
+    JS_ASSERT(isDynamicallyLinked());
+
+    if (profilingEnabled_ == enabled)
+        return;
+
+    // Conservatively flush the icache for the entire module.
+    AutoFlushICache afc("AsmJSModule::setProfilingEnabled");
+    setAutoFlushICacheRange();
+
+    // To enable profiling, we need to patch 3 kinds of things:
+
+    // Patch all internal (asm.js->asm.js) callsites to call the profiling
+    // prologues:
+    for (size_t i = 0; i < callSites_.length(); i++) {
+        CallSite &cs = callSites_[i];
+        if (cs.kind() != CallSite::Relative)
+            continue;
+
+        uint8_t *callerRetAddr = code_ + cs.returnAddressOffset();
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        void *callee = JSC::X86Assembler::getRel32Target(callerRetAddr);
+#elif defined(JS_CODEGEN_ARM)
+        uint8_t *caller = callerRetAddr - 4;
+        Instruction *callerInsn = reinterpret_cast<Instruction*>(caller);
+        BOffImm calleeOffset;
+        callerInsn->as<InstBLImm>()->extractImm(&calleeOffset);
+        void *callee = calleeOffset.getDest(callerInsn);
+#else
+# error "Missing architecture"
+#endif
+
+        const CodeRange *codeRange = lookupCodeRange(callee);
+        if (codeRange->kind() != CodeRange::Function)
+            continue;
+
+        uint8_t *profilingEntry = code_ + codeRange->begin();
+        uint8_t *entry = code_ + codeRange->entry();
+        JS_ASSERT_IF(profilingEnabled_, callee == profilingEntry);
+        JS_ASSERT_IF(!profilingEnabled_, callee == entry);
+        uint8_t *newCallee = enabled ? profilingEntry : entry;
+
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        JSC::X86Assembler::setRel32(callerRetAddr, newCallee);
+#elif defined(JS_CODEGEN_ARM)
+        new (caller) InstBLImm(BOffImm(newCallee - caller), Assembler::Always);
+#else
+# error "Missing architecture"
+#endif
+    }
+
+    // Update all the addresses in the function-pointer tables to point to the
+    // profiling prologues:
+    for (size_t i = 0; i < funcPtrTables_.length(); i++) {
+        FuncPtrTable &funcPtrTable = funcPtrTables_[i];
+        uint8_t **array = globalDataOffsetToFuncPtrTable(funcPtrTable.globalDataOffset());
+        for (size_t j = 0; j < funcPtrTable.numElems(); j++) {
+            void *callee = array[j];
+            const CodeRange *codeRange = lookupCodeRange(callee);
+            uint8_t *profilingEntry = code_ + codeRange->begin();
+            uint8_t *entry = code_ + codeRange->entry();
+            JS_ASSERT_IF(profilingEnabled_, callee == profilingEntry);
+            JS_ASSERT_IF(!profilingEnabled_, callee == entry);
+            if (enabled)
+                array[j] = profilingEntry;
+            else
+                array[j] = entry;
+        }
+    }
+
+    // Replace all the nops in all the epilogues of asm.js functions with jumps
+    // to the profiling epilogues.
+    for (size_t i = 0; i < codeRanges_.length(); i++) {
+        CodeRange &cr = codeRanges_[i];
+        if (!cr.isFunction())
+            continue;
+        uint8_t *jump = code_ + cr.profilingJump();
+        uint8_t *profilingEpilogue = code_ + cr.profilingEpilogue();
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        // An unconditional jump with a 1 byte offset immediate has the opcode
+        // 0x90. The offset is relative to the address of the instruction after
+        // the jump. 0x66 0x90 is the canonical two-byte nop.
+        ptrdiff_t jumpImmediate = profilingEpilogue - jump - 2;
+        JS_ASSERT(jumpImmediate > 0 && jumpImmediate <= 127);
+        if (enabled) {
+            JS_ASSERT(jump[0] == 0x66);
+            JS_ASSERT(jump[1] == 0x90);
+            jump[0] = 0xeb;
+            jump[1] = jumpImmediate;
+        } else {
+            JS_ASSERT(jump[0] == 0xeb);
+            JS_ASSERT(jump[1] == jumpImmediate);
+            jump[0] = 0x66;
+            jump[1] = 0x90;
+        }
+#elif defined(JS_CODEGEN_ARM)
+        if (enabled) {
+            JS_ASSERT(reinterpret_cast<Instruction*>(jump)->is<InstNOP>());
+            new (jump) InstBImm(BOffImm(profilingEpilogue - jump), Assembler::Always);
+        } else {
+            JS_ASSERT(reinterpret_cast<Instruction*>(jump)->is<InstBImm>());
+            new (jump) InstNOP();
+        }
+#else
+# error "Missing architecture"
+#endif
+    }
+
+    profilingEnabled_ = enabled;
+}
+
+void
 AsmJSModule::protectCode(JSRuntime *rt) const
 {
     JS_ASSERT(isDynamicallyLinked());
     JS_ASSERT(rt->currentThreadOwnsInterruptLock());
 
     codeIsProtected_ = true;
 
     if (!pod.functionBytes_)
--- a/js/src/jit/AsmJSModule.h
+++ b/js/src/jit/AsmJSModule.h
@@ -4,23 +4,25 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef jit_AsmJSModule_h
 #define jit_AsmJSModule_h
 
 #ifdef JS_ION
 
+#include "mozilla/Maybe.h"
 #include "mozilla/Move.h"
 #include "mozilla/PodOperations.h"
 
 #include "jsscript.h"
 
 #include "gc/Marking.h"
 #include "jit/AsmJS.h"
+#include "jit/AsmJSFrameIterator.h"
 #include "jit/IonMacroAssembler.h"
 #ifdef JS_ION_PERF
 # include "jit/PerfSpewer.h"
 #endif
 #include "jit/RegisterSets.h"
 #include "vm/TypedArrayObject.h"
 
 namespace js {
@@ -41,16 +43,33 @@ enum AsmJSMathBuiltinFunction
     AsmJSMathBuiltin_sin, AsmJSMathBuiltin_cos, AsmJSMathBuiltin_tan,
     AsmJSMathBuiltin_asin, AsmJSMathBuiltin_acos, AsmJSMathBuiltin_atan,
     AsmJSMathBuiltin_ceil, AsmJSMathBuiltin_floor, AsmJSMathBuiltin_exp,
     AsmJSMathBuiltin_log, AsmJSMathBuiltin_pow, AsmJSMathBuiltin_sqrt,
     AsmJSMathBuiltin_abs, AsmJSMathBuiltin_atan2, AsmJSMathBuiltin_imul,
     AsmJSMathBuiltin_fround, AsmJSMathBuiltin_min, AsmJSMathBuiltin_max
 };
 
+// These labels describe positions in the prologue/epilogue of functions while
+// compiling an AsmJSModule.
+struct AsmJSFunctionLabels
+{
+    AsmJSFunctionLabels(jit::Label &entry, jit::Label &overflowExit)
+      : entry(entry), overflowExit(overflowExit) {}
+
+    jit::Label begin;
+    jit::Label &entry;
+    jit::Label profilingJump;
+    jit::Label profilingEpilogue;
+    jit::Label profilingReturn;
+    jit::Label end;
+    mozilla::Maybe<jit::Label> overflowThunk;
+    jit::Label &overflowExit;
+};
+
 // An asm.js module represents the collection of functions nested inside a
 // single outer "use asm" function. For example, this asm.js module:
 //   function() { "use asm"; function f() {} function g() {} return f }
 // contains the functions 'f' and 'g'.
 //
 // An asm.js module contains both the jit-code produced by compiling all the
 // functions in the module as well all the data required to perform the
 // link-time validation step in the asm.js spec.
@@ -310,41 +329,83 @@ class AsmJSModule
         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;
     };
 
     class CodeRange
     {
-      public:
-        enum Kind { Entry, Function };
-
-      private:
-        Kind kind_;
+        uint32_t nameIndex_;
         uint32_t begin_;
+        uint32_t profilingReturn_;
         uint32_t end_;
-        uint32_t functionNameIndex_;
+        uint8_t beginToEntry_;
+        uint8_t profilingJumpToProfilingReturn_;
+        uint8_t profilingEpilogueToProfilingReturn_;
+        uint8_t kind_;
 
-        friend class AsmJSModule;
-        CodeRange(Kind k, uint32_t begin, uint32_t end, uint32_t functionNameIndex)
-          : kind_(k), begin_(begin), end_(end), functionNameIndex_(functionNameIndex)
-        {}
+        void setDeltas(uint32_t entry, uint32_t profilingJump, uint32_t profilingEpilogue);
 
       public:
+        enum Kind { Function, Entry, FFI, Interrupt, Inline };
+
         CodeRange() {}
-        Kind kind() const { return kind_; }
-        uint32_t begin() const { return begin_; }
-        uint32_t end() const { return end_; }
+        CodeRange(uint32_t nameIndex, const AsmJSFunctionLabels &l);
+        CodeRange(Kind kind, uint32_t begin, uint32_t end);
+        CodeRange(Kind kind, uint32_t begin, uint32_t profilingReturn, uint32_t end);
+        void updateOffsets(jit::MacroAssembler &masm);
+
+        Kind kind() const { return Kind(kind_); }
+        bool isFunction() const { return kind() == Function; }
+        bool isEntry() const { return kind() == Entry; }
+        bool isFFI() const { return kind() == FFI; }
+        bool isInterrupt() const { return kind() == Interrupt; }
+
+        uint32_t begin() const {
+            return begin_;
+        }
+        uint32_t entry() const {
+            JS_ASSERT(isFunction());
+            return begin_ + beginToEntry_;
+        }
+        uint32_t end() const {
+            return end_;
+        }
+        uint32_t profilingJump() const {
+            JS_ASSERT(isFunction());
+            return profilingReturn_ - profilingJumpToProfilingReturn_;
+        }
+        uint32_t profilingEpilogue() const {
+            JS_ASSERT(isFunction());
+            return profilingReturn_ - profilingEpilogueToProfilingReturn_;
+        }
+        uint32_t profilingReturn() const {
+            JS_ASSERT(isFunction() || isFFI() || isInterrupt());
+            return profilingReturn_;
+        }
         PropertyName *functionName(const AsmJSModule &module) const {
-            JS_ASSERT(kind_ == Function);
-            return module.functionNames_[functionNameIndex_].name();
+            JS_ASSERT(kind() == Function);
+            return module.names_[nameIndex_].name();
         }
     };
 
+    class FuncPtrTable
+    {
+        uint32_t globalDataOffset_;
+        uint32_t numElems_;
+      public:
+        FuncPtrTable() {}
+        FuncPtrTable(uint32_t globalDataOffset, uint32_t numElems)
+          : globalDataOffset_(globalDataOffset), numElems_(numElems)
+        {}
+        uint32_t globalDataOffset() const { return globalDataOffset_; }
+        uint32_t numElems() const { return numElems_; }
+    };
+
     class Name
     {
         PropertyName *name_;
       public:
         Name() : name_(nullptr) {}
         MOZ_IMPLICIT Name(PropertyName *name) : name_(name) {}
         PropertyName *name() const { return name_; }
         PropertyName *&name() { return name_; }
@@ -503,17 +564,18 @@ class AsmJSModule
     const uint32_t                        srcStart_;
     const uint32_t                        srcBodyStart_;
 
     Vector<Global,                 0, SystemAllocPolicy> globals_;
     Vector<Exit,                   0, SystemAllocPolicy> exits_;
     Vector<ExportedFunction,       0, SystemAllocPolicy> exports_;
     Vector<jit::CallSite,          0, SystemAllocPolicy> callSites_;
     Vector<CodeRange,              0, SystemAllocPolicy> codeRanges_;
-    Vector<Name,                   0, SystemAllocPolicy> functionNames_;
+    Vector<FuncPtrTable,           0, SystemAllocPolicy> funcPtrTables_;
+    Vector<Name,                   0, SystemAllocPolicy> names_;
     Vector<jit::AsmJSHeapAccess,   0, SystemAllocPolicy> heapAccesses_;
     Vector<jit::IonScriptCounts*,  0, SystemAllocPolicy> functionCounts_;
 #if defined(MOZ_VTUNE) || defined(JS_ION_PERF)
     Vector<ProfiledFunction,       0, SystemAllocPolicy> profiledFunctions_;
 #endif
 #if defined(JS_ION_PERF)
     Vector<ProfiledBlocksFunction, 0, SystemAllocPolicy> perfProfiledBlocksFunctions_;
 #endif
@@ -523,16 +585,17 @@ class AsmJSModule
     PropertyName *                        importArgumentName_;
     PropertyName *                        bufferArgumentName_;
     uint8_t *                             code_;
     uint8_t *                             interruptExit_;
     StaticLinkData                        staticLinkData_;
     HeapPtrArrayBufferObject              maybeHeap_;
     bool                                  dynamicallyLinked_;
     bool                                  loadedFromCache_;
+    bool                                  profilingEnabled_;
 
     // This field is accessed concurrently when requesting an interrupt.
     // Access must be synchronized via the runtime's interrupt lock.
     mutable bool                          codeIsProtected_;
 
   public:
     explicit AsmJSModule(ScriptSource *scriptSource, uint32_t srcStart, uint32_t srcBodyStart,
                          bool strict, bool canUseSignalHandlers);
@@ -701,28 +764,35 @@ class AsmJSModule
     /*************************************************************************/
     // These functions are called while parsing/compiling function bodies:
 
     void requireHeapLengthToBeAtLeast(uint32_t len) {
         JS_ASSERT(isFinishedWithModulePrologue() && !isFinishedWithFunctionBodies());
         if (len > pod.minHeapLength_)
             pod.minHeapLength_ = len;
     }
-    bool addFunctionCodeRange(PropertyName *name, uint32_t begin, uint32_t end) {
-        JS_ASSERT(isFinishedWithModulePrologue() && !isFinishedWithFunctionBodies());
+    bool addFunctionCodeRange(PropertyName *name, const AsmJSFunctionLabels &labels) {
+        JS_ASSERT(!isFinished());
         JS_ASSERT(name->isTenured());
-        if (functionNames_.length() >= UINT32_MAX)
+        if (names_.length() >= UINT32_MAX)
             return false;
-        CodeRange codeRange(CodeRange::Function, begin, end, functionNames_.length());
-        return functionNames_.append(name) && codeRanges_.append(codeRange);
+        uint32_t nameIndex = names_.length();
+        return names_.append(name) && codeRanges_.append(CodeRange(nameIndex, labels));
+    }
+    bool addEntryCodeRange(uint32_t begin, uint32_t end) {
+        return codeRanges_.append(CodeRange(CodeRange::Entry, begin, end));
     }
-    bool addEntryCodeRange(unsigned exportIndex, uint32_t end) {
-        uint32_t begin = exports_[exportIndex].pod.codeOffset_;
-        CodeRange codeRange(CodeRange::Entry, begin, end, UINT32_MAX);
-        return codeRanges_.append(codeRange);
+    bool addFFICodeRange(uint32_t begin, uint32_t pret, uint32_t end) {
+        return codeRanges_.append(CodeRange(CodeRange::FFI, begin, pret, end));
+    }
+    bool addInterruptCodeRange(uint32_t begin, uint32_t pret, uint32_t end) {
+        return codeRanges_.append(CodeRange(CodeRange::Interrupt, begin, pret, end));
+    }
+    bool addInlineCodeRange(uint32_t begin, uint32_t end) {
+        return codeRanges_.append(CodeRange(CodeRange::Inline, begin, end));
     }
     bool addExit(unsigned ffiIndex, unsigned *exitIndex) {
         JS_ASSERT(isFinishedWithModulePrologue() && !isFinishedWithFunctionBodies());
         if (SIZE_MAX - pod.funcPtrTableAndExitBytes_ < sizeof(ExitDatum))
             return false;
         uint32_t globalDataOffset = globalDataBytes();
         JS_STATIC_ASSERT(sizeof(ExitDatum) % sizeof(void*) == 0);
         pod.funcPtrTableAndExitBytes_ += sizeof(ExitDatum);
@@ -742,16 +812,18 @@ class AsmJSModule
         return exits_[i];
     }
     bool addFuncPtrTable(unsigned numElems, uint32_t *globalDataOffset) {
         JS_ASSERT(isFinishedWithModulePrologue() && !isFinished());
         JS_ASSERT(IsPowerOfTwo(numElems));
         if (SIZE_MAX - pod.funcPtrTableAndExitBytes_ < numElems * sizeof(void*))
             return false;
         *globalDataOffset = globalDataBytes();
+        if (!funcPtrTables_.append(FuncPtrTable(*globalDataOffset, numElems)))
+            return false;
         pod.funcPtrTableAndExitBytes_ += numElems * sizeof(void*);
         return true;
     }
     bool addFunctionCounts(jit::IonScriptCounts *counts) {
         JS_ASSERT(isFinishedWithModulePrologue() && !isFinishedWithFunctionBodies());
         return functionCounts_.append(counts);
     }
 #if defined(MOZ_VTUNE) || defined(JS_ION_PERF)
@@ -869,20 +941,24 @@ class AsmJSModule
     size_t functionBytes() const {
         JS_ASSERT(isFinished());
         return pod.functionBytes_;
     }
     size_t codeBytes() const {
         JS_ASSERT(isFinished());
         return pod.codeBytes_;
     }
-    bool containsPC(void *pc) const {
+    bool containsFunctionPC(void *pc) const {
         JS_ASSERT(isFinished());
         return pc >= code_ && pc < (code_ + functionBytes());
     }
+    bool containsCodePC(void *pc) const {
+        JS_ASSERT(isFinished());
+        return pc >= code_ && pc < (code_ + codeBytes());
+    }
     uint8_t *interpExitTrampoline(const Exit &exit) const {
         JS_ASSERT(isFinished());
         JS_ASSERT(exit.interpCodeOffset_);
         return code_ + exit.interpCodeOffset_;
     }
     uint8_t *ionExitTrampoline(const Exit &exit) const {
         JS_ASSERT(isFinished());
         JS_ASSERT(exit.ionCodeOffset_);
@@ -926,16 +1002,19 @@ class AsmJSModule
     }
     static unsigned activationGlobalDataOffset() {
         JS_STATIC_ASSERT(jit::AsmJSActivationGlobalDataOffset == 0);
         return 0;
     }
     AsmJSActivation *&activation() const {
         return *(AsmJSActivation**)(globalData() + activationGlobalDataOffset());
     }
+    bool active() const {
+        return activation() != nullptr;
+    }
     static unsigned heapGlobalDataOffset() {
         return sizeof(void*);
     }
     uint8_t *&heapDatum() const {
         JS_ASSERT(isFinished());
         return *(uint8_t**)(globalData() + heapGlobalDataOffset());
     }
     unsigned globalVariableOffset() const {
@@ -1029,16 +1108,21 @@ class AsmJSModule
     ArrayBufferObject *maybeHeapBufferObject() const {
         JS_ASSERT(isDynamicallyLinked());
         return maybeHeap_;
     }
     size_t heapLength() const {
         JS_ASSERT(isDynamicallyLinked());
         return maybeHeap_ ? maybeHeap_->byteLength() : 0;
     }
+    bool profilingEnabled() const {
+        JS_ASSERT(isDynamicallyLinked());
+        return profilingEnabled_;
+    }
+    void setProfilingEnabled(bool enabled);
 
     // Additionally, these functions may only be called while holding the
     // runtime's interrupt lock.
     void protectCode(JSRuntime *rt) const;
     void unprotectCode(JSRuntime *rt) const;
     bool codeIsProtected(JSRuntime *rt) const;
 };
 
--- a/js/src/jit/AsmJSSignalHandlers.cpp
+++ b/js/src/jit/AsmJSSignalHandlers.cpp
@@ -338,18 +338,18 @@ HandleSimulatorInterrupt(JSRuntime *rt, 
     // If the ARM simulator is enabled, the pc is in the simulator C++ code and
     // not in the generated code, so we check the simulator's pc manually. Also
     // note that we can't simply use simulator->set_pc() here because the
     // 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))
+    if (module.containsFunctionPC((void *)rt->mainThread.simulator()->get_pc()) &&
+        module.containsFunctionPC(faultingAddress))
     {
         activation->setResumePC(nullptr);
         int32_t nextpc = int32_t(module.interruptExit());
         rt->mainThread.simulator()->set_resume_pc(nextpc);
         return true;
     }
 #endif
     return false;
@@ -441,25 +441,25 @@ HandleException(PEXCEPTION_POINTERS exce
     if (rt->jitRuntime() && rt->jitRuntime()->handleAccessViolation(rt, faultingAddress))
         return true;
 
     AsmJSActivation *activation = PerThreadData::innermostAsmJSActivation();
     if (!activation)
         return false;
 
     const AsmJSModule &module = activation->module();
-    if (!module.containsPC(pc))
+    if (!module.containsFunctionPC(pc))
         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)) {
+    if (module.containsFunctionPC(faultingAddress)) {
         activation->setResumePC(pc);
         *ppc = module.interruptExit();
 
         JSRuntime::AutoLockForInterrupt lock(rt);
         module.unprotectCode(rt);
         return true;
     }
 
@@ -644,25 +644,25 @@ HandleMachException(JSRuntime *rt, const
 
     const AsmJSModule &module = activation->module();
     if (HandleSimulatorInterrupt(rt, activation, faultingAddress)) {
         JSRuntime::AutoLockForInterrupt lock(rt);
         module.unprotectCode(rt);
         return true;
     }
 
-    if (!module.containsPC(pc))
+    if (!module.containsFunctionPC(pc))
         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)) {
+    if (module.containsFunctionPC(faultingAddress)) {
         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);
@@ -894,25 +894,25 @@ HandleSignal(int signum, siginfo_t *info
 
     const AsmJSModule &module = activation->module();
     if (HandleSimulatorInterrupt(rt, activation, faultingAddress)) {
         JSRuntime::AutoLockForInterrupt lock(rt);
         module.unprotectCode(rt);
         return true;
     }
 
-    if (!module.containsPC(pc))
+    if (!module.containsFunctionPC(pc))
         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)) {
+    if (module.containsFunctionPC(faultingAddress)) {
         activation->setResumePC(pc);
         *ppc = module.interruptExit();
 
         JSRuntime::AutoLockForInterrupt lock(rt);
         module.unprotectCode(rt);
         return true;
     }
 
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -16,16 +16,17 @@
 #include "jsnum.h"
 #include "jsprf.h"
 
 #include "builtin/Eval.h"
 #include "builtin/TypedObject.h"
 #ifdef JSGC_GENERATIONAL
 # include "gc/Nursery.h"
 #endif
+#include "jit/AsmJSModule.h"
 #include "jit/IonCaches.h"
 #include "jit/IonLinker.h"
 #include "jit/IonOptimizationLevels.h"
 #include "jit/IonSpewer.h"
 #include "jit/Lowering.h"
 #include "jit/MIRGenerator.h"
 #include "jit/MoveEmitter.h"
 #include "jit/ParallelFunctions.h"
@@ -6477,42 +6478,44 @@ CodeGenerator::visitRestPar(LRestPar *li
 
     if (!emitAllocateGCThingPar(lir, temp2, cx, temp0, temp1, templateObject))
         return false;
 
     return emitRest(lir, temp2, numActuals, temp0, temp1, numFormals, templateObject, true, ToRegister(lir->output()));
 }
 
 bool
-CodeGenerator::generateAsmJS(Label *stackOverflowLabel)
+CodeGenerator::generateAsmJS(AsmJSFunctionLabels *labels)
 {
     IonSpew(IonSpew_Codegen, "# Emitting asm.js code");
 
     // AsmJS doesn't do SPS instrumentation.
     sps_.disable();
 
-    Label overflowThunk;
-    Label *maybeOverflowThunk = omitOverRecursedCheck() ? nullptr : &overflowThunk;
-
-    GenerateAsmJSFunctionPrologue(masm, frameSize(), maybeOverflowThunk, stackOverflowLabel);
+    if (!omitOverRecursedCheck())
+        labels->overflowThunk.construct();
+
+    GenerateAsmJSFunctionPrologue(masm, frameSize(), labels);
 
     if (!generateBody())
         return false;
 
     masm.bind(&returnLabel_);
-    GenerateAsmJSFunctionEpilogue(masm, frameSize(), maybeOverflowThunk, stackOverflowLabel);
+    GenerateAsmJSFunctionEpilogue(masm, frameSize(), labels);
 
 #if defined(JS_ION_PERF)
     // Note the end of the inline code and start of the OOL code.
     gen->perfSpewer().noteEndInlineCode(masm);
 #endif
 
     if (!generateOutOfLineCode())
         return false;
 
+    masm.bind(&labels->end);
+
     // The only remaining work needed to compile this function is to patch the
     // switch-statement jump tables (the entries of the table need the absolute
     // address of the cases). These table entries are accmulated as CodeLabels
     // in the MacroAssembler's codeLabels_ list and processed all at once at in
     // the "static-link" phase of module compilation. It is critical that there
     // is nothing else to do after this point since the LifoAlloc memory
     // holding the MIR graph is about to be popped and reused. In particular,
     // every step in CodeGenerator::link must be a nop, as asserted here:
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -48,17 +48,17 @@ class CodeGenerator : public CodeGenerat
     bool generateBody();
 
   public:
     CodeGenerator(MIRGenerator *gen, LIRGraph *graph, MacroAssembler *masm = nullptr);
     ~CodeGenerator();
 
   public:
     bool generate();
-    bool generateAsmJS(Label *stackOverflowLabel);
+    bool generateAsmJS(AsmJSFunctionLabels *labels);
     bool link(JSContext *cx, types::CompilerConstraintList *constraints);
 
     bool visitLabel(LLabel *lir);
     bool visitNop(LNop *lir);
     bool visitOsiPoint(LOsiPoint *lir);
     bool visitGoto(LGoto *lir);
     bool visitTableSwitch(LTableSwitch *ins);
     bool visitTableSwitchV(LTableSwitchV *ins);
--- a/js/src/jit/arm/Assembler-arm.cpp
+++ b/js/src/jit/arm/Assembler-arm.cpp
@@ -342,17 +342,17 @@ InstBImm::AsTHIS(const Instruction &i)
 
 bool
 InstBLImm::IsTHIS(const Instruction &i)
 {
     return (i.encode () & IsBImmMask) == IsBL;
 
 }
 InstBLImm *
-InstBLImm::AsTHIS(Instruction &i)
+InstBLImm::AsTHIS(const Instruction &i)
 {
     if (IsTHIS(i))
         return (InstBLImm*)&i;
     return nullptr;
 }
 
 bool
 InstMovWT::IsTHIS(Instruction &i)
--- a/js/src/jit/arm/Assembler-arm.h
+++ b/js/src/jit/arm/Assembler-arm.h
@@ -1902,17 +1902,17 @@ class InstBImm : public InstBranchImm
 class InstBLImm : public InstBranchImm
 {
   public:
     InstBLImm(BOffImm off, Assembler::Condition c)
       : InstBranchImm(IsBL, off, c)
     { }
 
     static bool IsTHIS (const Instruction &i);
-    static InstBLImm *AsTHIS (Instruction &i);
+    static InstBLImm *AsTHIS (const Instruction &i);
 };
 
 // Both movw and movt. The layout of both the immediate and the destination
 // register is the same so the code is being shared.
 class InstMovWT : public Instruction
 {
   protected:
     enum WT {
--- a/js/src/jit/arm/MacroAssembler-arm.h
+++ b/js/src/jit/arm/MacroAssembler-arm.h
@@ -594,16 +594,19 @@ class MacroAssemblerARMCompat : public M
         ma_bx(ScratchRegister);
     }
     void branch(const Register reg) {
         ma_bx(reg);
     }
     void nop() {
         ma_nop();
     }
+    void shortJumpSizedNop() {
+        ma_nop();
+    }
     void ret() {
         ma_pop(pc);
     }
     void retn(Imm32 n) {
         // pc <- [sp]; sp += n
         ma_dtr(IsLoad, sp, n, pc, PostIndex);
     }
     void push(Imm32 imm) {
--- a/js/src/jit/arm/Simulator-arm.cpp
+++ b/js/src/jit/arm/Simulator-arm.cpp
@@ -1135,16 +1135,19 @@ Simulator::Simulator(SimulatorRuntime *s
         MOZ_ReportAssertionFailure("[unhandlable oom] Simulator stack", __FILE__, __LINE__);
         MOZ_CRASH();
     }
     pc_modified_ = false;
     icount_ = 0L;
     resume_pc_ = 0;
     break_pc_ = nullptr;
     break_instr_ = 0;
+    single_stepping_ = false;
+    single_step_callback_ = nullptr;
+    single_step_callback_arg_ = nullptr;
     skipCalleeSavedRegsCheck = false;
 
     // Set up architecture state.
     // All registers are initialized to zero to start with.
     for (int i = 0; i < num_registers; i++)
         registers_[i] = 0;
 
     n_flag_ = false;
@@ -2127,16 +2130,19 @@ Simulator::softwareInterrupt(SimInstruct
         intptr_t external = reinterpret_cast<intptr_t>(redirection->nativeFunction());
 
         bool stack_aligned = (get_register(sp) & (StackAlignment - 1)) == 0;
         if (!stack_aligned) {
             fprintf(stderr, "Runtime call with unaligned stack!\n");
             MOZ_CRASH();
         }
 
+        if (single_stepping_)
+            single_step_callback_(single_step_callback_arg_, this, nullptr);
+
         switch (redirection->type()) {
           case Args_General0: {
             Prototype_General0 target = reinterpret_cast<Prototype_General0>(external);
             int64_t result = target();
             scratchVolatileRegisters(/* scratchFloat = true */);
             setCallResult(result);
             break;
           }
@@ -2295,16 +2301,19 @@ Simulator::softwareInterrupt(SimInstruct
             scratchVolatileRegisters(/* scratchFloat = true */);
             set_register(r0, result);
             break;
           }
           default:
             MOZ_ASSUME_UNREACHABLE("call");
         }
 
+        if (single_stepping_)
+            single_step_callback_(single_step_callback_arg_, this, nullptr);
+
         set_register(lr, saved_lr);
         set_pc(get_register(lr));
         break;
       }
       case kBreakpoint: {
         ArmDebugger dbg(this);
         dbg.debug();
         break;
@@ -4052,45 +4061,70 @@ Simulator::instructionDecode(SimInstruct
         // the inlined message address.
     } else if (instr->isStop()) {
         set_pc(get_pc() + 2 * SimInstruction::kInstrSize);
     }
     if (!pc_modified_)
         set_register(pc, reinterpret_cast<int32_t>(instr) + SimInstruction::kInstrSize);
 }
 
+void
+Simulator::enable_single_stepping(SingleStepCallback cb, void *arg)
+{
+    single_stepping_ = true;
+    single_step_callback_ = cb;
+    single_step_callback_arg_ = arg;
+    single_step_callback_(single_step_callback_arg_, this, (void*)get_pc());
+}
+
+void
+Simulator::disable_single_stepping()
+{
+    single_step_callback_(single_step_callback_arg_, this, (void*)get_pc());
+    single_stepping_ = false;
+    single_step_callback_ = nullptr;
+    single_step_callback_arg_ = nullptr;
+}
 
 template<bool EnableStopSimAt>
 void
 Simulator::execute()
 {
+    if (single_stepping_)
+        single_step_callback_(single_step_callback_arg_, this, nullptr);
+
     // Get the PC to simulate. Cannot use the accessor here as we need the raw
     // PC value and not the one used as input to arithmetic instructions.
     int program_counter = get_pc();
 
     while (program_counter != end_sim_pc) {
         if (EnableStopSimAt && (icount_ == Simulator::StopSimAt)) {
             fprintf(stderr, "\nStopped simulation at icount %lld\n", icount_);
             ArmDebugger dbg(this);
             dbg.debug();
         } else {
+            if (single_stepping_)
+                single_step_callback_(single_step_callback_arg_, this, (void*)program_counter);
             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.
                 PerThreadData::innermostAsmJSActivation()->setResumePC((void *)get_pc());
                 set_pc(rpc);
                 resume_pc_ = 0;
             }
         }
         program_counter = get_pc();
     }
+
+    if (single_stepping_)
+        single_step_callback_(single_step_callback_arg_, this, nullptr);
 }
 
 void
 Simulator::callInternal(uint8_t *entry)
 {
     // Prepare to execute the code at entry.
     set_register(pc, reinterpret_cast<int32_t>(entry));
 
--- a/js/src/jit/arm/Simulator-arm.h
+++ b/js/src/jit/arm/Simulator-arm.h
@@ -32,20 +32,26 @@
 #ifdef JS_ARM_SIMULATOR
 
 #include "jit/arm/Architecture-arm.h"
 #include "jit/IonTypes.h"
 
 namespace js {
 namespace jit {
 
+class Simulator;
 class SimulatorRuntime;
 SimulatorRuntime *CreateSimulatorRuntime();
 void DestroySimulatorRuntime(SimulatorRuntime *srt);
 
+// When the SingleStepCallback is called, the simulator is about to execute
+// sim->get_pc() and the current machine state represents the completed
+// execution of the previous pc.
+typedef void (*SingleStepCallback)(void *arg, Simulator *sim, void *pc);
+
 // VFP rounding modes. See ARM DDI 0406B Page A2-29.
 enum VFPRoundingMode {
     SimRN = 0 << 22,   // Round to Nearest.
     SimRP = 1 << 22,   // Round towards Plus Infinity.
     SimRM = 2 << 22,   // Round towards Minus Infinity.
     SimRZ = 3 << 22,   // Round towards zero.
 
     // Aliases.
@@ -143,16 +149,19 @@ class Simulator
     // Special case of set_register and get_register to access the raw PC value.
     void set_pc(int32_t value);
     int32_t get_pc() const;
 
     void set_resume_pc(int32_t value) {
         resume_pc_ = value;
     }
 
+    void enable_single_stepping(SingleStepCallback cb, void *arg);
+    void disable_single_stepping();
+
     uintptr_t stackLimit() const;
     bool overRecursed(uintptr_t newsp = 0) const;
     bool overRecursedWithExtra(uint32_t extra) const;
 
     // Executes ARM instructions until the PC reaches end_sim_pc.
     template<bool EnableStopSimAt>
     void execute();
 
@@ -327,16 +336,21 @@ class Simulator
 
     // Debugger input.
     char *lastDebuggerInput_;
 
     // Registered breakpoints.
     SimInstruction *break_pc_;
     Instr break_instr_;
 
+    // Single-stepping support
+    bool single_stepping_;
+    SingleStepCallback single_step_callback_;
+    void *single_step_callback_arg_;
+
     SimulatorRuntime *srt_;
 
     // A stop is watched if its code is less than kNumOfWatchedStops.
     // Only watched stops support enabling/disabling and the counter feature.
     static const uint32_t kNumOfWatchedStops = 256;
 
     // Breakpoint is disabled if bit 31 is set.
     static const uint32_t kStopDisabledBit = 1 << 31;
--- a/js/src/jit/shared/Assembler-shared.h
+++ b/js/src/jit/shared/Assembler-shared.h
@@ -579,27 +579,40 @@ class CodeLocationLabel
 
 // While the frame-pointer chain allows the stack to be unwound without
 // metadata, Error.stack still needs to know the line/column of every call in
 // the chain. A CallSiteDesc describes the line/column of a single callsite.
 // A CallSiteDesc is created by callers of MacroAssembler.
 class CallSiteDesc
 {
     uint32_t line_;
-    uint32_t column_;
+    uint32_t column_ : 31;
+    uint32_t kind_ : 1;
   public:
+    enum Kind {
+        Relative,  // pc-relative call
+        Register   // call *register
+    };
     CallSiteDesc() {}
-    CallSiteDesc(uint32_t line, uint32_t column) : line_(line), column_(column) {}
+    explicit CallSiteDesc(Kind kind)
+      : line_(0), column_(0), kind_(kind)
+    {}
+    CallSiteDesc(uint32_t line, uint32_t column, Kind kind)
+      : line_(line), column_(column), kind_(kind)
+    {
+        JS_ASSERT(column <= INT32_MAX);
+    }
     uint32_t line() const { return line_; }
     uint32_t column() const { return column_; }
+    Kind kind() const { return Kind(kind_); }
 };
 
 // Adds to CallSiteDesc the metadata necessary to walk the stack given an
 // initial stack-pointer.
-struct CallSite : public CallSiteDesc
+class CallSite : public CallSiteDesc
 {
     uint32_t returnAddressOffset_;
     uint32_t stackDepth_;
 
   public:
     CallSite() {}
 
     CallSite(CallSiteDesc desc, uint32_t returnAddressOffset, uint32_t stackDepth)
--- a/js/src/jit/shared/Assembler-x86-shared.h
+++ b/js/src/jit/shared/Assembler-x86-shared.h
@@ -648,16 +648,17 @@ class AssemblerX86Shared : public Assemb
             // Thread the jump list through the unpatched jump targets.
             label->use(j.offset());
         }
         return j;
     }
 
   public:
     void nop() { masm.nop(); }
+    void twoByteNop() { masm.twoByteNop(); }
     void j(Condition cond, Label *label) { jSrc(cond, label); }
     void jmp(Label *label) { jmpSrc(label); }
     void j(Condition cond, RepatchLabel *label) { jSrc(cond, label); }
     void jmp(RepatchLabel *label) { jmpSrc(label); }
 
     void jmp(const Operand &op) {
         switch (op.kind()) {
           case Operand::MEM_REG_DISP:
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -73,16 +73,17 @@ EXPORTS.js += [
     '../public/GCAPI.h',
     '../public/HashTable.h',
     '../public/HeapAPI.h',
     '../public/Id.h',
     '../public/LegacyIntTypes.h',
     '../public/MemoryMetrics.h',
     '../public/OldDebugAPI.h',
     '../public/Principals.h',
+    '../public/ProfilingFrameIterator.h',
     '../public/ProfilingStack.h',
     '../public/PropertyKey.h',
     '../public/RequiredDefines.h',
     '../public/RootingAPI.h',
     '../public/SliceBudget.h',
     '../public/StructuredClone.h',
     '../public/TracingAPI.h',
     '../public/TypeDecls.h',
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -82,16 +82,17 @@
 using namespace js;
 using namespace js::cli;
 
 using mozilla::ArrayLength;
 using mozilla::MakeUnique;
 using mozilla::Maybe;
 using mozilla::NumberEqualsInt32;
 using mozilla::PodCopy;
+using mozilla::PodEqual;
 using mozilla::UniquePtr;
 
 enum JSShellExitCode {
     EXITCODE_RUNTIME_ERROR      = 3,
     EXITCODE_FILE_NOT_FOUND     = 4,
     EXITCODE_OUT_OF_MEMORY      = 5,
     EXITCODE_TIMEOUT            = 6
 };
@@ -1322,17 +1323,17 @@ Evaluate(JSContext *cx, unsigned argc, j
 
     if (saveBytecode) {
         // If we are both loading and saving, we assert that we are going to
         // replace the current bytecode by the same stream of bytes.
         if (loadBytecode && assertEqBytecode) {
             if (saveLength != loadLength) {
                 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_CACHE_EQ_SIZE_FAILED,
                                      loadLength, saveLength);
-            } else if (!mozilla::PodEqual(loadBuffer, saveBuffer.get(), loadLength)) {
+            } else if (!PodEqual(loadBuffer, saveBuffer.get(), loadLength)) {
                 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr,
                                      JSSMSG_CACHE_EQ_CONTENT_FAILED);
             }
         }
 
         if (!CacheEntry_setBytecode(cx, cacheEntry, saveBuffer, saveLength))
             return false;
 
@@ -4005,25 +4006,23 @@ EscapeForShell(AutoCStringVector &argv)
         argv.replace(i, escaped);
     }
     return true;
 }
 #endif
 
 static Vector<const char*, 4, js::SystemAllocPolicy> sPropagatedFlags;
 
-#ifdef DEBUG
-#if (defined(JS_CPU_X86) || defined(JS_CPU_X64)) && defined(JS_ION)
+#if defined(DEBUG) && defined(JS_ION) && (defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64))
 static bool
 PropagateFlagToNestedShells(const char *flag)
 {
     return sPropagatedFlags.append(flag);
 }
 #endif
-#endif
 
 static bool
 NestedShell(JSContext *cx, unsigned argc, jsval *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     AutoCStringVector argv(cx);
 
@@ -4406,16 +4405,107 @@ PrintProfilerEvents(JSContext *cx, unsig
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     if (cx->runtime()->spsProfiler.enabled())
         js::RegisterRuntimeProfilingEventMarker(cx->runtime(), &PrintProfilerEvents_Callback);
     args.rval().setUndefined();
     return true;
 }
 
+#if defined(JS_ARM_SIMULATOR)
+typedef Vector<jschar, 0, SystemAllocPolicy> StackChars;
+Vector<StackChars, 0, SystemAllocPolicy> stacks;
+
+static void
+SingleStepCallback(void *arg, jit::Simulator *sim, void *pc)
+{
+    JSRuntime *rt = reinterpret_cast<JSRuntime*>(arg);
+
+    JS::ProfilingFrameIterator::RegisterState state;
+    state.pc = pc;
+    state.sp = (void*)sim->get_register(jit::Simulator::sp);
+    state.lr = (void*)sim->get_register(jit::Simulator::lr);
+
+    StackChars stack;
+    for (JS::ProfilingFrameIterator i(rt, state); !i.done(); ++i) {
+        switch (i.kind()) {
+          case JS::ProfilingFrameIterator::Function: {
+            JS::AutoCheckCannotGC nogc;
+            JSAtom *atom = i.functionDisplayAtom();
+            if (atom->hasLatin1Chars())
+                stack.append(atom->latin1Chars(nogc), atom->length());
+            else
+                stack.append(atom->twoByteChars(nogc), atom->length());
+            break;
+          }
+          case JS::ProfilingFrameIterator::AsmJSTrampoline: {
+            stack.append('*');
+            break;
+          }
+        }
+    }
+
+    // Only append the stack if it differs from the last stack.
+    if (stacks.empty() ||
+        stacks.back().length() != stack.length() ||
+        !PodEqual(stacks.back().begin(), stack.begin(), stack.length()))
+    {
+        stacks.append(Move(stack));
+    }
+}
+#endif
+
+static bool
+EnableSingleStepProfiling(JSContext *cx, unsigned argc, Value *vp)
+{
+#if defined(JS_ARM_SIMULATOR)
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    jit::Simulator *sim = cx->runtime()->mainThread.simulator();
+    sim->enable_single_stepping(SingleStepCallback, cx->runtime());
+
+    args.rval().setUndefined();
+    return true;
+#else
+    JS_ReportError(cx, "single-step profiling not enabled on this platform");
+    return false;
+#endif
+}
+
+static bool
+DisableSingleStepProfiling(JSContext *cx, unsigned argc, Value *vp)
+{
+#if defined(JS_ARM_SIMULATOR)
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    jit::Simulator *sim = cx->runtime()->mainThread.simulator();
+    sim->disable_single_stepping();
+
+    AutoValueVector elems(cx);
+    for (size_t i = 0; i < stacks.length(); i++) {
+        JSString *stack = JS_NewUCStringCopyN(cx, stacks[i].begin(), stacks[i].length());
+        if (!stack)
+            return false;
+        if (!elems.append(StringValue(stack)))
+            return false;
+    }
+
+    JSObject *array = JS_NewArrayObject(cx, elems);
+    if (!array)
+        return false;
+
+    stacks.clear();
+    args.rval().setObject(*array);
+    return true;
+#else
+    JS_ReportError(cx, "single-step profiling not enabled on this platform");
+    return false;
+#endif
+}
+
 static bool
 IsLatin1(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     bool isLatin1 = args.get(0).isString() && args[0].toString()->hasLatin1Chars();
     args.rval().setBoolean(isLatin1);
     return true;
 }
@@ -4795,16 +4885,27 @@ static const JSFunctionSpecWithHelp shel
 "  and read by the \"evaluate\" function by using it in place of the source, and\n"
 "  by setting \"saveBytecode\" and \"loadBytecode\" options."),
 
     JS_FN_HELP("printProfilerEvents", PrintProfilerEvents, 0, 0,
 "printProfilerEvents()",
 "  Register a callback with the profiler that prints javascript profiler events\n"
 "  to stderr.  Callback is only registered if profiling is enabled."),
 
+    JS_FN_HELP("enableSingleStepProfiling", EnableSingleStepProfiling, 0, 0,
+"enableSingleStepProfiling()",
+"  This function will fail on platforms that don't support single-step profiling\n"
+"  (currently everything but ARM-simulator). When enabled, at every instruction a\n"
+"  backtrace will be recorded and stored in an array. Adjacent duplicate backtraces\n"
+"  are discarded."),
+
+    JS_FN_HELP("disableSingleStepProfiling", DisableSingleStepProfiling, 0, 0,
+"disableSingleStepProfiling()",
+"  Return the array of backtraces recorded by enableSingleStepProfiling."),
+
     JS_FN_HELP("isLatin1", IsLatin1, 1, 0,
 "isLatin1(s)",
 "  Return true iff the string's characters are stored as Latin1."),
 
     JS_FS_HELP_END
 };
 
 static const JSFunctionSpecWithHelp fuzzing_unsafe_functions[] = {
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -565,17 +565,17 @@ class PerThreadData : public PerThreadDa
 
     /*
      * Points to the most recent activation running on the thread.
      * See Activation comment in vm/Stack.h.
      */
     js::Activation *activation_;
 
     /* See AsmJSActivation comment. Protected by rt->interruptLock. */
-    js::AsmJSActivation *asmJSActivationStack_;
+    js::AsmJSActivation * volatile asmJSActivationStack_;
 
     /* Pointer to the current AutoFlushICache. */
     js::jit::AutoFlushICache *autoFlushICache_;
 
 #if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR)
     js::jit::Simulator *simulator_;
     uintptr_t simulatorStackLimit_;
 #endif
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -7,16 +7,17 @@
 #include "vm/Stack-inl.h"
 
 #include "mozilla/PodOperations.h"
 
 #include "jscntxt.h"
 
 #include "gc/Marking.h"
 #ifdef JS_ION
+#include "jit/AsmJSFrameIterator.h"
 #include "jit/AsmJSModule.h"
 #include "jit/BaselineFrame.h"
 #include "jit/JitCompartment.h"
 #endif
 #include "js/GCAPI.h"
 #include "vm/Opcodes.h"
 
 #include "jit/JitFrameIterator-inl.h"
@@ -1683,17 +1684,18 @@ jit::JitActivation::markRematerializedFr
 #endif // JS_ION
 
 AsmJSActivation::AsmJSActivation(JSContext *cx, AsmJSModule &module)
   : Activation(cx, AsmJS),
     module_(module),
     errorRejoinSP_(nullptr),
     profiler_(nullptr),
     resumePC_(nullptr),
-    fp_(nullptr)
+    fp_(nullptr),
+    exitReason_(AsmJSNoExit)
 {
     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);
@@ -1771,8 +1773,73 @@ ActivationIterator::operator++()
 void
 ActivationIterator::settle()
 {
     // Stop at the next active activation. No need to update jitTop_, since
     // we don't iterate over an active jit activation.
     while (!done() && activation_->isJit() && !activation_->asJit()->isActive())
         activation_ = activation_->prev();
 }
+
+JS::ProfilingFrameIterator::ProfilingFrameIterator(JSRuntime *rt, const RegisterState &state)
+  : activation_(rt->mainThread.asmJSActivationStack())
+{
+    if (!activation_)
+        return;
+
+    static_assert(sizeof(AsmJSProfilingFrameIterator) <= StorageSpace, "Need to increase storage");
+    new (storage_.addr()) AsmJSProfilingFrameIterator(*activation_, state);
+    settle();
+}
+
+JS::ProfilingFrameIterator::~ProfilingFrameIterator()
+{
+    if (!done())
+        iter().~AsmJSProfilingFrameIterator();
+}
+
+void
+JS::ProfilingFrameIterator::operator++()
+{
+    JS_ASSERT(!done());
+    ++iter();
+    settle();
+}
+
+void
+JS::ProfilingFrameIterator::settle()
+{
+    while (iter().done()) {
+        iter().~AsmJSProfilingFrameIterator();
+        activation_ = activation_->prevAsmJS();
+        if (!activation_)
+            return;
+        new (storage_.addr()) AsmJSProfilingFrameIterator(*activation_);
+    }
+}
+
+JS::ProfilingFrameIterator::Kind
+JS::ProfilingFrameIterator::kind() const
+{
+    return iter().kind();
+}
+
+JSAtom *
+JS::ProfilingFrameIterator::functionDisplayAtom() const
+{
+    JS_ASSERT(kind() == Function);
+    return iter().functionDisplayAtom();
+}
+
+const char *
+JS::ProfilingFrameIterator::functionFilename() const
+{
+    JS_ASSERT(kind() == Function);
+    return iter().functionFilename();
+}
+
+const char *
+JS::ProfilingFrameIterator::nonFunctionDescription() const
+{
+    JS_ASSERT(kind() != Function);
+    return iter().nonFunctionDescription();
+}
+
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -1475,36 +1475,41 @@ class AsmJSActivation : public Activatio
 {
     AsmJSModule &module_;
     AsmJSActivation *prevAsmJS_;
     AsmJSActivation *prevAsmJSForModule_;
     void *errorRejoinSP_;
     SPSProfiler *profiler_;
     void *resumePC_;
     uint8_t *fp_;
+    uint32_t exitReason_;
 
   public:
     AsmJSActivation(JSContext *cx, AsmJSModule &module);
     ~AsmJSActivation();
 
     inline JSContext *cx();
     AsmJSModule &module() const { return module_; }
     AsmJSActivation *prevAsmJS() const { return prevAsmJS_; }
 
     // Returns a pointer to the base of the innermost stack frame of asm.js code
     // in this activation.
     uint8_t *fp() const { return fp_; }
 
+    // Returns the reason why asm.js code called out of asm.js code.
+    AsmJSExitReason exitReason() const { return AsmJSExitReason(exitReason_); }
+
     // Read by JIT code:
     static unsigned offsetOfContext() { return offsetof(AsmJSActivation, cx_); }
     static unsigned offsetOfResumePC() { return offsetof(AsmJSActivation, resumePC_); }
 
     // Written by JIT code:
     static unsigned offsetOfErrorRejoinSP() { return offsetof(AsmJSActivation, errorRejoinSP_); }
     static unsigned offsetOfFP() { return offsetof(AsmJSActivation, fp_); }
+    static unsigned offsetOfExitReason() { return offsetof(AsmJSActivation, exitReason_); }
 
     // Set from SIGSEGV handler:
     void setResumePC(void *pc) { resumePC_ = pc; }
 };
 
 // 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.