Bug 1027885 - OdinMonkey: properly represent calls to builtin functions in the profiling stack (r=dougc)
authorLuke Wagner <luke@mozilla.com>
Mon, 21 Jul 2014 11:05:44 -0500
changeset 195283 49c063c58f9f40d4232c7c1f5a1d9b27cbe4d3c8
parent 195282 a15e60a850ccbad8e0fe76e7614390a1eb800da6
child 195284 dc2a2ee82f10c15f8547d6811751e95d063c0393
push id46557
push userlwagner@mozilla.com
push dateMon, 21 Jul 2014 16:08:40 +0000
treeherdermozilla-inbound@49c063c58f9f [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: properly represent calls to builtin functions in the profiling stack (r=dougc)
js/public/ProfilingFrameIterator.h
js/src/jit-test/tests/asm.js/testProfiling.js
js/src/jit/AsmJS.cpp
js/src/jit/AsmJSFrameIterator.cpp
js/src/jit/AsmJSFrameIterator.h
js/src/jit/AsmJSModule.cpp
js/src/jit/AsmJSModule.h
js/src/jit/shared/Assembler-shared.h
js/src/shell/js.cpp
js/src/vm/Stack.cpp
js/src/vm/Stack.h
--- a/js/public/ProfilingFrameIterator.h
+++ b/js/public/ProfilingFrameIterator.h
@@ -52,17 +52,18 @@ class JS_PUBLIC_API(ProfilingFrameIterat
 
     ProfilingFrameIterator(JSRuntime *rt, const RegisterState &state);
     ~ProfilingFrameIterator();
     void operator++();
     bool done() const { return !activation_; }
 
     enum Kind {
         Function,
-        AsmJSTrampoline
+        AsmJSTrampoline,
+        CppFunction
     };
     Kind kind() const;
 
     // Methods available if kind() == Function:
     JSAtom *functionDisplayAtom() const;
     const char *functionFilename() const;
 
     // Methods available if kind() != Function
--- a/js/src/jit-test/tests/asm.js/testProfiling.js
+++ b/js/src/jit-test/tests/asm.js/testProfiling.js
@@ -43,22 +43,43 @@ 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*,*,");
+function testBuiltinD2D(name) {
+    var f = asmLink(asmCompile('g', USE_ASM + "var fun=g.Math." + name + "; function f(d) { d=+d; return +fun(d) } return f"), this);
+    enableSingleStepProfiling();
+    assertEq(f(.1), eval("Math." + name + "(.1)"));
+    var stacks = disableSingleStepProfiling();
+    assertEq(String(stacks), ",*,f*,Math." + name + "f*,f*,*,");
+}
+for (name of ['sin', 'cos', 'tan', 'asin', 'acos', 'atan', 'ceil', 'floor', 'exp', 'log'])
+    testBuiltinD2D(name);
+function testBuiltinF2F(name) {
+    var f = asmLink(asmCompile('g', USE_ASM + "var tof=g.Math.fround; var fun=g.Math." + name + "; function f(d) { d=tof(d); return tof(fun(d)) } return f"), this);
+    enableSingleStepProfiling();
+    assertEq(f(.1), eval("Math.fround(Math." + name + "(Math.fround(.1)))"));
+    var stacks = disableSingleStepProfiling();
+    assertEq(String(stacks), ",*,f*,Math." + name + "f*,f*,*,");
+}
+for (name of ['ceil', 'floor'])
+    testBuiltinF2F(name);
+function testBuiltinDD2D(name) {
+    var f = asmLink(asmCompile('g', USE_ASM + "var fun=g.Math." + name + "; function f(d, e) { d=+d; e=+e; return +fun(d,e) } return f"), this);
+    enableSingleStepProfiling();
+    assertEq(f(.1, .2), eval("Math." + name + "(.1, .2)"));
+    var stacks = disableSingleStepProfiling();
+    assertEq(String(stacks), ",*,f*,Math." + name + "f*,f*,*,");
+}
+for (name of ['atan2', 'pow'])
+    testBuiltinDD2D(name);
 
 // 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 }
--- a/js/src/jit/AsmJS.cpp
+++ b/js/src/jit/AsmJS.cpp
@@ -1498,16 +1498,21 @@ class MOZ_STACK_CLASS ModuleCompiler
         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);
     }
+    bool finishGeneratingBuiltinThunk(AsmJSExit::BuiltinKind builtin, Label *begin, Label *pret) {
+        JS_ASSERT(finishedFunctionBodies_);
+        uint32_t end = masm_.currentOffset();
+        return module_->addBuiltinThunkCodeRange(builtin, begin->offset(), pret->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()) {
             slowFuns.reset(JS_smprintf("; %d functions compiled slowly: ", slowFunctions_.length()));
@@ -6057,17 +6062,17 @@ GenerateEntry(ModuleCompiler &m, unsigne
 static void
 FillArgumentArray(ModuleCompiler &m, const VarTypeVector &argTypes,
                   unsigned offsetToArgs, unsigned offsetToCallerStackArgs,
                   Register scratch)
 {
     MacroAssembler &masm = m.masm();
 
     for (ABIArgTypeIter i(argTypes); !i.done(); i++) {
-        Address dstAddr = Address(StackPointer, offsetToArgs + i.index() * sizeof(Value));
+        Address dstAddr(StackPointer, offsetToArgs + i.index() * sizeof(Value));
         switch (i->kind()) {
           case ABIArg::GPR:
             masm.storeValue(JSVAL_TYPE_INT32, i->gpr(), dstAddr);
             break;
           case ABIArg::FPU:
             masm.canonicalizeDouble(i->fpu());
             masm.storeDouble(i->fpu(), dstAddr);
             break;
@@ -6110,17 +6115,17 @@ GenerateFFIInterpExit(ModuleCompiler &m,
     //   | 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);
 
     Label begin;
-    GenerateAsmJSExitPrologue(masm, framePushed, AsmJSFFI, &begin);
+    GenerateAsmJSExitPrologue(masm, framePushed, AsmJSExit::FFI, &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);
@@ -6169,17 +6174,17 @@ GenerateFFIInterpExit(ModuleCompiler &m,
         masm.loadDouble(argv, ReturnDoubleReg);
         break;
       case RetType::Float:
         MOZ_ASSUME_UNREACHABLE("Float32 shouldn't be returned from a FFI");
         break;
     }
 
     Label profilingReturn;
-    GenerateAsmJSExitEpilogue(masm, framePushed, AsmJSFFI, &profilingReturn);
+    GenerateAsmJSExitEpilogue(masm, framePushed, AsmJSExit::FFI, &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)
@@ -6224,17 +6229,17 @@ GenerateFFIIonExit(ModuleCompiler &m, co
     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);
 
     Label begin;
-    GenerateAsmJSExitPrologue(masm, framePushed, AsmJSFFI, &begin);
+    GenerateAsmJSExitPrologue(masm, framePushed, AsmJSExit::FFI, &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
@@ -6384,17 +6389,17 @@ GenerateFFIIonExit(ModuleCompiler &m, co
 #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
 
     Label profilingReturn;
-    GenerateAsmJSExitEpilogue(masm, framePushed, AsmJSFFI, &profilingReturn);
+    GenerateAsmJSExitEpilogue(masm, framePushed, AsmJSExit::FFI, &profilingReturn);
 
     if (oolConvert.used()) {
         masm.bind(&oolConvert);
         masm.setFramePushed(framePushed);
 
         // Store return value into argv[0]
         masm.storeValue(JSReturnOperand, Address(StackPointer, offsetToCoerceArgv));
 
@@ -6447,16 +6452,95 @@ GenerateFFIExits(ModuleCompiler &m, cons
 
     // Generate the fast path
     if (!GenerateFFIIonExit(m, exit, exitIndex, throwLabel))
         return false;
 
     return true;
 }
 
+// Generate a thunk that updates fp before calling the given builtin so that
+// both the builtin and the calling function show up in profiler stacks. (This
+// thunk is dynamically patched in when profiling is enabled.) Since the thunk
+// pushes an AsmJSFrame on the stack, that means we must rebuild the stack
+// frame. Fortunately, these are low arity functions and everything is passed in
+// regs on everything but x86 anyhow.
+static bool
+GenerateBuiltinThunk(ModuleCompiler &m, AsmJSExit::BuiltinKind builtin)
+{
+    MacroAssembler &masm = m.masm();
+    JS_ASSERT(masm.framePushed() == 0);
+
+    MIRTypeVector argTypes(m.cx());
+    switch (builtin) {
+      case AsmJSExit::Builtin_ToInt32:
+        argTypes.infallibleAppend(MIRType_Int32);
+        break;
+#if defined(JS_CODEGEN_ARM)
+      case AsmJSExit::Builtin_IDivMod:
+      case AsmJSExit::Builtin_UDivMod:
+        argTypes.infallibleAppend(MIRType_Int32);
+        argTypes.infallibleAppend(MIRType_Int32);
+        break;
+#endif
+      case AsmJSExit::Builtin_SinD:
+      case AsmJSExit::Builtin_CosD:
+      case AsmJSExit::Builtin_TanD:
+      case AsmJSExit::Builtin_ASinD:
+      case AsmJSExit::Builtin_ACosD:
+      case AsmJSExit::Builtin_ATanD:
+      case AsmJSExit::Builtin_CeilD:
+      case AsmJSExit::Builtin_FloorD:
+      case AsmJSExit::Builtin_ExpD:
+      case AsmJSExit::Builtin_LogD:
+        argTypes.infallibleAppend(MIRType_Double);
+        break;
+      case AsmJSExit::Builtin_ModD:
+      case AsmJSExit::Builtin_PowD:
+      case AsmJSExit::Builtin_ATan2D:
+        argTypes.infallibleAppend(MIRType_Double);
+        argTypes.infallibleAppend(MIRType_Double);
+        break;
+      case AsmJSExit::Builtin_CeilF:
+      case AsmJSExit::Builtin_FloorF:
+        argTypes.infallibleAppend(MIRType_Float32);
+        break;
+      case AsmJSExit::Builtin_Limit:
+        MOZ_ASSUME_UNREACHABLE("Bad builtin");
+    }
+
+    uint32_t framePushed = StackDecrementForCall(masm, argTypes);
+
+    Label begin;
+    GenerateAsmJSExitPrologue(masm, framePushed, AsmJSExit::Builtin(builtin), &begin);
+
+    unsigned offsetToCallerStackArgs = sizeof(AsmJSFrame) + masm.framePushed();
+    for (ABIArgMIRTypeIter i(argTypes); !i.done(); i++) {
+        if (i->kind() != ABIArg::Stack)
+            continue;
+        Address srcAddr(StackPointer, offsetToCallerStackArgs + i->offsetFromArgBase());
+        Address dstAddr(StackPointer, i->offsetFromArgBase());
+        if (i.mirType() == MIRType_Int32 || i.mirType() == MIRType_Float32) {
+            masm.load32(srcAddr, ABIArgGenerator::NonArgReturnVolatileReg0);
+            masm.store32(ABIArgGenerator::NonArgReturnVolatileReg0, dstAddr);
+        } else {
+            JS_ASSERT(i.mirType() == MIRType_Double);
+            masm.loadDouble(srcAddr, ScratchDoubleReg);
+            masm.storeDouble(ScratchDoubleReg, dstAddr);
+        }
+    }
+
+    AssertStackAlignment(masm);
+    masm.call(BuiltinToImmKind(builtin));
+
+    Label profilingReturn;
+    GenerateAsmJSExitEpilogue(masm, framePushed, AsmJSExit::Builtin(builtin), &profilingReturn);
+    return m.finishGeneratingBuiltinThunk(builtin, &begin, &profilingReturn) && !masm.oom();
+}
+
 static bool
 GenerateStackOverflowExit(ModuleCompiler &m, Label *throwLabel)
 {
     MacroAssembler &masm = m.masm();
     GenerateAsmJSStackOverflowExit(masm, &m.stackOverflowLabel(), throwLabel);
     return m.finishGeneratingInlineStub(&m.stackOverflowLabel()) && !masm.oom();
 }
 
@@ -6611,24 +6695,24 @@ GenerateAsyncInterruptExit(ModuleCompile
 static bool
 GenerateSyncInterruptExit(ModuleCompiler &m, Label *throwLabel)
 {
     MacroAssembler &masm = m.masm();
     masm.setFramePushed(0);
 
     unsigned framePushed = StackDecrementForCall(masm, ShadowStackSpace);
 
-    GenerateAsmJSExitPrologue(masm, framePushed, AsmJSInterrupt, &m.syncInterruptLabel());
+    GenerateAsmJSExitPrologue(masm, framePushed, AsmJSExit::Interrupt, &m.syncInterruptLabel());
 
     AssertStackAlignment(masm);
     masm.call(AsmJSImmPtr(AsmJSImm_HandleExecutionInterrupt));
     masm.branchIfFalseBool(ReturnReg, throwLabel);
 
     Label profilingReturn;
-    GenerateAsmJSExitEpilogue(masm, framePushed, AsmJSInterrupt, &profilingReturn);
+    GenerateAsmJSExitEpilogue(masm, framePushed, AsmJSExit::Interrupt, &profilingReturn);
     return m.finishGeneratingInterrupt(&m.syncInterruptLabel(), &profilingReturn) && !masm.oom();
 }
 
 // If an exception is thrown, simply pop all frames (since asm.js does not
 // contain try/catch). To do this:
 //  1. Restore 'sp' to it's value right after the PushRegsInMask in GenerateEntry.
 //  2. PopRegsInMask to restore the caller's non-volatile registers.
 //  3. Return (to CallAsmJS).
@@ -6663,34 +6747,37 @@ GenerateStubs(ModuleCompiler &m)
 {
     for (unsigned i = 0; i < m.module().numExportedFunctions(); i++) {
         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()) {
         if (!GenerateFFIExits(m, r.front().key(), r.front().value(), &throwLabel))
             return false;
     }
 
     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;
 
+    for (unsigned i = 0; i < AsmJSExit::Builtin_Limit; i++) {
+        if (!GenerateBuiltinThunk(m, AsmJSExit::BuiltinKind(i)))
+            return false;
+    }
+
     return true;
 }
 
 static bool
 FinishModule(ModuleCompiler &m,
              ScopedJSDeletePtr<AsmJSModule> *module)
 {
     LifoAlloc lifo(LIFO_ALLOC_PRIMARY_CHUNK_SIZE);
--- a/js/src/jit/AsmJSFrameIterator.cpp
+++ b/js/src/jit/AsmJSFrameIterator.cpp
@@ -65,16 +65,17 @@ AsmJSFrameIterator::settle()
         break;
       case AsmJSModule::CodeRange::Entry:
         fp_ = nullptr;
         JS_ASSERT(done());
         break;
       case AsmJSModule::CodeRange::FFI:
       case AsmJSModule::CodeRange::Interrupt:
       case AsmJSModule::CodeRange::Inline:
+      case AsmJSModule::CodeRange::Thunk:
         MOZ_ASSUME_UNREACHABLE("Should not encounter an exit during iteration");
     }
 }
 
 JSAtom *
 AsmJSFrameIterator::functionDisplayAtom() const
 {
     JS_ASSERT(!done());
@@ -121,17 +122,17 @@ PushRetAddr(MacroAssembler &masm)
     // 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,
+GenerateProfilingPrologue(MacroAssembler &masm, unsigned framePushed, AsmJSExit::Reason 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
@@ -149,37 +150,37 @@ GenerateProfilingPrologue(MacroAssembler
         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)
+    if (reason != AsmJSExit::None)
         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,
+GenerateProfilingEpilogue(MacroAssembler &masm, unsigned framePushed, AsmJSExit::Reason 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()));
+    if (reason != AsmJSExit::None)
+        masm.store32(Imm32(AsmJSExit::None), 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)
@@ -211,17 +212,17 @@ js::GenerateAsmJSFunctionPrologue(MacroA
 #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);
+    GenerateProfilingPrologue(masm, framePushed, AsmJSExit::None, &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);
@@ -280,17 +281,17 @@ js::GenerateAsmJSFunctionEpilogue(MacroA
 
     // Normal epilogue:
     masm.addPtr(Imm32(framePushed + AsmJSFrameBytesAfterReturnAddress), StackPointer);
     masm.ret();
     masm.setFramePushed(0);
 
     // Profiling epilogue:
     masm.bind(&labels->profilingEpilogue);
-    GenerateProfilingEpilogue(masm, framePushed, AsmJSNoExit, &labels->profilingReturn);
+    GenerateProfilingEpilogue(masm, framePushed, AsmJSExit::None, &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(labels->overflowThunk.addr());
         masm.addPtr(Imm32(framePushed), StackPointer);
         masm.jump(&labels->overflowExit);
@@ -341,61 +342,67 @@ js::GenerateAsmJSEntryEpilogue(MacroAsse
     // Inverse of GenerateAsmJSEntryPrologue:
     JS_ASSERT(masm.framePushed() == 0);
     masm.addPtr(Imm32(AsmJSFrameBytesAfterReturnAddress), StackPointer);
     masm.ret();
     masm.setFramePushed(0);
 }
 
 void
-js::GenerateAsmJSExitPrologue(MacroAssembler &masm, unsigned framePushed, AsmJSExitReason reason,
+js::GenerateAsmJSExitPrologue(MacroAssembler &masm, unsigned framePushed, AsmJSExit::Reason reason,
                               Label *begin)
 {
     masm.align(CodeAlignment);
     GenerateProfilingPrologue(masm, framePushed, reason, begin);
     masm.setFramePushed(framePushed);
 }
 
 void
-js::GenerateAsmJSExitEpilogue(MacroAssembler &masm, unsigned framePushed, AsmJSExitReason reason,
+js::GenerateAsmJSExitEpilogue(MacroAssembler &masm, unsigned framePushed, AsmJSExit::Reason reason,
                               Label *profilingReturn)
 {
     // Inverse of GenerateAsmJSExitPrologue:
     JS_ASSERT(masm.framePushed() == framePushed);
     GenerateProfilingEpilogue(masm, framePushed, reason, profilingReturn);
     masm.setFramePushed(0);
 }
 
 /*****************************************************************************/
 // AsmJSProfilingFrameIterator
 
 AsmJSProfilingFrameIterator::AsmJSProfilingFrameIterator(const AsmJSActivation &activation)
   : module_(&activation.module()),
     callerFP_(nullptr),
     callerPC_(nullptr),
-    exitReason_(AsmJSNoExit),
+    exitReason_(AsmJSExit::None),
     codeRange_(nullptr)
 {
     initFromFP(activation);
 }
 
 static inline void
-AssertMatchesCallSite(const AsmJSModule &module, void *pc, void *newfp, void *oldfp)
+AssertMatchesCallSite(const AsmJSModule &module, const AsmJSModule::CodeRange *calleeCodeRange,
+                      void *callerPC, void *callerFP, void *fp)
 {
 #ifdef DEBUG
-    const AsmJSModule::CodeRange *codeRange = module.lookupCodeRange(pc);
-    JS_ASSERT(codeRange);
-    if (codeRange->isEntry()) {
-        JS_ASSERT(newfp == nullptr);
+    const AsmJSModule::CodeRange *callerCodeRange = module.lookupCodeRange(callerPC);
+    JS_ASSERT(callerCodeRange);
+    if (callerCodeRange->isEntry()) {
+        JS_ASSERT(callerFP == nullptr);
         return;
     }
 
-    const CallSite *callsite = module.lookupCallSite(pc);
-    JS_ASSERT(callsite);
-    JS_ASSERT(newfp == (uint8_t*)oldfp + callsite->stackDepth());
+    const CallSite *callsite = module.lookupCallSite(callerPC);
+    if (calleeCodeRange->isThunk()) {
+        JS_ASSERT(!callsite);
+        JS_ASSERT(callerCodeRange->isFunction());
+    } else {
+        JS_ASSERT(callsite);
+        JS_ASSERT(callerFP == (uint8_t*)fp + callsite->stackDepth());
+    }
 #endif
 }
 
 void
 AsmJSProfilingFrameIterator::initFromFP(const AsmJSActivation &activation)
 {
     uint8_t *fp = activation.fp();
 
@@ -425,38 +432,39 @@ AsmJSProfilingFrameIterator::initFromFP(
     codeRange_ = codeRange;
 
     switch (codeRange->kind()) {
       case AsmJSModule::CodeRange::Entry:
         callerPC_ = nullptr;
         callerFP_ = nullptr;
         break;
       case AsmJSModule::CodeRange::Function:
+        fp = CallerFPFromFP(fp);
+        callerPC_ = ReturnAddressFromFP(fp);
+        callerFP_ = CallerFPFromFP(fp);
+        AssertMatchesCallSite(*module_, codeRange, callerPC_, callerFP_, fp);
+        break;
       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;
+      case AsmJSModule::CodeRange::Thunk:
+        MOZ_CRASH("Unexpected CodeRange kind");
     }
 
     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),
+    exitReason_(AsmJSExit::None),
     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()) {
@@ -473,17 +481,18 @@ AsmJSProfilingFrameIterator::AsmJSProfil
 
     // 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: {
+      case AsmJSModule::CodeRange::Interrupt:
+      case AsmJSModule::CodeRange::Thunk: {
         // 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)
@@ -493,32 +502,32 @@ AsmJSProfilingFrameIterator::AsmJSProfil
         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);
+            AssertMatchesCallSite(*module_, codeRange, callerPC_, callerFP_, sp - 2);
         } else
 #endif
         if (offsetInCodeRange < PushedFP || offsetInModule == codeRange->profilingReturn()) {
             callerPC_ = *sp;
             callerFP_ = fp;
-            AssertMatchesCallSite(*module_, callerPC_, callerFP_, sp - 1);
+            AssertMatchesCallSite(*module_, codeRange, callerPC_, callerFP_, sp - 1);
         } else if (offsetInCodeRange < StoredFP) {
             JS_ASSERT(fp == CallerFPFromFP(sp));
             callerPC_ = ReturnAddressFromFP(sp);
             callerFP_ = CallerFPFromFP(sp);
-            AssertMatchesCallSite(*module_, callerPC_, callerFP_, sp);
+            AssertMatchesCallSite(*module_, codeRange, callerPC_, callerFP_, sp);
         } else {
             callerPC_ = ReturnAddressFromFP(fp);
             callerFP_ = CallerFPFromFP(fp);
-            AssertMatchesCallSite(*module_, callerPC_, callerFP_, fp);
+            AssertMatchesCallSite(*module_, codeRange, 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);
@@ -533,31 +542,31 @@ AsmJSProfilingFrameIterator::AsmJSProfil
             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);
+        AssertMatchesCallSite(*module_, codeRange, callerPC_, callerFP_, fp);
         break;
       }
     }
 
     codeRange_ = codeRange;
     JS_ASSERT(!done());
 }
 
 void
 AsmJSProfilingFrameIterator::operator++()
 {
-    if (exitReason_ != AsmJSNoExit) {
+    if (exitReason_ != AsmJSExit::None) {
         JS_ASSERT(codeRange_);
-        exitReason_ = AsmJSNoExit;
+        exitReason_ = AsmJSExit::None;
         JS_ASSERT(!done());
         return;
     }
 
     if (!callerPC_) {
         JS_ASSERT(!callerFP_);
         codeRange_ = nullptr;
         JS_ASSERT(done());
@@ -573,47 +582,52 @@ AsmJSProfilingFrameIterator::operator++(
       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:
+      case AsmJSModule::CodeRange::Thunk:
         callerPC_ = ReturnAddressFromFP(callerFP_);
-        AssertMatchesCallSite(*module_, callerPC_, CallerFPFromFP(callerFP_), callerFP_);
+        AssertMatchesCallSite(*module_, codeRange, callerPC_, CallerFPFromFP(callerFP_), callerFP_);
         callerFP_ = CallerFPFromFP(callerFP_);
         break;
     }
 
     JS_ASSERT(!done());
 }
 
 AsmJSProfilingFrameIterator::Kind
 AsmJSProfilingFrameIterator::kind() const
 {
     JS_ASSERT(!done());
 
-    switch (exitReason_) {
-      case AsmJSNoExit:
+    switch (AsmJSExit::ExtractReasonKind(exitReason_)) {
+      case AsmJSExit::Reason_None:
         break;
-      case AsmJSInterrupt:
-      case AsmJSFFI:
+      case AsmJSExit::Reason_Interrupt:
+      case AsmJSExit::Reason_FFI:
         return JS::ProfilingFrameIterator::AsmJSTrampoline;
+      case AsmJSExit::Reason_Builtin:
+        return JS::ProfilingFrameIterator::CppFunction;
     }
 
     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;
+      case AsmJSModule::CodeRange::Thunk:
+        return JS::ProfilingFrameIterator::CppFunction;
     }
 
     MOZ_ASSUME_UNREACHABLE("Bad kind");
 }
 
 JSAtom *
 AsmJSProfilingFrameIterator::functionDisplayAtom() const
 {
@@ -623,39 +637,72 @@ AsmJSProfilingFrameIterator::functionDis
 
 const char *
 AsmJSProfilingFrameIterator::functionFilename() const
 {
     JS_ASSERT(kind() == JS::ProfilingFrameIterator::Function);
     return module_->scriptSource()->filename();
 }
 
+static const char *
+BuiltinToName(AsmJSExit::BuiltinKind builtin)
+{
+    switch (builtin) {
+      case AsmJSExit::Builtin_ToInt32:   return "ToInt32";
+#if defined(JS_CODEGEN_ARM)
+      case AsmJSExit::Builtin_IDivMod:   return "software idivmod";
+      case AsmJSExit::Builtin_UDivMod:   return "software uidivmod";
+#endif
+      case AsmJSExit::Builtin_ModD:      return "fmod";
+      case AsmJSExit::Builtin_SinD:      return "Math.sin";
+      case AsmJSExit::Builtin_CosD:      return "Math.cos";
+      case AsmJSExit::Builtin_TanD:      return "Math.tan";
+      case AsmJSExit::Builtin_ASinD:     return "Math.asin";
+      case AsmJSExit::Builtin_ACosD:     return "Math.acos";
+      case AsmJSExit::Builtin_ATanD:     return "Math.atan";
+      case AsmJSExit::Builtin_CeilD:
+      case AsmJSExit::Builtin_CeilF:     return "Math.ceil";
+      case AsmJSExit::Builtin_FloorD:
+      case AsmJSExit::Builtin_FloorF:    return "Math.floor";
+      case AsmJSExit::Builtin_ExpD:      return "Math.exp";
+      case AsmJSExit::Builtin_LogD:      return "Math.log";
+      case AsmJSExit::Builtin_PowD:      return "Math.pow";
+      case AsmJSExit::Builtin_ATan2D:    return "Math.atan2";
+      case AsmJSExit::Builtin_Limit:     break;
+    }
+    MOZ_ASSUME_UNREACHABLE("Bad builtin kind");
+}
+
 const char *
 AsmJSProfilingFrameIterator::nonFunctionDescription() const
 {
     JS_ASSERT(!done());
+    JS_ASSERT(kind() != JS::ProfilingFrameIterator::Function);
 
     // 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:
+    switch (AsmJSExit::ExtractReasonKind(exitReason_)) {
+      case AsmJSExit::Reason_None:
         break;
-      case AsmJSFFI:
+      case AsmJSExit::Reason_FFI:
         return ffiDescription;
-      case AsmJSInterrupt:
+      case AsmJSExit::Reason_Interrupt:
         return interruptDescription;
+      case AsmJSExit::Reason_Builtin:
+        return BuiltinToName(AsmJSExit::ExtractBuiltinKind(exitReason_));
     }
 
     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";
+      case AsmJSModule::CodeRange::Thunk:     return BuiltinToName(codeRange->thunkTarget());
     }
 
-    MOZ_ASSUME_UNREACHABLE("Bad kind");
+    MOZ_ASSUME_UNREACHABLE("Bad exit kind");
 }
 
--- a/js/src/jit/AsmJSFrameIterator.h
+++ b/js/src/jit/AsmJSFrameIterator.h
@@ -43,36 +43,84 @@ 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
+namespace AsmJSExit
 {
-    AsmJSNoExit,
-    AsmJSFFI,
-    AsmJSInterrupt
-};
+    // 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 ReasonKind {
+        Reason_None,
+        Reason_FFI,
+        Reason_Interrupt,
+        Reason_Builtin
+    };
+
+    // For Reason_Builtin, the list of builtins, so they can be displayed in the
+    // profile call stack.
+    enum BuiltinKind {
+        Builtin_ToInt32,
+#if defined(JS_CODEGEN_ARM)
+        Builtin_IDivMod,
+        Builtin_UDivMod,
+#endif
+        Builtin_ModD,
+        Builtin_SinD,
+        Builtin_CosD,
+        Builtin_TanD,
+        Builtin_ASinD,
+        Builtin_ACosD,
+        Builtin_ATanD,
+        Builtin_CeilD,
+        Builtin_CeilF,
+        Builtin_FloorD,
+        Builtin_FloorF,
+        Builtin_ExpD,
+        Builtin_LogD,
+        Builtin_PowD,
+        Builtin_ATan2D,
+        Builtin_Limit
+    };
+
+    // A Reason contains both a ReasonKind and (if Reason_Builtin) a
+    // BuiltinKind.
+    typedef uint32_t Reason;
+
+    static const uint32_t None = Reason_None;
+    static const uint32_t FFI = Reason_FFI;
+    static const uint32_t Interrupt = Reason_Interrupt;
+    static inline Reason Builtin(BuiltinKind builtin) {
+        return uint16_t(Reason_Builtin) | (uint16_t(builtin) << 16);
+    }
+    static inline ReasonKind ExtractReasonKind(Reason reason) {
+        return ReasonKind(uint16_t(reason));
+    }
+    static inline BuiltinKind ExtractBuiltinKind(Reason reason) {
+        JS_ASSERT(ExtractReasonKind(reason) == Reason_Builtin);
+        return BuiltinKind(uint16_t(reason >> 16));
+    }
+}
 
 // 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_;
+    AsmJSExit::Reason 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:
@@ -107,17 +155,18 @@ GenerateAsmJSStackOverflowExit(jit::Macr
                                jit::Label *throwLabel);
 
 void
 GenerateAsmJSEntryPrologue(jit::MacroAssembler &masm, jit::Label *begin);
 void
 GenerateAsmJSEntryEpilogue(jit::MacroAssembler &masm);
 
 void
-GenerateAsmJSExitPrologue(jit::MacroAssembler &masm, unsigned framePushed, AsmJSExitReason reason,
+GenerateAsmJSExitPrologue(jit::MacroAssembler &masm, unsigned framePushed, AsmJSExit::Reason reason,
                           jit::Label *begin);
 void
-GenerateAsmJSExitEpilogue(jit::MacroAssembler &masm, unsigned framePushed, AsmJSExitReason reason,
+GenerateAsmJSExitEpilogue(jit::MacroAssembler &masm, unsigned framePushed, AsmJSExit::Reason reason,
                           jit::Label *profilingReturn);
 
+
 } // namespace js
 
 #endif // jit_AsmJSFrameIterator_h
--- a/js/src/jit/AsmJSModule.cpp
+++ b/js/src/jit/AsmJSModule.cpp
@@ -36,16 +36,17 @@
 
 using namespace js;
 using namespace jit;
 using namespace frontend;
 using mozilla::BinarySearch;
 using mozilla::PodCopy;
 using mozilla::PodEqual;
 using mozilla::Compression::LZ4;
+using mozilla::Swap;
 
 static uint8_t *
 AllocateExecutableMemory(ExclusiveContext *cx, size_t totalBytes)
 {
     JS_ASSERT(totalBytes % AsmJSPageSize == 0);
 
 #ifdef XP_WIN
     void *p = VirtualAlloc(nullptr, totalBytes, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
@@ -167,16 +168,17 @@ 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) +
                         funcPtrTables_.sizeOfExcludingThis(mallocSizeOf) +
+                        builtinThunkOffsets_.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) +
@@ -336,16 +338,18 @@ AsmJSModule::finish(ExclusiveContext *cx
     for (size_t i = 0; i < callSites_.length(); i++) {
         CallSite &c = callSites_[i];
         c.setReturnAddressOffset(masm.actualOffset(c.returnAddressOffset()));
     }
     for (size_t i = 0; i < codeRanges_.length(); i++) {
         codeRanges_[i].updateOffsets(masm);
         JS_ASSERT_IF(i > 0, codeRanges_[i - 1].end() <= codeRanges_[i].begin());
     }
+    for (size_t i = 0; i < builtinThunkOffsets_.length(); i++)
+        builtinThunkOffsets_[i] = masm.actualOffset(builtinThunkOffsets_[i]);
 #endif
     JS_ASSERT(pod.functionBytes_ % AsmJSPageSize == 0);
 
     // Absolute link metadata: absolute addresses that refer to some fixed
     // address in the address space.
     AbsoluteLinkArray &absoluteLinks = staticLinkData_.absoluteLinks;
     for (size_t i = 0; i < masm.numAsmJSAbsoluteLinks(); i++) {
         AsmJSAbsoluteLink src = masm.asmJSAbsoluteLink(i);
@@ -1168,56 +1172,71 @@ AsmJSModule::ExportedFunction::clone(Exc
     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)
+    end_(l.end.offset())
 {
+    u.kind_ = Function;
+    setDeltas(l.entry.offset(), l.profilingJump.offset(), l.profilingEpilogue.offset());
+
     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_;
+    u.func.beginToEntry_ = entry - begin_;
 
     JS_ASSERT(profilingReturn_ - profilingJump <= UINT8_MAX);
-    profilingJumpToProfilingReturn_ = profilingReturn_ - profilingJump;
+    u.func.profilingJumpToProfilingReturn_ = profilingReturn_ - profilingJump;
 
     JS_ASSERT(profilingReturn_ - profilingEpilogue <= UINT8_MAX);
-    profilingEpilogueToProfilingReturn_ = profilingReturn_ - profilingEpilogue;
+    u.func.profilingEpilogueToProfilingReturn_ = profilingReturn_ - profilingEpilogue;
 }
 
 AsmJSModule::CodeRange::CodeRange(Kind kind, uint32_t begin, uint32_t end)
   : begin_(begin),
-    end_(end),
-    kind_(kind)
+    end_(end)
 {
+    u.kind_ = kind;
+
     JS_ASSERT(begin_ <= end_);
-    JS_ASSERT(kind_ == Entry || kind_ == Inline);
+    JS_ASSERT(u.kind_ == Entry || u.kind_ == Inline);
 }
 
 AsmJSModule::CodeRange::CodeRange(Kind kind, uint32_t begin, uint32_t profilingReturn, uint32_t end)
   : begin_(begin),
     profilingReturn_(profilingReturn),
-    end_(end),
-    kind_(kind)
+    end_(end)
 {
+    u.kind_ = kind;
+
+    JS_ASSERT(begin_ < profilingReturn_);
+    JS_ASSERT(profilingReturn_ < end_);
+}
+
+AsmJSModule::CodeRange::CodeRange(AsmJSExit::BuiltinKind builtin, uint32_t begin,
+                                  uint32_t profilingReturn, uint32_t end)
+  : begin_(begin),
+    profilingReturn_(profilingReturn),
+    end_(end)
+{
+    u.kind_ = Thunk;
+    u.thunk.target_ = builtin;
+
     JS_ASSERT(begin_ < profilingReturn_);
     JS_ASSERT(profilingReturn_ < end_);
 }
 
 void
 AsmJSModule::CodeRange::updateOffsets(jit::MacroAssembler &masm)
 {
     uint32_t entryBefore, profilingJumpBefore, profilingEpilogueBefore;
@@ -1357,16 +1376,17 @@ AsmJSModule::serializedSize() const
            SerializedNameSize(importArgumentName_) +
            SerializedNameSize(bufferArgumentName_) +
            SerializedVectorSize(globals_) +
            SerializedVectorSize(exits_) +
            SerializedVectorSize(exports_) +
            SerializedPodVectorSize(callSites_) +
            SerializedPodVectorSize(codeRanges_) +
            SerializedPodVectorSize(funcPtrTables_) +
+           SerializedPodVectorSize(builtinThunkOffsets_) +
            SerializedVectorSize(names_) +
            SerializedPodVectorSize(heapAccesses_) +
 #if defined(MOZ_VTUNE) || defined(JS_ION_PERF)
            SerializedVectorSize(profiledFunctions_) +
 #endif
            staticLinkData_.serializedSize();
 }
 
@@ -1379,16 +1399,17 @@ AsmJSModule::serialize(uint8_t *cursor) 
     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 = SerializePodVector(cursor, funcPtrTables_);
+    cursor = SerializePodVector(cursor, builtinThunkOffsets_);
     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;
 }
@@ -1407,16 +1428,17 @@ AsmJSModule::deserialize(ExclusiveContex
     (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 = DeserializePodVector(cx, cursor, &funcPtrTables_)) &&
+    (cursor = DeserializePodVector(cx, cursor, &builtinThunkOffsets_)) &&
     (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;
@@ -1480,16 +1502,17 @@ AsmJSModule::clone(JSContext *cx, Scoped
     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_) ||
         !ClonePodVector(cx, funcPtrTables_, &out.funcPtrTables_) ||
+        !ClonePodVector(cx, builtinThunkOffsets_, &out.builtinThunkOffsets_) ||
         !CloneVector(cx, names_, &out.names_) ||
         !ClonePodVector(cx, heapAccesses_, &out.heapAccesses_) ||
         !staticLinkData_.clone(cx, &out.staticLinkData_))
     {
         return false;
     }
 
     out.loadedFromCache_ = loadedFromCache_;
@@ -1608,16 +1631,38 @@ AsmJSModule::setProfilingEnabled(bool en
             JS_ASSERT(reinterpret_cast<Instruction*>(jump)->is<InstBImm>());
             new (jump) InstNOP();
         }
 #else
 # error "Missing architecture"
 #endif
     }
 
+    // Replace all calls to builtins with calls to profiling thunks that push a
+    // frame pointer. Since exit unwinding always starts at the caller of fp,
+    // this avoids losing the innermost asm.js function.
+    for (unsigned builtin = 0; builtin < AsmJSExit::Builtin_Limit; builtin++) {
+        AsmJSImmKind imm = BuiltinToImmKind(AsmJSExit::BuiltinKind(builtin));
+        const AsmJSModule::OffsetVector &offsets = staticLinkData_.absoluteLinks[imm];
+        void *from = AddressOf(AsmJSImmKind(imm), nullptr);
+        void *to = code_ + builtinThunkOffsets_[builtin];
+        if (!enabled)
+            Swap(from, to);
+        for (size_t j = 0; j < offsets.length(); j++) {
+            uint8_t *caller = code_ + offsets[j];
+            const AsmJSModule::CodeRange *codeRange = lookupCodeRange(caller);
+            if (codeRange->isThunk())
+                continue;
+            JS_ASSERT(codeRange->isFunction());
+            Assembler::PatchDataWithValueCheck(CodeLocationLabel(caller),
+                                               PatchedImmPtr(to),
+                                               PatchedImmPtr(from));
+        }
+    }
+
     profilingEnabled_ = enabled;
 }
 
 void
 AsmJSModule::protectCode(JSRuntime *rt) const
 {
     JS_ASSERT(isDynamicallyLinked());
     JS_ASSERT(rt->currentThreadOwnsInterruptLock());
--- a/js/src/jit/AsmJSModule.h
+++ b/js/src/jit/AsmJSModule.h
@@ -333,64 +333,79 @@ class AsmJSModule
     };
 
     class CodeRange
     {
         uint32_t nameIndex_;
         uint32_t begin_;
         uint32_t profilingReturn_;
         uint32_t end_;
-        uint8_t beginToEntry_;
-        uint8_t profilingJumpToProfilingReturn_;
-        uint8_t profilingEpilogueToProfilingReturn_;
-        uint8_t kind_;
+        union {
+            struct {
+                uint8_t kind_;
+                uint8_t beginToEntry_;
+                uint8_t profilingJumpToProfilingReturn_;
+                uint8_t profilingEpilogueToProfilingReturn_;
+            } func;
+            struct {
+                uint8_t kind_;
+                uint16_t target_;
+            } thunk;
+            uint8_t kind_;
+        } u;
 
         void setDeltas(uint32_t entry, uint32_t profilingJump, uint32_t profilingEpilogue);
 
       public:
-        enum Kind { Function, Entry, FFI, Interrupt, Inline };
+        enum Kind { Function, Entry, FFI, Interrupt, Thunk, Inline };
 
         CodeRange() {}
         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);
+        CodeRange(AsmJSExit::BuiltinKind builtin, uint32_t begin, uint32_t pret, uint32_t end);
         void updateOffsets(jit::MacroAssembler &masm);
 
-        Kind kind() const { return Kind(kind_); }
+        Kind kind() const { return Kind(u.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; }
+        bool isThunk() const { return kind() == Thunk; }
 
         uint32_t begin() const {
             return begin_;
         }
         uint32_t entry() const {
             JS_ASSERT(isFunction());
-            return begin_ + beginToEntry_;
+            return begin_ + u.func.beginToEntry_;
         }
         uint32_t end() const {
             return end_;
         }
         uint32_t profilingJump() const {
             JS_ASSERT(isFunction());
-            return profilingReturn_ - profilingJumpToProfilingReturn_;
+            return profilingReturn_ - u.func.profilingJumpToProfilingReturn_;
         }
         uint32_t profilingEpilogue() const {
             JS_ASSERT(isFunction());
-            return profilingReturn_ - profilingEpilogueToProfilingReturn_;
+            return profilingReturn_ - u.func.profilingEpilogueToProfilingReturn_;
         }
         uint32_t profilingReturn() const {
-            JS_ASSERT(isFunction() || isFFI() || isInterrupt());
+            JS_ASSERT(isFunction() || isFFI() || isInterrupt() || isThunk());
             return profilingReturn_;
         }
         PropertyName *functionName(const AsmJSModule &module) const {
-            JS_ASSERT(kind() == Function);
+            JS_ASSERT(isFunction());
             return module.names_[nameIndex_].name();
         }
+        AsmJSExit::BuiltinKind thunkTarget() const {
+            JS_ASSERT(isThunk());
+            return AsmJSExit::BuiltinKind(u.thunk.target_);
+        }
     };
 
     class FuncPtrTable
     {
         uint32_t globalDataOffset_;
         uint32_t numElems_;
       public:
         FuncPtrTable() {}
@@ -581,16 +596,17 @@ class AsmJSModule
     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<FuncPtrTable,           0, SystemAllocPolicy> funcPtrTables_;
+    Vector<uint32_t,               0, SystemAllocPolicy> builtinThunkOffsets_;
     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_;
@@ -797,16 +813,22 @@ class AsmJSModule
         return codeRanges_.append(CodeRange(CodeRange::Entry, begin, end));
     }
     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 addBuiltinThunkCodeRange(AsmJSExit::BuiltinKind builtin, uint32_t begin,
+                                  uint32_t profilingReturn, uint32_t end)
+    {
+        return builtinThunkOffsets_.append(begin) &&
+               codeRanges_.append(CodeRange(builtin, begin, profilingReturn, 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();
--- a/js/src/jit/shared/Assembler-shared.h
+++ b/js/src/jit/shared/Assembler-shared.h
@@ -6,16 +6,17 @@
 
 #ifndef jit_shared_Assembler_shared_h
 #define jit_shared_Assembler_shared_h
 
 #include "mozilla/PodOperations.h"
 
 #include <limits.h>
 
+#include "jit/AsmJSFrameIterator.h"
 #include "jit/IonAllocPolicy.h"
 #include "jit/Label.h"
 #include "jit/Registers.h"
 #include "jit/RegisterSets.h"
 #include "vm/HelperThreads.h"
 
 #if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM)
 // JS_SMALL_BRANCH means the range on a branch instruction
@@ -729,49 +730,55 @@ struct AsmJSGlobalAccess
     {}
 };
 
 // Describes the intended pointee of an immediate to be embedded in asm.js
 // code. By representing the pointee as a symbolic enum, the pointee can be
 // patched after deserialization when the address of global things has changed.
 enum AsmJSImmKind
 {
+    AsmJSImm_ToInt32         = AsmJSExit::Builtin_ToInt32,
+#if defined(JS_CODEGEN_ARM)
+    AsmJSImm_aeabi_idivmod   = AsmJSExit::Builtin_IDivMod,
+    AsmJSImm_aeabi_uidivmod  = AsmJSExit::Builtin_UDivMod,
+#endif
+    AsmJSImm_ModD            = AsmJSExit::Builtin_ModD,
+    AsmJSImm_SinD            = AsmJSExit::Builtin_SinD,
+    AsmJSImm_CosD            = AsmJSExit::Builtin_CosD,
+    AsmJSImm_TanD            = AsmJSExit::Builtin_TanD,
+    AsmJSImm_ASinD           = AsmJSExit::Builtin_ASinD,
+    AsmJSImm_ACosD           = AsmJSExit::Builtin_ACosD,
+    AsmJSImm_ATanD           = AsmJSExit::Builtin_ATanD,
+    AsmJSImm_CeilD           = AsmJSExit::Builtin_CeilD,
+    AsmJSImm_CeilF           = AsmJSExit::Builtin_CeilF,
+    AsmJSImm_FloorD          = AsmJSExit::Builtin_FloorD,
+    AsmJSImm_FloorF          = AsmJSExit::Builtin_FloorF,
+    AsmJSImm_ExpD            = AsmJSExit::Builtin_ExpD,
+    AsmJSImm_LogD            = AsmJSExit::Builtin_LogD,
+    AsmJSImm_PowD            = AsmJSExit::Builtin_PowD,
+    AsmJSImm_ATan2D          = AsmJSExit::Builtin_ATan2D,
     AsmJSImm_Runtime,
     AsmJSImm_RuntimeInterrupt,
     AsmJSImm_StackLimit,
     AsmJSImm_ReportOverRecursed,
     AsmJSImm_HandleExecutionInterrupt,
     AsmJSImm_InvokeFromAsmJS_Ignore,
     AsmJSImm_InvokeFromAsmJS_ToInt32,
     AsmJSImm_InvokeFromAsmJS_ToNumber,
     AsmJSImm_CoerceInPlace_ToInt32,
     AsmJSImm_CoerceInPlace_ToNumber,
-    AsmJSImm_ToInt32,
-#if defined(JS_CODEGEN_ARM)
-    AsmJSImm_aeabi_idivmod,
-    AsmJSImm_aeabi_uidivmod,
-#endif
-    AsmJSImm_ModD,
-    AsmJSImm_SinD,
-    AsmJSImm_CosD,
-    AsmJSImm_TanD,
-    AsmJSImm_ASinD,
-    AsmJSImm_ACosD,
-    AsmJSImm_ATanD,
-    AsmJSImm_CeilD,
-    AsmJSImm_CeilF,
-    AsmJSImm_FloorD,
-    AsmJSImm_FloorF,
-    AsmJSImm_ExpD,
-    AsmJSImm_LogD,
-    AsmJSImm_PowD,
-    AsmJSImm_ATan2D,
     AsmJSImm_Limit
 };
 
+static inline AsmJSImmKind
+BuiltinToImmKind(AsmJSExit::BuiltinKind builtin)
+{
+    return AsmJSImmKind(builtin);
+}
+
 // Pointer to be embedded as an immediate in asm.js code.
 class AsmJSImmPtr
 {
     AsmJSImmKind kind_;
   public:
     AsmJSImmKind kind() const { return kind_; }
     // This needs to be MOZ_IMPLICIT in order to make MacroAssember::CallWithABINoProfiling compile.
     MOZ_IMPLICIT AsmJSImmPtr(AsmJSImmKind kind) : kind_(kind) { JS_ASSERT(IsCompilingAsmJS()); }
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -4435,16 +4435,21 @@ SingleStepCallback(void *arg, jit::Simul
             else
                 stack.append(atom->twoByteChars(nogc), atom->length());
             break;
           }
           case JS::ProfilingFrameIterator::AsmJSTrampoline: {
             stack.append('*');
             break;
           }
+          case JS::ProfilingFrameIterator::CppFunction: {
+            const char *desc = i.nonFunctionDescription();
+            stack.append(desc, strlen(desc));
+            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()))
     {
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -1685,17 +1685,17 @@ jit::JitActivation::markRematerializedFr
 
 AsmJSActivation::AsmJSActivation(JSContext *cx, AsmJSModule &module)
   : Activation(cx, AsmJS),
     module_(module),
     errorRejoinSP_(nullptr),
     profiler_(nullptr),
     resumePC_(nullptr),
     fp_(nullptr),
-    exitReason_(AsmJSNoExit)
+    exitReason_(AsmJSExit::None)
 {
     if (cx->runtime()->spsProfiler.enabled()) {
         // Use a profiler string that matches jsMatch regex in
         // browser/devtools/profiler/cleopatra/js/parserWorker.js.
         // (For now use a single static string to avoid further slowing down
         // calls into asm.js.)
         profiler_ = &cx->runtime()->spsProfiler;
         profiler_->enterNative("asm.js code :0", this);
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -1475,32 +1475,32 @@ class AsmJSActivation : public Activatio
 {
     AsmJSModule &module_;
     AsmJSActivation *prevAsmJS_;
     AsmJSActivation *prevAsmJSForModule_;
     void *errorRejoinSP_;
     SPSProfiler *profiler_;
     void *resumePC_;
     uint8_t *fp_;
-    uint32_t exitReason_;
+    AsmJSExit::Reason 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_); }
+    AsmJSExit::Reason exitReason() const { return 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_); }