Add interface for accessing PC counter information from chrome code, bug 687134. r=sfink,waldo

This commit is contained in:
Brian Hackett 2011-12-16 13:11:08 -08:00
parent ed70a01420
commit 3983e0f31e
15 changed files with 1230 additions and 355 deletions

View File

@ -60,6 +60,7 @@
#include "nsGUIEvent.h"
#include "nsIParser.h"
#include "nsJSEnvironment.h"
#include "nsJSUtils.h"
#include "nsIViewManager.h"
@ -1972,3 +1973,105 @@ nsDOMWindowUtils::GetFileReferences(const nsAString& aDatabaseName,
*aResult = false;
return NS_OK;
}
static inline JSContext *
GetJSContext()
{
nsCOMPtr<nsIXPConnect> xpc = nsContentUtils::XPConnect();
// get the xpconnect native call context
nsAXPCNativeCallContext *cc = nsnull;
xpc->GetCurrentNativeCallContext(&cc);
if(!cc)
return NULL;
// Get JSContext of current call
JSContext* cx;
nsresult rv = cc->GetJSContext(&cx);
if(NS_FAILED(rv) || !cx)
return NULL;
return cx;
}
NS_IMETHODIMP
nsDOMWindowUtils::StartPCCountProfiling()
{
JSContext *cx = GetJSContext();
if (!cx)
return NS_ERROR_FAILURE;
js::StartPCCountProfiling(cx);
return NS_OK;
}
NS_IMETHODIMP
nsDOMWindowUtils::StopPCCountProfiling()
{
JSContext *cx = GetJSContext();
if (!cx)
return NS_ERROR_FAILURE;
js::StopPCCountProfiling(cx);
return NS_OK;
}
NS_IMETHODIMP
nsDOMWindowUtils::PurgePCCounts()
{
JSContext *cx = GetJSContext();
if (!cx)
return NS_ERROR_FAILURE;
js::PurgePCCounts(cx);
return NS_OK;
}
NS_IMETHODIMP
nsDOMWindowUtils::GetPCCountScriptCount(PRInt32 *result)
{
JSContext *cx = GetJSContext();
if (!cx)
return NS_ERROR_FAILURE;
*result = js::GetPCCountScriptCount(cx);
return NS_OK;
}
NS_IMETHODIMP
nsDOMWindowUtils::GetPCCountScriptSummary(PRInt32 script, nsAString& result)
{
JSContext *cx = GetJSContext();
if (!cx)
return NS_ERROR_FAILURE;
JSString *text = js::GetPCCountScriptSummary(cx, script);
if (!text)
return NS_ERROR_FAILURE;
nsDependentJSString str;
if (!str.init(cx, text))
return NS_ERROR_FAILURE;
result = str;
return NS_OK;
}
NS_IMETHODIMP
nsDOMWindowUtils::GetPCCountScriptContents(PRInt32 script, nsAString& result)
{
JSContext *cx = GetJSContext();
if (!cx)
return NS_ERROR_FAILURE;
JSString *text = js::GetPCCountScriptContents(cx, script);
if (!text)
return NS_ERROR_FAILURE;
nsDependentJSString str;
if (!str.init(cx, text))
return NS_ERROR_FAILURE;
result = str;
return NS_OK;
}

View File

@ -69,7 +69,7 @@ interface nsIDOMBlob;
interface nsIDOMFile;
interface nsIFile;
[scriptable, uuid(36adf309-e5c4-4912-9152-7fb151dc754a)]
[scriptable, uuid(3af3c5ce-6f2a-47e7-acd0-555ed576fa82)]
interface nsIDOMWindowUtils : nsISupports {
/**
@ -941,4 +941,38 @@ interface nsIDOMWindowUtils : nsISupports {
[optional] out long aDBRefCnt,
[optional] out long aSliceRefCnt);
/**
* Begin opcode-level profiling of all JavaScript execution in the window's
* runtime.
*/
void startPCCountProfiling();
/**
* Stop opcode-level profiling of JavaScript execution in the runtime, and
* collect all counts for use by getPCCount methods.
*/
void stopPCCountProfiling();
/**
* Purge collected PC counters.
*/
void purgePCCounts();
/**
* Get the number of scripts with opcode-level profiling information.
*/
long getPCCountScriptCount();
/**
* Get a JSON string for a short summary of a script and the PC counts
* accumulated for it.
*/
AString getPCCountScriptSummary(in long script);
/**
* Get a JSON string with full information about a profiled script,
* including the decompilation of the script and placement of decompiled
* operations within it, and PC counts for each operation.
*/
AString getPCCountScriptContents(in long script);
};

View File

@ -681,11 +681,13 @@ JSRuntime::JSRuntime()
gcBlackRootsData(NULL),
gcGrayRootsTraceOp(NULL),
gcGrayRootsData(NULL),
scriptPCCounters(NULL),
NaNValue(UndefinedValue()),
negativeInfinityValue(UndefinedValue()),
positiveInfinityValue(UndefinedValue()),
emptyString(NULL),
debugMode(false),
profilingScripts(false),
hadOutOfMemory(false),
data(NULL),
#ifdef JS_THREADSAFE

View File

@ -89,6 +89,9 @@ class JaegerCompartment;
class WeakMapBase;
class InterpreterFrames;
class ScriptOpcodeCounts;
struct ScriptOpcodeCountsPair;
/*
* GetSrcNote cache to avoid O(n^2) growth in finding a source note for a
* given pc in a script. We use the script->code pointer to tag the cache,
@ -342,7 +345,8 @@ typedef void
namespace js {
typedef js::Vector<JSCompartment *, 0, js::SystemAllocPolicy> CompartmentVector;
typedef Vector<JSCompartment *, 0, SystemAllocPolicy> CompartmentVector;
typedef Vector<ScriptOpcodeCountsPair, 0, SystemAllocPolicy> ScriptOpcodeCountsVector;
}
@ -529,6 +533,9 @@ struct JSRuntime
JSTraceDataOp gcGrayRootsTraceOp;
void *gcGrayRootsData;
/* Strong references on scripts held for PCCount profiling API. */
js::ScriptOpcodeCountsVector *scriptPCCounters;
/* Well-known numbers held for use by this runtime's contexts. */
js::Value NaNValue;
js::Value negativeInfinityValue;
@ -545,6 +552,9 @@ struct JSRuntime
/* If true, new compartments are initially in debug mode. */
bool debugMode;
/* If true, new scripts must be created with PC counter information. */
bool profilingScripts;
/* Had an out-of-memory error which did not populate an exception. */
JSBool hadOutOfMemory;

View File

@ -1672,32 +1672,28 @@ DumpBytecodeScriptCallback(JSContext *cx, void *data, void *thing,
JSGCTraceKind traceKind, size_t thingSize)
{
JS_ASSERT(traceKind == JSTRACE_SCRIPT);
JS_ASSERT(!data);
JSScript *script = static_cast<JSScript *>(thing);
JS_DumpBytecode(cx, script);
reinterpret_cast<Vector<JSScript *> *>(data)->append(script);
}
JS_PUBLIC_API(void)
JS_DumpCompartmentBytecode(JSContext *cx)
{
IterateCells(cx, cx->compartment, gc::FINALIZE_SCRIPT, NULL, DumpBytecodeScriptCallback);
}
Vector<JSScript *> scripts(cx);
IterateCells(cx, cx->compartment, gc::FINALIZE_SCRIPT, &scripts, DumpBytecodeScriptCallback);
static void
DumpPCCountsScriptCallback(JSContext *cx, void *data, void *thing,
JSGCTraceKind traceKind, size_t thingSize)
{
JS_ASSERT(traceKind == JSTRACE_SCRIPT);
JS_ASSERT(!data);
JSScript *script = static_cast<JSScript *>(thing);
if (script->pcCounters)
JS_DumpPCCounts(cx, script);
for (size_t i = 0; i < scripts.length(); i++)
JS_DumpBytecode(cx, scripts[i]);
}
JS_PUBLIC_API(void)
JS_DumpCompartmentPCCounts(JSContext *cx)
{
IterateCells(cx, cx->compartment, gc::FINALIZE_SCRIPT, NULL, DumpPCCountsScriptCallback);
for (CellIter i(cx, cx->compartment, gc::FINALIZE_SCRIPT); !i.done(); i.next()) {
JSScript *script = i.get<JSScript>();
if (script->pcCounters)
JS_DumpPCCounts(cx, script);
}
}
JS_PUBLIC_API(JSObject *)

View File

@ -442,6 +442,24 @@ SetPreserveWrapperCallback(JSRuntime *rt, PreserveWrapperCallback callback);
#define JSITER_OWNONLY 0x8 /* iterate over obj's own properties only */
#define JSITER_HIDDEN 0x10 /* also enumerate non-enumerable properties */
JS_FRIEND_API(void)
StartPCCountProfiling(JSContext *cx);
JS_FRIEND_API(void)
StopPCCountProfiling(JSContext *cx);
JS_FRIEND_API(void)
PurgePCCounts(JSContext *cx);
JS_FRIEND_API(size_t)
GetPCCountScriptCount(JSContext *cx);
JS_FRIEND_API(JSString *)
GetPCCountScriptSummary(JSContext *cx, size_t script);
JS_FRIEND_API(JSString *)
GetPCCountScriptContents(JSContext *cx, size_t script);
} /* namespace js */
#endif

View File

@ -92,6 +92,7 @@
#include "vm/Debugger.h"
#include "vm/String.h"
#include "jsinterpinlines.h"
#include "jsobjinlines.h"
#include "vm/CallObject-inl.h"
@ -2051,6 +2052,12 @@ MarkRuntime(JSTracer *trc)
for (GCLocks::Range r = rt->gcLocksHash.all(); !r.empty(); r.popFront())
gc_lock_traversal(r.front(), trc);
if (rt->scriptPCCounters) {
const ScriptOpcodeCountsVector &vec = *rt->scriptPCCounters;
for (size_t i = 0; i < vec.length(); i++)
MarkRoot(trc, vec[i].script, "scriptPCCounters");
}
js_TraceAtomState(trc);
rt->staticStrings.trace(trc);
@ -2061,6 +2068,15 @@ MarkRuntime(JSTracer *trc)
for (GCCompartmentsIter c(rt); !c.done(); c.next()) {
if (c->activeAnalysis)
c->markTypes(trc);
/* Do not discard scripts with counters while profiling. */
if (rt->profilingScripts) {
for (CellIterUnderGC i(c, FINALIZE_SCRIPT); !i.done(); i.next()) {
JSScript *script = i.get<JSScript>();
if (script->pcCounters)
MarkRoot(trc, script, "profilingScripts");
}
}
}
for (ThreadDataIter i(rt); !i.empty(); i.popFront())
@ -3559,6 +3575,121 @@ VerifyBarriers(JSContext *cx, bool always)
} /* namespace gc */
static void ReleaseAllJITCode(JSContext *cx)
{
#ifdef JS_METHODJIT
for (GCCompartmentsIter c(cx->runtime); !c.done(); c.next()) {
mjit::ClearAllFrames(c);
for (CellIter i(cx, c, FINALIZE_SCRIPT); !i.done(); i.next()) {
JSScript *script = i.get<JSScript>();
mjit::ReleaseScriptCode(cx, script);
}
}
#endif
}
/*
* There are three possible PCCount profiling states:
*
* 1. None: Neither scripts nor the runtime have counter information.
* 2. Profile: Active scripts have counter information, the runtime does not.
* 3. Query: Scripts do not have counter information, the runtime does.
*
* When starting to profile scripts, counting begins immediately, with all JIT
* code discarded and recompiled with counters as necessary. Active interpreter
* frames will not begin profiling until they begin executing another script
* (via a call or return).
*
* The below API functions manage transitions to new states, according
* to the table below.
*
* Old State
* -------------------------
* Function None Profile Query
* --------
* StartPCCountProfiling Profile Profile Profile
* StopPCCountProfiling None Query Query
* PurgePCCounts None None None
*/
static void
ReleaseScriptPCCounters(JSContext *cx)
{
JSRuntime *rt = cx->runtime;
JS_ASSERT(rt->scriptPCCounters);
ScriptOpcodeCountsVector &vec = *rt->scriptPCCounters;
for (size_t i = 0; i < vec.length(); i++)
vec[i].counters.destroy(cx);
cx->delete_(rt->scriptPCCounters);
rt->scriptPCCounters = NULL;
}
JS_FRIEND_API(void)
StartPCCountProfiling(JSContext *cx)
{
JSRuntime *rt = cx->runtime;
AutoLockGC lock(rt);
if (rt->profilingScripts)
return;
if (rt->scriptPCCounters)
ReleaseScriptPCCounters(cx);
ReleaseAllJITCode(cx);
rt->profilingScripts = true;
}
JS_FRIEND_API(void)
StopPCCountProfiling(JSContext *cx)
{
JSRuntime *rt = cx->runtime;
AutoLockGC lock(rt);
if (!rt->profilingScripts)
return;
JS_ASSERT(!rt->scriptPCCounters);
ReleaseAllJITCode(cx);
ScriptOpcodeCountsVector *vec = cx->new_<ScriptOpcodeCountsVector>(SystemAllocPolicy());
if (!vec)
return;
for (GCCompartmentsIter c(rt); !c.done(); c.next()) {
for (CellIter i(cx, c, FINALIZE_SCRIPT); !i.done(); i.next()) {
JSScript *script = i.get<JSScript>();
if (script->pcCounters && script->types) {
ScriptOpcodeCountsPair info;
info.script = script;
info.counters.steal(script->pcCounters);
if (!vec->append(info))
info.counters.destroy(cx);
}
}
}
rt->profilingScripts = false;
rt->scriptPCCounters = vec;
}
JS_FRIEND_API(void)
PurgePCCounts(JSContext *cx)
{
JSRuntime *rt = cx->runtime;
AutoLockGC lock(rt);
if (!rt->scriptPCCounters)
return;
JS_ASSERT(!rt->profilingScripts);
ReleaseScriptPCCounters(cx);
}
} /* namespace js */
#if JS_HAS_XML_SUPPORT

View File

@ -1757,6 +1757,9 @@ js::Interpret(JSContext *cx, StackFrame *entryFrame, InterpMode interpMode)
Value *argv = regs.fp()->maybeFormalArgs();
CHECK_INTERRUPT_HANDLER();
if (rt->profilingScripts)
ENABLE_INTERRUPTS();
if (!entryFrame)
entryFrame = regs.fp();
@ -1869,6 +1872,12 @@ js::Interpret(JSContext *cx, StackFrame *entryFrame, InterpMode interpMode)
{
bool moreInterrupts = false;
if (cx->runtime->profilingScripts) {
if (!script->pcCounters)
script->initCounts(cx);
moreInterrupts = true;
}
if (script->pcCounters) {
OpcodeCounts counts = script->getCounts(regs.pc);
counts.get(OpcodeCounts::BASE_INTERP)++;

File diff suppressed because it is too large Load Diff

View File

@ -677,6 +677,11 @@ class OpcodeCounts
JS_ASSERT(which < capacity);
return counts[which];
}
// Boolean conversion, for 'if (counters) ...'
operator void*() const {
return counts;
}
};
} /* namespace js */

View File

@ -38,6 +38,8 @@
#include "jsautooplen.h"
#include "frontend/BytecodeEmitter.h"
namespace js {
class BytecodeRange {
@ -69,4 +71,80 @@ AdvanceOverBlockchainOp(jsbytecode *pc)
return pc;
}
class SrcNoteLineScanner
{
/* offset of the current JSOp in the bytecode */
ptrdiff_t offset;
/* next src note to process */
jssrcnote *sn;
/* line number of the current JSOp */
uint32_t lineno;
/*
* Is the current op the first one after a line change directive? Note that
* multiple ops may be "first" if a line directive is used to return to a
* previous line (eg, with a for loop increment expression.)
*/
bool lineHeader;
public:
SrcNoteLineScanner(jssrcnote *sn, uint32_t lineno)
: offset(0), sn(sn), lineno(lineno)
{
}
/*
* This is called repeatedly with always-advancing relpc values. The src
* notes are tuples of <PC offset from prev src note, type, args>. Scan
* through, updating the lineno, until the next src note is for a later
* bytecode.
*
* When looking at the desired PC offset ('relpc'), the op is first in that
* line iff there is a SRC_SETLINE or SRC_NEWLINE src note for that exact
* bytecode.
*
* Note that a single bytecode may have multiple line-modifying notes (even
* though only one should ever be needed.)
*/
void advanceTo(ptrdiff_t relpc) {
// Must always advance! If the same or an earlier PC is erroneously
// passed in, we will already be past the relevant src notes
JS_ASSERT_IF(offset > 0, relpc > offset);
// Next src note should be for after the current offset
JS_ASSERT_IF(offset > 0, SN_IS_TERMINATOR(sn) || SN_DELTA(sn) > 0);
// The first PC requested is always considered to be a line header
lineHeader = (offset == 0);
if (SN_IS_TERMINATOR(sn))
return;
ptrdiff_t nextOffset;
while ((nextOffset = offset + SN_DELTA(sn)) <= relpc && !SN_IS_TERMINATOR(sn)) {
offset = nextOffset;
SrcNoteType type = (SrcNoteType) SN_TYPE(sn);
if (type == SRC_SETLINE || type == SRC_NEWLINE) {
if (type == SRC_SETLINE)
lineno = js_GetSrcNoteOffset(sn, 0);
else
lineno++;
if (offset == relpc)
lineHeader = true;
}
sn = SN_NEXT(sn);
}
}
bool isLineHeader() const {
return lineHeader;
}
uint32_t getLine() const { return lineno; }
};
}

View File

@ -776,6 +776,11 @@ JSScript::initCounts(JSContext *cx)
JS_ASSERT(size_t(cursor - base) == bytes);
/* Enable interrupts in any interpreter frames running on this script. */
InterpreterFrames *frames;
for (frames = JS_THREAD_DATA(cx)->interpreterFrames; frames; frames = frames->older)
frames->enableInterruptsIfRunning(this);
return true;
}

View File

@ -342,6 +342,7 @@ namespace analyze { class ScriptAnalysis; }
class ScriptOpcodeCounts
{
friend struct ::JSScript;
friend struct ScriptOpcodeCountsPair;
OpcodeCounts *counts;
public:
@ -349,8 +350,11 @@ class ScriptOpcodeCounts
ScriptOpcodeCounts() : counts(NULL) {
}
~ScriptOpcodeCounts() {
JS_ASSERT(!counts);
inline void destroy(JSContext *cx);
void steal(ScriptOpcodeCounts &other) {
*this = other;
js::PodZero(&other);
}
// Boolean conversion, for 'if (counters) ...'
@ -849,6 +853,17 @@ js_CallDestroyScriptHook(JSContext *cx, JSScript *script);
namespace js {
struct ScriptOpcodeCountsPair
{
JSScript *script;
ScriptOpcodeCounts counters;
OpcodeCounts &getCounts(jsbytecode *pc) const {
JS_ASSERT(unsigned(pc - script->code) < script->length);
return counters.counts[pc - script->code];
}
};
#ifdef JS_CRASH_DIAGNOSTICS
void

View File

@ -132,6 +132,13 @@ CurrentScriptFileLineOrigin(JSContext *cx, const char **file, uintN *linenop, JS
CurrentScriptFileLineOriginSlow(cx, file, linenop, origin);
}
inline void
ScriptOpcodeCounts::destroy(JSContext *cx)
{
if (counts)
cx->free_(counts);
}
} // namespace js
inline void

View File

@ -406,6 +406,9 @@ mjit::Compiler::scanInlineCalls(uint32_t index, uint32_t depth)
CompileStatus
mjit::Compiler::pushActiveFrame(JSScript *script, uint32_t argc)
{
if (cx->runtime->profilingScripts && !script->pcCounters)
script->initCounts(cx);
ActiveFrame *newa = cx->new_<ActiveFrame>(cx);
if (!newa)
return Compile_Error;
@ -1396,81 +1399,6 @@ mjit::Compiler::finishThisUp(JITScript **jitp)
return Compile_Okay;
}
class SrcNoteLineScanner {
/* offset of the current JSOp in the bytecode */
ptrdiff_t offset;
/* next src note to process */
jssrcnote *sn;
/* line number of the current JSOp */
uint32_t lineno;
/*
* Is the current op the first one after a line change directive? Note that
* multiple ops may be "first" if a line directive is used to return to a
* previous line (eg, with a for loop increment expression.)
*/
bool lineHeader;
public:
SrcNoteLineScanner(jssrcnote *sn, uint32_t lineno)
: offset(0), sn(sn), lineno(lineno)
{
}
/*
* This is called repeatedly with always-advancing relpc values. The src
* notes are tuples of <PC offset from prev src note, type, args>. Scan
* through, updating the lineno, until the next src note is for a later
* bytecode.
*
* When looking at the desired PC offset ('relpc'), the op is first in that
* line iff there is a SRC_SETLINE or SRC_NEWLINE src note for that exact
* bytecode.
*
* Note that a single bytecode may have multiple line-modifying notes (even
* though only one should ever be needed.)
*/
void advanceTo(ptrdiff_t relpc) {
// Must always advance! If the same or an earlier PC is erroneously
// passed in, we will already be past the relevant src notes
JS_ASSERT_IF(offset > 0, relpc > offset);
// Next src note should be for after the current offset
JS_ASSERT_IF(offset > 0, SN_IS_TERMINATOR(sn) || SN_DELTA(sn) > 0);
// The first PC requested is always considered to be a line header
lineHeader = (offset == 0);
if (SN_IS_TERMINATOR(sn))
return;
ptrdiff_t nextOffset;
while ((nextOffset = offset + SN_DELTA(sn)) <= relpc && !SN_IS_TERMINATOR(sn)) {
offset = nextOffset;
SrcNoteType type = (SrcNoteType) SN_TYPE(sn);
if (type == SRC_SETLINE || type == SRC_NEWLINE) {
if (type == SRC_SETLINE)
lineno = js_GetSrcNoteOffset(sn, 0);
else
lineno++;
if (offset == relpc)
lineHeader = true;
}
sn = SN_NEXT(sn);
}
}
bool isLineHeader() const {
return lineHeader;
}
uint32_t getLine() const { return lineno; }
};
#ifdef DEBUG
#define SPEW_OPCODE() \
JS_BEGIN_MACRO \