fune/js/src/vm/GeckoProfiler.cpp
Nicholas Nethercote c649ebbc0f Backout 72f2cb8b917e (bug 1329923) for causing crashes.
--HG--
extra : rebase_source : c8dd4ebeb3b3e320947a56358d25d8108eb7e040
2017-07-05 14:42:29 +10:00

565 lines
17 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "vm/GeckoProfiler-inl.h"
#include "mozilla/DebugOnly.h"
#include "jsnum.h"
#include "jsprf.h"
#include "jsscript.h"
#include "jit/BaselineFrame.h"
#include "jit/BaselineJIT.h"
#include "jit/JitcodeMap.h"
#include "jit/JitFrameIterator.h"
#include "jit/JitFrames.h"
#include "vm/StringBuffer.h"
#include "jsgcinlines.h"
using namespace js;
using mozilla::DebugOnly;
GeckoProfiler::GeckoProfiler(JSRuntime* rt)
: rt(rt),
strings(mutexid::GeckoProfilerStrings),
pseudoStack_(nullptr),
slowAssertions(false),
enabled_(false),
eventMarker_(nullptr)
{
MOZ_ASSERT(rt != nullptr);
}
bool
GeckoProfiler::init()
{
auto locked = strings.lock();
if (!locked->init())
return false;
return true;
}
void
GeckoProfiler::setProfilingStack(PseudoStack* pseudoStack)
{
MOZ_ASSERT_IF(pseudoStack_, !enabled());
MOZ_ASSERT(strings.lock()->initialized());
pseudoStack_ = pseudoStack;
}
void
GeckoProfiler::setEventMarker(void (*fn)(const char*))
{
eventMarker_ = fn;
}
/* Get a pointer to the top-most profiling frame, given the exit frame pointer. */
static void*
GetTopProfilingJitFrame(Activation* act)
{
if (!act || !act->isJit())
return nullptr;
// For null exitFrame, there is no previous exit frame, just return.
uint8_t* exitFP = act->asJit()->exitFP();
if (!exitFP)
return nullptr;
jit::JitProfilingFrameIterator iter(exitFP);
MOZ_ASSERT(!iter.done());
return iter.fp();
}
bool
GeckoProfiler::enable(bool enabled)
{
MOZ_ASSERT(installed());
if (enabled_ == enabled)
return true;
// Execution in the runtime must be single threaded if the Gecko profiler
// is enabled. There is only a single profiler stack in the runtime, from
// which entries must be added/removed in a LIFO fashion.
JSContext* cx = rt->activeContextFromOwnThread();
if (enabled) {
if (!rt->beginSingleThreadedExecution(cx))
return false;
} else {
rt->endSingleThreadedExecution(cx);
}
/*
* Ensure all future generated code will be instrumented, or that all
* currently instrumented code is discarded
*/
ReleaseAllJITCode(rt->defaultFreeOp());
// This function is called when the Gecko profiler makes a new Sampler
// (and thus, a new circular buffer). Set all current entries in the
// JitcodeGlobalTable as expired and reset the buffer generation and lap
// count.
if (rt->hasJitRuntime() && rt->jitRuntime()->hasJitcodeGlobalTable())
rt->jitRuntime()->getJitcodeGlobalTable()->setAllEntriesAsExpired(rt);
rt->resetProfilerSampleBufferGen();
rt->resetProfilerSampleBufferLapCount();
// Ensure that lastProfilingFrame is null for all threads before 'enabled' becomes true.
for (const CooperatingContext& target : rt->cooperatingContexts()) {
if (target.context()->jitActivation) {
target.context()->jitActivation->setLastProfilingFrame(nullptr);
target.context()->jitActivation->setLastProfilingCallSite(nullptr);
}
}
enabled_ = enabled;
/* Toggle Gecko Profiler-related jumps on baseline jitcode.
* The call to |ReleaseAllJITCode| above will release most baseline jitcode, but not
* jitcode for scripts with active frames on the stack. These scripts need to have
* their profiler state toggled so they behave properly.
*/
jit::ToggleBaselineProfiling(rt, enabled);
/* Update lastProfilingFrame to point to the top-most JS jit-frame currently on
* stack.
*/
for (const CooperatingContext& target : rt->cooperatingContexts()) {
if (target.context()->jitActivation) {
// Walk through all activations, and set their lastProfilingFrame appropriately.
if (enabled) {
Activation* act = target.context()->activation();
void* lastProfilingFrame = GetTopProfilingJitFrame(act);
jit::JitActivation* jitActivation = target.context()->jitActivation;
while (jitActivation) {
jitActivation->setLastProfilingFrame(lastProfilingFrame);
jitActivation->setLastProfilingCallSite(nullptr);
jitActivation = jitActivation->prevJitActivation();
lastProfilingFrame = GetTopProfilingJitFrame(jitActivation);
}
} else {
jit::JitActivation* jitActivation = target.context()->jitActivation;
while (jitActivation) {
jitActivation->setLastProfilingFrame(nullptr);
jitActivation->setLastProfilingCallSite(nullptr);
jitActivation = jitActivation->prevJitActivation();
}
}
}
}
// WebAssembly code does not need to be released, but profiling string
// labels have to be generated so that they are available during async
// profiling stack iteration.
for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next())
c->wasm.ensureProfilingLabels(enabled);
return true;
}
/* Lookup the string for the function/script, creating one if necessary */
const char*
GeckoProfiler::profileString(JSScript* script, JSFunction* maybeFun)
{
auto locked = strings.lock();
MOZ_ASSERT(locked->initialized());
ProfileStringMap::AddPtr s = locked->lookupForAdd(script);
if (!s) {
auto str = allocProfileString(script, maybeFun);
if (!str || !locked->add(s, script, mozilla::Move(str)))
return nullptr;
}
return s->value().get();
}
void
GeckoProfiler::onScriptFinalized(JSScript* script)
{
/*
* This function is called whenever a script is destroyed, regardless of
* whether profiling has been turned on, so don't invoke a function on an
* invalid hash set. Also, even if profiling was enabled but then turned
* off, we still want to remove the string, so no check of enabled() is
* done.
*/
auto locked = strings.lock();
if (!locked->initialized())
return;
if (ProfileStringMap::Ptr entry = locked->lookup(script))
locked->remove(entry);
}
void
GeckoProfiler::markEvent(const char* event)
{
MOZ_ASSERT(enabled());
if (eventMarker_) {
JS::AutoSuppressGCAnalysis nogc;
eventMarker_(event);
}
}
bool
GeckoProfiler::enter(JSContext* cx, JSScript* script, JSFunction* maybeFun)
{
const char* dynamicString = profileString(script, maybeFun);
if (dynamicString == nullptr) {
ReportOutOfMemory(cx);
return false;
}
#ifdef DEBUG
// In debug builds, assert the JS pseudo frames already on the stack
// have a non-null pc. Only look at the top frames to avoid quadratic
// behavior.
uint32_t sp = pseudoStack_->stackPointer;
if (sp > 0 && sp - 1 < PseudoStack::MaxEntries) {
size_t start = (sp > 4) ? sp - 4 : 0;
for (size_t i = start; i < sp - 1; i++)
MOZ_ASSERT_IF(pseudoStack_->entries[i].isJs(), pseudoStack_->entries[i].pc());
}
#endif
pseudoStack_->pushJsFrame("", dynamicString, script, script->code());
return true;
}
void
GeckoProfiler::exit(JSScript* script, JSFunction* maybeFun)
{
pseudoStack_->pop();
#ifdef DEBUG
/* Sanity check to make sure push/pop balanced */
uint32_t sp = pseudoStack_->stackPointer;
if (sp < PseudoStack::MaxEntries) {
const char* dynamicString = profileString(script, maybeFun);
/* Can't fail lookup because we should already be in the set */
MOZ_ASSERT(dynamicString);
// Bug 822041
if (!pseudoStack_->entries[sp].isJs()) {
fprintf(stderr, "--- ABOUT TO FAIL ASSERTION ---\n");
fprintf(stderr, " entries=%p size=%u/%u\n",
(void*) pseudoStack_->entries,
uint32_t(pseudoStack_->stackPointer),
PseudoStack::MaxEntries);
for (int32_t i = sp; i >= 0; i--) {
ProfileEntry& entry = pseudoStack_->entries[i];
if (entry.isJs())
fprintf(stderr, " [%d] JS %s\n", i, entry.dynamicString());
else
fprintf(stderr, " [%d] C line %d %s\n", i, entry.line(), entry.dynamicString());
}
}
ProfileEntry& entry = pseudoStack_->entries[sp];
MOZ_ASSERT(entry.isJs());
MOZ_ASSERT(entry.script() == script);
MOZ_ASSERT(strcmp((const char*) entry.dynamicString(), dynamicString) == 0);
}
#endif
}
/*
* Serializes the script/function pair into a "descriptive string" which is
* allowed to fail. This function cannot trigger a GC because it could finalize
* some scripts, resize the hash table of profile strings, and invalidate the
* AddPtr held while invoking allocProfileString.
*/
UniqueChars
GeckoProfiler::allocProfileString(JSScript* script, JSFunction* maybeFun)
{
// Note: this profiler string is regexp-matched by
// devtools/client/profiler/cleopatra/js/parserWorker.js.
// Get the function name, if any.
JSAtom* atom = maybeFun ? maybeFun->displayAtom() : nullptr;
// Get the script filename, if any, and its length.
const char* filename = script->filename();
if (filename == nullptr)
filename = "<unknown>";
size_t lenFilename = strlen(filename);
// Get the line number and its length as a string.
uint64_t lineno = script->lineno();
size_t lenLineno = 1;
for (uint64_t i = lineno; i /= 10; lenLineno++);
// Determine the required buffer size.
size_t len = lenFilename + lenLineno + 1; // +1 for the ":" separating them.
if (atom) {
len += JS::GetDeflatedUTF8StringLength(atom) + 3; // +3 for the " (" and ")" it adds.
}
// Allocate the buffer.
UniqueChars cstr(js_pod_malloc<char>(len + 1));
if (!cstr)
return nullptr;
// Construct the descriptive string.
DebugOnly<size_t> ret;
if (atom) {
UniqueChars atomStr = StringToNewUTF8CharsZ(nullptr, *atom);
if (!atomStr)
return nullptr;
ret = snprintf(cstr.get(), len + 1, "%s (%s:%" PRIu64 ")", atomStr.get(), filename, lineno);
} else {
ret = snprintf(cstr.get(), len + 1, "%s:%" PRIu64, filename, lineno);
}
MOZ_ASSERT(ret == len, "Computed length should match actual length!");
return cstr;
}
void
GeckoProfiler::trace(JSTracer* trc)
{
if (pseudoStack_) {
size_t size = pseudoStack_->stackSize();
for (size_t i = 0; i < size; i++)
pseudoStack_->entries[i].trace(trc);
}
}
void
GeckoProfiler::fixupStringsMapAfterMovingGC()
{
auto locked = strings.lock();
if (!locked->initialized())
return;
for (ProfileStringMap::Enum e(locked.get()); !e.empty(); e.popFront()) {
JSScript* script = e.front().key();
if (IsForwarded(script)) {
script = Forwarded(script);
e.rekeyFront(script);
}
}
}
#ifdef JSGC_HASH_TABLE_CHECKS
void
GeckoProfiler::checkStringsMapAfterMovingGC()
{
auto locked = strings.lock();
if (!locked->initialized())
return;
for (auto r = locked->all(); !r.empty(); r.popFront()) {
JSScript* script = r.front().key();
CheckGCThingAfterMovingGC(script);
auto ptr = locked->lookup(script);
MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &r.front());
}
}
#endif
void
ProfileEntry::trace(JSTracer* trc)
{
if (isJs()) {
JSScript* s = rawScript();
TraceNullableRoot(trc, &s, "ProfileEntry script");
spOrScript = s;
}
}
GeckoProfilerEntryMarker::GeckoProfilerEntryMarker(JSRuntime* rt,
JSScript* script
MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
: profiler(&rt->geckoProfiler())
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
if (!profiler->installed()) {
profiler = nullptr;
return;
}
spBefore_ = profiler->stackPointer();
// We want to push a CPP frame so the profiler can correctly order JS and native stacks.
// Only the sp value is important.
profiler->pseudoStack_->pushCppFrame(
/* label = */ "", /* dynamicString = */ nullptr, /* sp = */ this, /* line = */ 0,
ProfileEntry::Kind::CPP_MARKER_FOR_JS, ProfileEntry::Category::OTHER);
profiler->pseudoStack_->pushJsFrame(
"js::RunScript", /* dynamicString = */ nullptr, script, script->code());
}
GeckoProfilerEntryMarker::~GeckoProfilerEntryMarker()
{
if (profiler == nullptr)
return;
profiler->pseudoStack_->pop(); // the JS frame
profiler->pseudoStack_->pop(); // the BEGIN_PSEUDO_JS frame
MOZ_ASSERT(spBefore_ == profiler->stackPointer());
}
AutoGeckoProfilerEntry::AutoGeckoProfilerEntry(JSRuntime* rt, const char* label,
ProfileEntry::Category category
MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
: profiler_(&rt->geckoProfiler())
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
if (!profiler_->installed()) {
profiler_ = nullptr;
return;
}
spBefore_ = profiler_->stackPointer();
profiler_->pseudoStack_->pushCppFrame(
label, /* dynamicString = */ nullptr, /* sp = */ this, /* line = */ 0,
ProfileEntry::Kind::CPP_NORMAL, category);
}
AutoGeckoProfilerEntry::~AutoGeckoProfilerEntry()
{
if (!profiler_)
return;
profiler_->pseudoStack_->pop();
MOZ_ASSERT(spBefore_ == profiler_->stackPointer());
}
GeckoProfilerBaselineOSRMarker::GeckoProfilerBaselineOSRMarker(JSRuntime* rt, bool hasProfilerFrame
MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
: profiler(&rt->geckoProfiler())
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
if (!hasProfilerFrame || !profiler->enabled()) {
profiler = nullptr;
return;
}
uint32_t sp = profiler->pseudoStack_->stackPointer;
if (sp >= PseudoStack::MaxEntries) {
profiler = nullptr;
return;
}
spBefore_ = sp;
if (sp == 0)
return;
ProfileEntry& entry = profiler->pseudoStack_->entries[sp - 1];
MOZ_ASSERT(entry.kind() == ProfileEntry::Kind::JS_NORMAL);
entry.setKind(ProfileEntry::Kind::JS_OSR);
}
GeckoProfilerBaselineOSRMarker::~GeckoProfilerBaselineOSRMarker()
{
if (profiler == nullptr)
return;
uint32_t sp = profiler->stackPointer();
MOZ_ASSERT(spBefore_ == sp);
if (sp == 0)
return;
ProfileEntry& entry = profiler->stack()[sp - 1];
MOZ_ASSERT(entry.kind() == ProfileEntry::Kind::JS_OSR);
entry.setKind(ProfileEntry::Kind::JS_NORMAL);
}
JS_PUBLIC_API(JSScript*)
ProfileEntry::script() const
{
MOZ_ASSERT(isJs());
auto script = reinterpret_cast<JSScript*>(spOrScript);
if (!script)
return nullptr;
// If profiling is supressed then we can't trust the script pointers to be
// valid as they could be in the process of being moved by a compacting GC
// (although it's still OK to get the runtime from them).
//
// We only need to check the active context here, as
// AutoSuppressProfilerSampling prohibits the runtime's active context from
// being changed while it exists.
JSContext* cx = script->runtimeFromAnyThread()->activeContext();
if (!cx->isProfilerSamplingEnabled())
return nullptr;
MOZ_ASSERT(!IsForwarded(script));
return script;
}
JS_FRIEND_API(jsbytecode*)
ProfileEntry::pc() const
{
MOZ_ASSERT(isJs());
if (lineOrPcOffset == NullPCOffset)
return nullptr;
JSScript* script = this->script();
return script ? script->offsetToPC(lineOrPcOffset) : nullptr;
}
/* static */ int32_t
ProfileEntry::pcToOffset(JSScript* aScript, jsbytecode* aPc) {
return aPc ? aScript->pcToOffset(aPc) : NullPCOffset;
}
void
ProfileEntry::setPC(jsbytecode* pc)
{
MOZ_ASSERT(isJs());
JSScript* script = this->script();
MOZ_ASSERT(script); // This should not be called while profiling is suppressed.
lineOrPcOffset = pcToOffset(script, pc);
}
JS_FRIEND_API(void)
js::SetContextProfilingStack(JSContext* cx, PseudoStack* pseudoStack)
{
cx->runtime()->geckoProfiler().setProfilingStack(pseudoStack);
}
JS_FRIEND_API(void)
js::EnableContextProfilingStack(JSContext* cx, bool enabled)
{
if (!cx->runtime()->geckoProfiler().enable(enabled))
MOZ_CRASH("Execution in this runtime should already be single threaded");
}
JS_FRIEND_API(void)
js::RegisterContextProfilingEventMarker(JSContext* cx, void (*fn)(const char*))
{
MOZ_ASSERT(cx->runtime()->geckoProfiler().enabled());
cx->runtime()->geckoProfiler().setEventMarker(fn);
}
AutoSuppressProfilerSampling::AutoSuppressProfilerSampling(JSContext* cx
MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
: cx_(cx),
previouslyEnabled_(cx->isProfilerSamplingEnabled()),
prohibitContextChange_(cx->runtime())
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
if (previouslyEnabled_)
cx_->disableProfilerSampling();
}
AutoSuppressProfilerSampling::~AutoSuppressProfilerSampling()
{
if (previouslyEnabled_)
cx_->enableProfilerSampling();
}