Bug 980059 - Have AsmJSActivation add the entry asm.js function to the callstack (r=jandem)
authorLuke Wagner <luke@mozilla.com>
Wed, 05 Mar 2014 17:15:33 -0600
changeset 189583 797981dc56951f4ad2f58beac956702be2392848
parent 189582 85f02194cd286f49d378255529153a3f4d99e6fc
child 189584 3aa4c0b57f214eb690cf8e0e2bff6d2ffd6e8590
push id3503
push userraliiev@mozilla.com
push dateMon, 28 Apr 2014 18:51:11 +0000
treeherdermozilla-beta@c95ac01e332e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs980059
milestone30.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 980059 - Have AsmJSActivation add the entry asm.js function to the callstack (r=jandem)
js/src/builtin/Eval.cpp
js/src/jit-test/tests/asm.js/testStackWalking.js
js/src/jit/AsmJS.cpp
js/src/jit/AsmJSLink.cpp
js/src/jit/AsmJSModule.h
js/src/jsapi.cpp
js/src/jscntxt.cpp
js/src/jscntxtinlines.h
js/src/jsexn.cpp
js/src/jsfun.cpp
js/src/jsopcode.cpp
js/src/jsscript.cpp
js/src/jsscript.h
js/src/vm/Interpreter.cpp
js/src/vm/OldDebugAPI.cpp
js/src/vm/Stack.cpp
js/src/vm/Stack.h
--- a/js/src/builtin/Eval.cpp
+++ b/js/src/builtin/Eval.cpp
@@ -295,37 +295,39 @@ EvalKernel(JSContext *cx, const CallArgs
         return ejr == EvalJSON_Success;
 
     EvalScriptGuard esg(cx);
 
     if (evalType == DIRECT_EVAL && caller.isNonEvalFunctionFrame())
         esg.lookupInEvalCache(flatStr, callerScript, pc);
 
     if (!esg.foundScript()) {
-        JSScript *script;
+        JSScript *maybeScript;
         unsigned lineno;
         const char *filename;
         JSPrincipals *originPrincipals;
         uint32_t pcOffset;
-        CurrentScriptFileLineOrigin(cx, &script, &filename, &lineno, &pcOffset, &originPrincipals,
-                                    evalType == DIRECT_EVAL ? CALLED_FROM_JSOP_EVAL
-                                                            : NOT_CALLED_FROM_JSOP_EVAL);
+        DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno, &pcOffset,
+                                             &originPrincipals,
+                                             evalType == DIRECT_EVAL
+                                             ? CALLED_FROM_JSOP_EVAL
+                                             : NOT_CALLED_FROM_JSOP_EVAL);
 
         const char *introducerFilename = filename;
-        if (script && script->scriptSource()->introducerFilename())
-            introducerFilename = script->scriptSource()->introducerFilename();
+        if (maybeScript && maybeScript->scriptSource()->introducerFilename())
+            introducerFilename = maybeScript->scriptSource()->introducerFilename();
 
         CompileOptions options(cx);
         options.setFileAndLine(filename, 1)
                .setCompileAndGo(true)
                .setForEval(true)
                .setNoScriptRval(false)
                .setPrincipals(principals)
                .setOriginPrincipals(originPrincipals)
-               .setIntroductionInfo(introducerFilename, "eval", lineno, script, pcOffset);
+               .setIntroductionInfo(introducerFilename, "eval", lineno, maybeScript, pcOffset);
         JSScript *compiled = frontend::CompileScript(cx, &cx->tempLifoAlloc(),
                                                      scopeobj, callerScript, options,
                                                      chars.get(), length, flatStr, staticLevel);
         if (!compiled)
             return false;
 
         MarkFunctionsWithinEvalScript(compiled);
 
@@ -368,36 +370,36 @@ js::DirectEvalStringFromIon(JSContext *c
     EvalScriptGuard esg(cx);
 
     // Ion will not perform cross compartment direct eval calls.
     JSPrincipals *principals = cx->compartment()->principals;
 
     esg.lookupInEvalCache(flatStr, callerScript, pc);
 
     if (!esg.foundScript()) {
-        JSScript *script;
+        JSScript *maybeScript;
         const char *filename;
         unsigned lineno;
         JSPrincipals *originPrincipals;
         uint32_t pcOffset;
-        CurrentScriptFileLineOrigin(cx, &script, &filename, &lineno, &pcOffset,
-                                    &originPrincipals, CALLED_FROM_JSOP_EVAL);
+        DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno, &pcOffset,
+                                              &originPrincipals, CALLED_FROM_JSOP_EVAL);
 
         const char *introducerFilename = filename;
-        if (script && script->scriptSource()->introducerFilename())
-            introducerFilename = script->scriptSource()->introducerFilename();
+        if (maybeScript && maybeScript->scriptSource()->introducerFilename())
+            introducerFilename = maybeScript->scriptSource()->introducerFilename();
 
         CompileOptions options(cx);
         options.setFileAndLine(filename, 1)
                .setCompileAndGo(true)
                .setForEval(true)
                .setNoScriptRval(false)
                .setPrincipals(principals)
                .setOriginPrincipals(originPrincipals)
-               .setIntroductionInfo(introducerFilename, "eval", lineno, script, pcOffset);
+               .setIntroductionInfo(introducerFilename, "eval", lineno, maybeScript, pcOffset);
         JSScript *compiled = frontend::CompileScript(cx, &cx->tempLifoAlloc(),
                                                      scopeobj, callerScript, options,
                                                      chars.get(), length, flatStr, staticLevel);
         if (!compiled)
             return false;
 
         MarkFunctionsWithinEvalScript(compiled);
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/asm.js/testStackWalking.js
@@ -0,0 +1,38 @@
+load(libdir + "asm.js");
+load(libdir + "asserts.js");
+
+var callFFI1 = asmCompile('global', 'ffis', USE_ASM + "var ffi=ffis.ffi; function asmfun1() { return ffi(1)|0 } return asmfun1");
+var callFFI2 = asmCompile('global', 'ffis', USE_ASM + "var ffi=ffis.ffi; function asmfun2() { return ffi(2)|0 } return asmfun2");
+
+var stack;
+function dumpStack(i) { stack = new Error().stack; return i+11 }
+
+var asmfun1 = asmLink(callFFI1, null, {ffi:dumpStack});
+assertEq(asmfun1(), 12);
+assertEq(stack.indexOf("asmfun1") === -1, false);
+
+var asmfun2 = asmLink(callFFI2, null, {ffi:function ffi(i){return asmfun1()+20}});
+assertEq(asmfun2(), 32);
+assertEq(stack.indexOf("asmfun1") == -1, false);
+assertEq(stack.indexOf("asmfun2") == -1, false);
+assertEq(stack.indexOf("asmfun2") > stack.indexOf("asmfun1"), true);
+
+var caught = false;
+try {
+    asmLink(asmCompile(USE_ASM + "function asmRec() { asmRec() } return asmRec"))();
+} catch (e) {
+    caught = true;
+}
+assertEq(caught, true);
+
+var caught = false;
+try {
+    callFFI1(null, {ffi:Object.preventExtensions})();
+} catch (e) {
+    caught = true;
+}
+assertEq(caught, true);
+
+assertEq(asmLink(callFFI1, null, {ffi:eval})(), 1);
+assertEq(asmLink(callFFI1, null, {ffi:Function})(), 0);
+assertEq(asmLink(callFFI1, null, {ffi:Error})(), 0);
--- a/js/src/jit/AsmJS.cpp
+++ b/js/src/jit/AsmJS.cpp
@@ -1383,17 +1383,19 @@ class MOZ_STACK_CLASS ModuleCompiler
     bool addExportedFunction(const Func *func, PropertyName *maybeFieldName) {
         AsmJSModule::ArgCoercionVector argCoercions;
         const VarTypeVector &args = func->sig().args();
         if (!argCoercions.resize(args.length()))
             return false;
         for (unsigned i = 0; i < args.length(); i++)
             argCoercions[i] = args[i].toCoercion();
         AsmJSModule::ReturnType retType = func->sig().retType().toModuleReturnType();
-        return module_->addExportedFunction(func->name(), maybeFieldName,
+        uint32_t line, column;
+        parser_.tokenStream.srcCoords.lineNumAndColumnIndex(func->srcOffset(), &line, &column);
+        return module_->addExportedFunction(func->name(), line, column, maybeFieldName,
                                             Move(argCoercions), retType);
     }
     bool addExit(unsigned ffiIndex, PropertyName *name, Signature &&sig, unsigned *exitIndex) {
         ExitDescriptor exitDescriptor(name, Move(sig));
         ExitMap::AddPtr p = exits_.lookupForAdd(exitDescriptor);
         if (p) {
             *exitIndex = p->value();
             return true;
--- a/js/src/jit/AsmJSLink.cpp
+++ b/js/src/jit/AsmJSLink.cpp
@@ -382,17 +382,17 @@ CallAsmJS(JSContext *cx, unsigned argc, 
     }
 
     {
         // Push an AsmJSActivation to describe the asm.js frames we're about to
         // push when running this module. Additionally, push a JitActivation so
         // that the optimized asm.js-to-Ion FFI call path (which we want to be
         // very fast) can avoid doing so. The JitActivation is marked as
         // inactive so stack iteration will skip over it.
-        AsmJSActivation activation(cx, module);
+        AsmJSActivation activation(cx, module, exportIndex);
         JitActivation jitActivation(cx, /* firstFrameIsConstructing = */ false, /* active */ false);
 
         // Call the per-exported-function trampoline created by GenerateEntry.
         AsmJSModule::CodePtr enter = module.entryTrampoline(func);
         if (!CALL_GENERATED_ASMJS(enter, coercedArgs.begin(), module.globalData()))
             return false;
     }
 
--- a/js/src/jit/AsmJSModule.h
+++ b/js/src/jit/AsmJSModule.h
@@ -216,30 +216,35 @@ class AsmJSModule
     class ExportedFunction
     {
         PropertyName *name_;
         PropertyName *maybeFieldName_;
         ArgCoercionVector argCoercions_;
         struct Pod {
             ReturnType returnType_;
             uint32_t codeOffset_;
+            uint32_t line_;
+            uint32_t column_;
         } pod;
 
         friend class AsmJSModule;
 
         ExportedFunction(PropertyName *name,
+                         uint32_t line, uint32_t column,
                          PropertyName *maybeFieldName,
                          ArgCoercionVector &&argCoercions,
                          ReturnType returnType)
         {
             name_ = name;
             maybeFieldName_ = maybeFieldName;
             argCoercions_ = mozilla::Move(argCoercions);
             pod.returnType_ = returnType;
             pod.codeOffset_ = UINT32_MAX;
+            pod.line_ = line;
+            pod.column_ = column;
             JS_ASSERT_IF(maybeFieldName_, name_->isTenured());
         }
 
         void trace(JSTracer *trc) {
             MarkStringUnbarriered(trc, &name_, "asm.js export name");
             if (maybeFieldName_)
                 MarkStringUnbarriered(trc, &maybeFieldName_, "asm.js export field");
         }
@@ -256,16 +261,22 @@ class AsmJSModule
         void initCodeOffset(unsigned off) {
             JS_ASSERT(pod.codeOffset_ == UINT32_MAX);
             pod.codeOffset_ = off;
         }
 
         PropertyName *name() const {
             return name_;
         }
+        uint32_t line() const {
+            return pod.line_;
+        }
+        uint32_t column() const {
+            return pod.column_;
+        }
         PropertyName *maybeFieldName() const {
             return maybeFieldName_;
         }
         unsigned numArgs() const {
             return argCoercions_.length();
         }
         AsmJSCoercion argCoercion(unsigned i) const {
             return argCoercions_[i];
@@ -535,21 +546,22 @@ class AsmJSModule
         pod.funcPtrTableAndExitBytes_ += sizeof(ExitDatum);
         *exitIndex = unsigned(exits_.length());
         return exits_.append(Exit(ffiIndex, globalDataOffset));
     }
     bool addFunctionCounts(jit::IonScriptCounts *counts) {
         return functionCounts_.append(counts);
     }
 
-    bool addExportedFunction(PropertyName *name, PropertyName *maybeFieldName,
+    bool addExportedFunction(PropertyName *name, uint32_t line, uint32_t column,
+                             PropertyName *maybeFieldName,
                              ArgCoercionVector &&argCoercions,
                              ReturnType returnType)
     {
-        ExportedFunction func(name, maybeFieldName, mozilla::Move(argCoercions), returnType);
+        ExportedFunction func(name, line, column, maybeFieldName, mozilla::Move(argCoercions), returnType);
         return exports_.append(mozilla::Move(func));
     }
     unsigned numExportedFunctions() const {
         return exports_.length();
     }
     const ExportedFunction &exportedFunction(unsigned i) const {
         return exports_[i];
     }
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -6191,36 +6191,36 @@ AutoFilename::get() const
 }
 
 JS_PUBLIC_API(bool)
 DescribeScriptedCaller(JSContext *cx, AutoFilename *filename, unsigned *lineno)
 {
     if (lineno)
         *lineno = 0;
 
-    NonBuiltinScriptFrameIter i(cx);
+    NonBuiltinFrameIter i(cx);
     if (i.done())
         return false;
 
     // If the caller is hidden, the embedding wants us to return false here so
     // that it can check its own stack (see HideScriptedCaller).
     if (i.activation()->scriptedCallerIsHidden())
         return false;
 
     if (filename)
-        filename->reset(i.script()->scriptSource());
+        filename->reset(i.scriptSource());
     if (lineno)
-        *lineno = js::PCToLineNumber(i.script(), i.pc());
+        *lineno = i.computeLine();
     return true;
 }
 
 JS_PUBLIC_API(JSObject *)
 GetScriptedCallerGlobal(JSContext *cx)
 {
-    NonBuiltinScriptFrameIter i(cx);
+    NonBuiltinFrameIter i(cx);
     if (i.done())
         return nullptr;
 
     // If the caller is hidden, the embedding wants us to return null here so
     // that it can check its own stack (see HideScriptedCaller).
     if (i.activation()->scriptedCallerIsHidden())
         return nullptr;
 
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -328,23 +328,23 @@ ReportError(JSContext *cx, const char *m
  */
 static void
 PopulateReportBlame(JSContext *cx, JSErrorReport *report)
 {
     /*
      * Walk stack until we find a frame that is associated with a non-builtin
      * rather than a builtin frame.
      */
-    NonBuiltinScriptFrameIter iter(cx);
+    NonBuiltinFrameIter iter(cx);
     if (iter.done())
         return;
 
-    report->filename = iter.script()->filename();
-    report->lineno = PCToLineNumber(iter.script(), iter.pc(), &report->column);
-    report->originPrincipals = iter.script()->originPrincipals();
+    report->filename = iter.scriptFilename();
+    report->lineno = iter.computeLine(&report->column);
+    report->originPrincipals = iter.originPrincipals();
 }
 
 /*
  * Since memory has been exhausted, avoid the normal error-handling path which
  * allocates an error object, report and callstack. If code is running, simply
  * throw the static atom "out of memory". If code is not running, call the
  * error reporter directly.
  *
--- a/js/src/jscntxtinlines.h
+++ b/js/src/jscntxtinlines.h
@@ -468,16 +468,19 @@ JSContext::currentScript(jsbytecode **pp
 #ifdef JS_ION
     if (act->isJit()) {
         JSScript *script = nullptr;
         js::jit::GetPcScript(const_cast<JSContext *>(this), &script, ppc);
         if (!allowCrossCompartment && script->compartment() != compartment())
             return nullptr;
         return script;
     }
+
+    if (act->isAsmJS())
+        return nullptr;
 #endif
 
     JS_ASSERT(act->isInterpreter());
 
     js::StackFrame *fp = act->asInterpreter()->current();
     JS_ASSERT(!fp->runningInJit());
 
     JSScript *script = fp->script();
--- a/js/src/jsexn.cpp
+++ b/js/src/jsexn.cpp
@@ -207,42 +207,42 @@ struct SuppressErrorsGuard
 JSString *
 js::ComputeStackString(JSContext *cx)
 {
     StringBuffer sb(cx);
 
     {
         RootedAtom atom(cx);
         SuppressErrorsGuard seg(cx);
-        for (NonBuiltinScriptFrameIter i(cx, ScriptFrameIter::ALL_CONTEXTS,
-                                         ScriptFrameIter::GO_THROUGH_SAVED,
-                                         cx->compartment()->principals);
-            !i.done(); ++i)
+        for (NonBuiltinFrameIter i(cx, FrameIter::ALL_CONTEXTS, FrameIter::GO_THROUGH_SAVED,
+                                   cx->compartment()->principals);
+             !i.done();
+             ++i)
         {
             /* First append the function name, if any. */
-            atom = nullptr;
-            if (i.isNonEvalFunctionFrame() && i.callee()->displayAtom())
-                atom = i.callee()->displayAtom();
+            if (i.isNonEvalFunctionFrame())
+                atom = i.functionDisplayAtom();
+            else
+                atom = nullptr;
             if (atom && !sb.append(atom))
                 return nullptr;
 
             /* Next a @ separating function name from source location. */
             if (!sb.append('@'))
                 return nullptr;
 
             /* Now the filename. */
-            RootedScript script(cx, i.script());
-            const char *cfilename = script->filename();
+            const char *cfilename = i.scriptFilename();
             if (!cfilename)
                 cfilename = "";
             if (!sb.appendInflated(cfilename, strlen(cfilename)))
                 return nullptr;
 
             uint32_t column = 0;
-            uint32_t line = PCToLineNumber(script, i.pc(), &column);
+            uint32_t line = i.computeLine(&column);
             // Now the line number
             if (!sb.append(':') || !NumberValueToStringBuffer(cx, NumberValue(line), sb))
                 return nullptr;
 
             // Finally, : followed by the column number (1-based, as in other browsers)
             // and a newline.
             if (!sb.append(':') || !NumberValueToStringBuffer(cx, NumberValue(column + 1), sb) ||
                 !sb.append('\n'))
@@ -299,40 +299,39 @@ Error(JSContext *cx, unsigned argc, Valu
     RootedString message(cx, nullptr);
     if (args.hasDefined(0)) {
         message = ToString<CanGC>(cx, args[0]);
         if (!message)
             return false;
     }
 
     /* Find the scripted caller. */
-    NonBuiltinScriptFrameIter iter(cx);
+    NonBuiltinFrameIter iter(cx);
 
     /* Set the 'fileName' property. */
-    RootedScript script(cx, iter.done() ? nullptr : iter.script());
     RootedString fileName(cx);
     if (args.length() > 1) {
         fileName = ToString<CanGC>(cx, args[1]);
     } else {
         fileName = cx->runtime()->emptyString;
         if (!iter.done()) {
-            if (const char *cfilename = script->filename())
+            if (const char *cfilename = iter.scriptFilename())
                 fileName = JS_NewStringCopyZ(cx, cfilename);
         }
     }
     if (!fileName)
         return false;
 
     /* Set the 'lineNumber' property. */
     uint32_t lineNumber, columnNumber = 0;
     if (args.length() > 2) {
         if (!ToUint32(cx, args[2], &lineNumber))
             return false;
     } else {
-        lineNumber = iter.done() ? 0 : PCToLineNumber(script, iter.pc(), &columnNumber);
+        lineNumber = iter.done() ? 0 : iter.computeLine(&columnNumber);
     }
 
     Rooted<JSString*> stack(cx, ComputeStackString(cx));
     if (!stack)
         return false;
 
     /*
      * ECMA ed. 3, 15.11.1 requires Error, etc., to construct even when
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -1419,39 +1419,41 @@ FunctionConstructor(JSContext *cx, unsig
     AutoKeepAtoms keepAtoms(cx->perThreadData);
     AutoNameVector formals(cx);
 
     bool hasRest = false;
 
     bool isStarGenerator = generatorKind == StarGenerator;
     JS_ASSERT(generatorKind != LegacyGenerator);
 
-    JSScript *script = nullptr;
+    JSScript *maybeScript = nullptr;
     const char *filename;
     unsigned lineno;
     JSPrincipals *originPrincipals;
     uint32_t pcOffset;
-    CurrentScriptFileLineOrigin(cx, &script, &filename, &lineno, &pcOffset, &originPrincipals);
+    DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno, &pcOffset,
+                                         &originPrincipals);
+
     JSPrincipals *principals = PrincipalsForCompiledCode(args, cx);
 
     const char *introductionType = "Function";
     if (generatorKind != NotGenerator)
         introductionType = "GeneratorFunction";
 
     const char *introducerFilename = filename;
-    if (script && script->scriptSource()->introducerFilename())
-        introducerFilename = script->scriptSource()->introducerFilename();
+    if (maybeScript && maybeScript->scriptSource()->introducerFilename())
+        introducerFilename = maybeScript->scriptSource()->introducerFilename();
 
     CompileOptions options(cx);
     options.setPrincipals(principals)
            .setOriginPrincipals(originPrincipals)
            .setFileAndLine(filename, 1)
            .setNoScriptRval(false)
            .setCompileAndGo(true)
-           .setIntroductionInfo(introducerFilename, introductionType, lineno, script, pcOffset);
+           .setIntroductionInfo(introducerFilename, introductionType, lineno, maybeScript, pcOffset);
 
     unsigned n = args.length() ? args.length() - 1 : 0;
     if (n > 0) {
         /*
          * Collect the function-argument arguments into one string, separated
          * by commas, then make a tokenstream from that string, and scan it to
          * get the arguments.  We need to throw the full scanner at the
          * problem, because the argument string can legitimately contain
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -1689,17 +1689,17 @@ ExpressionDecompiler::getOutput(char **r
     js_memcpy(*res, sprinter.stringAt(0), len);
     (*res)[len] = 0;
     return true;
 }
 
 }  // anonymous namespace
 
 static bool
-FindStartPC(JSContext *cx, ScriptFrameIter &iter, int spindex, int skipStackHits, Value v,
+FindStartPC(JSContext *cx, const FrameIter &iter, int spindex, int skipStackHits, Value v,
             jsbytecode **valuepc)
 {
     jsbytecode *current = *valuepc;
 
     if (spindex == JSDVG_IGNORE_STACK)
         return true;
 
     /*
@@ -1760,19 +1760,19 @@ DecompileExpressionFromStack(JSContext *
     /*
      * Give up if we need deterministic behavior for differential testing.
      * IonMonkey doesn't use StackFrames and this ensures we get the same
      * error messages.
      */
     return true;
 #endif
 
-    ScriptFrameIter frameIter(cx);
+    FrameIter frameIter(cx);
 
-    if (frameIter.done())
+    if (frameIter.done() || !frameIter.hasScript())
         return true;
 
     RootedScript script(cx, frameIter.script());
     AutoCompartment ac(cx, &script->global());
     jsbytecode *valuepc = frameIter.pc();
     RootedFunction fun(cx, frameIter.isFunctionFrame()
                            ? frameIter.callee()
                            : nullptr);
@@ -1838,25 +1838,25 @@ DecompileArgumentFromStack(JSContext *cx
     /* See note in DecompileExpressionFromStack. */
     return true;
 #endif
 
     /*
      * Settle on the nearest script frame, which should be the builtin that
      * called the intrinsic.
      */
-    ScriptFrameIter frameIter(cx);
+    FrameIter frameIter(cx);
     JS_ASSERT(!frameIter.done());
 
     /*
      * Get the second-to-top frame, the caller of the builtin that called the
      * intrinsic.
      */
     ++frameIter;
-    if (frameIter.done())
+    if (frameIter.done() || !frameIter.hasScript())
         return true;
 
     RootedScript script(cx, frameIter.script());
     AutoCompartment ac(cx, &script->global());
     jsbytecode *current = frameIter.pc();
     RootedFunction fun(cx, frameIter.isFunctionFrame()
                        ? frameIter.callee()
                        : nullptr);
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -2726,50 +2726,59 @@ js_GetScriptLineExtent(JSScript *script)
         if (maxLineNo < lineno)
             maxLineNo = lineno;
     }
 
     return 1 + maxLineNo - script->lineno();
 }
 
 void
-js::CurrentScriptFileLineOrigin(JSContext *cx, JSScript **script,
-                                const char **file, unsigned *linenop,
-                                uint32_t *pcOffset, JSPrincipals **origin, LineOption opt)
+js::DescribeScriptedCallerForCompilation(JSContext *cx, JSScript **maybeScript,
+                                         const char **file, unsigned *linenop,
+                                         uint32_t *pcOffset, JSPrincipals **origin,
+                                         LineOption opt)
 {
     if (opt == CALLED_FROM_JSOP_EVAL) {
         jsbytecode *pc = nullptr;
-        *script = cx->currentScript(&pc);
+        *maybeScript = cx->currentScript(&pc);
         JS_ASSERT(JSOp(*pc) == JSOP_EVAL || JSOp(*pc) == JSOP_SPREADEVAL);
         JS_ASSERT(*(pc + (JSOp(*pc) == JSOP_EVAL ? JSOP_EVAL_LENGTH
                                                  : JSOP_SPREADEVAL_LENGTH)) == JSOP_LINENO);
-        *file = (*script)->filename();
+        *file = (*maybeScript)->filename();
         *linenop = GET_UINT16(pc + (JSOp(*pc) == JSOP_EVAL ? JSOP_EVAL_LENGTH
                                                            : JSOP_SPREADEVAL_LENGTH));
-        *pcOffset = pc - (*script)->code();
-        *origin = (*script)->originPrincipals();
+        *pcOffset = pc - (*maybeScript)->code();
+        *origin = (*maybeScript)->originPrincipals();
         return;
     }
 
-    NonBuiltinScriptFrameIter iter(cx);
+    NonBuiltinFrameIter iter(cx);
 
     if (iter.done()) {
-        *script = nullptr;
+        *maybeScript = nullptr;
         *file = nullptr;
         *linenop = 0;
         *pcOffset = 0;
         *origin = cx->compartment()->principals;
         return;
     }
 
-    *script = iter.script();
-    *file = (*script)->filename();
-    *linenop = PCToLineNumber(*script, iter.pc());
-    *pcOffset = iter.pc() - (*script)->code();
-    *origin = (*script)->originPrincipals();
+    *file = iter.scriptFilename();
+    *linenop = iter.computeLine();
+    *origin = iter.originPrincipals();
+
+    // These values are only used for introducer fields which are debugging
+    // information and can be safely left null for asm.js frames.
+    if (iter.hasScript()) {
+        *maybeScript = iter.script();
+        *pcOffset = iter.pc() - (*maybeScript)->code();
+    } else {
+        *maybeScript = nullptr;
+        *pcOffset = 0;
+    }
 }
 
 template <class T>
 static inline T *
 Rebase(JSScript *dst, JSScript *src, T *srcp)
 {
     size_t off = reinterpret_cast<uint8_t *>(srcp) - src->data;
     return reinterpret_cast<T *>(dst->data + off);
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -1964,20 +1964,20 @@ PCToLineNumber(unsigned startLine, jssrc
  */
 
 enum LineOption {
     CALLED_FROM_JSOP_EVAL,
     NOT_CALLED_FROM_JSOP_EVAL
 };
 
 extern void
-CurrentScriptFileLineOrigin(JSContext *cx, JSScript **script,
-                            const char **file, unsigned *linenop,
-                            uint32_t *pcOffset, JSPrincipals **origin,
-                            LineOption opt = NOT_CALLED_FROM_JSOP_EVAL);
+DescribeScriptedCallerForCompilation(JSContext *cx, JSScript **maybeScript,
+                                     const char **file, unsigned *linenop,
+                                     uint32_t *pcOffset, JSPrincipals **origin,
+                                     LineOption opt = NOT_CALLED_FROM_JSOP_EVAL);
 
 bool
 CloneFunctionScript(JSContext *cx, HandleFunction original, HandleFunction clone,
                     NewObjectKind newKind = GenericObject);
 
 /*
  * JSAPI clients are allowed to leave CompileOptions.originPrincipals nullptr in
  * which case the JS engine sets options.originPrincipals = origin.principals.
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -478,18 +478,18 @@ js::Invoke(JSContext *cx, CallArgs args,
     if (!fun->getOrCreateScript(cx))
         return false;
 
     /* Run function until JSOP_RETRVAL, JSOP_RETURN or error. */
     InvokeState state(cx, args, initial);
 
     // Check to see if useNewType flag should be set for this frame.
     if (construct && cx->typeInferenceEnabled()) {
-        ScriptFrameIter iter(cx);
-        if (!iter.done()) {
+        FrameIter iter(cx);
+        if (!iter.done() && iter.hasScript()) {
             JSScript *script = iter.script();
             jsbytecode *pc = iter.pc();
             if (UseNewType(cx, script, pc))
                 state.setUseNewType();
         }
     }
 
     bool ok = RunScript(cx, state);
--- a/js/src/vm/OldDebugAPI.cpp
+++ b/js/src/vm/OldDebugAPI.cpp
@@ -895,18 +895,27 @@ CallContextDebugHandler(JSContext *cx, J
 
     return cx->runtime()->debugHooks.debuggerHandler(cx, script, bc, rval,
                                                      cx->runtime()->debugHooks.debuggerHandlerData);
 }
 
 JS_FRIEND_API(bool)
 js_CallContextDebugHandler(JSContext *cx)
 {
-    NonBuiltinScriptFrameIter iter(cx);
-    JS_ASSERT(!iter.done());
+    NonBuiltinFrameIter iter(cx);
+
+    // If there is no script to debug, then abort execution even if the user
+    // clicks 'Debug' in the slow-script dialog.
+    if (!iter.hasScript())
+        return false;
+
+    // Even if script was running during the operation callback, it's possible
+    // it was a builtin which 'iter' will have skipped over.
+    if (iter.done())
+        return false;
 
     RootedValue rval(cx);
     RootedScript script(cx, iter.script());
     switch (CallContextDebugHandler(cx, script, iter.pc(), rval.address())) {
       case JSTRAP_ERROR:
         JS_ClearPendingException(cx);
         return false;
       case JSTRAP_THROW:
--- 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/AsmJSModule.h"
 #include "jit/BaselineFrame.h"
 #include "jit/JitCompartment.h"
 #endif
 #include "vm/Opcodes.h"
 
 #include "jit/IonFrameIterator-inl.h"
 #include "vm/Interpreter-inl.h"
 #include "vm/Probes-inl.h"
@@ -45,18 +46,19 @@ StackFrame::initExecuteFrame(JSContext *
             JS_ASSERT(evalInFramePrev.isFunctionFrame() || evalInFramePrev.isGlobalFrame());
             if (evalInFramePrev.isFunctionFrame()) {
                 callee = evalInFramePrev.callee();
                 flags_ |= FUNCTION;
             } else {
                 flags_ |= GLOBAL;
             }
         } else {
-            ScriptFrameIter iter(cx);
+            FrameIter iter(cx);
             JS_ASSERT(iter.isFunctionFrame() || iter.isGlobalFrame());
+            JS_ASSERT(!iter.isAsmJS());
             if (iter.isFunctionFrame()) {
                 callee = iter.callee();
                 flags_ |= FUNCTION;
             } else {
                 flags_ |= GLOBAL;
             }
         }
     }
@@ -589,20 +591,21 @@ FrameIter::settleOnActivation()
         }
 
         // ForkJoin activations don't contain iterable frames, so skip them.
         if (activation->isForkJoin()) {
             ++data_.activations_;
             continue;
         }
 
-        // AsmJS activations will soon contain iterable frames, but not atm.
+        // Until asm.js has real stack-walking, we have each AsmJSActivation
+        // expose a single function (the entry function).
         if (activation->isAsmJS()) {
-            ++data_.activations_;
-            continue;
+            data_.state_ = ASMJS;
+            return;
         }
 #endif
 
         JS_ASSERT(activation->isInterpreter());
 
         InterpreterActivation *interpAct = activation->asInterpreter();
         data_.interpFrames_ = InterpreterFrameIterator(interpAct);
 
@@ -770,17 +773,22 @@ FrameIter::operator++()
       case JIT:
 #ifdef JS_ION
         popJitFrame();
         break;
 #else
         MOZ_ASSUME_UNREACHABLE("Unexpected state");
 #endif
       case ASMJS:
-        MOZ_ASSUME_UNREACHABLE("Next patch...");
+        // As described in settleOnActivation, an AsmJSActivation currently only
+        // represents a single asm.js function, so, if the FrameIter is
+        // currently stopped on an ASMJS frame, then we can pop the entire
+        // AsmJSActivation.
+        popActivation();
+        break;
     }
     return *this;
 }
 
 FrameIter::Data *
 FrameIter::copyData() const
 {
 #ifdef JS_ION
@@ -914,16 +922,126 @@ FrameIter::isGeneratorFrame() const
       case JIT:
         return false;
       case ASMJS:
         return false;
     }
     MOZ_ASSUME_UNREACHABLE("Unexpected state");
 }
 
+JSAtom *
+FrameIter::functionDisplayAtom() const
+{
+    JS_ASSERT(isNonEvalFunctionFrame());
+
+    switch (data_.state_) {
+      case DONE:
+        break;
+      case INTERP:
+      case JIT:
+        return callee()->displayAtom();
+      case ASMJS: {
+#ifdef JS_ION
+        AsmJSActivation &act = *data_.activations_.activation()->asAsmJS();
+        return act.module().exportedFunction(act.exportIndex()).name();
+#else
+        break;
+#endif
+      }
+    }
+
+    MOZ_ASSUME_UNREACHABLE("Unexpected state");
+}
+
+ScriptSource *
+FrameIter::scriptSource() const
+{
+    switch (data_.state_) {
+      case DONE:
+        break;
+      case INTERP:
+      case JIT:
+        return script()->scriptSource();
+      case ASMJS:
+#ifdef JS_ION
+        return data_.activations_.activation()->asAsmJS()->module().scriptSource();
+#else
+        break;
+#endif
+    }
+
+    MOZ_ASSUME_UNREACHABLE("Unexpected state");
+}
+
+const char *
+FrameIter::scriptFilename() const
+{
+    switch (data_.state_) {
+      case DONE:
+        break;
+      case INTERP:
+      case JIT:
+        return script()->filename();
+      case ASMJS:
+#ifdef JS_ION
+        return data_.activations_.activation()->asAsmJS()->module().scriptSource()->filename();
+#else
+        break;
+#endif
+    }
+
+    MOZ_ASSUME_UNREACHABLE("Unexpected state");
+}
+
+unsigned
+FrameIter::computeLine(uint32_t *column) const
+{
+    switch (data_.state_) {
+      case DONE:
+        break;
+      case INTERP:
+      case JIT:
+        return PCToLineNumber(script(), pc(), column);
+      case ASMJS: {
+#ifdef JS_ION
+        AsmJSActivation &act = *data_.activations_.activation()->asAsmJS();
+        AsmJSModule::ExportedFunction &func = act.module().exportedFunction(act.exportIndex());
+        if (column)
+            *column = func.column();
+        return func.line();
+#else
+        break;
+#endif
+      }
+    }
+
+    MOZ_ASSUME_UNREACHABLE("Unexpected state");
+}
+
+JSPrincipals *
+FrameIter::originPrincipals() const
+{
+    switch (data_.state_) {
+      case DONE:
+        break;
+      case INTERP:
+      case JIT:
+        return script()->originPrincipals();
+      case ASMJS: {
+#ifdef JS_ION
+        return data_.activations_.activation()->asAsmJS()->module().scriptSource()->originPrincipals();
+#else
+        break;
+#endif
+      }
+    }
+
+    MOZ_ASSUME_UNREACHABLE("Unexpected state");
+}
+
 bool
 FrameIter::isConstructing() const
 {
     switch (data_.state_) {
       case DONE:
       case ASMJS:
         break;
       case JIT:
@@ -1317,16 +1435,25 @@ js::SelfHostedFramesVisible()
         char *env = getenv("MOZ_SHOW_ALL_JS_FRAMES");
         visible = !!env;
     }
     return visible;
 }
 #endif
 
 void
+NonBuiltinFrameIter::settle()
+{
+    if (!SelfHostedFramesVisible()) {
+        while (!done() && hasScript() && script()->selfHosted())
+            FrameIter::operator++();
+    }
+}
+
+void
 NonBuiltinScriptFrameIter::settle()
 {
     if (!SelfHostedFramesVisible()) {
         while (!done() && script()->selfHosted())
             ScriptFrameIter::operator++();
     }
 }
 
@@ -1409,22 +1536,23 @@ jit::JitActivation::setActive(JSContext 
         prevJitJSContext_ = cx->mainThread().jitJSContext;
         cx->mainThread().jitJSContext = cx;
     } else {
         cx->mainThread().ionTop = prevIonTop_;
         cx->mainThread().jitJSContext = prevJitJSContext_;
     }
 }
 
-AsmJSActivation::AsmJSActivation(JSContext *cx, AsmJSModule &module)
+AsmJSActivation::AsmJSActivation(JSContext *cx, AsmJSModule &module, unsigned exportIndex)
   : Activation(cx, AsmJS),
     module_(module),
     errorRejoinSP_(nullptr),
     profiler_(nullptr),
-    resumePC_(nullptr)
+    resumePC_(nullptr),
+    exportIndex_(exportIndex)
 {
     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
@@ -1427,22 +1427,27 @@ class InterpreterFrameIterator
 class AsmJSActivation : public Activation
 {
     AsmJSModule &module_;
     AsmJSActivation *prevAsmJS_;
     void *errorRejoinSP_;
     SPSProfiler *profiler_;
     void *resumePC_;
 
+    // These bits are temporary and will be replaced when real asm.js
+    // stack-walking support lands:
+    unsigned exportIndex_;
+
   public:
-    AsmJSActivation(JSContext *cx, AsmJSModule &module);
+    AsmJSActivation(JSContext *cx, AsmJSModule &module, unsigned exportIndex);
     ~AsmJSActivation();
 
     JSContext *cx() { return cx_; }
     AsmJSModule &module() const { return module_; }
+    unsigned exportIndex() const { return exportIndex_; }
     AsmJSActivation *prevAsmJS() const { return prevAsmJS_; }
 
     // Read by JIT code:
     static unsigned offsetOfContext() { return offsetof(AsmJSActivation, cx_); }
     static unsigned offsetOfResumePC() { return offsetof(AsmJSActivation, resumePC_); }
 
     // Initialized by JIT code:
     static unsigned offsetOfErrorRejoinSP() { return offsetof(AsmJSActivation, errorRejoinSP_); }
@@ -1531,16 +1536,22 @@ class FrameIter
 
     bool isFunctionFrame() const;
     bool isGlobalFrame() const;
     bool isEvalFrame() const;
     bool isNonEvalFunctionFrame() const;
     bool isGeneratorFrame() const;
     bool hasArgs() const { return isNonEvalFunctionFrame(); }
 
+    ScriptSource *scriptSource() const;
+    const char *scriptFilename() const;
+    unsigned computeLine(uint32_t *column = nullptr) const;
+    JSAtom *functionDisplayAtom() const;
+    JSPrincipals *originPrincipals() const;
+
     bool hasScript() const { return !isAsmJS(); }
 
     // -----------------------------------------------------------
     // The following functions can only be called when hasScript()
     // -----------------------------------------------------------
 
     inline JSScript *script() const;
 
@@ -1608,28 +1619,32 @@ class ScriptFrameIter : public FrameIter
     void settle() {
         while (!done() && !hasScript())
             FrameIter::operator++();
     }
 
   public:
     ScriptFrameIter(JSContext *cx, SavedOption savedOption = STOP_AT_SAVED)
       : FrameIter(cx, savedOption)
-    {}
+    {
+        settle();
+    }
 
     ScriptFrameIter(JSContext *cx,
                     ContextOption cxOption,
                     SavedOption savedOption,
                     JSPrincipals *prin = nullptr)
       : FrameIter(cx, cxOption, savedOption, prin)
-    {}
+    {
+        settle();
+    }
 
-    ScriptFrameIter(const ScriptFrameIter &iter) : FrameIter(iter) {}
-    ScriptFrameIter(const FrameIter::Data &data) : FrameIter(data) {}
-    ScriptFrameIter(AbstractFramePtr frame) : FrameIter(frame) {}
+    ScriptFrameIter(const ScriptFrameIter &iter) : FrameIter(iter) { settle(); }
+    ScriptFrameIter(const FrameIter::Data &data) : FrameIter(data) { settle(); }
+    ScriptFrameIter(AbstractFramePtr frame) : FrameIter(frame) { settle(); }
 
     ScriptFrameIter &operator++() {
         FrameIter::operator++();
         settle();
         return *this;
     }
 };
 
@@ -1638,16 +1653,49 @@ bool SelfHostedFramesVisible();
 #else
 static inline bool
 SelfHostedFramesVisible()
 {
     return false;
 }
 #endif
 
+/* A filtering of the FrameIter to only stop at non-self-hosted scripts. */
+class NonBuiltinFrameIter : public FrameIter
+{
+    void settle();
+
+  public:
+    NonBuiltinFrameIter(JSContext *cx,
+                        FrameIter::SavedOption opt = FrameIter::STOP_AT_SAVED)
+      : FrameIter(cx, opt)
+    {
+        settle();
+    }
+
+    NonBuiltinFrameIter(JSContext *cx,
+                        FrameIter::ContextOption contextOption,
+                        FrameIter::SavedOption savedOption,
+                        JSPrincipals *principals = nullptr)
+      : FrameIter(cx, contextOption, savedOption, principals)
+    {
+        settle();
+    }
+
+    NonBuiltinFrameIter(const FrameIter::Data &data)
+      : FrameIter(data)
+    {}
+
+    NonBuiltinFrameIter &operator++() {
+        FrameIter::operator++();
+        settle();
+        return *this;
+    }
+};
+
 /* A filtering of the ScriptFrameIter to only stop at non-self-hosted scripts. */
 class NonBuiltinScriptFrameIter : public ScriptFrameIter
 {
     void settle();
 
   public:
     NonBuiltinScriptFrameIter(JSContext *cx,
                               ScriptFrameIter::SavedOption opt = ScriptFrameIter::STOP_AT_SAVED)