mirror of
https://github.com/classilla/tenfourfox.git
synced 2024-09-26 23:54:56 +00:00
527 lines
15 KiB
C++
527 lines
15 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/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();
|
|
}
|