1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/js/src/vm/SPSProfiler.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,362 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- 1.5 + * vim: set ts=8 sts=4 et sw=4 tw=99: 1.6 + * This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "vm/SPSProfiler.h" 1.11 + 1.12 +#include "mozilla/DebugOnly.h" 1.13 + 1.14 +#include "jsnum.h" 1.15 +#include "jsprf.h" 1.16 +#include "jsscript.h" 1.17 + 1.18 +#include "jit/BaselineJIT.h" 1.19 +#include "vm/StringBuffer.h" 1.20 + 1.21 +using namespace js; 1.22 + 1.23 +using mozilla::DebugOnly; 1.24 + 1.25 +SPSProfiler::SPSProfiler(JSRuntime *rt) 1.26 + : rt(rt), 1.27 + stack_(nullptr), 1.28 + size_(nullptr), 1.29 + max_(0), 1.30 + slowAssertions(false), 1.31 + enabled_(false), 1.32 + lock_(nullptr), 1.33 + eventMarker_(nullptr) 1.34 +{ 1.35 + JS_ASSERT(rt != nullptr); 1.36 +} 1.37 + 1.38 +bool 1.39 +SPSProfiler::init() 1.40 +{ 1.41 +#ifdef JS_THREADSAFE 1.42 + lock_ = PR_NewLock(); 1.43 + if (lock_ == nullptr) 1.44 + return false; 1.45 +#endif 1.46 + return true; 1.47 +} 1.48 + 1.49 +SPSProfiler::~SPSProfiler() 1.50 +{ 1.51 + if (strings.initialized()) { 1.52 + for (ProfileStringMap::Enum e(strings); !e.empty(); e.popFront()) 1.53 + js_free(const_cast<char *>(e.front().value())); 1.54 + } 1.55 +#ifdef JS_THREADSAFE 1.56 + if (lock_) 1.57 + PR_DestroyLock(lock_); 1.58 +#endif 1.59 +} 1.60 + 1.61 +void 1.62 +SPSProfiler::setProfilingStack(ProfileEntry *stack, uint32_t *size, uint32_t max) 1.63 +{ 1.64 + AutoSPSLock lock(lock_); 1.65 + JS_ASSERT_IF(size_ && *size_ != 0, !enabled()); 1.66 + if (!strings.initialized()) 1.67 + strings.init(); 1.68 + stack_ = stack; 1.69 + size_ = size; 1.70 + max_ = max; 1.71 +} 1.72 + 1.73 +void 1.74 +SPSProfiler::setEventMarker(void (*fn)(const char *)) 1.75 +{ 1.76 + eventMarker_ = fn; 1.77 +} 1.78 + 1.79 +void 1.80 +SPSProfiler::enable(bool enabled) 1.81 +{ 1.82 + JS_ASSERT(installed()); 1.83 + 1.84 + if (enabled_ == enabled) 1.85 + return; 1.86 + 1.87 + /* 1.88 + * Ensure all future generated code will be instrumented, or that all 1.89 + * currently instrumented code is discarded 1.90 + */ 1.91 + ReleaseAllJITCode(rt->defaultFreeOp()); 1.92 + 1.93 + enabled_ = enabled; 1.94 + 1.95 +#ifdef JS_ION 1.96 + /* Toggle SPS-related jumps on baseline jitcode. 1.97 + * The call to |ReleaseAllJITCode| above will release most baseline jitcode, but not 1.98 + * jitcode for scripts with active frames on the stack. These scripts need to have 1.99 + * their profiler state toggled so they behave properly. 1.100 + */ 1.101 + jit::ToggleBaselineSPS(rt, enabled); 1.102 +#endif 1.103 +} 1.104 + 1.105 +/* Lookup the string for the function/script, creating one if necessary */ 1.106 +const char* 1.107 +SPSProfiler::profileString(JSScript *script, JSFunction *maybeFun) 1.108 +{ 1.109 + AutoSPSLock lock(lock_); 1.110 + JS_ASSERT(strings.initialized()); 1.111 + ProfileStringMap::AddPtr s = strings.lookupForAdd(script); 1.112 + if (s) 1.113 + return s->value(); 1.114 + const char *str = allocProfileString(script, maybeFun); 1.115 + if (str == nullptr) 1.116 + return nullptr; 1.117 + if (!strings.add(s, script, str)) { 1.118 + js_free(const_cast<char *>(str)); 1.119 + return nullptr; 1.120 + } 1.121 + return str; 1.122 +} 1.123 + 1.124 +void 1.125 +SPSProfiler::onScriptFinalized(JSScript *script) 1.126 +{ 1.127 + /* 1.128 + * This function is called whenever a script is destroyed, regardless of 1.129 + * whether profiling has been turned on, so don't invoke a function on an 1.130 + * invalid hash set. Also, even if profiling was enabled but then turned 1.131 + * off, we still want to remove the string, so no check of enabled() is 1.132 + * done. 1.133 + */ 1.134 + AutoSPSLock lock(lock_); 1.135 + if (!strings.initialized()) 1.136 + return; 1.137 + if (ProfileStringMap::Ptr entry = strings.lookup(script)) { 1.138 + const char *tofree = entry->value(); 1.139 + strings.remove(entry); 1.140 + js_free(const_cast<char *>(tofree)); 1.141 + } 1.142 +} 1.143 + 1.144 +void 1.145 +SPSProfiler::markEvent(const char *event) 1.146 +{ 1.147 + JS_ASSERT(enabled()); 1.148 + if (eventMarker_) { 1.149 + JS::AutoAssertNoGC nogc; 1.150 + eventMarker_(event); 1.151 + } 1.152 +} 1.153 + 1.154 +bool 1.155 +SPSProfiler::enter(JSScript *script, JSFunction *maybeFun) 1.156 +{ 1.157 + const char *str = profileString(script, maybeFun); 1.158 + if (str == nullptr) 1.159 + return false; 1.160 + 1.161 +#ifdef DEBUG 1.162 + // In debug builds, assert the JS pseudo frames already on the stack 1.163 + // have a non-null pc. Only look at the top frames to avoid quadratic 1.164 + // behavior. 1.165 + if (*size_ > 0 && *size_ - 1 < max_) { 1.166 + size_t start = (*size_ > 4) ? *size_ - 4 : 0; 1.167 + for (size_t i = start; i < *size_ - 1; i++) 1.168 + MOZ_ASSERT_IF(stack_[i].js(), stack_[i].pc() != nullptr); 1.169 + } 1.170 +#endif 1.171 + 1.172 + push(str, nullptr, script, script->code()); 1.173 + return true; 1.174 +} 1.175 + 1.176 +void 1.177 +SPSProfiler::exit(JSScript *script, JSFunction *maybeFun) 1.178 +{ 1.179 + pop(); 1.180 + 1.181 +#ifdef DEBUG 1.182 + /* Sanity check to make sure push/pop balanced */ 1.183 + if (*size_ < max_) { 1.184 + const char *str = profileString(script, maybeFun); 1.185 + /* Can't fail lookup because we should already be in the set */ 1.186 + JS_ASSERT(str != nullptr); 1.187 + 1.188 + // Bug 822041 1.189 + if (!stack_[*size_].js()) { 1.190 + fprintf(stderr, "--- ABOUT TO FAIL ASSERTION ---\n"); 1.191 + fprintf(stderr, " stack=%p size=%d/%d\n", (void*) stack_, *size_, max_); 1.192 + for (int32_t i = *size_; i >= 0; i--) { 1.193 + if (stack_[i].js()) 1.194 + fprintf(stderr, " [%d] JS %s\n", i, stack_[i].label()); 1.195 + else 1.196 + fprintf(stderr, " [%d] C line %d %s\n", i, stack_[i].line(), stack_[i].label()); 1.197 + } 1.198 + } 1.199 + 1.200 + JS_ASSERT(stack_[*size_].js()); 1.201 + JS_ASSERT(stack_[*size_].script() == script); 1.202 + JS_ASSERT(strcmp((const char*) stack_[*size_].label(), str) == 0); 1.203 + stack_[*size_].setLabel(nullptr); 1.204 + stack_[*size_].setPC(nullptr); 1.205 + } 1.206 +#endif 1.207 +} 1.208 + 1.209 +void 1.210 +SPSProfiler::enterNative(const char *string, void *sp) 1.211 +{ 1.212 + /* these operations cannot be re-ordered, so volatile-ize operations */ 1.213 + volatile ProfileEntry *stack = stack_; 1.214 + volatile uint32_t *size = size_; 1.215 + uint32_t current = *size; 1.216 + 1.217 + JS_ASSERT(enabled()); 1.218 + if (current < max_) { 1.219 + stack[current].setLabel(string); 1.220 + stack[current].setStackAddress(sp); 1.221 + stack[current].setScript(nullptr); 1.222 + stack[current].setLine(0); 1.223 + } 1.224 + *size = current + 1; 1.225 +} 1.226 + 1.227 +void 1.228 +SPSProfiler::push(const char *string, void *sp, JSScript *script, jsbytecode *pc) 1.229 +{ 1.230 + /* these operations cannot be re-ordered, so volatile-ize operations */ 1.231 + volatile ProfileEntry *stack = stack_; 1.232 + volatile uint32_t *size = size_; 1.233 + uint32_t current = *size; 1.234 + 1.235 + JS_ASSERT(installed()); 1.236 + if (current < max_) { 1.237 + stack[current].setLabel(string); 1.238 + stack[current].setStackAddress(sp); 1.239 + stack[current].setScript(script); 1.240 + stack[current].setPC(pc); 1.241 + } 1.242 + *size = current + 1; 1.243 +} 1.244 + 1.245 +void 1.246 +SPSProfiler::pop() 1.247 +{ 1.248 + JS_ASSERT(installed()); 1.249 + (*size_)--; 1.250 + JS_ASSERT(*(int*)size_ >= 0); 1.251 +} 1.252 + 1.253 +/* 1.254 + * Serializes the script/function pair into a "descriptive string" which is 1.255 + * allowed to fail. This function cannot trigger a GC because it could finalize 1.256 + * some scripts, resize the hash table of profile strings, and invalidate the 1.257 + * AddPtr held while invoking allocProfileString. 1.258 + */ 1.259 +const char * 1.260 +SPSProfiler::allocProfileString(JSScript *script, JSFunction *maybeFun) 1.261 +{ 1.262 + // Note: this profiler string is regexp-matched by 1.263 + // browser/devtools/profiler/cleopatra/js/parserWorker.js. 1.264 + 1.265 + // Determine if the function (if any) has an explicit or guessed name. 1.266 + bool hasAtom = maybeFun && maybeFun->displayAtom(); 1.267 + 1.268 + // Get the function name, if any, and its length. 1.269 + const jschar *atom = nullptr; 1.270 + size_t lenAtom = 0; 1.271 + if (hasAtom) { 1.272 + atom = maybeFun->displayAtom()->charsZ(); 1.273 + lenAtom = maybeFun->displayAtom()->length(); 1.274 + } 1.275 + 1.276 + // Get the script filename, if any, and its length. 1.277 + const char *filename = script->filename(); 1.278 + if (filename == nullptr) 1.279 + filename = "<unknown>"; 1.280 + size_t lenFilename = strlen(filename); 1.281 + 1.282 + // Get the line number and its length as a string. 1.283 + uint64_t lineno = script->lineno(); 1.284 + size_t lenLineno = 1; 1.285 + for (uint64_t i = lineno; i /= 10; lenLineno++); 1.286 + 1.287 + // Determine the required buffer size. 1.288 + size_t len = lenFilename + lenLineno + 1; // +1 for the ":" separating them. 1.289 + if (hasAtom) 1.290 + len += lenAtom + 3; // +3 for the " (" and ")" it adds. 1.291 + 1.292 + // Allocate the buffer. 1.293 + char *cstr = js_pod_malloc<char>(len + 1); 1.294 + if (cstr == nullptr) 1.295 + return nullptr; 1.296 + 1.297 + // Construct the descriptive string. 1.298 + DebugOnly<size_t> ret; 1.299 + if (hasAtom) 1.300 + ret = JS_snprintf(cstr, len + 1, "%hs (%s:%llu)", atom, filename, lineno); 1.301 + else 1.302 + ret = JS_snprintf(cstr, len + 1, "%s:%llu", filename, lineno); 1.303 + 1.304 + MOZ_ASSERT(ret == len, "Computed length should match actual length!"); 1.305 + 1.306 + return cstr; 1.307 +} 1.308 + 1.309 +SPSEntryMarker::SPSEntryMarker(JSRuntime *rt 1.310 + MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) 1.311 + : profiler(&rt->spsProfiler) 1.312 +{ 1.313 + MOZ_GUARD_OBJECT_NOTIFIER_INIT; 1.314 + if (!profiler->installed()) { 1.315 + profiler = nullptr; 1.316 + return; 1.317 + } 1.318 + size_before = *profiler->size_; 1.319 + profiler->pushNoCopy("js::RunScript", this, nullptr, nullptr); 1.320 +} 1.321 + 1.322 +SPSEntryMarker::~SPSEntryMarker() 1.323 +{ 1.324 + if (profiler != nullptr) { 1.325 + profiler->pop(); 1.326 + JS_ASSERT(size_before == *profiler->size_); 1.327 + } 1.328 +} 1.329 + 1.330 +JS_FRIEND_API(jsbytecode*) 1.331 +ProfileEntry::pc() const volatile 1.332 +{ 1.333 + return idx == NullPCIndex ? nullptr : script()->offsetToPC(idx); 1.334 +} 1.335 + 1.336 +JS_FRIEND_API(void) 1.337 +ProfileEntry::setPC(jsbytecode *pc) volatile 1.338 +{ 1.339 + idx = pc == nullptr ? NullPCIndex : script()->pcToOffset(pc); 1.340 +} 1.341 + 1.342 +JS_FRIEND_API(void) 1.343 +js::SetRuntimeProfilingStack(JSRuntime *rt, ProfileEntry *stack, uint32_t *size, uint32_t max) 1.344 +{ 1.345 + rt->spsProfiler.setProfilingStack(stack, size, max); 1.346 +} 1.347 + 1.348 +JS_FRIEND_API(void) 1.349 +js::EnableRuntimeProfilingStack(JSRuntime *rt, bool enabled) 1.350 +{ 1.351 + rt->spsProfiler.enable(enabled); 1.352 +} 1.353 + 1.354 +JS_FRIEND_API(void) 1.355 +js::RegisterRuntimeProfilingEventMarker(JSRuntime *rt, void (*fn)(const char *)) 1.356 +{ 1.357 + JS_ASSERT(rt->spsProfiler.enabled()); 1.358 + rt->spsProfiler.setEventMarker(fn); 1.359 +} 1.360 + 1.361 +JS_FRIEND_API(jsbytecode*) 1.362 +js::ProfilingGetPC(JSRuntime *rt, JSScript *script, void *ip) 1.363 +{ 1.364 + return rt->spsProfiler.ipToPC(script, size_t(ip)); 1.365 +}