blob: db84ac24fd28330e62ece84ae508ed8dfa9dfc3c [file] [log] [blame]
/* -*- 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/SPSProfiler.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"
using namespace js;
using mozilla::DebugOnly;
SPSProfiler::SPSProfiler(JSRuntime* rt)
: rt(rt),
stack_(nullptr),
size_(nullptr),
max_(0),
slowAssertions(false),
enabled_(false),
lock_(nullptr),
eventMarker_(nullptr)
{
MOZ_ASSERT(rt != nullptr);
}
bool
SPSProfiler::init()
{
lock_ = PR_NewLock();
if (lock_ == nullptr)
return false;
return true;
}
SPSProfiler::~SPSProfiler()
{
if (strings.initialized()) {
for (ProfileStringMap::Enum e(strings); !e.empty(); e.popFront())
js_free(const_cast<char*>(e.front().value()));
}
if (lock_)
PR_DestroyLock(lock_);
}
void
SPSProfiler::setProfilingStack(ProfileEntry* stack, uint32_t* size, uint32_t max)
{
AutoSPSLock lock(lock_);
MOZ_ASSERT_IF(size_ && *size_ != 0, !enabled());
if (!strings.initialized())
strings.init();
stack_ = stack;
size_ = size;
max_ = max;
}
void
SPSProfiler::setEventMarker(void (*fn)(const char*))
{
eventMarker_ = fn;
}
void
SPSProfiler::enable(bool enabled)
{
MOZ_ASSERT(installed());
if (enabled_ == enabled)
return;
/*
* 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 TableTicker
// (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 before 'enabled' becomes true.
if (rt->jitActivation) {
rt->jitActivation->setLastProfilingFrame(nullptr);
rt->jitActivation->setLastProfilingCallSite(nullptr);
}
enabled_ = enabled;
/* Toggle SPS-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.
*/
if (rt->jitActivation) {
// Walk through all activations, and set their lastProfilingFrame appropriately.
if (enabled) {
void* lastProfilingFrame = GetTopProfilingJitFrame(rt->jitTop);
jit::JitActivation* jitActivation = rt->jitActivation;
while (jitActivation) {
jitActivation->setLastProfilingFrame(lastProfilingFrame);
jitActivation->setLastProfilingCallSite(nullptr);
lastProfilingFrame = GetTopProfilingJitFrame(jitActivation->prevJitTop());
jitActivation = jitActivation->prevJitActivation();
}
} else {
jit::JitActivation* jitActivation = rt->jitActivation;
while (jitActivation) {
jitActivation->setLastProfilingFrame(nullptr);
jitActivation->setLastProfilingCallSite(nullptr);
jitActivation = jitActivation->prevJitActivation();
}
}
}
}
/* Lookup the string for the function/script, creating one if necessary */
const char*
SPSProfiler::profileString(JSScript* script, JSFunction* maybeFun)
{
AutoSPSLock lock(lock_);
MOZ_ASSERT(strings.initialized());
ProfileStringMap::AddPtr s = strings.lookupForAdd(script);
if (s)
return s->value();
const char* str = allocProfileString(script, maybeFun);
if (str == nullptr)
return nullptr;
if (!strings.add(s, script, str)) {
js_free(const_cast<char*>(str));
return nullptr;
}
return str;
}
void
SPSProfiler::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.
*/
AutoSPSLock lock(lock_);
if (!strings.initialized())
return;
if (ProfileStringMap::Ptr entry = strings.lookup(script)) {
const char* tofree = entry->value();
strings.remove(entry);
js_free(const_cast<char*>(tofree));
}
}
void
SPSProfiler::markEvent(const char* event)
{
MOZ_ASSERT(enabled());
if (eventMarker_) {
JS::AutoSuppressGCAnalysis nogc;
eventMarker_(event);
}
}
bool
SPSProfiler::enter(JSContext* cx, JSScript* script, JSFunction* maybeFun)
{
const char* str = profileString(script, maybeFun);
if (str == 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.
if (*size_ > 0 && *size_ - 1 < max_) {
size_t start = (*size_ > 4) ? *size_ - 4 : 0;
for (size_t i = start; i < *size_ - 1; i++)
MOZ_ASSERT_IF(stack_[i].isJs(), stack_[i].pc() != nullptr);
}
#endif
push(str, nullptr, script, script->code(), /* copy = */ true);
return true;
}
void
SPSProfiler::exit(JSScript* script, JSFunction* maybeFun)
{
pop();
#ifdef DEBUG
/* Sanity check to make sure push/pop balanced */
if (*size_ < max_) {
const char* str = profileString(script, maybeFun);
/* Can't fail lookup because we should already be in the set */
MOZ_ASSERT(str != nullptr);
// Bug 822041
if (!stack_[*size_].isJs()) {
fprintf(stderr, "--- ABOUT TO FAIL ASSERTION ---\n");
fprintf(stderr, " stack=%p size=%d/%d\n", (void*) stack_, *size_, max_);
for (int32_t i = *size_; i >= 0; i--) {
if (stack_[i].isJs())
fprintf(stderr, " [%d] JS %s\n", i, stack_[i].label());
else
fprintf(stderr, " [%d] C line %d %s\n", i, stack_[i].line(), stack_[i].label());
}
}
MOZ_ASSERT(stack_[*size_].isJs());
MOZ_ASSERT(stack_[*size_].script() == script);
MOZ_ASSERT(strcmp((const char*) stack_[*size_].label(), str) == 0);
stack_[*size_].setLabel(nullptr);
stack_[*size_].setPC(nullptr);
}
#endif
}
void
SPSProfiler::beginPseudoJS(const char* string, void* sp)
{
/* these operations cannot be re-ordered, so volatile-ize operations */
volatile ProfileEntry* stack = stack_;
volatile uint32_t* size = size_;
uint32_t current = *size;
MOZ_ASSERT(installed());
if (current < max_) {
stack[current].setLabel(string);
stack[current].initCppFrame(sp, 0);
stack[current].setFlag(ProfileEntry::BEGIN_PSEUDO_JS);
}
*size = current + 1;
}
void
SPSProfiler::push(const char* string, void* sp, JSScript* script, jsbytecode* pc, bool copy,
ProfileEntry::Category category)
{
MOZ_ASSERT_IF(sp != nullptr, script == nullptr && pc == nullptr);
MOZ_ASSERT_IF(sp == nullptr, script != nullptr && pc != nullptr);
/* these operations cannot be re-ordered, so volatile-ize operations */
volatile ProfileEntry* stack = stack_;
volatile uint32_t* size = size_;
uint32_t current = *size;
MOZ_ASSERT(installed());
if (current < max_) {
volatile ProfileEntry& entry = stack[current];
if (sp != nullptr) {
entry.initCppFrame(sp, 0);
MOZ_ASSERT(entry.flags() == js::ProfileEntry::IS_CPP_ENTRY);
}
else {
entry.initJsFrame(script, pc);
MOZ_ASSERT(entry.flags() == 0);
}
entry.setLabel(string);
entry.setCategory(category);
// Track if mLabel needs a copy.
if (copy)
entry.setFlag(js::ProfileEntry::FRAME_LABEL_COPY);
else
entry.unsetFlag(js::ProfileEntry::FRAME_LABEL_COPY);
}
*size = current + 1;
}
void
SPSProfiler::pop()
{
MOZ_ASSERT(installed());
(*size_)--;
MOZ_ASSERT(*(int*)size_ >= 0);
}
/*
* 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.
*/
const char*
SPSProfiler::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.
char* cstr = js_pod_malloc<char>(len + 1);
if (cstr == nullptr)
return nullptr;
// Construct the descriptive string.
DebugOnly<size_t> ret;
if (atom) {
JS::AutoCheckCannotGC nogc;
auto atomStr = mozilla::UniquePtr<char, JS::FreePolicy>(
atom->hasLatin1Chars()
? JS::CharsToNewUTF8CharsZ(nullptr, atom->latin1Range(nogc)).c_str()
: JS::CharsToNewUTF8CharsZ(nullptr, atom->twoByteRange(nogc)).c_str());
if (!atomStr)
return nullptr;
ret = JS_snprintf(cstr, len + 1, "%s (%s:%llu)", atomStr.get(), filename, lineno);
} else {
ret = JS_snprintf(cstr, len + 1, "%s:%llu", filename, lineno);
}
MOZ_ASSERT(ret == len, "Computed length should match actual length!");
return cstr;
}
SPSEntryMarker::SPSEntryMarker(JSRuntime* rt,
JSScript* script
MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
: profiler(&rt->spsProfiler)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
if (!profiler->installed()) {
profiler = nullptr;
return;
}
size_before = *profiler->size_;
// We want to push a CPP frame so the profiler can correctly order JS and native stacks.
profiler->beginPseudoJS("js::RunScript", this);
profiler->push("js::RunScript", nullptr, script, script->code(), /* copy = */ false);
}
SPSEntryMarker::~SPSEntryMarker()
{
if (profiler == nullptr)
return;
profiler->pop();
profiler->endPseudoJS();
MOZ_ASSERT(size_before == *profiler->size_);
}
AutoSPSEntry::AutoSPSEntry(JSRuntime* rt, const char* label, ProfileEntry::Category category
MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
: profiler_(&rt->spsProfiler)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
if (!profiler_->installed()) {
profiler_ = nullptr;
return;
}
sizeBefore_ = *profiler_->size_;
profiler_->beginPseudoJS(label, this);
profiler_->push(label, this, nullptr, nullptr, /* copy = */ false, category);
}
AutoSPSEntry::~AutoSPSEntry()
{
if (!profiler_)
return;
profiler_->pop();
profiler_->endPseudoJS();
MOZ_ASSERT(sizeBefore_ == *profiler_->size_);
}
SPSBaselineOSRMarker::SPSBaselineOSRMarker(JSRuntime* rt, bool hasSPSFrame
MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
: profiler(&rt->spsProfiler)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
if (!hasSPSFrame || !profiler->enabled() ||
profiler->size() >= profiler->maxSize())
{
profiler = nullptr;
return;
}
size_before = profiler->size();
if (profiler->size() == 0)
return;
ProfileEntry& entry = profiler->stack()[profiler->size() - 1];
MOZ_ASSERT(entry.isJs());
entry.setOSR();
}
SPSBaselineOSRMarker::~SPSBaselineOSRMarker()
{
if (profiler == nullptr)
return;
MOZ_ASSERT(size_before == *profiler->size_);
if (profiler->size() == 0)
return;
ProfileEntry& entry = profiler->stack()[profiler->size() - 1];
MOZ_ASSERT(entry.isJs());
entry.unsetOSR();
}
JS_FRIEND_API(jsbytecode*)
ProfileEntry::pc() const volatile
{
MOZ_ASSERT(isJs());
return lineOrPc == NullPCOffset ? nullptr : script()->offsetToPC(lineOrPc);
}
JS_FRIEND_API(void)
ProfileEntry::setPC(jsbytecode* pc) volatile
{
MOZ_ASSERT(isJs());
lineOrPc = pc == nullptr ? NullPCOffset : script()->pcToOffset(pc);
}
JS_FRIEND_API(void)
js::SetRuntimeProfilingStack(JSRuntime* rt, ProfileEntry* stack, uint32_t* size, uint32_t max)
{
rt->spsProfiler.setProfilingStack(stack, size, max);
}
JS_FRIEND_API(void)
js::EnableRuntimeProfilingStack(JSRuntime* rt, bool enabled)
{
rt->spsProfiler.enable(enabled);
}
JS_FRIEND_API(void)
js::RegisterRuntimeProfilingEventMarker(JSRuntime* rt, void (*fn)(const char*))
{
MOZ_ASSERT(rt->spsProfiler.enabled());
rt->spsProfiler.setEventMarker(fn);
}
JS_FRIEND_API(jsbytecode*)
js::ProfilingGetPC(JSRuntime* rt, JSScript* script, void* ip)
{
return rt->spsProfiler.ipToPC(script, size_t(ip));
}
AutoSuppressProfilerSampling::AutoSuppressProfilerSampling(JSContext* cx
MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
: rt_(cx->runtime()),
previouslyEnabled_(rt_->isProfilerSamplingEnabled())
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
if (previouslyEnabled_)
rt_->disableProfilerSampling();
}
AutoSuppressProfilerSampling::AutoSuppressProfilerSampling(JSRuntime* rt
MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
: rt_(rt),
previouslyEnabled_(rt_->isProfilerSamplingEnabled())
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
if (previouslyEnabled_)
rt_->disableProfilerSampling();
}
AutoSuppressProfilerSampling::~AutoSuppressProfilerSampling()
{
if (previouslyEnabled_)
rt_->enableProfilerSampling();
}
void*
js::GetTopProfilingJitFrame(uint8_t* exitFramePtr)
{
// For null exitFrame, there is no previous exit frame, just return.
if (!exitFramePtr)
return nullptr;
jit::JitProfilingFrameIterator iter(exitFramePtr);
MOZ_ASSERT(!iter.done());
return iter.fp();
}