From d93f49c8ed3c15d8bcce297f7470f25ec0185c31 Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Wed, 16 Apr 2014 18:46:03 -0500 Subject: [PATCH] Bug 998490 - OdinMonkey: make asm.js frames show up in FrameIter (r=dougc) --HG-- extra : rebase_source : 8ed09da1be0afda32c1f669a6736b266019a10ae --- .../jit-test/tests/asm.js/testStackWalking.js | 85 ++++++-- js/src/jit/AsmJS.cpp | 187 ++++++++++-------- js/src/jit/AsmJSLink.cpp | 101 +++++++++- js/src/jit/AsmJSLink.h | 25 +++ js/src/jit/AsmJSModule.cpp | 35 ++++ js/src/jit/AsmJSModule.h | 62 ++++-- js/src/jit/AsmJSSignalHandlers.cpp | 8 +- js/src/jit/CodeGenerator.cpp | 7 +- js/src/jit/MIR.cpp | 8 +- js/src/jit/MIR.h | 12 +- js/src/jit/arm/MacroAssembler-arm.cpp | 13 ++ js/src/jit/arm/MacroAssembler-arm.h | 36 +++- js/src/jit/arm/Simulator-arm.cpp | 2 +- js/src/jit/shared/Assembler-shared.h | 75 +++++++ js/src/jit/shared/MacroAssembler-x86-shared.h | 20 ++ js/src/jit/x64/MacroAssembler-x64.h | 8 + js/src/jit/x86/MacroAssembler-x86.h | 8 + js/src/vm/Stack.cpp | 53 ++--- js/src/vm/Stack.h | 20 +- 19 files changed, 611 insertions(+), 154 deletions(-) diff --git a/js/src/jit-test/tests/asm.js/testStackWalking.js b/js/src/jit-test/tests/asm.js/testStackWalking.js index b1f51a5e89ca..29ade6757f25 100644 --- a/js/src/jit-test/tests/asm.js/testStackWalking.js +++ b/js/src/jit-test/tests/asm.js/testStackWalking.js @@ -1,38 +1,93 @@ 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"); +function matchStack(stackString, stackArray) +{ + var match = 0; + for (name of stackArray) { + match = stackString.indexOf(name, match); + if (match === -1) + throw name + " not found in the stack " + stack; + } +} var stack; -function dumpStack(i) { stack = new Error().stack; return i+11 } +function dumpStack() +{ + stack = new Error().stack +} -var asmfun1 = asmLink(callFFI1, null, {ffi:dumpStack}); -assertEq(asmfun1(), 12); -assertEq(stack.indexOf("asmfun1") === -1, false); +var callFFI = asmCompile('global', 'ffis', USE_ASM + "var ffi=ffis.ffi; function f() { return ffi()|0 } return f"); -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 f = asmLink(callFFI, null, {ffi:dumpStack}); +for (var i = 0; i < 5000; i++) { + stack = null; + f(); + matchStack(stack, ['dumpStack', 'f']); +} + +if (isAsmJSCompilationAvailable() && isCachingEnabled()) { + var callFFI = asmCompile('global', 'ffis', USE_ASM + "var ffi=ffis.ffi; function f() { return ffi()|0 } return f"); + assertEq(isAsmJSModuleLoadedFromCache(callFFI), true); + stack = null; + f(); + matchStack(stack, ['dumpStack', 'f']); +} + +var f1 = asmLink(callFFI, null, {ffi:dumpStack}); +var f2 = asmLink(callFFI, null, {ffi:function middle() { f1() }}); +stack = null; +(function outer() { f2() })(); +matchStack(stack, ["dumpStack", "f", "middle", "f"]); + +function returnStackDumper() { return { valueOf:function() { stack = new Error().stack } } } +var f = asmLink(callFFI, null, {ffi:returnStackDumper}); +for (var i = 0; i < 5000; i++) { + stack = null; + f(); + matchStack(stack, ['valueOf', 'f']); +} var caught = false; try { + stack = null; asmLink(asmCompile(USE_ASM + "function asmRec() { asmRec() } return asmRec"))(); } catch (e) { caught = true; + matchStack(e.stack, ['asmRec', 'asmRec', 'asmRec', 'asmRec']); } assertEq(caught, true); var caught = false; try { - callFFI1(null, {ffi:Object.preventExtensions})(); + callFFI(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); +asmLink(callFFI, null, {ffi:eval})(); +asmLink(callFFI, null, {ffi:Function})(); +asmLink(callFFI, null, {ffi:Error})(); + +var manyCalls = asmCompile('global', 'ffis', + USE_ASM + + "var ffi=ffis.ffi;\ + function f1(a,b,c,d,e,f,g,h,i,j,k) { \ + a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;k=k|0; \ + ffi(); \ + return (a+b+c+d+e+f+g+h+i+j+k)|0; \ + } \ + function f2() { \ + return f1(1,2,3,4,5,6,7,8,f1(1,2,3,4,5,6,7,8,9,10,11)|0,10,11)|0; \ + } \ + function f3() { return 13 } \ + function f4(i) { \ + i=i|0; \ + return TBL[i&3]()|0; \ + } \ + var TBL=[f3, f3, f2, f3]; \ + return f4;"); +stack = null; +assertEq(asmLink(manyCalls, null, {ffi:dumpStack})(2), 123); +matchStack(stack, ['dumpStack', 'f1', 'f2', 'f4']); diff --git a/js/src/jit/AsmJS.cpp b/js/src/jit/AsmJS.cpp index 6f24655abfe3..fe1691183ae9 100644 --- a/js/src/jit/AsmJS.cpp +++ b/js/src/jit/AsmJS.cpp @@ -1089,9 +1089,9 @@ class MOZ_STACK_CLASS ModuleCompiler ~ModuleCompiler() { if (errorString_) { JS_ASSERT(errorOffset_ != UINT32_MAX); - parser_.tokenStream.reportAsmJSError(errorOffset_, - JSMSG_USE_ASM_TYPE_FAIL, - errorString_); + tokenStream().reportAsmJSError(errorOffset_, + JSMSG_USE_ASM_TYPE_FAIL, + errorString_); js_free(errorString_); } if (errorOverRecursed_) @@ -1140,7 +1140,7 @@ class MOZ_STACK_CLASS ModuleCompiler } uint32_t funcStart = parser_.pc->maybeFunction->pn_body->pn_pos.begin; - uint32_t offsetToEndOfUseAsm = parser_.tokenStream.currentToken().pos.end; + uint32_t offsetToEndOfUseAsm = tokenStream().currentToken().pos.end; // "use strict" should be added to the source if we are in an implicit // strict context, see also comment above addUseStrict in @@ -1172,14 +1172,14 @@ class MOZ_STACK_CLASS ModuleCompiler // Since pn is typically only null under OOM, this suppression simply forces any GC to be // delayed until the compilation is off the stack and more memory can be freed. gc::AutoSuppressGC nogc(cx_); - return failOffset(parser_.tokenStream.peekTokenPos().begin, str); + return failOffset(tokenStream().peekTokenPos().begin, str); } bool failfVA(ParseNode *pn, const char *fmt, va_list ap) { JS_ASSERT(!errorString_); JS_ASSERT(errorOffset_ == UINT32_MAX); JS_ASSERT(fmt); - errorOffset_ = pn ? pn->pn_pos.begin : parser_.tokenStream.currentToken().pos.end; + errorOffset_ = pn ? pn->pn_pos.begin : tokenStream().currentToken().pos.end; errorString_ = JS_vsmprintf(fmt, ap); return false; } @@ -1214,7 +1214,7 @@ class MOZ_STACK_CLASS ModuleCompiler SlowFunction sf; sf.name = func.name(); sf.ms = func.compileTime(); - parser_.tokenStream.srcCoords.lineNumAndColumnIndex(func.srcOffset(), &sf.line, &sf.column); + tokenStream().srcCoords.lineNumAndColumnIndex(func.srcOffset(), &sf.line, &sf.column); return slowFunctions_.append(sf); } @@ -1222,6 +1222,7 @@ class MOZ_STACK_CLASS ModuleCompiler ExclusiveContext *cx() const { return cx_; } AsmJSParser &parser() const { return parser_; } + TokenStream &tokenStream() const { return parser_.tokenStream; } MacroAssembler &masm() { return masm_; } Label &stackOverflowLabel() { return stackOverflowLabel_; } Label &interruptLabel() { return interruptLabel_; } @@ -1395,11 +1396,8 @@ class MOZ_STACK_CLASS ModuleCompiler for (unsigned i = 0; i < args.length(); i++) argCoercions[i] = args[i].toCoercion(); AsmJSModule::ReturnType retType = func->sig().retType().toModuleReturnType(); - uint32_t line, column; - parser_.tokenStream.srcCoords.lineNumAndColumnIndex(func->srcOffset(), &line, &column); - return module_->addExportedFunction(func->name(), line, column, - func->srcOffset(), func->endOffset(), maybeFieldName, - Move(argCoercions), retType); + return module_->addExportedFunction(func->name(), func->srcOffset(), func->endOffset(), + maybeFieldName, Move(argCoercions), retType); } bool addExit(unsigned ffiIndex, PropertyName *name, Signature &&sig, unsigned *exitIndex) { ExitDescriptor exitDescriptor(name, Move(sig)); @@ -1412,6 +1410,9 @@ class MOZ_STACK_CLASS ModuleCompiler return false; return exits_.add(p, Move(exitDescriptor), *exitIndex); } + bool addFunctionName(PropertyName *name, uint32_t *index) { + return module_->addFunctionName(name, index); + } // Note a constraint on the minimum size of the heap. The heap size is // constrained when linking to be at least the maximum of all such constraints. @@ -1428,7 +1429,7 @@ class MOZ_STACK_CLASS ModuleCompiler #if defined(MOZ_VTUNE) || defined(JS_ION_PERF) bool trackProfiledFunction(const Func &func, unsigned endCodeOffset) { unsigned lineno = 0U, columnIndex = 0U; - parser().tokenStream.srcCoords.lineNumAndColumnIndex(func.srcOffset(), &lineno, &columnIndex); + tokenStream().srcCoords.lineNumAndColumnIndex(func.srcOffset(), &lineno, &columnIndex); unsigned startCodeOffset = func.code()->offset(); return module_->trackProfiledFunction(func.name(), startCodeOffset, endCodeOffset, lineno, columnIndex); @@ -1501,12 +1502,13 @@ class MOZ_STACK_CLASS ModuleCompiler bool finish(ScopedJSDeletePtr *module) { - module_->initFuncEnd(parser_.tokenStream.currentToken().pos.end, - parser_.tokenStream.peekTokenPos().end); + module_->initFuncEnd(tokenStream().currentToken().pos.end, + tokenStream().peekTokenPos().end); masm_.finish(); if (masm_.oom()) return false; + module_->assignCallSites(masm_.extractCallSites()); module_->assignHeapAccesses(masm_.extractAsmJSHeapAccesses()); #if defined(JS_CODEGEN_ARM) @@ -1516,6 +1518,10 @@ class MOZ_STACK_CLASS ModuleCompiler AsmJSHeapAccess &a = module_->heapAccess(i); a.setOffset(masm_.actualOffset(a.offset())); } + for (unsigned i = 0; i < module_->numCallSites(); i++) { + CallSite &c = module_->callSite(i); + c.setReturnAddressOffset(masm_.actualOffset(c.returnAddressOffset())); + } #endif // The returned memory is owned by module_. @@ -1591,7 +1597,7 @@ class MOZ_STACK_CLASS ModuleCompiler // address of the global. Globals are allocated sequentially after the // code section so we can just use an RelativeLink. for (unsigned i = 0; i < masm_.numAsmJSGlobalAccesses(); i++) { - AsmJSGlobalAccess a = masm_.asmJSGlobalAccesses(i); + AsmJSGlobalAccess a = masm_.asmJSGlobalAccess(i); AsmJSModule::RelativeLink link; link.patchAtOffset = masm_.labelOffsetToPatchOffset(a.patchAt.offset()); link.targetOffset = module_->offsetOfGlobalData() + a.globalDataOffset; @@ -1887,6 +1893,7 @@ class FunctionCompiler ModuleCompiler & m_; LifoAlloc & lifo_; ParseNode * fn_; + uint32_t functionNameIndex_; LocalMap locals_; VarInitializerVector varInitializers_; @@ -1907,11 +1914,15 @@ class FunctionCompiler LabeledBlockMap labeledBreaks_; LabeledBlockMap labeledContinues_; + static const uint32_t NO_FUNCTION_NAME_INDEX = UINT32_MAX; + JS_STATIC_ASSERT(NO_FUNCTION_NAME_INDEX > CallSiteDesc::FUNCTION_NAME_INDEX_MAX); + public: FunctionCompiler(ModuleCompiler &m, ParseNode *fn, LifoAlloc &lifo) : m_(m), lifo_(lifo), fn_(fn), + functionNameIndex_(NO_FUNCTION_NAME_INDEX), locals_(m.cx()), varInitializers_(m.cx()), alloc_(nullptr), @@ -2261,6 +2272,7 @@ class FunctionCompiler class Call { + ParseNode *node_; ABIArgGenerator abi_; uint32_t prevMaxStackBytes_; uint32_t maxChildStackBytes_; @@ -2273,15 +2285,16 @@ class FunctionCompiler friend class FunctionCompiler; public: - Call(FunctionCompiler &f, RetType retType) - : prevMaxStackBytes_(0), + Call(FunctionCompiler &f, ParseNode *callNode, RetType retType) + : node_(callNode), + prevMaxStackBytes_(0), maxChildStackBytes_(0), spIncrement_(0), sig_(f.m().lifo(), retType), regArgs_(f.cx()), stackArgs_(f.cx()), childClobbers_(false) - {} + { } Signature &sig() { return sig_; } const Signature &sig() const { return sig_; } }; @@ -2347,10 +2360,21 @@ class FunctionCompiler *def = nullptr; return true; } - MAsmJSCall *ins = MAsmJSCall::New(alloc(), callee, call.regArgs_, returnType, + + uint32_t line, column; + m_.tokenStream().srcCoords.lineNumAndColumnIndex(call.node_->pn_pos.begin, &line, &column); + + if (functionNameIndex_ == NO_FUNCTION_NAME_INDEX) { + if (!m_.addFunctionName(FunctionName(fn_), &functionNameIndex_)) + return false; + } + + CallSiteDesc desc(line, column, functionNameIndex_); + MAsmJSCall *ins = MAsmJSCall::New(alloc(), desc, callee, call.regArgs_, returnType, call.spIncrement_); if (!ins) return false; + curBlock_->add(ins); *def = ins; return true; @@ -2751,7 +2775,7 @@ class FunctionCompiler #if defined(JS_ION_PERF) if (pn) { unsigned line = 0U, column = 0U; - m().parser().tokenStream.srcCoords.lineNumAndColumnIndex(pn->pn_pos.begin, &line, &column); + m().tokenStream().srcCoords.lineNumAndColumnIndex(pn->pn_pos.begin, &line, &column); blk->setLineno(line); blk->setColumnIndex(column); } @@ -3866,7 +3890,7 @@ static bool CheckInternalCall(FunctionCompiler &f, ParseNode *callNode, PropertyName *calleeName, RetType retType, MDefinition **def, Type *type) { - FunctionCompiler::Call call(f, retType); + FunctionCompiler::Call call(f, callNode, retType); if (!CheckCallArgs(f, callNode, CheckIsVarType, &call)) return false; @@ -3942,7 +3966,7 @@ CheckFuncPtrCall(FunctionCompiler &f, ParseNode *callNode, RetType retType, MDef if (!indexType.isIntish()) return f.failf(indexNode, "%s is not a subtype of intish", indexType.toChars()); - FunctionCompiler::Call call(f, retType); + FunctionCompiler::Call call(f, callNode, retType); if (!CheckCallArgs(f, callNode, CheckIsVarType, &call)) return false; @@ -3975,7 +3999,7 @@ CheckFFICall(FunctionCompiler &f, ParseNode *callNode, unsigned ffiIndex, RetTyp if (retType == RetType::Float) return f.fail(callNode, "FFI calls can't return float"); - FunctionCompiler::Call call(f, retType); + FunctionCompiler::Call call(f, callNode, retType); if (!CheckCallArgs(f, callNode, CheckIsExternType, &call)) return false; @@ -4100,7 +4124,7 @@ CheckMathBuiltinCall(FunctionCompiler &f, ParseNode *callNode, AsmJSMathBuiltinF if (retType != RetType::Double && retType != RetType::Float) return f.failf(callNode, "return type of math function is double or float, used as %s", retType.toType().toChars()); - FunctionCompiler::Call call(f, retType); + FunctionCompiler::Call call(f, callNode, retType); if (retType == RetType::Float && !CheckCallArgs(f, callNode, CheckIsMaybeFloat, &call)) return false; if (retType == RetType::Double && !CheckCallArgs(f, callNode, CheckIsMaybeDouble, &call)) @@ -5302,7 +5326,7 @@ CheckStatement(FunctionCompiler &f, ParseNode *stmt, LabelVector *maybeLabels) static bool ParseFunction(ModuleCompiler &m, ParseNode **fnOut) { - TokenStream &tokenStream = m.parser().tokenStream; + TokenStream &tokenStream = m.tokenStream(); DebugOnly tk = tokenStream.getToken(); JS_ASSERT(tk == TOK_FUNCTION); @@ -5342,7 +5366,7 @@ ParseFunction(ModuleCompiler &m, ParseNode **fnOut) AsmJSParseContext funpc(&m.parser(), outerpc, fn, funbox, &newDirectives, outerpc->staticLevel + 1, outerpc->blockidGen, /* blockScopeDepth = */ 0); - if (!funpc.init(m.parser().tokenStream)) + if (!funpc.init(tokenStream)) return false; if (!m.parser().functionArgsAndBodyGeneric(fn, fun, Normal, Statement, &newDirectives)) @@ -6022,6 +6046,16 @@ StackDecrementForCall(MacroAssembler &masm, const VectorT &argTypes, unsigned ex static const unsigned FramePushedAfterSave = NonVolatileRegs.gprs().size() * sizeof(intptr_t) + NonVolatileRegs.fpus().size() * sizeof(double); +// On arm, 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. +#ifdef JS_CODEGEN_ARM +static const unsigned MaybeRetAddr = sizeof(void*); +#else +static const unsigned MaybeRetAddr = 0; +#endif + static bool GenerateEntry(ModuleCompiler &m, const AsmJSModule::ExportedFunction &exportedFunc) { @@ -6101,7 +6135,7 @@ GenerateEntry(ModuleCompiler &m, const AsmJSModule::ExportedFunction &exportedFu // Call into the real function. AssertStackAlignment(masm); - masm.call(func.code()); + masm.call(CallSiteDesc::Entry(), func.code()); // Pop the stack and recover the original 'argv' argument passed to the // trampoline (which was pushed on the stack). @@ -6295,15 +6329,15 @@ GenerateFFIInterpreterExit(ModuleCompiler &m, const ModuleCompiler::ExitDescript MIRTypeVector invokeArgTypes(m.cx()); invokeArgTypes.infallibleAppend(typeArray, ArrayLength(typeArray)); - // Reserve space for a call to InvokeFromAsmJS_* and an array of values - // passed to this FFI call. + // The stack layout looks like: + // | return address | stack arguments | array of values | unsigned arraySize = Max(1, exit.sig().args().length()) * sizeof(Value); - unsigned stackDec = StackDecrementForCall(masm, invokeArgTypes, arraySize); + unsigned stackDec = StackDecrementForCall(masm, invokeArgTypes, arraySize + MaybeRetAddr); masm.reserveStack(stackDec); // Fill the argument array. unsigned offsetToCallerStackArgs = AlignmentAtPrologue + masm.framePushed(); - unsigned offsetToArgv = StackArgBytes(invokeArgTypes); + unsigned offsetToArgv = StackArgBytes(invokeArgTypes) + MaybeRetAddr; Register scratch = ABIArgGenerator::NonArgReturnVolatileReg0; FillArgumentArray(m, exit.sig().args(), offsetToArgv, offsetToCallerStackArgs, scratch); @@ -6312,6 +6346,9 @@ GenerateFFIInterpreterExit(ModuleCompiler &m, const ModuleCompiler::ExitDescript Register activation = ABIArgGenerator::NonArgReturnVolatileReg1; LoadAsmJSActivationIntoRegister(masm, activation); + // Record sp in the AsmJSActivation for stack-walking. + masm.storePtr(StackPointer, Address(activation, AsmJSActivation::offsetOfExitSP())); + // argument 0: cx if (i->kind() == ABIArg::GPR) { LoadJSContextFromActivation(masm, activation, i->gpr()); @@ -6351,16 +6388,16 @@ GenerateFFIInterpreterExit(ModuleCompiler &m, const ModuleCompiler::ExitDescript AssertStackAlignment(masm); switch (exit.sig().retType().which()) { case RetType::Void: - masm.call(AsmJSImm_InvokeFromAsmJS_Ignore); + masm.callExit(AsmJSImm_InvokeFromAsmJS_Ignore, i.stackBytesConsumedSoFar()); masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel); break; case RetType::Signed: - masm.call(AsmJSImm_InvokeFromAsmJS_ToInt32); + masm.callExit(AsmJSImm_InvokeFromAsmJS_ToInt32, i.stackBytesConsumedSoFar()); masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel); masm.unboxInt32(argv, ReturnReg); break; case RetType::Double: - masm.call(AsmJSImm_InvokeFromAsmJS_ToNumber); + masm.callExit(AsmJSImm_InvokeFromAsmJS_ToNumber, i.stackBytesConsumedSoFar()); masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel); masm.loadDouble(argv, ReturnFloatReg); break; @@ -6389,16 +6426,20 @@ GenerateOOLConvert(ModuleCompiler &m, RetType retType, Label *throwLabel) // the stack usage here needs to kept in sync with GenerateFFIIonExit. // Store value - unsigned offsetToArgv = StackArgBytes(callArgTypes); + unsigned offsetToArgv = StackArgBytes(callArgTypes) + MaybeRetAddr; masm.storeValue(JSReturnOperand, Address(StackPointer, offsetToArgv)); + Register scratch = ABIArgGenerator::NonArgReturnVolatileReg0; + Register activation = ABIArgGenerator::NonArgReturnVolatileReg1; + LoadAsmJSActivationIntoRegister(masm, activation); + + // Record sp in the AsmJSActivation for stack-walking. + masm.storePtr(StackPointer, Address(activation, AsmJSActivation::offsetOfExitSP())); + // Store real arguments ABIArgMIRTypeIter i(callArgTypes); - Register scratch = ABIArgGenerator::NonArgReturnVolatileReg0; // argument 0: cx - Register activation = ABIArgGenerator::NonArgReturnVolatileReg1; - LoadAsmJSActivationIntoRegister(masm, activation); if (i->kind() == ABIArg::GPR) { LoadJSContextFromActivation(masm, activation, i->gpr()); } else { @@ -6422,17 +6463,17 @@ GenerateOOLConvert(ModuleCompiler &m, RetType retType, Label *throwLabel) AssertStackAlignment(masm); switch (retType.which()) { case RetType::Signed: - masm.call(AsmJSImm_CoerceInPlace_ToInt32); - masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel); - masm.unboxInt32(Address(StackPointer, offsetToArgv), ReturnReg); - break; + masm.callExit(AsmJSImm_CoerceInPlace_ToInt32, i.stackBytesConsumedSoFar()); + masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel); + masm.unboxInt32(Address(StackPointer, offsetToArgv), ReturnReg); + break; case RetType::Double: - masm.call(AsmJSImm_CoerceInPlace_ToNumber); - masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel); - masm.loadDouble(Address(StackPointer, offsetToArgv), ReturnFloatReg); - break; + masm.callExit(AsmJSImm_CoerceInPlace_ToNumber, i.stackBytesConsumedSoFar()); + masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel); + masm.loadDouble(Address(StackPointer, offsetToArgv), ReturnFloatReg); + break; default: - MOZ_ASSUME_UNREACHABLE("Unsupported convert type"); + MOZ_ASSUME_UNREACHABLE("Unsupported convert type"); } } @@ -6462,19 +6503,9 @@ GenerateFFIIonExit(ModuleCompiler &m, const ModuleCompiler::ExitDescriptor &exit // conversion of the result. A frame large enough for both is allocated. // // Arguments to the Ion function are in the following order on the stack: - // descriptor | callee | argc | this | arg1 | arg2 | ... + // | return address | descriptor | callee | argc | this | arg1 | arg2 | ... unsigned argBytes = 3 * sizeof(size_t) + (1 + exit.sig().args().length()) * sizeof(Value); - - // On ARM, we call with ma_callIonNoPush which, following the Ion calling convention, - // stores the return address into *sp. This means we need to include an extra word of - // space before the arguments in the stack allocation. (On x86/x64, the call - // instruction does the push itself and the ABI just requires us to be aligned before - // the call instruction.) - unsigned offsetToArgs = 0; -#if defined(JS_CODEGEN_ARM) - offsetToArgs += sizeof(size_t); -#endif - + unsigned offsetToArgs = MaybeRetAddr; unsigned stackDecForIonCall = StackDecrementForCall(masm, argBytes + offsetToArgs); // Reserve space for a call to AsmJSImm_CoerceInPlace_* and an array of values used by @@ -6483,7 +6514,8 @@ GenerateFFIIonExit(ModuleCompiler &m, const ModuleCompiler::ExitDescriptor &exit MIRType typeArray[] = { MIRType_Pointer, MIRType_Pointer }; // cx, argv MIRTypeVector callArgTypes(m.cx()); callArgTypes.infallibleAppend(typeArray, ArrayLength(typeArray)); - unsigned stackDecForOOLCall = StackDecrementForCall(masm, callArgTypes, sizeof(Value)); + unsigned oolExtraBytes = sizeof(Value) + MaybeRetAddr; + unsigned stackDecForOOLCall = StackDecrementForCall(masm, callArgTypes, oolExtraBytes); // Allocate a frame large enough for both of the above calls. unsigned stackDec = Max(stackDecForIonCall, stackDecForOOLCall); @@ -6566,6 +6598,9 @@ GenerateFFIIonExit(ModuleCompiler &m, const ModuleCompiler::ExitDescriptor &exit LoadAsmJSActivationIntoRegister(masm, reg0); + // Record sp in the AsmJSActivation for stack-walking. + masm.storePtr(StackPointer, Address(reg0, AsmJSActivation::offsetOfExitSP())); + // The following is inlined: // JSContext *cx = activation->cx(); // Activation *act = cx->mainThread().activation(); @@ -6592,14 +6627,7 @@ GenerateFFIIonExit(ModuleCompiler &m, const ModuleCompiler::ExitDescriptor &exit // 2. Call AssertStackAlignment(masm); -#if defined(JS_CODEGEN_ARM) - masm.ma_callIonNoPush(callee); - // The return address has been popped from the stack, so adjust the stack - // without changing the frame-pushed counter to keep the stack aligned. - masm.subPtr(Imm32(4), sp); -#else - masm.callIon(callee); -#endif + masm.callIonFromAsmJS(callee); AssertStackAlignment(masm); { @@ -6720,27 +6748,30 @@ GenerateStackOverflowExit(ModuleCompiler &m, Label *throwLabel) MIRTypeVector argTypes(m.cx()); argTypes.infallibleAppend(MIRType_Pointer); // cx - unsigned stackDec = StackDecrementForCall(masm, argTypes); + unsigned stackDec = StackDecrementForCall(masm, argTypes, MaybeRetAddr); masm.reserveStack(stackDec); + Register activation = ABIArgGenerator::NonArgReturnVolatileReg0; + LoadAsmJSActivationIntoRegister(masm, activation); + + // Record sp in the AsmJSActivation for stack-walking. + masm.storePtr(StackPointer, Address(activation, AsmJSActivation::offsetOfExitSP())); + ABIArgMIRTypeIter i(argTypes); - Register scratch = ABIArgGenerator::NonArgReturnVolatileReg0; - LoadAsmJSActivationIntoRegister(masm, scratch); - // argument 0: cx if (i->kind() == ABIArg::GPR) { - LoadJSContextFromActivation(masm, scratch, i->gpr()); + LoadJSContextFromActivation(masm, activation, i->gpr()); } else { - LoadJSContextFromActivation(masm, scratch, scratch); - masm.storePtr(scratch, Address(StackPointer, i->offsetFromArgBase())); + LoadJSContextFromActivation(masm, activation, activation); + masm.storePtr(activation, Address(StackPointer, i->offsetFromArgBase())); } i++; JS_ASSERT(i.done()); AssertStackAlignment(masm); - masm.call(AsmJSImm_ReportOverRecursed); + masm.callExit(AsmJSImm_ReportOverRecursed, i.stackBytesConsumedSoFar()); // Don't worry about restoring the stack; throwLabel will pop everything. masm.jump(throwLabel); @@ -6895,6 +6926,8 @@ GenerateStubs(ModuleCompiler &m) m.setEntryOffset(i); if (!GenerateEntry(m, m.module().exportedFunction(i))) return false; + if (m.masm().oom()) + return false; } Label throwLabel; diff --git a/js/src/jit/AsmJSLink.cpp b/js/src/jit/AsmJSLink.cpp index bd71166a0e1a..d19605de281a 100644 --- a/js/src/jit/AsmJSLink.cpp +++ b/js/src/jit/AsmJSLink.cpp @@ -6,6 +6,9 @@ #include "jit/AsmJSLink.h" +#include "mozilla/BinarySearch.h" +#include "mozilla/PodOperations.h" + #ifdef MOZ_VTUNE # include "vtune/VTuneWrapper.h" #endif @@ -29,7 +32,102 @@ using namespace js; using namespace js::jit; +using mozilla::BinarySearch; using mozilla::IsNaN; +using mozilla::PodZero; + +AsmJSFrameIterator::AsmJSFrameIterator(const AsmJSActivation *activation) +{ + if (!activation || activation->isInterruptedSP()) { + PodZero(this); + JS_ASSERT(done()); + return; + } + + module_ = &activation->module(); + sp_ = activation->exitSP(); + +#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) + // For calls to Ion/C++ on x86/x64, the exitSP is the SP right before the call + // to C++. Since the call instruction pushes the return address, we know + // that the return address is 1 word below exitSP. + returnAddress_ = *(uint8_t**)(sp_ - sizeof(void*)); +#else + // For calls to Ion/C++ on ARM, the *caller* pushes the return address on + // the stack. For Ion, this is just part of the ABI. For C++, the return + // address is explicitly pushed before the call since we cannot expect the + // callee to immediately push lr. This means that exitSP points to the + // return address. + returnAddress_ = *(uint8_t**)sp_; +#endif + + settle(); +} + +struct GetCallSite +{ + const AsmJSModule &module; + GetCallSite(const AsmJSModule &module) : module(module) {} + uint32_t operator[](size_t index) const { + return module.callSite(index).returnAddressOffset(); + } +}; + +void +AsmJSFrameIterator::popFrame() +{ + // After adding stackDepth, sp points to the word before the return address, + // on both ARM and x86/x64. + sp_ += callsite_->stackDepth(); + returnAddress_ = *(uint8_t**)(sp_ - sizeof(void*)); +} + +void +AsmJSFrameIterator::settle() +{ + while (true) { + uint32_t target = returnAddress_ - module_->codeBase(); + size_t lowerBound = 0; + size_t upperBound = module_->numCallSites(); + + size_t match; + if (!BinarySearch(GetCallSite(*module_), lowerBound, upperBound, target, &match)) { + callsite_ = nullptr; + return; + } + + callsite_ = &module_->callSite(match); + + if (callsite_->isExit()) { + popFrame(); + continue; + } + + if (callsite_->isEntry()) { + callsite_ = nullptr; + return; + } + + JS_ASSERT(callsite_->isNormal()); + return; + } +} + +JSAtom * +AsmJSFrameIterator::functionDisplayAtom() const +{ + JS_ASSERT(!done()); + return module_->functionName(callsite_->functionNameIndex()); +} + +unsigned +AsmJSFrameIterator::computeLine(uint32_t *column) const +{ + JS_ASSERT(!done()); + if (column) + *column = callsite_->column(); + return callsite_->line(); +} static bool CloneModule(JSContext *cx, MutableHandle moduleObj) @@ -408,8 +506,7 @@ CallAsmJS(JSContext *cx, unsigned argc, Value *vp) // 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. - unsigned exportIndex = FunctionToExportedFunctionIndex(callee); - AsmJSActivation activation(cx, module, exportIndex); + AsmJSActivation activation(cx, module); JitActivation jitActivation(cx, /* firstFrameIsConstructing = */ false, /* active */ false); // Call the per-exported-function trampoline created by GenerateEntry. diff --git a/js/src/jit/AsmJSLink.h b/js/src/jit/AsmJSLink.h index 6e14cd49d7d8..2b80c1ff6460 100644 --- a/js/src/jit/AsmJSLink.h +++ b/js/src/jit/AsmJSLink.h @@ -9,8 +9,33 @@ #include "NamespaceImports.h" +class JSAtom; + namespace js { +class AsmJSActivation; +class AsmJSModule; +namespace jit { class CallSite; } + +// Iterates over the frames of a single AsmJSActivation. +class AsmJSFrameIterator +{ + const AsmJSModule *module_; + const jit::CallSite *callsite_; + uint8_t *sp_; + uint8_t *returnAddress_; + + void popFrame(); + void settle(); + + public: + AsmJSFrameIterator(const AsmJSActivation *activation); + void operator++() { popFrame(); settle(); } + bool done() const { return !callsite_; } + JSAtom *functionDisplayAtom() const; + unsigned computeLine(uint32_t *column) const; +}; + #ifdef JS_ION // Create a new JSFunction to replace originalFun as the representation of the diff --git a/js/src/jit/AsmJSModule.cpp b/js/src/jit/AsmJSModule.cpp index 198ddcdf394c..536e4a6e5d82 100644 --- a/js/src/jit/AsmJSModule.cpp +++ b/js/src/jit/AsmJSModule.cpp @@ -382,6 +382,8 @@ AsmJSModule::addSizeOfMisc(mozilla::MallocSizeOf mallocSizeOf, size_t *asmJSModu globals_.sizeOfExcludingThis(mallocSizeOf) + exits_.sizeOfExcludingThis(mallocSizeOf) + exports_.sizeOfExcludingThis(mallocSizeOf) + + callSites_.sizeOfExcludingThis(mallocSizeOf) + + functionNames_.sizeOfExcludingThis(mallocSizeOf) + heapAccesses_.sizeOfExcludingThis(mallocSizeOf) + #if defined(MOZ_VTUNE) || defined(JS_ION_PERF) profiledFunctions_.sizeOfExcludingThis(mallocSizeOf) + @@ -477,6 +479,12 @@ SerializedNameSize(PropertyName *name) (name ? name->length() * sizeof(jschar) : 0); } +size_t +AsmJSModule::Name::serializedSize() const +{ + return SerializedNameSize(name_); +} + static uint8_t * SerializeName(uint8_t *cursor, PropertyName *name) { @@ -490,6 +498,12 @@ SerializeName(uint8_t *cursor, PropertyName *name) return cursor; } +uint8_t * +AsmJSModule::Name::serialize(uint8_t *cursor) const +{ + return SerializeName(cursor, name_); +} + static const uint8_t * DeserializeName(ExclusiveContext *cx, const uint8_t *cursor, PropertyName **name) { @@ -521,6 +535,19 @@ DeserializeName(ExclusiveContext *cx, const uint8_t *cursor, PropertyName **name return cursor + length * sizeof(jschar); } +const uint8_t * +AsmJSModule::Name::deserialize(ExclusiveContext *cx, const uint8_t *cursor) +{ + return DeserializeName(cx, cursor, &name_); +} + +bool +AsmJSModule::Name::clone(ExclusiveContext *cx, Name *out) const +{ + out->name_ = name_; + return true; +} + template size_t SerializedVectorSize(const js::Vector &vec) @@ -788,6 +815,8 @@ AsmJSModule::serializedSize() const SerializedVectorSize(globals_) + SerializedVectorSize(exits_) + SerializedVectorSize(exports_) + + SerializedPodVectorSize(callSites_) + + SerializedVectorSize(functionNames_) + SerializedPodVectorSize(heapAccesses_) + #if defined(MOZ_VTUNE) || defined(JS_ION_PERF) SerializedVectorSize(profiledFunctions_) + @@ -806,6 +835,8 @@ AsmJSModule::serialize(uint8_t *cursor) const cursor = SerializeVector(cursor, globals_); cursor = SerializeVector(cursor, exits_); cursor = SerializeVector(cursor, exports_); + cursor = SerializePodVector(cursor, callSites_); + cursor = SerializeVector(cursor, functionNames_); cursor = SerializePodVector(cursor, heapAccesses_); #if defined(MOZ_VTUNE) || defined(JS_ION_PERF) cursor = SerializeVector(cursor, profiledFunctions_); @@ -830,6 +861,8 @@ AsmJSModule::deserialize(ExclusiveContext *cx, const uint8_t *cursor) (cursor = DeserializeVector(cx, cursor, &globals_)) && (cursor = DeserializeVector(cx, cursor, &exits_)) && (cursor = DeserializeVector(cx, cursor, &exports_)) && + (cursor = DeserializePodVector(cx, cursor, &callSites_)) && + (cursor = DeserializeVector(cx, cursor, &functionNames_)) && (cursor = DeserializePodVector(cx, cursor, &heapAccesses_)) && #if defined(MOZ_VTUNE) || defined(JS_ION_PERF) (cursor = DeserializeVector(cx, cursor, &profiledFunctions_)) && @@ -897,6 +930,8 @@ AsmJSModule::clone(JSContext *cx, ScopedJSDeletePtr *moduleOut) con if (!CloneVector(cx, globals_, &out.globals_) || !CloneVector(cx, exits_, &out.exits_) || !CloneVector(cx, exports_, &out.exports_) || + !ClonePodVector(cx, callSites_, &out.callSites_) || + !CloneVector(cx, functionNames_, &out.functionNames_) || !ClonePodVector(cx, heapAccesses_, &out.heapAccesses_) || !staticLinkData_.clone(cx, &out.staticLinkData_)) { diff --git a/js/src/jit/AsmJSModule.h b/js/src/jit/AsmJSModule.h index 588db60a1cf2..e051674ccc5a 100644 --- a/js/src/jit/AsmJSModule.h +++ b/js/src/jit/AsmJSModule.h @@ -221,8 +221,6 @@ class AsmJSModule struct Pod { ReturnType returnType_; uint32_t codeOffset_; - uint32_t line_; - uint32_t column_; // These two fields are offsets to the beginning of the ScriptSource // of the module, and thus invariant under serialization (unlike // absolute offsets into ScriptSource). @@ -233,7 +231,6 @@ class AsmJSModule friend class AsmJSModule; ExportedFunction(PropertyName *name, - uint32_t line, uint32_t column, uint32_t startOffsetInModule, uint32_t endOffsetInModule, PropertyName *maybeFieldName, ArgCoercionVector &&argCoercions, @@ -244,8 +241,6 @@ class AsmJSModule argCoercions_ = mozilla::Move(argCoercions); pod.returnType_ = returnType; pod.codeOffset_ = UINT32_MAX; - pod.line_ = line; - pod.column_ = column; pod.startOffsetInModule_ = startOffsetInModule; pod.endOffsetInModule_ = endOffsetInModule; JS_ASSERT_IF(maybeFieldName_, name_->isTenured()); @@ -274,12 +269,6 @@ class AsmJSModule PropertyName *name() const { return name_; } - uint32_t line() const { - return pod.line_; - } - uint32_t column() const { - return pod.column_; - } uint32_t startOffsetInModule() const { return pod.startOffsetInModule_; } @@ -305,6 +294,20 @@ class AsmJSModule bool clone(ExclusiveContext *cx, ExportedFunction *out) const; }; + class Name + { + PropertyName *name_; + public: + Name() : name_(nullptr) {} + Name(PropertyName *name) : name_(name) {} + PropertyName *name() const { return name_; } + PropertyName *&name() { return name_; } + size_t serializedSize() const; + uint8_t *serialize(uint8_t *cursor) const; + const uint8_t *deserialize(ExclusiveContext *cx, const uint8_t *cursor); + bool clone(ExclusiveContext *cx, Name *out) const; + }; + #if defined(MOZ_VTUNE) || defined(JS_ION_PERF) // Function information to add to the VTune JIT profiler following linking. struct ProfiledFunction @@ -400,9 +403,11 @@ class AsmJSModule }; private: - typedef Vector ExportedFunctionVector; typedef Vector GlobalVector; typedef Vector ExitVector; + typedef Vector ExportedFunctionVector; + typedef Vector CallSiteVector; + typedef Vector FunctionNameVector; typedef Vector HeapAccessVector; typedef Vector FunctionCountsVector; #if defined(MOZ_VTUNE) || defined(JS_ION_PERF) @@ -420,6 +425,8 @@ class AsmJSModule GlobalVector globals_; ExitVector exits_; ExportedFunctionVector exports_; + CallSiteVector callSites_; + FunctionNameVector functionNames_; HeapAccessVector heapAccesses_; #if defined(MOZ_VTUNE) || defined(JS_ION_PERF) ProfiledFunctionVector profiledFunctions_; @@ -476,6 +483,8 @@ class AsmJSModule if (exitIndexToGlobalDatum(i).fun) MarkObject(trc, &exitIndexToGlobalDatum(i).fun, "asm.js imported function"); } + for (unsigned i = 0; i < functionNames_.length(); i++) + MarkStringUnbarriered(trc, &functionNames_[i].name(), "asm.js module function name"); #if defined(MOZ_VTUNE) || defined(JS_ION_PERF) for (unsigned i = 0; i < profiledFunctions_.length(); i++) profiledFunctions_[i].trace(trc); @@ -595,13 +604,12 @@ class AsmJSModule return exits_.append(Exit(ffiIndex, globalDataOffset)); } - bool addExportedFunction(PropertyName *name, uint32_t line, uint32_t column, - uint32_t srcStart, uint32_t srcEnd, + bool addExportedFunction(PropertyName *name, uint32_t srcStart, uint32_t srcEnd, PropertyName *maybeFieldName, ArgCoercionVector &&argCoercions, ReturnType returnType) { - ExportedFunction func(name, line, column, srcStart, srcEnd, maybeFieldName, + ExportedFunction func(name, srcStart, srcEnd, maybeFieldName, mozilla::Move(argCoercions), returnType); if (exports_.length() >= UINT32_MAX) return false; @@ -621,6 +629,17 @@ class AsmJSModule return JS_DATA_TO_FUNC_PTR(CodePtr, code_ + func.pod.codeOffset_); } + bool addFunctionName(PropertyName *name, uint32_t *nameIndex) { + JS_ASSERT(name->isTenured()); + if (functionNames_.length() > jit::CallSiteDesc::FUNCTION_NAME_INDEX_MAX) + return false; + *nameIndex = functionNames_.length(); + return functionNames_.append(name); + } + PropertyName *functionName(uint32_t i) const { + return functionNames_[i].name(); + } + #if defined(MOZ_VTUNE) || defined(JS_ION_PERF) bool trackProfiledFunction(PropertyName *name, unsigned startCodeOffset, unsigned endCodeOffset, unsigned line, unsigned column) @@ -766,6 +785,19 @@ class AsmJSModule return heapAccesses_[i]; } + void assignCallSites(jit::CallSiteVector &&callsites) { + callSites_ = Move(callsites); + } + unsigned numCallSites() const { + return callSites_.length(); + } + const jit::CallSite &callSite(unsigned i) const { + return callSites_[i]; + } + jit::CallSite &callSite(unsigned i) { + return callSites_[i]; + } + void initHeap(Handle heap, JSContext *cx); void requireHeapLengthToBeAtLeast(uint32_t len) { diff --git a/js/src/jit/AsmJSSignalHandlers.cpp b/js/src/jit/AsmJSSignalHandlers.cpp index a324961f8e5f..e89ae2f918b1 100644 --- a/js/src/jit/AsmJSSignalHandlers.cpp +++ b/js/src/jit/AsmJSSignalHandlers.cpp @@ -342,7 +342,7 @@ HandleSimulatorInterrupt(JSRuntime *rt, AsmJSActivation *activation, void *fault if (module.containsPC((void *)rt->mainThread.simulator()->get_pc()) && module.containsPC(faultingAddress)) { - activation->setResumePC(nullptr); + activation->setInterrupted(nullptr); int32_t nextpc = int32_t(module.interruptExit()); rt->mainThread.simulator()->set_resume_pc(nextpc); return true; @@ -452,7 +452,7 @@ HandleException(PEXCEPTION_POINTERS exception) // The trampoline will jump to activation->resumePC if execution isn't // interrupted. if (module.containsPC(faultingAddress)) { - activation->setResumePC(pc); + activation->setInterrupted(pc); *ppc = module.interruptExit(); JSRuntime::AutoLockForInterrupt lock(rt); @@ -655,7 +655,7 @@ HandleMachException(JSRuntime *rt, const ExceptionRequest &request) // The trampoline will jump to activation->resumePC if execution isn't // interrupted. if (module.containsPC(faultingAddress)) { - activation->setResumePC(pc); + activation->setInterrupted(pc); *ppc = module.interruptExit(); JSRuntime::AutoLockForInterrupt lock(rt); @@ -905,7 +905,7 @@ HandleSignal(int signum, siginfo_t *info, void *ctx) // The trampoline will jump to activation->resumePC if execution isn't // interrupted. if (module.containsPC(faultingAddress)) { - activation->setResumePC(pc); + activation->setInterrupted(pc); *ppc = module.interruptExit(); JSRuntime::AutoLockForInterrupt lock(rt); diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 44f511aa7fe0..3f3c80d1a74c 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -8218,6 +8218,7 @@ CodeGenerator::visitAsmJSCall(LAsmJSCall *ins) } } #endif + if (mir->spIncrement()) masm.freeStack(mir->spIncrement()); @@ -8234,13 +8235,13 @@ CodeGenerator::visitAsmJSCall(LAsmJSCall *ins) MAsmJSCall::Callee callee = mir->callee(); switch (callee.which()) { case MAsmJSCall::Callee::Internal: - masm.call(callee.internal()); + masm.call(mir->desc(), callee.internal()); break; case MAsmJSCall::Callee::Dynamic: - masm.call(ToRegister(ins->getOperand(mir->dynamicCalleeOperandIndex()))); + masm.call(mir->desc(), ToRegister(ins->getOperand(mir->dynamicCalleeOperandIndex()))); break; case MAsmJSCall::Callee::Builtin: - masm.call(callee.builtin()); + masm.call(mir->desc(), callee.builtin()); break; } diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp index 45ed00204812..ba28f98217d4 100644 --- a/js/src/jit/MIR.cpp +++ b/js/src/jit/MIR.cpp @@ -2968,12 +2968,10 @@ MAsmJSUnsignedToFloat32::foldsTo(TempAllocator &alloc, bool useValueNumbers) } MAsmJSCall * -MAsmJSCall::New(TempAllocator &alloc, Callee callee, const Args &args, MIRType resultType, - size_t spIncrement) +MAsmJSCall::New(TempAllocator &alloc, const CallSiteDesc &desc, Callee callee, + const Args &args, MIRType resultType, size_t spIncrement) { - MAsmJSCall *call = new(alloc) MAsmJSCall; - call->spIncrement_ = spIncrement; - call->callee_ = callee; + MAsmJSCall *call = new(alloc) MAsmJSCall(desc, callee, spIncrement); call->setResultType(resultType); if (!call->argRegs_.init(alloc, args.length())) diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 6658c886f71a..1e365491c3c6 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -9982,11 +9982,16 @@ class MAsmJSCall MOZ_FINAL : public MInstruction MUse use; }; + CallSiteDesc desc_; Callee callee_; FixedList operands_; FixedList argRegs_; size_t spIncrement_; + MAsmJSCall(const CallSiteDesc &desc, Callee callee, size_t spIncrement) + : desc_(desc), callee_(callee), spIncrement_(spIncrement) + { } + protected: void setOperand(size_t index, MDefinition *operand) { operands_[index].set(operand, this, index); @@ -10006,8 +10011,8 @@ class MAsmJSCall MOZ_FINAL : public MInstruction }; typedef Vector Args; - static MAsmJSCall *New(TempAllocator &alloc, Callee callee, const Args &args, - MIRType resultType, size_t spIncrement); + static MAsmJSCall *New(TempAllocator &alloc, const CallSiteDesc &desc, Callee callee, + const Args &args, MIRType resultType, size_t spIncrement); size_t numOperands() const { return operands_.length(); @@ -10023,6 +10028,9 @@ class MAsmJSCall MOZ_FINAL : public MInstruction JS_ASSERT(index < numArgs()); return argRegs_[index]; } + const CallSiteDesc &desc() const { + return desc_; + } Callee callee() const { return callee_; } diff --git a/js/src/jit/arm/MacroAssembler-arm.cpp b/js/src/jit/arm/MacroAssembler-arm.cpp index da9b408419f9..92196ec43703 100644 --- a/js/src/jit/arm/MacroAssembler-arm.cpp +++ b/js/src/jit/arm/MacroAssembler-arm.cpp @@ -3568,6 +3568,19 @@ MacroAssemblerARM::ma_call(ImmPtr dest) as_blx(CallReg); } +void +MacroAssemblerARM::ma_callAndStoreRet(const Register r, uint32_t stackArgBytes) +{ + // Note: this function stores the return address to sp[0]. The caller must + // anticipate this by pushing additional space on the stack. The ABI does + // not provide space for a return address so this function may only be + // called if no argument are passed. + JS_ASSERT(stackArgBytes == 0); + AutoForbidPools afp(this); + as_dtr(IsStore, 32, Offset, pc, DTRAddr(sp, DtrOffImm(0))); + as_blx(r); +} + void MacroAssemblerARMCompat::breakpoint() { diff --git a/js/src/jit/arm/MacroAssembler-arm.h b/js/src/jit/arm/MacroAssembler-arm.h index c22383c0aad7..33291318db19 100644 --- a/js/src/jit/arm/MacroAssembler-arm.h +++ b/js/src/jit/arm/MacroAssembler-arm.h @@ -399,6 +399,9 @@ class MacroAssemblerARM : public Assembler void ma_call(ImmPtr dest); + // calls reg, storing the return address into sp[0] + void ma_callAndStoreRet(const Register reg, uint32_t stackArgBytes); + // Float registers can only be loaded/stored in continuous runs // when using vstm/vldm. // This function breaks set into continuous runs and loads/stores @@ -543,7 +546,6 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM void call(const Register reg) { as_blx(reg); } - void call(Label *label) { // for now, assume that it'll be nearby? as_bl(label, Always); @@ -572,6 +574,38 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM ma_movPatchable(ImmPtr(c->raw()), ScratchRegister, Always, rs); ma_callIonHalfPush(ScratchRegister); } + + void appendCallSite(const CallSiteDesc &desc) { + enoughMemory_ &= append(CallSite(desc, currentOffset(), framePushed_)); + } + + void call(const CallSiteDesc &desc, const Register reg) { + call(reg); + appendCallSite(desc); + } + void call(const CallSiteDesc &desc, Label *label) { + call(label); + appendCallSite(desc); + } + void call(const CallSiteDesc &desc, AsmJSImmPtr imm) { + call(imm); + appendCallSite(desc); + } + void callExit(AsmJSImmPtr imm, uint32_t stackArgBytes) { + movePtr(imm, CallReg); + ma_callAndStoreRet(CallReg, stackArgBytes); + appendCallSite(CallSiteDesc::Exit()); + } + void callIonFromAsmJS(const Register reg) { + ma_callIonNoPush(reg); + appendCallSite(CallSiteDesc::Exit()); + + // The Ion ABI has the callee pop the return address off the stack. + // The asm.js caller assumes that the call leaves sp unchanged, so bump + // the stack. + subPtr(Imm32(sizeof(void*)), sp); + } + void branch(JitCode *c) { BufferOffset bo = m_buffer.nextOffset(); addPendingJump(bo, ImmPtr(c->raw()), Relocation::JITCODE); diff --git a/js/src/jit/arm/Simulator-arm.cpp b/js/src/jit/arm/Simulator-arm.cpp index d73a494cb4c8..cf90525b9738 100644 --- a/js/src/jit/arm/Simulator-arm.cpp +++ b/js/src/jit/arm/Simulator-arm.cpp @@ -4071,7 +4071,7 @@ Simulator::execute() int32_t rpc = resume_pc_; if (MOZ_UNLIKELY(rpc != 0)) { // AsmJS signal handler ran and we have to adjust the pc. - activation->setResumePC((void *)get_pc()); + activation->setInterrupted((void *)get_pc()); set_pc(rpc); resume_pc_ = 0; } diff --git a/js/src/jit/shared/Assembler-shared.h b/js/src/jit/shared/Assembler-shared.h index 3f16b2d7c481..2cb446e7eda4 100644 --- a/js/src/jit/shared/Assembler-shared.h +++ b/js/src/jit/shared/Assembler-shared.h @@ -663,6 +663,77 @@ class CodeLocationLabel } }; +// Describes the user-visible properties of a callsite. +// +// A few general notes about the stack-walking supported by CallSite(Desc): +// - This information facilitates stack-walking performed by FrameIter which +// is used by Error.stack and other user-visible stack-walking functions. +// - Ion/asm.js calling conventions do not maintain a frame-pointer so +// stack-walking must lookup the stack depth based on the PC. +// - Stack-walking only occurs from C++ after a synchronous calls (JS-to-JS and +// JS-to-C++). Thus, we do not need to map arbitrary PCs to stack-depths, +// just the return address at callsites. +// - An exception to the above rule is the interrupt callback which can happen +// at arbitrary PCs. In such cases, we drop frames from the stack-walk. In +// the future when a full PC->stack-depth map is maintained, we handle this +// case. +class CallSiteDesc +{ + uint32_t line_; + uint32_t column_; + uint32_t functionNameIndex_; + + static const uint32_t sEntryTrampoline = UINT32_MAX; + static const uint32_t sExit = UINT32_MAX - 1; + + public: + static const uint32_t FUNCTION_NAME_INDEX_MAX = UINT32_MAX - 2; + + CallSiteDesc() {} + + CallSiteDesc(uint32_t line, uint32_t column, uint32_t functionNameIndex) + : line_(line), column_(column), functionNameIndex_(functionNameIndex) + {} + + static CallSiteDesc Entry() { return CallSiteDesc(0, 0, sEntryTrampoline); } + static CallSiteDesc Exit() { return CallSiteDesc(0, 0, sExit); } + + bool isEntry() const { return functionNameIndex_ == sEntryTrampoline; } + bool isExit() const { return functionNameIndex_ == sExit; } + bool isNormal() const { return !(isEntry() || isExit()); } + + uint32_t line() const { JS_ASSERT(isNormal()); return line_; } + uint32_t column() const { JS_ASSERT(isNormal()); return column_; } + uint32_t functionNameIndex() const { JS_ASSERT(isNormal()); return functionNameIndex_; } +}; + +// Adds to CallSiteDesc the metadata necessary to walk the stack given an +// initial stack-pointer. +struct CallSite : public CallSiteDesc +{ + uint32_t returnAddressOffset_; + uint32_t stackDepth_; + + public: + CallSite() {} + + CallSite(CallSiteDesc desc, uint32_t returnAddressOffset, uint32_t stackDepth) + : CallSiteDesc(desc), + returnAddressOffset_(returnAddressOffset), + stackDepth_(stackDepth) + { } + + void setReturnAddressOffset(uint32_t r) { returnAddressOffset_ = r; } + uint32_t returnAddressOffset() const { return returnAddressOffset_; } + + // The stackDepth measures the amount of stack space pushed since the + // function was called. In particular, this includes the word pushed by the + // call instruction on x86/x64. + uint32_t stackDepth() const { JS_ASSERT(!isEntry()); return stackDepth_; } +}; + +typedef Vector CallSiteVector; + // Summarizes a heap access made by asm.js code that needs to be patched later // and/or looked up by the asm.js signal handlers. Different architectures need // to know different things (x64: offset and length, ARM: where to patch in @@ -814,11 +885,15 @@ struct AsmJSAbsoluteLink // The base class of all Assemblers for all archs. class AssemblerShared { + Vector callsites_; Vector asmJSHeapAccesses_; Vector asmJSGlobalAccesses_; Vector asmJSAbsoluteLinks_; public: + bool append(CallSite callsite) { return callsites_.append(callsite); } + CallSiteVector &&extractCallSites() { return Move(callsites_); } + bool append(AsmJSHeapAccess access) { return asmJSHeapAccesses_.append(access); } AsmJSHeapAccessVector &&extractAsmJSHeapAccesses() { return Move(asmJSHeapAccesses_); } diff --git a/js/src/jit/shared/MacroAssembler-x86-shared.h b/js/src/jit/shared/MacroAssembler-x86-shared.h index 319dd6986365..01d1a4bb7e62 100644 --- a/js/src/jit/shared/MacroAssembler-x86-shared.h +++ b/js/src/jit/shared/MacroAssembler-x86-shared.h @@ -30,6 +30,8 @@ class MacroAssemblerX86Shared : public Assembler uint32_t framePushed_; public: + using Assembler::call; + MacroAssemblerX86Shared() : framePushed_(0) { } @@ -662,6 +664,24 @@ class MacroAssemblerX86Shared : public Assembler call(callee); } + void appendCallSite(const CallSiteDesc &desc) { + // Add an extra sizeof(void*) to include the return address that was + // pushed by the call instruction (see CallSite::stackDepth). + enoughMemory_ &= append(CallSite(desc, currentOffset(), framePushed_ + sizeof(void*))); + } + + void call(const CallSiteDesc &desc, Label *label) { + call(label); + appendCallSite(desc); + } + void call(const CallSiteDesc &desc, const Register ®) { + call(reg); + appendCallSite(desc); + } + void callIonFromAsmJS(const Register ®) { + call(CallSiteDesc::Exit(), reg); + } + void checkStackAlignment() { // Exists for ARM compatibility. } diff --git a/js/src/jit/x64/MacroAssembler-x64.h b/js/src/jit/x64/MacroAssembler-x64.h index 6ccf900673bc..2c11e9a829ad 100644 --- a/js/src/jit/x64/MacroAssembler-x64.h +++ b/js/src/jit/x64/MacroAssembler-x64.h @@ -110,6 +110,14 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared call(rax); } + void call(const CallSiteDesc &desc, AsmJSImmPtr target) { + call(target); + appendCallSite(desc); + } + void callExit(AsmJSImmPtr target, uint32_t stackArgBytes) { + call(CallSiteDesc::Exit(), target); + } + // Refers to the upper 32 bits of a 64-bit Value operand. // On x86_64, the upper 32 bits do not necessarily only contain the type. Operand ToUpper32(Operand base) { diff --git a/js/src/jit/x86/MacroAssembler-x86.h b/js/src/jit/x86/MacroAssembler-x86.h index ab591173701d..17de7a7d0719 100644 --- a/js/src/jit/x86/MacroAssembler-x86.h +++ b/js/src/jit/x86/MacroAssembler-x86.h @@ -71,6 +71,7 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared using MacroAssemblerX86Shared::branch32; using MacroAssemblerX86Shared::load32; using MacroAssemblerX86Shared::store32; + using MacroAssemblerX86Shared::call; MacroAssemblerX86() : inCall_(false), @@ -1105,6 +1106,13 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared Push(dynStack); call(target); } + void call(const CallSiteDesc &desc, AsmJSImmPtr target) { + call(target); + appendCallSite(desc); + } + void callExit(AsmJSImmPtr target, uint32_t stackArgBytes) { + call(CallSiteDesc::Exit(), target); + } // Save an exit frame to the thread data of the current thread, given a // register that holds a PerThreadData *. diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp index 0dcc1e34644b..ff75a77864b2 100644 --- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -586,18 +586,23 @@ FrameIter::settleOnActivation() return; } + if (activation->isAsmJS()) { + data_.asmJSFrames_ = AsmJSFrameIterator(data_.activations_->asAsmJS()); + + if (data_.asmJSFrames_.done()) { + ++data_.activations_; + continue; + } + + data_.state_ = ASMJS; + return; + } + // ForkJoin activations don't contain iterable frames, so skip them. if (activation->isForkJoin()) { ++data_.activations_; continue; } - - // Until asm.js has real stack-walking, we have each AsmJSActivation - // expose a single function (the entry function). - if (activation->isAsmJS()) { - data_.state_ = ASMJS; - return; - } #endif JS_ASSERT(activation->isInterpreter()); @@ -634,6 +639,7 @@ FrameIter::Data::Data(JSContext *cx, SavedOption savedOption, ContextOption cont #ifdef JS_ION , jitFrames_((uint8_t *)nullptr, SequentialExecution) , ionInlineFrameNo_(0) + , asmJSFrames_(nullptr) #endif { } @@ -650,6 +656,7 @@ FrameIter::Data::Data(const FrameIter::Data &other) #ifdef JS_ION , jitFrames_(other.jitFrames_) , ionInlineFrameNo_(other.ionInlineFrameNo_) + , asmJSFrames_(other.asmJSFrames_) #endif { } @@ -731,6 +738,16 @@ FrameIter::popJitFrame() popActivation(); } + +void +FrameIter::popAsmJSFrame() +{ + JS_ASSERT(data_.state_ == ASMJS); + + ++data_.asmJSFrames_; + if (data_.asmJSFrames_.done()) + popActivation(); +} #endif FrameIter & @@ -780,11 +797,7 @@ FrameIter::operator++() MOZ_ASSUME_UNREACHABLE("Unexpected state"); #endif case ASMJS: - // 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(); + popAsmJSFrame(); break; } return *this; @@ -940,8 +953,7 @@ FrameIter::functionDisplayAtom() const return callee()->displayAtom(); case ASMJS: { #ifdef JS_ION - AsmJSActivation &act = *data_.activations_->asAsmJS(); - return act.module().exportedFunction(act.exportIndex()).name(); + return data_.asmJSFrames_.functionDisplayAtom(); #else break; #endif @@ -1000,17 +1012,12 @@ FrameIter::computeLine(uint32_t *column) const case INTERP: case JIT: return PCToLineNumber(script(), pc(), column); - case ASMJS: { + case ASMJS: #ifdef JS_ION - AsmJSActivation &act = *data_.activations_->asAsmJS(); - AsmJSModule::ExportedFunction &func = act.module().exportedFunction(act.exportIndex()); - if (column) - *column = func.column(); - return func.line(); + return data_.asmJSFrames_.computeLine(column); #else break; #endif - } } MOZ_ASSUME_UNREACHABLE("Unexpected state"); @@ -1654,13 +1661,13 @@ jit::JitActivation::markRematerializedFrames(JSTracer *trc) #endif // JS_ION -AsmJSActivation::AsmJSActivation(JSContext *cx, AsmJSModule &module, unsigned exportIndex) +AsmJSActivation::AsmJSActivation(JSContext *cx, AsmJSModule &module) : Activation(cx, AsmJS), module_(module), errorRejoinSP_(nullptr), profiler_(nullptr), resumePC_(nullptr), - exportIndex_(exportIndex) + exitSP_(nullptr) { if (cx->runtime()->spsProfiler.enabled()) { // Use a profiler string that matches jsMatch regex in diff --git a/js/src/vm/Stack.h b/js/src/vm/Stack.h index 2602d51d5c81..52259f6d2b47 100644 --- a/js/src/vm/Stack.h +++ b/js/src/vm/Stack.h @@ -12,6 +12,7 @@ #include "jsfun.h" #include "jsscript.h" +#include "jit/AsmJSLink.h" #include "jit/JitFrameIterator.h" #ifdef CHECK_OSIPOINT_REGISTERS #include "jit/Registers.h" // for RegisterDump @@ -1502,18 +1503,16 @@ class AsmJSActivation : public Activation void *errorRejoinSP_; SPSProfiler *profiler_; void *resumePC_; + uint8_t *exitSP_; - // These bits are temporary and will be replaced when real asm.js - // stack-walking support lands: - unsigned exportIndex_; + static const intptr_t InterruptedSP = -1; public: - AsmJSActivation(JSContext *cx, AsmJSModule &module, unsigned exportIndex); + AsmJSActivation(JSContext *cx, AsmJSModule &module); ~AsmJSActivation(); JSContext *cx() { return cx_; } AsmJSModule &module() const { return module_; } - unsigned exportIndex() const { return exportIndex_; } AsmJSActivation *prevAsmJS() const { return prevAsmJS_; } // Read by JIT code: @@ -1522,9 +1521,16 @@ class AsmJSActivation : public Activation // Initialized by JIT code: static unsigned offsetOfErrorRejoinSP() { return offsetof(AsmJSActivation, errorRejoinSP_); } + static unsigned offsetOfExitSP() { return offsetof(AsmJSActivation, exitSP_); } // Set from SIGSEGV handler: - void setResumePC(void *pc) { resumePC_ = pc; } + void setInterrupted(void *pc) { resumePC_ = pc; exitSP_ = (uint8_t*)InterruptedSP; } + bool isInterruptedSP() const { return exitSP_ == (uint8_t*)InterruptedSP; } + + // Note: exitSP is the sp right before the call instruction. On x86, this + // means before the return address is pushed on the stack, on ARM, this + // means after. + uint8_t *exitSP() const { JS_ASSERT(!isInterruptedSP()); return exitSP_; } }; // A FrameIter walks over the runtime's stack of JS script activations, @@ -1575,6 +1581,7 @@ class FrameIter #ifdef JS_ION jit::JitFrameIterator jitFrames_; unsigned ionInlineFrameNo_; + AsmJSFrameIterator asmJSFrames_; #endif Data(JSContext *cx, SavedOption savedOption, ContextOption contextOption, @@ -1701,6 +1708,7 @@ class FrameIter #ifdef JS_ION void nextJitFrame(); void popJitFrame(); + void popAsmJSFrame(); #endif void settleOnActivation();