summaryrefslogtreecommitdiffstats
path: root/js/src/vm/SPSProfiler.cpp
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /js/src/vm/SPSProfiler.cpp
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'js/src/vm/SPSProfiler.cpp')
-rw-r--r--js/src/vm/SPSProfiler.cpp586
1 files changed, 586 insertions, 0 deletions
diff --git a/js/src/vm/SPSProfiler.cpp b/js/src/vm/SPSProfiler.cpp
new file mode 100644
index 000000000..8872af8d2
--- /dev/null
+++ b/js/src/vm/SPSProfiler.cpp
@@ -0,0 +1,586 @@
+/* -*- 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),
+ strings(mutexid::SPSProfilerStrings),
+ stack_(nullptr),
+ size_(nullptr),
+ max_(0),
+ slowAssertions(false),
+ enabled_(false),
+ eventMarker_(nullptr)
+{
+ MOZ_ASSERT(rt != nullptr);
+}
+
+bool
+SPSProfiler::init()
+{
+ auto locked = strings.lock();
+ if (!locked->init())
+ return false;
+
+ return true;
+}
+
+void
+SPSProfiler::setProfilingStack(ProfileEntry* stack, uint32_t* size, uint32_t max)
+{
+ MOZ_ASSERT_IF(size_ && *size_ != 0, !enabled());
+ MOZ_ASSERT(strings.lock()->initialized());
+
+ 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)
+{
+ 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
+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.
+ */
+ auto locked = strings.lock();
+ if (!locked->initialized())
+ return;
+ if (ProfileStringMap::Ptr entry = locked->lookup(script))
+ locked->remove(entry);
+}
+
+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());
+ MOZ_ASSERT(*size_ > 0);
+ (*size_)--;
+}
+
+/*
+ * 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
+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.
+ 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
+SPSProfiler::trace(JSTracer* trc)
+{
+ if (stack_) {
+ size_t limit = Min(*size_, max_);
+ for (size_t i = 0; i < limit; i++)
+ stack_[i].trace(trc);
+ }
+}
+
+void
+SPSProfiler::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
+SPSProfiler::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;
+ }
+}
+
+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_PUBLIC_API(JSScript*)
+ProfileEntry::script() const volatile
+{
+ 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).
+ JSRuntime* rt = script->zoneFromAnyThread()->runtimeFromAnyThread();
+ if (!rt->isProfilerSamplingEnabled())
+ return nullptr;
+
+ MOZ_ASSERT(!IsForwarded(script));
+ return script;
+}
+
+JS_FRIEND_API(jsbytecode*)
+ProfileEntry::pc() const volatile
+{
+ MOZ_ASSERT(isJs());
+ if (lineOrPcOffset == NullPCOffset)
+ return nullptr;
+
+ JSScript* script = this->script();
+ return script ? script->offsetToPC(lineOrPcOffset) : nullptr;
+}
+
+JS_FRIEND_API(void)
+ProfileEntry::setPC(jsbytecode* pc) volatile
+{
+ MOZ_ASSERT(isJs());
+ JSScript* script = this->script();
+ MOZ_ASSERT(script); // This should not be called while profiling is suppressed.
+ lineOrPcOffset = pc == nullptr ? NullPCOffset : script->pcToOffset(pc);
+}
+
+JS_FRIEND_API(void)
+js::SetContextProfilingStack(JSContext* cx, ProfileEntry* stack, uint32_t* size, uint32_t max)
+{
+ cx->spsProfiler.setProfilingStack(stack, size, max);
+}
+
+JS_FRIEND_API(void)
+js::EnableContextProfilingStack(JSContext* cx, bool enabled)
+{
+ cx->spsProfiler.enable(enabled);
+}
+
+JS_FRIEND_API(void)
+js::RegisterContextProfilingEventMarker(JSContext* cx, void (*fn)(const char*))
+{
+ MOZ_ASSERT(cx->spsProfiler.enabled());
+ cx->spsProfiler.setEventMarker(fn);
+}
+
+JS_FRIEND_API(jsbytecode*)
+js::ProfilingGetPC(JSContext* cx, JSScript* script, void* ip)
+{
+ return cx->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();
+}