michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "vm/SPSProfiler.h" michael@0: michael@0: #include "mozilla/DebugOnly.h" michael@0: michael@0: #include "jsnum.h" michael@0: #include "jsprf.h" michael@0: #include "jsscript.h" michael@0: michael@0: #include "jit/BaselineJIT.h" michael@0: #include "vm/StringBuffer.h" michael@0: michael@0: using namespace js; michael@0: michael@0: using mozilla::DebugOnly; michael@0: michael@0: SPSProfiler::SPSProfiler(JSRuntime *rt) michael@0: : rt(rt), michael@0: stack_(nullptr), michael@0: size_(nullptr), michael@0: max_(0), michael@0: slowAssertions(false), michael@0: enabled_(false), michael@0: lock_(nullptr), michael@0: eventMarker_(nullptr) michael@0: { michael@0: JS_ASSERT(rt != nullptr); michael@0: } michael@0: michael@0: bool michael@0: SPSProfiler::init() michael@0: { michael@0: #ifdef JS_THREADSAFE michael@0: lock_ = PR_NewLock(); michael@0: if (lock_ == nullptr) michael@0: return false; michael@0: #endif michael@0: return true; michael@0: } michael@0: michael@0: SPSProfiler::~SPSProfiler() michael@0: { michael@0: if (strings.initialized()) { michael@0: for (ProfileStringMap::Enum e(strings); !e.empty(); e.popFront()) michael@0: js_free(const_cast(e.front().value())); michael@0: } michael@0: #ifdef JS_THREADSAFE michael@0: if (lock_) michael@0: PR_DestroyLock(lock_); michael@0: #endif michael@0: } michael@0: michael@0: void michael@0: SPSProfiler::setProfilingStack(ProfileEntry *stack, uint32_t *size, uint32_t max) michael@0: { michael@0: AutoSPSLock lock(lock_); michael@0: JS_ASSERT_IF(size_ && *size_ != 0, !enabled()); michael@0: if (!strings.initialized()) michael@0: strings.init(); michael@0: stack_ = stack; michael@0: size_ = size; michael@0: max_ = max; michael@0: } michael@0: michael@0: void michael@0: SPSProfiler::setEventMarker(void (*fn)(const char *)) michael@0: { michael@0: eventMarker_ = fn; michael@0: } michael@0: michael@0: void michael@0: SPSProfiler::enable(bool enabled) michael@0: { michael@0: JS_ASSERT(installed()); michael@0: michael@0: if (enabled_ == enabled) michael@0: return; michael@0: michael@0: /* michael@0: * Ensure all future generated code will be instrumented, or that all michael@0: * currently instrumented code is discarded michael@0: */ michael@0: ReleaseAllJITCode(rt->defaultFreeOp()); michael@0: michael@0: enabled_ = enabled; michael@0: michael@0: #ifdef JS_ION michael@0: /* Toggle SPS-related jumps on baseline jitcode. michael@0: * The call to |ReleaseAllJITCode| above will release most baseline jitcode, but not michael@0: * jitcode for scripts with active frames on the stack. These scripts need to have michael@0: * their profiler state toggled so they behave properly. michael@0: */ michael@0: jit::ToggleBaselineSPS(rt, enabled); michael@0: #endif michael@0: } michael@0: michael@0: /* Lookup the string for the function/script, creating one if necessary */ michael@0: const char* michael@0: SPSProfiler::profileString(JSScript *script, JSFunction *maybeFun) michael@0: { michael@0: AutoSPSLock lock(lock_); michael@0: JS_ASSERT(strings.initialized()); michael@0: ProfileStringMap::AddPtr s = strings.lookupForAdd(script); michael@0: if (s) michael@0: return s->value(); michael@0: const char *str = allocProfileString(script, maybeFun); michael@0: if (str == nullptr) michael@0: return nullptr; michael@0: if (!strings.add(s, script, str)) { michael@0: js_free(const_cast(str)); michael@0: return nullptr; michael@0: } michael@0: return str; michael@0: } michael@0: michael@0: void michael@0: SPSProfiler::onScriptFinalized(JSScript *script) michael@0: { michael@0: /* michael@0: * This function is called whenever a script is destroyed, regardless of michael@0: * whether profiling has been turned on, so don't invoke a function on an michael@0: * invalid hash set. Also, even if profiling was enabled but then turned michael@0: * off, we still want to remove the string, so no check of enabled() is michael@0: * done. michael@0: */ michael@0: AutoSPSLock lock(lock_); michael@0: if (!strings.initialized()) michael@0: return; michael@0: if (ProfileStringMap::Ptr entry = strings.lookup(script)) { michael@0: const char *tofree = entry->value(); michael@0: strings.remove(entry); michael@0: js_free(const_cast(tofree)); michael@0: } michael@0: } michael@0: michael@0: void michael@0: SPSProfiler::markEvent(const char *event) michael@0: { michael@0: JS_ASSERT(enabled()); michael@0: if (eventMarker_) { michael@0: JS::AutoAssertNoGC nogc; michael@0: eventMarker_(event); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: SPSProfiler::enter(JSScript *script, JSFunction *maybeFun) michael@0: { michael@0: const char *str = profileString(script, maybeFun); michael@0: if (str == nullptr) michael@0: return false; michael@0: michael@0: #ifdef DEBUG michael@0: // In debug builds, assert the JS pseudo frames already on the stack michael@0: // have a non-null pc. Only look at the top frames to avoid quadratic michael@0: // behavior. michael@0: if (*size_ > 0 && *size_ - 1 < max_) { michael@0: size_t start = (*size_ > 4) ? *size_ - 4 : 0; michael@0: for (size_t i = start; i < *size_ - 1; i++) michael@0: MOZ_ASSERT_IF(stack_[i].js(), stack_[i].pc() != nullptr); michael@0: } michael@0: #endif michael@0: michael@0: push(str, nullptr, script, script->code()); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: SPSProfiler::exit(JSScript *script, JSFunction *maybeFun) michael@0: { michael@0: pop(); michael@0: michael@0: #ifdef DEBUG michael@0: /* Sanity check to make sure push/pop balanced */ michael@0: if (*size_ < max_) { michael@0: const char *str = profileString(script, maybeFun); michael@0: /* Can't fail lookup because we should already be in the set */ michael@0: JS_ASSERT(str != nullptr); michael@0: michael@0: // Bug 822041 michael@0: if (!stack_[*size_].js()) { michael@0: fprintf(stderr, "--- ABOUT TO FAIL ASSERTION ---\n"); michael@0: fprintf(stderr, " stack=%p size=%d/%d\n", (void*) stack_, *size_, max_); michael@0: for (int32_t i = *size_; i >= 0; i--) { michael@0: if (stack_[i].js()) michael@0: fprintf(stderr, " [%d] JS %s\n", i, stack_[i].label()); michael@0: else michael@0: fprintf(stderr, " [%d] C line %d %s\n", i, stack_[i].line(), stack_[i].label()); michael@0: } michael@0: } michael@0: michael@0: JS_ASSERT(stack_[*size_].js()); michael@0: JS_ASSERT(stack_[*size_].script() == script); michael@0: JS_ASSERT(strcmp((const char*) stack_[*size_].label(), str) == 0); michael@0: stack_[*size_].setLabel(nullptr); michael@0: stack_[*size_].setPC(nullptr); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: void michael@0: SPSProfiler::enterNative(const char *string, void *sp) michael@0: { michael@0: /* these operations cannot be re-ordered, so volatile-ize operations */ michael@0: volatile ProfileEntry *stack = stack_; michael@0: volatile uint32_t *size = size_; michael@0: uint32_t current = *size; michael@0: michael@0: JS_ASSERT(enabled()); michael@0: if (current < max_) { michael@0: stack[current].setLabel(string); michael@0: stack[current].setStackAddress(sp); michael@0: stack[current].setScript(nullptr); michael@0: stack[current].setLine(0); michael@0: } michael@0: *size = current + 1; michael@0: } michael@0: michael@0: void michael@0: SPSProfiler::push(const char *string, void *sp, JSScript *script, jsbytecode *pc) michael@0: { michael@0: /* these operations cannot be re-ordered, so volatile-ize operations */ michael@0: volatile ProfileEntry *stack = stack_; michael@0: volatile uint32_t *size = size_; michael@0: uint32_t current = *size; michael@0: michael@0: JS_ASSERT(installed()); michael@0: if (current < max_) { michael@0: stack[current].setLabel(string); michael@0: stack[current].setStackAddress(sp); michael@0: stack[current].setScript(script); michael@0: stack[current].setPC(pc); michael@0: } michael@0: *size = current + 1; michael@0: } michael@0: michael@0: void michael@0: SPSProfiler::pop() michael@0: { michael@0: JS_ASSERT(installed()); michael@0: (*size_)--; michael@0: JS_ASSERT(*(int*)size_ >= 0); michael@0: } michael@0: michael@0: /* michael@0: * Serializes the script/function pair into a "descriptive string" which is michael@0: * allowed to fail. This function cannot trigger a GC because it could finalize michael@0: * some scripts, resize the hash table of profile strings, and invalidate the michael@0: * AddPtr held while invoking allocProfileString. michael@0: */ michael@0: const char * michael@0: SPSProfiler::allocProfileString(JSScript *script, JSFunction *maybeFun) michael@0: { michael@0: // Note: this profiler string is regexp-matched by michael@0: // browser/devtools/profiler/cleopatra/js/parserWorker.js. michael@0: michael@0: // Determine if the function (if any) has an explicit or guessed name. michael@0: bool hasAtom = maybeFun && maybeFun->displayAtom(); michael@0: michael@0: // Get the function name, if any, and its length. michael@0: const jschar *atom = nullptr; michael@0: size_t lenAtom = 0; michael@0: if (hasAtom) { michael@0: atom = maybeFun->displayAtom()->charsZ(); michael@0: lenAtom = maybeFun->displayAtom()->length(); michael@0: } michael@0: michael@0: // Get the script filename, if any, and its length. michael@0: const char *filename = script->filename(); michael@0: if (filename == nullptr) michael@0: filename = ""; michael@0: size_t lenFilename = strlen(filename); michael@0: michael@0: // Get the line number and its length as a string. michael@0: uint64_t lineno = script->lineno(); michael@0: size_t lenLineno = 1; michael@0: for (uint64_t i = lineno; i /= 10; lenLineno++); michael@0: michael@0: // Determine the required buffer size. michael@0: size_t len = lenFilename + lenLineno + 1; // +1 for the ":" separating them. michael@0: if (hasAtom) michael@0: len += lenAtom + 3; // +3 for the " (" and ")" it adds. michael@0: michael@0: // Allocate the buffer. michael@0: char *cstr = js_pod_malloc(len + 1); michael@0: if (cstr == nullptr) michael@0: return nullptr; michael@0: michael@0: // Construct the descriptive string. michael@0: DebugOnly ret; michael@0: if (hasAtom) michael@0: ret = JS_snprintf(cstr, len + 1, "%hs (%s:%llu)", atom, filename, lineno); michael@0: else michael@0: ret = JS_snprintf(cstr, len + 1, "%s:%llu", filename, lineno); michael@0: michael@0: MOZ_ASSERT(ret == len, "Computed length should match actual length!"); michael@0: michael@0: return cstr; michael@0: } michael@0: michael@0: SPSEntryMarker::SPSEntryMarker(JSRuntime *rt michael@0: MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) michael@0: : profiler(&rt->spsProfiler) michael@0: { michael@0: MOZ_GUARD_OBJECT_NOTIFIER_INIT; michael@0: if (!profiler->installed()) { michael@0: profiler = nullptr; michael@0: return; michael@0: } michael@0: size_before = *profiler->size_; michael@0: profiler->pushNoCopy("js::RunScript", this, nullptr, nullptr); michael@0: } michael@0: michael@0: SPSEntryMarker::~SPSEntryMarker() michael@0: { michael@0: if (profiler != nullptr) { michael@0: profiler->pop(); michael@0: JS_ASSERT(size_before == *profiler->size_); michael@0: } michael@0: } michael@0: michael@0: JS_FRIEND_API(jsbytecode*) michael@0: ProfileEntry::pc() const volatile michael@0: { michael@0: return idx == NullPCIndex ? nullptr : script()->offsetToPC(idx); michael@0: } michael@0: michael@0: JS_FRIEND_API(void) michael@0: ProfileEntry::setPC(jsbytecode *pc) volatile michael@0: { michael@0: idx = pc == nullptr ? NullPCIndex : script()->pcToOffset(pc); michael@0: } michael@0: michael@0: JS_FRIEND_API(void) michael@0: js::SetRuntimeProfilingStack(JSRuntime *rt, ProfileEntry *stack, uint32_t *size, uint32_t max) michael@0: { michael@0: rt->spsProfiler.setProfilingStack(stack, size, max); michael@0: } michael@0: michael@0: JS_FRIEND_API(void) michael@0: js::EnableRuntimeProfilingStack(JSRuntime *rt, bool enabled) michael@0: { michael@0: rt->spsProfiler.enable(enabled); michael@0: } michael@0: michael@0: JS_FRIEND_API(void) michael@0: js::RegisterRuntimeProfilingEventMarker(JSRuntime *rt, void (*fn)(const char *)) michael@0: { michael@0: JS_ASSERT(rt->spsProfiler.enabled()); michael@0: rt->spsProfiler.setEventMarker(fn); michael@0: } michael@0: michael@0: JS_FRIEND_API(jsbytecode*) michael@0: js::ProfilingGetPC(JSRuntime *rt, JSScript *script, void *ip) michael@0: { michael@0: return rt->spsProfiler.ipToPC(script, size_t(ip)); michael@0: }