| /* -*- 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 "mozilla/DebugOnly.h" |
| |
| #include "jsnum.h" |
| #include "jsscript.h" |
| |
| #include "vm/SPSProfiler.h" |
| #include "vm/StringBuffer.h" |
| |
| #include "jit/BaselineJIT.h" |
| |
| using namespace js; |
| |
| using mozilla::DebugOnly; |
| |
| SPSProfiler::SPSProfiler(JSRuntime *rt) |
| : rt(rt), |
| stack_(NULL), |
| size_(NULL), |
| max_(0), |
| slowAssertions(false), |
| enabled_(false) |
| { |
| JS_ASSERT(rt != NULL); |
| } |
| |
| SPSProfiler::~SPSProfiler() |
| { |
| if (strings.initialized()) { |
| for (ProfileStringMap::Enum e(strings); !e.empty(); e.popFront()) |
| js_free(const_cast<char *>(e.front().value)); |
| } |
| } |
| |
| void |
| SPSProfiler::setProfilingStack(ProfileEntry *stack, uint32_t *size, uint32_t max) |
| { |
| JS_ASSERT_IF(size_ && *size_ != 0, !enabled()); |
| if (!strings.initialized()) |
| strings.init(); |
| stack_ = stack; |
| size_ = size; |
| max_ = max; |
| } |
| |
| void |
| SPSProfiler::enable(bool enabled) |
| { |
| JS_ASSERT(installed()); |
| enabled_ = enabled; |
| /* |
| * Ensure all future generated code will be instrumented, or that all |
| * currently instrumented code is discarded |
| */ |
| ReleaseAllJITCode(rt->defaultFreeOp()); |
| |
| #ifdef JS_ION |
| /* 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::ToggleBaselineSPS(rt, enabled); |
| #endif |
| } |
| |
| /* Lookup the string for the function/script, creating one if necessary */ |
| const char* |
| SPSProfiler::profileString(JSContext *cx, JSScript *script, JSFunction *maybeFun) |
| { |
| JS_ASSERT(strings.initialized()); |
| ProfileStringMap::AddPtr s = strings.lookupForAdd(script); |
| if (s) |
| return s->value; |
| const char *str = allocProfileString(cx, script, maybeFun); |
| if (str == NULL) |
| return NULL; |
| if (!strings.add(s, script, str)) { |
| js_free(const_cast<char *>(str)); |
| return NULL; |
| } |
| 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. |
| */ |
| 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)); |
| } |
| } |
| |
| bool |
| SPSProfiler::enter(JSContext *cx, JSScript *script, JSFunction *maybeFun) |
| { |
| const char *str = profileString(cx, script, maybeFun); |
| if (str == NULL) |
| return false; |
| |
| JS_ASSERT_IF(*size_ > 0 && *size_ - 1 < max_ && stack_[*size_ - 1].js(), |
| stack_[*size_ - 1].pc() != NULL); |
| push(str, NULL, script, script->code); |
| return true; |
| } |
| |
| void |
| SPSProfiler::exit(JSContext *cx, JSScript *script, JSFunction *maybeFun) |
| { |
| pop(); |
| |
| #ifdef DEBUG |
| /* Sanity check to make sure push/pop balanced */ |
| if (*size_ < max_) { |
| const char *str = profileString(cx, script, maybeFun); |
| /* Can't fail lookup because we should already be in the set */ |
| JS_ASSERT(str != NULL); |
| |
| // Bug 822041 |
| if (!stack_[*size_].js()) { |
| 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].js()) |
| 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()); |
| } |
| } |
| |
| JS_ASSERT(stack_[*size_].js()); |
| JS_ASSERT(stack_[*size_].script() == script); |
| JS_ASSERT(strcmp((const char*) stack_[*size_].label(), str) == 0); |
| stack_[*size_].setLabel(NULL); |
| stack_[*size_].setPC(NULL); |
| } |
| #endif |
| } |
| |
| void |
| SPSProfiler::enterNative(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; |
| |
| JS_ASSERT(enabled()); |
| if (current < max_) { |
| stack[current].setLabel(string); |
| stack[current].setStackAddress(sp); |
| stack[current].setScript(NULL); |
| stack[current].setLine(0); |
| } |
| *size = current + 1; |
| } |
| |
| void |
| SPSProfiler::push(const char *string, void *sp, JSScript *script, jsbytecode *pc) |
| { |
| /* these operations cannot be re-ordered, so volatile-ize operations */ |
| volatile ProfileEntry *stack = stack_; |
| volatile uint32_t *size = size_; |
| uint32_t current = *size; |
| |
| JS_ASSERT(enabled()); |
| if (current < max_) { |
| stack[current].setLabel(string); |
| stack[current].setStackAddress(sp); |
| stack[current].setScript(script); |
| stack[current].setPC(pc); |
| } |
| *size = current + 1; |
| } |
| |
| void |
| SPSProfiler::pop() |
| { |
| JS_ASSERT(installed()); |
| (*size_)--; |
| JS_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(JSContext *cx, JSScript *script, JSFunction *maybeFun) |
| { |
| DebugOnly<uint64_t> gcBefore = cx->runtime()->gcNumber; |
| StringBuffer buf(cx); |
| bool hasAtom = maybeFun != NULL && maybeFun->displayAtom() != NULL; |
| if (hasAtom) { |
| if (!buf.append(maybeFun->displayAtom())) |
| return NULL; |
| if (!buf.append(" (")) |
| return NULL; |
| } |
| if (script->filename()) { |
| if (!buf.appendInflated(script->filename(), strlen(script->filename()))) |
| return NULL; |
| } else if (!buf.append("<unknown>")) { |
| return NULL; |
| } |
| if (!buf.append(":")) |
| return NULL; |
| if (!NumberValueToStringBuffer(cx, NumberValue(script->lineno), buf)) |
| return NULL; |
| if (hasAtom && !buf.append(")")) |
| return NULL; |
| |
| size_t len = buf.length(); |
| char *cstr = js_pod_malloc<char>(len + 1); |
| if (cstr == NULL) |
| return NULL; |
| |
| const jschar *ptr = buf.begin(); |
| for (size_t i = 0; i < len; i++) |
| cstr[i] = ptr[i]; |
| cstr[len] = 0; |
| |
| JS_ASSERT(gcBefore == cx->runtime()->gcNumber); |
| return cstr; |
| } |
| |
| SPSEntryMarker::SPSEntryMarker(JSRuntime *rt |
| MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) |
| : profiler(&rt->spsProfiler) |
| { |
| MOZ_GUARD_OBJECT_NOTIFIER_INIT; |
| if (!profiler->enabled()) { |
| profiler = NULL; |
| return; |
| } |
| size_before = *profiler->size_; |
| profiler->push("js::RunScript", this, NULL, NULL); |
| } |
| |
| SPSEntryMarker::~SPSEntryMarker() |
| { |
| if (profiler != NULL) { |
| profiler->pop(); |
| JS_ASSERT(size_before == *profiler->size_); |
| } |
| } |
| |
| JS_FRIEND_API(jsbytecode*) |
| ProfileEntry::pc() volatile { |
| JS_ASSERT_IF(idx != NullPCIndex, idx >= 0 && uint32_t(idx) < script()->length); |
| return idx == NullPCIndex ? NULL : script()->code + idx; |
| } |
| |
| JS_FRIEND_API(void) |
| ProfileEntry::setPC(jsbytecode *pc) volatile { |
| JS_ASSERT_IF(pc != NULL, script()->code <= pc && |
| pc < script()->code + script()->length); |
| idx = pc == NULL ? NullPCIndex : pc - script()->code; |
| } |