From d4a24437409cda409af8d37cc731334ca7bdb156 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 6 Aug 2012 11:48:12 -0700 Subject: [PATCH] Bug 778979 - Part 3: Track the line number in the JS engine for SPS profiling. r=bhackett --- js/src/jsapi-tests/testProfileStrings.cpp | 52 ++--- js/src/jsfriendapi.h | 6 +- js/src/jsinterp.cpp | 2 + js/src/methodjit/BaseAssembler.h | 270 +++++++++++++++++++++- js/src/methodjit/Compiler.cpp | 99 +++++--- js/src/methodjit/Compiler.h | 1 + js/src/methodjit/MonoIC.cpp | 3 +- js/src/methodjit/PolyIC.cpp | 39 ++-- js/src/methodjit/StubCompiler.cpp | 1 + js/src/vm/SPSProfiler.cpp | 61 +++-- js/src/vm/SPSProfiler.h | 80 +++++-- 11 files changed, 494 insertions(+), 120 deletions(-) diff --git a/js/src/jsapi-tests/testProfileStrings.cpp b/js/src/jsapi-tests/testProfileStrings.cpp index dddf5c068ef..8536c8609ce 100644 --- a/js/src/jsapi-tests/testProfileStrings.cpp +++ b/js/src/jsapi-tests/testProfileStrings.cpp @@ -12,15 +12,15 @@ #include "jscntxt.h" -static js::ProfileEntry stack[10]; -static uint32_t size = 0; +static js::ProfileEntry pstack[10]; +static uint32_t psize = 0; static uint32_t max_stack = 0; static void reset(JSContext *cx) { - size = max_stack = 0; - memset(stack, 0, sizeof(stack)); + psize = max_stack = 0; + memset(pstack, 0, sizeof(pstack)); cx->runtime->spsProfiler.stringsReset(); cx->runtime->spsProfiler.enableSlowAssertions(true); js::EnableRuntimeProfilingStack(cx->runtime, true); @@ -34,7 +34,7 @@ static JSClass ptestClass = { static JSBool test_fn(JSContext *cx, unsigned argc, jsval *vp) { - max_stack = size; + max_stack = psize; return JS_TRUE; } @@ -81,13 +81,13 @@ static JSFunctionSpec ptestFunctions[] = { static JSObject* initialize(JSContext *cx) { - js::SetRuntimeProfilingStack(cx->runtime, stack, &size, 10); + js::SetRuntimeProfilingStack(cx->runtime, pstack, &psize, 10); JS::RootedObject global(cx, JS_GetGlobalObject(cx)); return JS_InitClass(cx, global, NULL, &ptestClass, Prof, 0, NULL, ptestFunctions, NULL, NULL); } -BEGIN_TEST(testProfileStrings_isCalled) +BEGIN_TEST(testProfileStrings_isCalledWithInterpreter) { CHECK(initialize(cx)); @@ -106,13 +106,13 @@ BEGIN_TEST(testProfileStrings_isCalled) JS::RootedValue rval(cx); /* Make sure the stack resets and we have an entry for each stack */ CHECK(JS_CallFunctionName(cx, global, "check", 0, NULL, rval.address())); - CHECK(size == 0); + CHECK(psize == 0); CHECK(max_stack == 9); CHECK(cx->runtime->spsProfiler.stringsCount() == 8); /* Make sure the stack resets and we added no new entries */ max_stack = 0; CHECK(JS_CallFunctionName(cx, global, "check", 0, NULL, rval.address())); - CHECK(size == 0); + CHECK(psize == 0); CHECK(max_stack == 9); CHECK(cx->runtime->spsProfiler.stringsCount() == 8); } @@ -122,22 +122,22 @@ BEGIN_TEST(testProfileStrings_isCalled) CHECK(JS_CallFunctionName(cx, global, "check2", 0, NULL, rval.address())); CHECK(cx->runtime->spsProfiler.stringsCount() == 5); CHECK(max_stack == 7); - CHECK(size == 0); + CHECK(psize == 0); } js::EnableRuntimeProfilingStack(cx->runtime, false); - js::SetRuntimeProfilingStack(cx->runtime, stack, &size, 3); + js::SetRuntimeProfilingStack(cx->runtime, pstack, &psize, 3); reset(cx); { JS::RootedValue rval(cx); - stack[3].string = (char*) 1234; + pstack[3].setLabel((char*) 1234); CHECK(JS_CallFunctionName(cx, global, "check", 0, NULL, rval.address())); - CHECK((size_t) stack[3].string == 1234); + CHECK((size_t) pstack[3].label() == 1234); CHECK(max_stack == 9); - CHECK(size == 0); + CHECK(psize == 0); } return true; } -END_TEST(testProfileStrings_isCalled) +END_TEST(testProfileStrings_isCalledWithInterpreter) BEGIN_TEST(testProfileStrings_isCalledWithJIT) { @@ -160,29 +160,29 @@ BEGIN_TEST(testProfileStrings_isCalledWithJIT) JS::RootedValue rval(cx); /* Make sure the stack resets and we have an entry for each stack */ CHECK(JS_CallFunctionName(cx, global, "check", 0, NULL, rval.address())); - CHECK(size == 0); + CHECK(psize == 0); CHECK(max_stack == 9); /* Make sure the stack resets and we added no new entries */ uint32_t cnt = cx->runtime->spsProfiler.stringsCount(); max_stack = 0; CHECK(JS_CallFunctionName(cx, global, "check", 0, NULL, rval.address())); - CHECK(size == 0); + CHECK(psize == 0); CHECK(cx->runtime->spsProfiler.stringsCount() == cnt); CHECK(max_stack == 9); } js::EnableRuntimeProfilingStack(cx->runtime, false); - js::SetRuntimeProfilingStack(cx->runtime, stack, &size, 3); + js::SetRuntimeProfilingStack(cx->runtime, pstack, &psize, 3); reset(cx); { /* Limit the size of the stack and make sure we don't overflow */ JS::RootedValue rval(cx); - stack[3].string = (char*) 1234; + pstack[3].setLabel((char*) 1234); CHECK(JS_CallFunctionName(cx, global, "check", 0, NULL, rval.address())); - CHECK(size == 0); + CHECK(psize == 0); CHECK(max_stack == 9); - CHECK((size_t) stack[3].string == 1234); + CHECK((size_t) pstack[3].label() == 1234); } return true; } @@ -200,7 +200,7 @@ BEGIN_TEST(testProfileStrings_isCalledWhenError) JS::RootedValue rval(cx); /* Make sure the stack resets and we have an entry for each stack */ JS_CallFunctionName(cx, global, "check2", 0, NULL, rval.address()); - CHECK(size == 0); + CHECK(psize == 0); CHECK(cx->runtime->spsProfiler.stringsCount() == 1); } return true; @@ -220,7 +220,7 @@ BEGIN_TEST(testProfileStrings_worksWhenEnabledOnTheFly) /* enable it in the middle of JS and make sure things check out */ JS::RootedValue rval(cx); JS_CallFunctionName(cx, global, "a", 0, NULL, rval.address()); - CHECK(size == 0); + CHECK(psize == 0); CHECK(max_stack == 1); CHECK(cx->runtime->spsProfiler.stringsCount() == 1); } @@ -232,7 +232,7 @@ BEGIN_TEST(testProfileStrings_worksWhenEnabledOnTheFly) /* now disable in the middle of js */ JS::RootedValue rval(cx); JS_CallFunctionName(cx, global, "c", 0, NULL, rval.address()); - CHECK(size == 0); + CHECK(psize == 0); } EXEC("function e() { var p = new Prof(); d(p); p.enable(); b(p); }"); @@ -241,7 +241,7 @@ BEGIN_TEST(testProfileStrings_worksWhenEnabledOnTheFly) /* now disable in the middle of js, but re-enable before final exit */ JS::RootedValue rval(cx); JS_CallFunctionName(cx, global, "e", 0, NULL, rval.address()); - CHECK(size == 0); + CHECK(psize == 0); CHECK(max_stack == 3); } @@ -255,7 +255,7 @@ BEGIN_TEST(testProfileStrings_worksWhenEnabledOnTheFly) /* disable, and make sure that if we try to re-enter the JIT the pop * will still happen */ JS_CallFunctionName(cx, global, "f", 0, NULL, rval.address()); - CHECK(size == 0); + CHECK(psize == 0); } return true; } diff --git a/js/src/jsfriendapi.h b/js/src/jsfriendapi.h index 07f92b8dc9c..7d5e362e0f5 100644 --- a/js/src/jsfriendapi.h +++ b/js/src/jsfriendapi.h @@ -588,17 +588,19 @@ class ProfileEntry } uint32_t line() volatile { JS_ASSERT(!js()); return idx; } - jsbytecode *pc() volatile; JSScript *script() volatile { JS_ASSERT(js()); return script_; } void *stackAddress() volatile { return sp; } const char *label() volatile { return string; } void setLine(uint32_t line) volatile { JS_ASSERT(!js()); idx = line; } - void setPC(jsbytecode *pc) volatile; void setLabel(const char *string) volatile { this->string = string; } void setStackAddress(void *sp) volatile { this->sp = sp; } void setScript(JSScript *script) volatile { script_ = script; } + /* we can't know the layout of JSScript, so look in vm/SPSProfiler.cpp */ + JS_FRIEND_API(jsbytecode *) pc() volatile; + JS_FRIEND_API(void) setPC(jsbytecode *pc) volatile; + static size_t offsetOfString() { return offsetof(ProfileEntry, string); } static size_t offsetOfStackAddress() { return offsetof(ProfileEntry, sp); } static size_t offsetOfPCIdx() { return offsetof(ProfileEntry, idx); } diff --git a/js/src/jsinterp.cpp b/js/src/jsinterp.cpp index eb67a6a962c..df1271e2132 100644 --- a/js/src/jsinterp.cpp +++ b/js/src/jsinterp.cpp @@ -2393,6 +2393,8 @@ BEGIN_CASE(JSOP_NEW) BEGIN_CASE(JSOP_CALL) BEGIN_CASE(JSOP_FUNCALL) { + if (regs.fp()->hasPushedSPSFrame()) + cx->runtime->spsProfiler.updatePC(script, regs.pc); JS_ASSERT(regs.stackDepth() >= 2 + GET_ARGC(regs.pc)); CallArgs args = CallArgsFromSp(GET_ARGC(regs.pc), regs.sp); diff --git a/js/src/methodjit/BaseAssembler.h b/js/src/methodjit/BaseAssembler.h index f1f0ff44171..c7b60c97336 100644 --- a/js/src/methodjit/BaseAssembler.h +++ b/js/src/methodjit/BaseAssembler.h @@ -23,6 +23,8 @@ namespace js { namespace mjit { +class Assembler; + // Represents an int32_t property name in generated code, which must be either // a RegisterID or a constant value. struct Int32Key { @@ -76,6 +78,128 @@ struct StackMarker { { } }; +/* + * SPS is the profiling backend used by the JS engine to enable time profiling. + * More information can be found in vm/SPSProfiler.{h,cpp}. This class manages + * the instrumentation portion of the profiling for JIT code. + * + * The instrumentation tracks entry into functions, leaving those functions via + * a function call, reentering the functions from a function call, and exiting + * the functions from returning. This class also handles inline frames and + * manages the instrumentation which needs to be attached to them as well. + * + * The basic methods which emit instrumentation are at the end of this class, + * and the management functions are all described in the middle. + */ +class SPSInstrumentation { + typedef JSC::MacroAssembler::RegisterID RegisterID; + + /* Because of inline frames, this is a nested structure in a vector */ + struct FrameState { + bool pushed; // has sps pushed a frame yet? + bool skipNext; // should the next call to reenter be skipped? + int left; // number of leave() calls made without a matching reenter() + }; + + SPSProfiler *profiler_; // Instrumentation location management + JSScript **script_; // Used from Compiler.cpp + jsbytecode **pc_; // same purpose as script_ + VMFrame *vmframe; // Used in PolyIC/MonoIC compilations + + Vector frames; + FrameState *frame; + + /* + * When the instrumentation pushes some information, it needs to know about + * the script/pc current in play. When originally compiling via + * Compiler.cpp, the script and pc change rapidly, hence the **. During a + * recompilation or some form of IC, the script/pc don't change, hence using + * the VMFrame as the source of this information. + */ + JSScript *script() { return script_ ? *script_ : vmframe->script(); } + jsbytecode *pc() { return pc_ ? *pc_ : vmframe->pc(); } + + public: + /* Constructor meant to be used from the compilers */ + SPSInstrumentation(SPSProfiler *profiler, JSScript **script, jsbytecode **pc) + : profiler_(profiler), + script_(script), + pc_(pc), + vmframe(NULL), + frame(NULL) + { + enterInlineFrame(); + } + + /* Constructor used for recompilations and ICs */ + SPSInstrumentation(VMFrame *f) + : profiler_(&f->cx->runtime->spsProfiler), + script_(NULL), + pc_(NULL), + vmframe(f), + frame(NULL) + { + enterInlineFrame(); + setPushed(); + } + + /* Small proxies around SPSProfiler */ + bool enabled() { return profiler_ && profiler_->enabled(); } + SPSProfiler *profiler() { JS_ASSERT(enabled()); return profiler_; } + bool slowAssertions() { return enabled() && profiler_->slowAssertionsEnabled(); } + + /* Signals an inline function returned, reverting to the previous state */ + void leaveInlineFrame() { + if (!enabled()) + return; + frames.shrinkBy(1); + JS_ASSERT(frames.length() > 0); + frame = &frames[frames.length() - 1]; + } + + /* Saves the current state and assumes a fresh one for the inline function */ + bool enterInlineFrame() { + if (!enabled()) + return true; + if (!frames.growBy(1)) + return false; + frame = &frames[frames.length() - 1]; + frame->pushed = frame->skipNext = false; + frame->left = 0; + return true; + } + + /* + * When debugging or with slow assertions, sometimes a C++ method will be + * invoked to perform the pop operation from the SPS stack. When we leave + * JIT code, we need to record the current PC, but upon reentering JIT code, + * no update back to NULL should happen. This method exists to flag this + * behavior. The next leave() will emit instrumentation, but the following + * reenter() will be a no-op. + */ + void skipNextReenter() { + JS_ASSERT(!frame->skipNext && frame->left == 0); + frame->skipNext = true; + } + + /* + * In some cases, a frame needs to be flagged as having been pushed, but no + * instrumentation should be emitted. This updates internal state to flag + * that further instrumentation should actually be emitted. + */ + void setPushed() { + JS_ASSERT(!frame->pushed); + frame->pushed = true; + } + + /* Actual instrumentation emitters, for more information see below */ + bool push(JSContext *cx, Assembler &masm, RegisterID scratch); + void pushManual(Assembler &masm, RegisterID scratch); + void leave(Assembler &masm, RegisterID scratch); + void reenter(Assembler &masm, RegisterID scratch); + void pop(Assembler &masm); +}; + class Assembler : public ValueAssembler { struct CallPatch { @@ -116,15 +240,20 @@ class Assembler : public ValueAssembler bool callIsAligned; #endif + // When instrumentation is enabled, these fields are used to manage the + // instrumentation which occurs at call() locations + SPSInstrumentation *sps; + public: - Assembler() + Assembler(SPSInstrumentation *sps = NULL) : callPatches(SystemAllocPolicy()), availInCall(0), extraStackSpace(0), - stackAdjust(0) + stackAdjust(0), #ifdef DEBUG - , callIsAligned(false) + callIsAligned(false), #endif + sps(sps) { startLabel = label(); } @@ -574,7 +703,17 @@ static const JSC::MacroAssembler::RegisterID JSParamReg_Argc = JSC::MIPSRegiste JS_ASSERT(callIsAligned); - Call cl = call(); + Call cl; + if (sps && sps->enabled()) { + RegisterID reg = availInCall.takeAnyReg().reg(); + sps->leave(*this, reg); + cl = call(); + sps->reenter(*this, reg); + availInCall.putReg(reg); + } else { + cl = call(); + } + callPatches.append(CallPatch(cl, fun)); #ifdef JS_CPU_ARM JS_ASSERT(initFlushCount == flushCount()); @@ -1371,6 +1510,49 @@ static const JSC::MacroAssembler::RegisterID JSParamReg_Argc = JSC::MIPSRegiste } } + private: + /* + * Performs address arithmetic to return the base of the ProfileEntry into + * the register provided. The Jump returned is taken if the SPS stack is + * overflowing and no data should be written to it. + */ + Jump spsProfileEntryAddress(SPSProfiler *p, int offset, RegisterID reg) + { + load32(p->size(), reg); + if (offset != 0) + add32(Imm32(offset), reg); + Jump j = branch32(Assembler::GreaterThanOrEqual, reg, Imm32(p->maxSize())); + JS_STATIC_ASSERT(sizeof(ProfileEntry) == 4 * sizeof(void*)); + // 4 * sizeof(void*) * idx = idx << (2 + log(sizeof(void*))) + lshift32(Imm32(2 + (sizeof(void*) == 4 ? 2 : 3)), reg); + addPtr(ImmPtr(p->stack()), reg); + return j; + } + + public: + void spsUpdatePCIdx(SPSProfiler *p, uint32_t idx, RegisterID reg) { + Jump j = spsProfileEntryAddress(p, -1, reg); + store32(Imm32(idx), Address(reg, ProfileEntry::offsetOfPCIdx())); + j.linkTo(label(), this); + } + + void spsPushFrame(SPSProfiler *p, const char *str, JSScript *s, RegisterID reg) { + Jump j = spsProfileEntryAddress(p, 0, reg); + + storePtr(ImmPtr(str), Address(reg, ProfileEntry::offsetOfString())); + storePtr(ImmPtr(s), Address(reg, ProfileEntry::offsetOfScript())); + storePtr(ImmPtr(NULL), Address(reg, ProfileEntry::offsetOfStackAddress())); + store32(Imm32(0), Address(reg, ProfileEntry::offsetOfPCIdx())); + + /* Always increment the stack size, regardless if we actually pushed */ + j.linkTo(label(), this); + add32(Imm32(1), AbsoluteAddress(p->size())); + } + + void spsPopFrame(SPSProfiler *p) { + sub32(Imm32(1), AbsoluteAddress(p->size())); + } + static const double oneDouble; }; @@ -1418,6 +1600,86 @@ class PreserveRegisters { } }; +/* + * Flags entry into a JS function for the first time. Before this is called, no + * instrumentation is emitted, but after this instrumentation is emitted. + */ +inline bool +SPSInstrumentation::push(JSContext *cx, Assembler &masm, RegisterID scratch) +{ + JS_ASSERT(!frame->pushed); + JS_ASSERT(frame->left == 0); + if (!enabled()) + return true; + JSScript *s = script(); + const char *string = profiler_->profileString(cx, s, s->function()); + if (string == NULL) + return false; + masm.spsPushFrame(profiler_, string, script(), scratch); + frame->pushed = true; + return true; +} + +/* + * Signifies that C++ performed the push() for this function. C++ always sets + * the current PC to something non-null, however, so as soon as JIT code is + * reentered this updates the current pc to NULL. + */ +inline void +SPSInstrumentation::pushManual(Assembler &masm, RegisterID scratch) +{ + JS_ASSERT(!frame->pushed); + JS_ASSERT(frame->left == 0); + if (!enabled()) + return; + masm.spsUpdatePCIdx(profiler_, 0, scratch); + frame->pushed = true; +} + +/* + * Signals that the current function is leaving for a function call. This can + * happen both on JS function calls and also calls to C++. This internally + * manages how many leave() calls have been seen, and only the first leave() + * emits instrumentation. Similarly, only the last corresponding reenter() + * actually emits instrumentation. + */ +inline void +SPSInstrumentation::leave(Assembler &masm, RegisterID scratch) +{ + if (enabled() && frame->pushed && frame->left++ == 0) + masm.spsUpdatePCIdx(profiler_, pc() - script()->code, scratch); +} + +/* + * Flags that the leaving of the current function has returned. This tracks + * state with leave() to only emit instrumentation at proper times. + */ +inline void +SPSInstrumentation::reenter(Assembler &masm, RegisterID scratch) +{ + if (!enabled() || !frame->pushed || frame->left-- != 1) + return; + if (frame->skipNext) + frame->skipNext = false; + else + masm.spsUpdatePCIdx(profiler_, 0, scratch); +} + +/* + * Signifies exiting a JS frame, popping the SPS entry. Because there can be + * multiple return sites of a function, this does not cease instrumentation + * emission. + */ +inline void +SPSInstrumentation::pop(Assembler &masm) +{ + if (enabled()) { + JS_ASSERT(frame->left == 0); + JS_ASSERT(frame->pushed); + masm.spsPopFrame(profiler_); + } +} + } /* namespace mjit */ } /* namespace js */ diff --git a/js/src/methodjit/Compiler.cpp b/js/src/methodjit/Compiler.cpp index 3ea435fa3c7..4049b69062c 100644 --- a/js/src/methodjit/Compiler.cpp +++ b/js/src/methodjit/Compiler.cpp @@ -61,6 +61,8 @@ mjit::Compiler::Compiler(JSContext *cx, JSScript *outerScript, ssa(cx, outerScript), globalObj(cx, outerScript->hasGlobal() ? &outerScript->global() : NULL), globalSlots(globalObj ? globalObj->getRawSlots() : NULL), + sps(&cx->runtime->spsProfiler, &script, &PC), + masm(&sps), frame(cx, *thisFromCtor(), masm, stubcc), a(NULL), outer(NULL), script(NULL), PC(NULL), loop(NULL), inlineFrames(CompilerAllocPolicy(cx, *thisFromCtor())), @@ -447,6 +449,9 @@ mjit::Compiler::pushActiveFrame(JSScript *script, uint32_t argc) return status; } + if (!sps.enterInlineFrame()) + return Compile_Error; + this->script = script; this->analysis = newAnalysis; this->PC = script->code; @@ -467,6 +472,7 @@ mjit::Compiler::popActiveFrame() this->analysis = this->script->analysis(); frame.popActiveFrame(); + sps.leaveInlineFrame(); } #define CHECK_STATUS(expr) \ @@ -524,6 +530,8 @@ mjit::Compiler::performCompilation() if (chunkIndex == 0) CHECK_STATUS(generatePrologue()); + else + sps.setPushed(); CHECK_STATUS(generateMethod()); if (outerJIT() && chunkIndex == outerJIT()->nchunks - 1) CHECK_STATUS(generateEpilogue()); @@ -883,9 +891,12 @@ MakeJITScript(JSContext *cx, JSScript *script) } /* Generate a pool with all cross chunk shims, and set shimLabel for each edge. */ - Assembler masm; + jsbytecode *pc; + SPSInstrumentation sps(&cx->runtime->spsProfiler, &script, &pc); + Assembler masm(&sps); + sps.setPushed(); for (unsigned i = 0; i < jit->nedges; i++) { - jsbytecode *pc = script->code + jitEdges[i].target; + pc = script->code + jitEdges[i].target; jitEdges[i].shimLabel = (void *) masm.distanceOf(masm.label()); masm.move(JSC::MacroAssembler::ImmPtr(&jitEdges[i]), Registers::ArgReg1); masm.fallibleVMCall(true, JS_FUNC_TO_DATA_PTR(void *, stubs::CrossChunkShim), @@ -3789,6 +3800,7 @@ mjit::Compiler::emitReturn(FrameEntry *fe) /* Inline StackFrame::epilogue. */ if (debugMode()) { + sps.skipNextReenter(); prepareStubCall(Uses(0)); INLINE_STUBCALL(stubs::Epilogue, REJOIN_NONE); } else { @@ -3892,51 +3904,38 @@ mjit::Compiler::methodEntryHelper() if (debugMode()) { prepareStubCall(Uses(0)); INLINE_STUBCALL(stubs::ScriptDebugPrologue, REJOIN_RESUME); - } else if (Probes::callTrackingActive(cx)) { + + /* + * If necessary, call the tracking probe to trigger SPS assertions. We can + * only do this when not inlining because the same StackFrame instance will + * be used to enter a function, triggering an assertion in enterScript + */ + } else if (Probes::callTrackingActive(cx) || + (sps.slowAssertions() && a->inlineIndex == UINT32_MAX)) { prepareStubCall(Uses(0)); INLINE_STUBCALL(stubs::ScriptProbeOnlyPrologue, REJOIN_RESUME); } else { return profilingPushHelper(); } + /* Ensure that we've flagged that the push has happened */ + if (sps.enabled()) { + RegisterID reg = frame.allocReg(); + sps.pushManual(masm, reg); + frame.freeReg(reg); + } return Compile_Okay; } CompileStatus mjit::Compiler::profilingPushHelper() { - SPSProfiler *p = &cx->runtime->spsProfiler; - if (!p->enabled()) + if (!sps.enabled()) return Compile_Okay; - /* If allocation fails, make sure no PopHelper() is emitted */ - const char *str = p->profileString(cx, script, script->function()); - if (str == NULL) + RegisterID reg = frame.allocReg(); + if (!sps.push(cx, masm, reg)) return Compile_Error; - /* Check if there's still space on the stack */ - RegisterID size = frame.allocReg(); - RegisterID base = frame.allocReg(); - masm.load32(p->size(), size); - Jump j = masm.branch32(Assembler::GreaterThanOrEqual, size, - Imm32(p->maxSize())); - - /* With room, store our string onto the stack */ - masm.move(ImmPtr(p->stack()), base); - JS_STATIC_ASSERT(sizeof(ProfileEntry) == 2 * sizeof(void*)); - masm.lshift32(Imm32(sizeof(void*) == 4 ? 3 : 4), size); - masm.addPtr(size, base); - - masm.storePtr(ImmPtr(str), Address(base, offsetof(ProfileEntry, string))); - masm.storePtr(ImmPtr(NULL), Address(base, offsetof(ProfileEntry, sp))); - - frame.freeReg(base); - frame.freeReg(size); - - /* Always increment the stack size (paired with a decrement below) */ - j.linkTo(masm.label(), &masm); - masm.add32(Imm32(1), AbsoluteAddress(p->size())); - /* Set the flags that we've pushed information onto the SPS stack */ - RegisterID reg = frame.allocReg(); masm.load32(FrameFlagsAddress(), reg); masm.or32(Imm32(StackFrame::HAS_PUSHED_SPS_FRAME), reg); masm.store32(reg, FrameFlagsAddress()); @@ -3948,13 +3947,12 @@ mjit::Compiler::profilingPushHelper() void mjit::Compiler::profilingPopHelper() { - if (Probes::callTrackingActive(cx) || - cx->runtime->spsProfiler.slowAssertionsEnabled()) - { + if (Probes::callTrackingActive(cx) || sps.slowAssertions()) { + sps.skipNextReenter(); prepareStubCall(Uses(0)); INLINE_STUBCALL(stubs::ScriptProbeOnlyEpilogue, REJOIN_RESUME); - } else if (cx->runtime->spsProfiler.enabled()) { - masm.sub32(Imm32(1), AbsoluteAddress(cx->runtime->spsProfiler.size())); + } else { + sps.pop(masm); } } @@ -4007,6 +4005,11 @@ mjit::Compiler::emitUncachedCall(uint32_t argc, bool callingNew) callPatches.append(callPatch); finishBarrier(barrier, REJOIN_FALLTHROUGH, 0); + if (sps.enabled()) { + RegisterID reg = frame.allocReg(); + sps.reenter(masm, reg); + frame.freeReg(reg); + } } void @@ -4076,6 +4079,11 @@ mjit::Compiler::inlineCallHelper(uint32_t argc, bool callingNew, FrameSize &call * do the interrupt check at the start of the JSOP_ARGUMENTS. */ interruptCheckHelper(); + if (sps.enabled()) { + RegisterID reg = frame.allocReg(); + sps.leave(masm, reg); + frame.freeReg(reg); + } FrameEntry *origCallee = frame.peek(-(int(argc) + 2)); FrameEntry *origThis = frame.peek(-(int(argc) + 1)); @@ -4401,6 +4409,11 @@ mjit::Compiler::inlineCallHelper(uint32_t argc, bool callingNew, FrameSize &call callPatches.append(uncachedCallPatch); finishBarrier(barrier, REJOIN_FALLTHROUGH, 0); + if (sps.enabled()) { + RegisterID reg = frame.allocReg(); + sps.reenter(masm, reg); + frame.freeReg(reg); + } return true; #endif } @@ -4428,6 +4441,12 @@ mjit::Compiler::inlineScriptedFunction(uint32_t argc, bool callingNew) if (inlineCallees.empty()) return Compile_InlineAbort; + if (sps.enabled()) { + RegisterID reg = frame.allocReg(); + sps.leave(masm, reg); + frame.freeReg(reg); + } + JS_ASSERT(!monitored(PC)); /* @@ -4576,6 +4595,12 @@ mjit::Compiler::inlineScriptedFunction(uint32_t argc, bool callingNew) JaegerSpew(JSpew_Inlining, "finished inlining call to script (file \"%s\") (line \"%d\")\n", script->filename, script->lineno); + if (sps.enabled()) { + RegisterID reg = frame.allocReg(); + sps.reenter(masm, reg); + frame.freeReg(reg); + } + return Compile_Okay; } diff --git a/js/src/methodjit/Compiler.h b/js/src/methodjit/Compiler.h index 108bbf685bb..e9035399ceb 100644 --- a/js/src/methodjit/Compiler.h +++ b/js/src/methodjit/Compiler.h @@ -368,6 +368,7 @@ class Compiler : public BaseCompiler Rooted globalObj; const HeapSlot *globalSlots; /* Original slots pointer. */ + SPSInstrumentation sps; Assembler masm; FrameState frame; diff --git a/js/src/methodjit/MonoIC.cpp b/js/src/methodjit/MonoIC.cpp index 6a5eb55c26b..e599c54d858 100644 --- a/js/src/methodjit/MonoIC.cpp +++ b/js/src/methodjit/MonoIC.cpp @@ -342,7 +342,8 @@ class EqualityCompiler : public BaseCompiler bool update() { if (!ic.generated) { - Assembler masm; + SPSInstrumentation sps(&f); + Assembler masm(&sps); Value rval = f.regs.sp[-1]; Value lval = f.regs.sp[-2]; diff --git a/js/src/methodjit/PolyIC.cpp b/js/src/methodjit/PolyIC.cpp index fa1d55b0e36..6d46c114ad5 100644 --- a/js/src/methodjit/PolyIC.cpp +++ b/js/src/methodjit/PolyIC.cpp @@ -257,7 +257,8 @@ class SetPropCompiler : public PICStubCompiler Vector slowExits(cx); Vector otherGuards(cx); - Assembler masm; + SPSInstrumentation sps(&f); + Assembler masm(&sps); // Shape guard. if (pic.shapeNeedsRemat()) { @@ -776,7 +777,8 @@ class GetPropCompiler : public PICStubCompiler LookupStatus generateArrayLengthStub() { - Assembler masm; + SPSInstrumentation sps(&f); + Assembler masm(&sps); masm.loadObjClass(pic.objReg, pic.shapeReg); Jump isDense = masm.testClass(Assembler::Equal, pic.shapeReg, &ArrayClass); @@ -817,7 +819,8 @@ class GetPropCompiler : public PICStubCompiler LookupStatus generateStringObjLengthStub() { - Assembler masm; + SPSInstrumentation sps(&f); + Assembler masm(&sps); Jump notStringObj = masm.guardShape(pic.objReg, obj); @@ -877,7 +880,8 @@ class GetPropCompiler : public PICStubCompiler if (hadGC()) return Lookup_Uncacheable; - Assembler masm; + SPSInstrumentation sps(&f); + Assembler masm(&sps); /* Only strings are allowed. */ Jump notString = masm.branchPtr(Assembler::NotEqual, pic.typeReg(), @@ -931,7 +935,8 @@ class GetPropCompiler : public PICStubCompiler { JS_ASSERT(pic.hasTypeCheck()); - Assembler masm; + SPSInstrumentation sps(&f); + Assembler masm(&sps); Jump notString = masm.branchPtr(Assembler::NotEqual, pic.typeReg(), ImmType(JSVAL_TYPE_STRING)); masm.loadPtr(Address(pic.objReg, JSString::offsetOfLengthAndFlags()), pic.objReg); @@ -1208,7 +1213,8 @@ class GetPropCompiler : public PICStubCompiler { Vector shapeMismatches(cx); - Assembler masm; + SPSInstrumentation sps(&f); + Assembler masm(&sps); // Ignore GC pointers baked into assembly visible on the stack. SkipRoot skip(cx, &masm); @@ -1542,7 +1548,8 @@ class ScopeNameCompiler : public PICStubCompiler LookupStatus generateGlobalStub(JSObject *obj) { - Assembler masm; + SPSInstrumentation sps(&f); + Assembler masm(&sps); JumpList fails(cx); ScopeNameLabels &labels = pic.scopeNameLabels(); @@ -1614,7 +1621,8 @@ class ScopeNameCompiler : public PICStubCompiler LookupStatus generateCallStub(JSObject *obj) { - Assembler masm; + SPSInstrumentation sps(&f); + Assembler masm(&sps); Vector fails(cx); ScopeNameLabels &labels = pic.scopeNameLabels(); @@ -1800,7 +1808,8 @@ class BindNameCompiler : public PICStubCompiler LookupStatus generateStub(JSObject *obj) { - Assembler masm; + SPSInstrumentation sps(&f); + Assembler masm(&sps); Vector fails(cx); BindNameLabels &labels = pic.bindNameLabels(); @@ -2246,7 +2255,8 @@ GetElementIC::attachGetProp(VMFrame &f, HandleObject obj, HandleValue v, HandleP if (cx->typeInferenceEnabled() && !forcedTypeBarrier) return disable(f, "string element access may not have type barrier"); - Assembler masm; + SPSInstrumentation sps(&f); + Assembler masm(&sps); // Guard on the string's type and identity. MaybeJump atomTypeGuard; @@ -2417,7 +2427,8 @@ GetElementIC::attachTypedArray(VMFrame &f, HandleObject obj, HandleValue v, Hand // known to be int32, either via type inference or the inline type check. JS_ASSERT(hasInlineTypeGuard() || idRemat.knownType() == JSVAL_TYPE_INT32); - Assembler masm; + SPSInstrumentation sps(&f); + Assembler masm(&sps); // Guard on this typed array's shape/class. Jump shapeGuard = masm.guardShape(objReg, obj); @@ -2644,7 +2655,8 @@ SetElementIC::attachHoleStub(VMFrame &f, JSObject *obj, int32_t keyval) if (js_PrototypeHasIndexedProperties(cx, obj)) return disable(f, "prototype has indexed properties"); - Assembler masm; + SPSInstrumentation sps(&f); + Assembler masm(&sps); Vector fails(cx); @@ -2738,8 +2750,9 @@ SetElementIC::attachTypedArray(VMFrame &f, JSObject *obj, int32_t key) // Right now, only one shape guard extension is supported. JS_ASSERT(!inlineShapeGuardPatched); - Assembler masm; JSContext *cx = f.cx; + SPSInstrumentation sps(&f); + Assembler masm(&sps); // Restore |obj|. masm.rematPayload(StateRemat::FromInt32(objRemat), objReg); diff --git a/js/src/methodjit/StubCompiler.cpp b/js/src/methodjit/StubCompiler.cpp index fd5760a71d7..da1722cdbfb 100644 --- a/js/src/methodjit/StubCompiler.cpp +++ b/js/src/methodjit/StubCompiler.cpp @@ -18,6 +18,7 @@ StubCompiler::StubCompiler(JSContext *cx, mjit::Compiler &cc, FrameState &frame) : cx(cx), cc(cc), frame(frame), + masm(&cc.sps), generation(1), lastGeneration(0), exits(CompilerAllocPolicy(cx, cc)), diff --git a/js/src/vm/SPSProfiler.cpp b/js/src/vm/SPSProfiler.cpp index a3fd75f3a9d..dae0d97332e 100644 --- a/js/src/vm/SPSProfiler.cpp +++ b/js/src/vm/SPSProfiler.cpp @@ -6,17 +6,29 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "jsnum.h" +#include "jsscript.h" #include "vm/SPSProfiler.h" #include "vm/StringBuffer.h" using namespace js; +SPSProfiler::SPSProfiler(JSRuntime *rt) + : rt(rt), + stack_(NULL), + size_(NULL), + max_(0), + slowAssertions(false), + enabled_(false) +{ + JS_ASSERT(rt != NULL); +} + SPSProfiler::~SPSProfiler() { if (strings.initialized()) { for (ProfileStringMap::Enum e(strings); !e.empty(); e.popFront()) - js_free((void*) e.front().value); + rt->array_delete(e.front().value); } } @@ -55,7 +67,7 @@ SPSProfiler::profileString(JSContext *cx, JSScript *script, JSFunction *maybeFun if (str == NULL) return NULL; if (!strings.add(s, script, str)) { - js_free((void*) str); + rt->array_delete(str); return NULL; } return str; @@ -76,7 +88,7 @@ SPSProfiler::onScriptFinalized(JSScript *script) if (ProfileStringMap::Ptr entry = strings.lookup(script)) { const char *tofree = entry->value; strings.remove(entry); - js_free((void*) tofree); + rt->array_delete(tofree); } } @@ -87,7 +99,9 @@ SPSProfiler::enter(JSContext *cx, JSScript *script, JSFunction *maybeFun) if (str == NULL) return false; - push(str, NULL); + JS_ASSERT_IF(*size_ > 0 && *size_ - 1 < max_ && stack_[*size_ - 1].js(), + stack_[*size_ - 1].pc() != NULL); + push(str, NULL, script, script->code); return true; } @@ -102,22 +116,31 @@ SPSProfiler::exit(JSContext *cx, JSScript *script, JSFunction *maybeFun) const char *str = profileString(cx, script, maybeFun); /* Can't fail lookup because we should already be in the set */ JS_ASSERT(str != NULL); - JS_ASSERT(strcmp((const char*) stack_[*size_].string, str) == 0); - stack_[*size_].string = NULL; - stack_[*size_].sp = NULL; + JS_ASSERT(stack_[*size_].js()); + JS_ASSERT(strcmp((const char*) stack_[*size_].label(), str) == 0); + stack_[*size_].setLabel(NULL); + stack_[*size_].setPC(NULL); } #endif } void -SPSProfiler::push(const char *string, void *sp) +SPSProfiler::push(const char *string, void *sp, JSScript *script, jsbytecode *pc) { + /* these operations cannot be re-ordered, so volatile-ize operations */ + volatile ProfileEntry *stack = stack_; + volatile uint32_t *size = size_; + uint32_t current = *size; + JS_ASSERT(enabled()); - if (*size_ < max_) { - stack_[*size_].string = string; - stack_[*size_].sp = sp; + if (current < max_) { + stack[current].setLabel(string); + stack[current].setStackAddress(sp); + stack[current].setScript(script); + if (pc != NULL) + stack_[current].setPC(pc); } - (*size_)++; + *size = current + 1; } void @@ -160,7 +183,7 @@ SPSProfiler::allocProfileString(JSContext *cx, JSScript *script, JSFunction *may return NULL; size_t len = buf.length(); - char *cstr = (char*) js_malloc(len + 1); + char *cstr = rt->array_new(len + 1); if (cstr == NULL) return NULL; @@ -181,7 +204,7 @@ SPSEntryMarker::SPSEntryMarker(JSRuntime *rt JS_GUARD_OBJECT_NOTIFIER_PARAM_NO_I profiler = NULL; return; } - profiler->push("js::RunScript", this); + profiler->push("js::RunScript", this, NULL, NULL); } SPSEntryMarker::~SPSEntryMarker() @@ -189,3 +212,13 @@ SPSEntryMarker::~SPSEntryMarker() if (profiler != NULL) profiler->pop(); } + +JS_FRIEND_API(jsbytecode*) +ProfileEntry::pc() volatile { + return script()->code + idx; +} + +JS_FRIEND_API(void) +ProfileEntry::setPC(jsbytecode *pc) volatile { + idx = pc - script()->code; +} diff --git a/js/src/vm/SPSProfiler.h b/js/src/vm/SPSProfiler.h index 4c0b29b0a8f..99907ddf44e 100644 --- a/js/src/vm/SPSProfiler.h +++ b/js/src/vm/SPSProfiler.h @@ -8,13 +8,10 @@ #ifndef SPSProfiler_h__ #define SPSProfiler_h__ -#include "mozilla/HashFunctions.h" - #include -#include "jsfriendapi.h" -#include "jsfun.h" -#include "jsscript.h" +#include "mozilla/HashFunctions.h" +#include "js/Utility.h" /* * SPS Profiler integration with the JS Engine @@ -44,13 +41,14 @@ * but it will still maintain the size of the stack. SPS code is aware of this * and iterates the stack accordingly. * - * There are two pointers of information pushed on the SPS stack for every JS - * function that is entered. First is a char* pointer of a description of what - * function was entered. Currently this string is of the form - * "function (file:line)" if there's a function name, or just "file:line" if - * there's no function name available. The other bit of information is the - * relevant C++ (native) stack pointer. This stack pointer is what enables the - * interleaving of the C++ and the JS stack. + * There is some information pushed on the SPS stack for every JS function that + * is entered. First is a char* pointer of a description of what function was + * entered. Currently this string is of the form "function (file:line)" if + * there's a function name, or just "file:line" if there's no function name + * available. The other bit of information is the relevant C++ (native) stack + * pointer. This stack pointer is what enables the interleaving of the C++ and + * the JS stack. Finally, throughout execution of the function, some extra + * information may be updated on the ProfileEntry structure. * * = Profile Strings * @@ -80,10 +78,35 @@ * interleaving C++ and JS, if SPS sees a NULL native stack pointer on the SPS * stack, it looks backwards for the first non-NULL pointer and uses that for * all subsequent NULL native stack pointers. + * + * = Line Numbers + * + * One goal of sampling is to get both a backtrace of the JS stack, but also + * know where within each function on the stack execution currently is. For + * this, each ProfileEntry has a 'pc' field to tell where its execution + * currently is. This field is updated whenever a call is made to another JS + * function, and for the JIT it is also updated whenever the JIT is left. + * + * This field is in a union with a uint32_t 'line' so that C++ can make use of + * the field as well. It was observed that tracking 'line' via PCToLineNumber in + * JS was far too expensive, so that is why the pc instead of the translated + * line number is stored. + * + * As an invariant, if the pc is NULL, then the JIT is currently executing + * generated code. Otherwise execution is in another JS function or in C++. With + * this in place, only the top entry of the stack can ever have NULL as its pc. + * Additionally with this invariant, it is possible to maintain mappings of JIT + * code to pc which can be accessed safely because they will only be accessed + * from a signal handler when the JIT code is executing. */ +struct JSFunction; +struct JSScript; + namespace js { +class ProfileEntry; + typedef HashMap, SystemAllocPolicy> ProfileStringMap; @@ -101,33 +124,44 @@ class SPSProfiler bool slowAssertions; bool enabled_; - static const char *allocProfileString(JSContext *cx, JSScript *script, - JSFunction *function); - void push(const char *string, void *sp); + const char *allocProfileString(JSContext *cx, JSScript *script, + JSFunction *function); + void push(const char *string, void *sp, JSScript *script, jsbytecode *pc); void pop(); public: - SPSProfiler(JSRuntime *rt) - : rt(rt), - stack_(NULL), - size_(NULL), - max_(0), - slowAssertions(false), - enabled_(false) - {} + SPSProfiler(JSRuntime *rt); ~SPSProfiler(); uint32_t *size() { return size_; } uint32_t maxSize() { return max_; } ProfileEntry *stack() { return stack_; } + /* management of whether instrumentation is on or off */ bool enabled() { JS_ASSERT_IF(enabled_, installed()); return enabled_; } bool installed() { return stack_ != NULL && size_ != NULL; } void enable(bool enabled); void enableSlowAssertions(bool enabled) { slowAssertions = enabled; } bool slowAssertionsEnabled() { return slowAssertions; } + + /* + * Functions which are the actual instrumentation to track run information + * + * - enter: a function has started to execute + * - updatePC: updates the pc information about where a function + * is currently executing + * - exit: this function has ceased execution, and no further + * entries/exits will be made + */ bool enter(JSContext *cx, JSScript *script, JSFunction *maybeFun); void exit(JSContext *cx, JSScript *script, JSFunction *maybeFun); + void updatePC(JSScript *script, jsbytecode *pc) { + if (enabled() && *size_ - 1 < max_) { + JS_ASSERT(*size_ > 0); + stack_[*size_ - 1].setPC(pc); + } + } + void setProfilingStack(ProfileEntry *stack, uint32_t *size, uint32_t max); const char *profileString(JSContext *cx, JSScript *script, JSFunction *maybeFun); void onScriptFinalized(JSScript *script);