1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/js/src/vm/SPSProfiler.h Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,518 @@ 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 +#ifndef vm_SPSProfiler_h 1.11 +#define vm_SPSProfiler_h 1.12 + 1.13 +#include "mozilla/DebugOnly.h" 1.14 +#include "mozilla/GuardObjects.h" 1.15 + 1.16 +#include <stddef.h> 1.17 + 1.18 +#include "jslock.h" 1.19 +#include "jsscript.h" 1.20 + 1.21 +#include "js/ProfilingStack.h" 1.22 + 1.23 +/* 1.24 + * SPS Profiler integration with the JS Engine 1.25 + * https://developer.mozilla.org/en/Performance/Profiling_with_the_Built-in_Profiler 1.26 + * 1.27 + * The SPS profiler (found in tools/profiler) is an implementation of a profiler 1.28 + * which has the ability to walk the C++ stack as well as use instrumentation to 1.29 + * gather information. When dealing with JS, however, SPS needs integration 1.30 + * with the engine because otherwise it is very difficult to figure out what 1.31 + * javascript is executing. 1.32 + * 1.33 + * The current method of integration with SPS is a form of instrumentation: 1.34 + * every time a JS function is entered, a bit of information is pushed onto a 1.35 + * stack that SPS owns and maintains. This information is then popped at the end 1.36 + * of the JS function. SPS informs the JS engine of this stack at runtime, and 1.37 + * it can by turned on/off dynamically. 1.38 + * 1.39 + * The SPS stack has three parameters: a base pointer, a size, and a maximum 1.40 + * size. The stack is the ProfileEntry stack which will have information written 1.41 + * to it. The size location is a pointer to an integer which represents the 1.42 + * current size of the stack (number of valid frames). This size will be 1.43 + * modified when JS functions are called. The maximum specified is the maximum 1.44 + * capacity of the ProfileEntry stack. 1.45 + * 1.46 + * Throughout execution, the size of the stack recorded in memory may exceed the 1.47 + * maximum. The JS engine will not write any information past the maximum limit, 1.48 + * but it will still maintain the size of the stack. SPS code is aware of this 1.49 + * and iterates the stack accordingly. 1.50 + * 1.51 + * There is some information pushed on the SPS stack for every JS function that 1.52 + * is entered. First is a char* pointer of a description of what function was 1.53 + * entered. Currently this string is of the form "function (file:line)" if 1.54 + * there's a function name, or just "file:line" if there's no function name 1.55 + * available. The other bit of information is the relevant C++ (native) stack 1.56 + * pointer. This stack pointer is what enables the interleaving of the C++ and 1.57 + * the JS stack. Finally, throughout execution of the function, some extra 1.58 + * information may be updated on the ProfileEntry structure. 1.59 + * 1.60 + * = Profile Strings 1.61 + * 1.62 + * The profile strings' allocations and deallocation must be carefully 1.63 + * maintained, and ideally at a very low overhead cost. For this reason, the JS 1.64 + * engine maintains a mapping of all known profile strings. These strings are 1.65 + * keyed in lookup by a JSScript*, but are serialized with a JSFunction*, 1.66 + * JSScript* pair. A JSScript will destroy its corresponding profile string when 1.67 + * the script is finalized. 1.68 + * 1.69 + * For this reason, a char* pointer pushed on the SPS stack is valid only while 1.70 + * it is on the SPS stack. SPS uses sampling to read off information from this 1.71 + * instrumented stack, and it therefore copies the string byte for byte when a 1.72 + * JS function is encountered during sampling. 1.73 + * 1.74 + * = Native Stack Pointer 1.75 + * 1.76 + * The actual value pushed as the native pointer is nullptr for most JS 1.77 + * functions. The reason for this is that there's actually very little 1.78 + * correlation between the JS stack and the C++ stack because many JS functions 1.79 + * all run in the same C++ frame, or can even go backwards in C++ when going 1.80 + * from the JIT back to the interpreter. 1.81 + * 1.82 + * To alleviate this problem, all JS functions push nullptr as their "native 1.83 + * stack pointer" to indicate that it's a JS function call. The function 1.84 + * RunScript(), however, pushes an actual C++ stack pointer onto the SPS stack. 1.85 + * This way when interleaving C++ and JS, if SPS sees a nullptr native stack 1.86 + * pointer on the SPS stack, it looks backwards for the first non-nullptr 1.87 + * pointer and uses that for all subsequent nullptr native stack pointers. 1.88 + * 1.89 + * = Line Numbers 1.90 + * 1.91 + * One goal of sampling is to get both a backtrace of the JS stack, but also 1.92 + * know where within each function on the stack execution currently is. For 1.93 + * this, each ProfileEntry has a 'pc' field to tell where its execution 1.94 + * currently is. This field is updated whenever a call is made to another JS 1.95 + * function, and for the JIT it is also updated whenever the JIT is left. 1.96 + * 1.97 + * This field is in a union with a uint32_t 'line' so that C++ can make use of 1.98 + * the field as well. It was observed that tracking 'line' via PCToLineNumber in 1.99 + * JS was far too expensive, so that is why the pc instead of the translated 1.100 + * line number is stored. 1.101 + * 1.102 + * As an invariant, if the pc is nullptr, then the JIT is currently executing 1.103 + * generated code. Otherwise execution is in another JS function or in C++. With 1.104 + * this in place, only the top entry of the stack can ever have nullptr as its 1.105 + * pc. Additionally with this invariant, it is possible to maintain mappings of 1.106 + * JIT code to pc which can be accessed safely because they will only be 1.107 + * accessed from a signal handler when the JIT code is executing. 1.108 + */ 1.109 + 1.110 +namespace js { 1.111 + 1.112 +class ProfileEntry; 1.113 + 1.114 +typedef HashMap<JSScript*, const char*, DefaultHasher<JSScript*>, SystemAllocPolicy> 1.115 + ProfileStringMap; 1.116 + 1.117 +class SPSEntryMarker; 1.118 + 1.119 +class SPSProfiler 1.120 +{ 1.121 + friend class SPSEntryMarker; 1.122 + 1.123 + JSRuntime *rt; 1.124 + ProfileStringMap strings; 1.125 + ProfileEntry *stack_; 1.126 + uint32_t *size_; 1.127 + uint32_t max_; 1.128 + bool slowAssertions; 1.129 + uint32_t enabled_; 1.130 + PRLock *lock_; 1.131 + void (*eventMarker_)(const char *); 1.132 + 1.133 + const char *allocProfileString(JSScript *script, JSFunction *function); 1.134 + void push(const char *string, void *sp, JSScript *script, jsbytecode *pc); 1.135 + void pushNoCopy(const char *string, void *sp, 1.136 + JSScript *script, jsbytecode *pc) { 1.137 + push(string, reinterpret_cast<void*>( 1.138 + reinterpret_cast<uintptr_t>(sp) | ProfileEntry::NoCopyBit), 1.139 + script, pc); 1.140 + } 1.141 + void pop(); 1.142 + 1.143 + public: 1.144 + SPSProfiler(JSRuntime *rt); 1.145 + ~SPSProfiler(); 1.146 + 1.147 + bool init(); 1.148 + 1.149 + uint32_t **addressOfSizePointer() { 1.150 + return &size_; 1.151 + } 1.152 + 1.153 + uint32_t *addressOfMaxSize() { 1.154 + return &max_; 1.155 + } 1.156 + 1.157 + ProfileEntry **addressOfStack() { 1.158 + return &stack_; 1.159 + } 1.160 + 1.161 + uint32_t *sizePointer() { return size_; } 1.162 + uint32_t maxSize() { return max_; } 1.163 + ProfileEntry *stack() { return stack_; } 1.164 + 1.165 + /* management of whether instrumentation is on or off */ 1.166 + bool enabled() { JS_ASSERT_IF(enabled_, installed()); return enabled_; } 1.167 + bool installed() { return stack_ != nullptr && size_ != nullptr; } 1.168 + void enable(bool enabled); 1.169 + void enableSlowAssertions(bool enabled) { slowAssertions = enabled; } 1.170 + bool slowAssertionsEnabled() { return slowAssertions; } 1.171 + 1.172 + /* 1.173 + * Functions which are the actual instrumentation to track run information 1.174 + * 1.175 + * - enter: a function has started to execute 1.176 + * - updatePC: updates the pc information about where a function 1.177 + * is currently executing 1.178 + * - exit: this function has ceased execution, and no further 1.179 + * entries/exits will be made 1.180 + */ 1.181 + bool enter(JSScript *script, JSFunction *maybeFun); 1.182 + void exit(JSScript *script, JSFunction *maybeFun); 1.183 + void updatePC(JSScript *script, jsbytecode *pc) { 1.184 + if (enabled() && *size_ - 1 < max_) { 1.185 + JS_ASSERT(*size_ > 0); 1.186 + JS_ASSERT(stack_[*size_ - 1].script() == script); 1.187 + stack_[*size_ - 1].setPC(pc); 1.188 + } 1.189 + } 1.190 + 1.191 + /* Enter a C++ function. */ 1.192 + void enterNative(const char *string, void *sp); 1.193 + void exitNative() { pop(); } 1.194 + 1.195 + jsbytecode *ipToPC(JSScript *script, size_t ip) { return nullptr; } 1.196 + 1.197 + void setProfilingStack(ProfileEntry *stack, uint32_t *size, uint32_t max); 1.198 + void setEventMarker(void (*fn)(const char *)); 1.199 + const char *profileString(JSScript *script, JSFunction *maybeFun); 1.200 + void onScriptFinalized(JSScript *script); 1.201 + 1.202 + void markEvent(const char *event); 1.203 + 1.204 + /* meant to be used for testing, not recommended to call in normal code */ 1.205 + size_t stringsCount(); 1.206 + void stringsReset(); 1.207 + 1.208 + uint32_t *addressOfEnabled() { 1.209 + return &enabled_; 1.210 + } 1.211 +}; 1.212 + 1.213 +/* 1.214 + * This class is used to make sure the strings table 1.215 + * is only accessed on one thread at a time. 1.216 + */ 1.217 +class AutoSPSLock 1.218 +{ 1.219 + public: 1.220 +#ifdef JS_THREADSAFE 1.221 + AutoSPSLock(PRLock *lock) 1.222 + { 1.223 + MOZ_ASSERT(lock, "Parameter should not be null!"); 1.224 + lock_ = lock; 1.225 + PR_Lock(lock); 1.226 + } 1.227 + ~AutoSPSLock() { PR_Unlock(lock_); } 1.228 +#else 1.229 + AutoSPSLock(PRLock *) {} 1.230 +#endif 1.231 + 1.232 + private: 1.233 + PRLock *lock_; 1.234 +}; 1.235 + 1.236 +inline size_t 1.237 +SPSProfiler::stringsCount() 1.238 +{ 1.239 + AutoSPSLock lock(lock_); 1.240 + return strings.count(); 1.241 +} 1.242 + 1.243 +inline void 1.244 +SPSProfiler::stringsReset() 1.245 +{ 1.246 + AutoSPSLock lock(lock_); 1.247 + strings.clear(); 1.248 +} 1.249 + 1.250 +/* 1.251 + * This class is used in RunScript() to push the marker onto the sampling stack 1.252 + * that we're about to enter JS function calls. This is the only time in which a 1.253 + * valid stack pointer is pushed to the sampling stack. 1.254 + */ 1.255 +class SPSEntryMarker 1.256 +{ 1.257 + public: 1.258 + SPSEntryMarker(JSRuntime *rt 1.259 + MOZ_GUARD_OBJECT_NOTIFIER_PARAM); 1.260 + ~SPSEntryMarker(); 1.261 + 1.262 + private: 1.263 + SPSProfiler *profiler; 1.264 + mozilla::DebugOnly<uint32_t> size_before; 1.265 + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER 1.266 +}; 1.267 + 1.268 +/* 1.269 + * SPS is the profiling backend used by the JS engine to enable time profiling. 1.270 + * More information can be found in vm/SPSProfiler.{h,cpp}. This class manages 1.271 + * the instrumentation portion of the profiling for JIT code. 1.272 + * 1.273 + * The instrumentation tracks entry into functions, leaving those functions via 1.274 + * a function call, reentering the functions from a function call, and exiting 1.275 + * the functions from returning. This class also handles inline frames and 1.276 + * manages the instrumentation which needs to be attached to them as well. 1.277 + * 1.278 + * The basic methods which emit instrumentation are at the end of this class, 1.279 + * and the management functions are all described in the middle. 1.280 + */ 1.281 +template<class Assembler, class Register> 1.282 +class SPSInstrumentation 1.283 +{ 1.284 + /* Because of inline frames, this is a nested structure in a vector */ 1.285 + struct FrameState { 1.286 + JSScript *script; // script for this frame, nullptr if not pushed yet 1.287 + jsbytecode *pc; // pc at which this frame was left for entry into a callee 1.288 + bool skipNext; // should the next call to reenter be skipped? 1.289 + int left; // number of leave() calls made without a matching reenter() 1.290 + }; 1.291 + 1.292 + SPSProfiler *profiler_; // Instrumentation location management 1.293 + 1.294 + Vector<FrameState, 1, SystemAllocPolicy> frames; 1.295 + FrameState *frame; 1.296 + 1.297 + static void clearFrame(FrameState *frame) { 1.298 + frame->script = nullptr; 1.299 + frame->pc = nullptr; 1.300 + frame->skipNext = false; 1.301 + frame->left = 0; 1.302 + } 1.303 + 1.304 + public: 1.305 + /* 1.306 + * Creates instrumentation which writes information out the the specified 1.307 + * profiler's stack and constituent fields. 1.308 + */ 1.309 + SPSInstrumentation(SPSProfiler *profiler) 1.310 + : profiler_(profiler), frame(nullptr) 1.311 + { 1.312 + enterInlineFrame(nullptr); 1.313 + } 1.314 + 1.315 + /* Small proxies around SPSProfiler */ 1.316 + bool enabled() { return profiler_ && profiler_->enabled(); } 1.317 + SPSProfiler *profiler() { JS_ASSERT(enabled()); return profiler_; } 1.318 + void disable() { profiler_ = nullptr; } 1.319 + 1.320 + /* Signals an inline function returned, reverting to the previous state */ 1.321 + void leaveInlineFrame() { 1.322 + if (!enabled()) 1.323 + return; 1.324 + JS_ASSERT(frame->left == 0); 1.325 + JS_ASSERT(frame->script != nullptr); 1.326 + frames.shrinkBy(1); 1.327 + JS_ASSERT(frames.length() > 0); 1.328 + frame = &frames[frames.length() - 1]; 1.329 + } 1.330 + 1.331 + /* Saves the current state and assumes a fresh one for the inline function */ 1.332 + bool enterInlineFrame(jsbytecode *callerPC) { 1.333 + if (!enabled()) 1.334 + return true; 1.335 + JS_ASSERT_IF(frames.empty(), callerPC == nullptr); 1.336 + 1.337 + JS_ASSERT_IF(frame != nullptr, frame->script != nullptr); 1.338 + JS_ASSERT_IF(frame != nullptr, frame->left == 1); 1.339 + if (!frames.empty()) { 1.340 + JS_ASSERT(frame == &frames[frames.length() - 1]); 1.341 + frame->pc = callerPC; 1.342 + } 1.343 + if (!frames.growBy(1)) 1.344 + return false; 1.345 + frame = &frames[frames.length() - 1]; 1.346 + clearFrame(frame); 1.347 + return true; 1.348 + } 1.349 + 1.350 + /* Prepares the instrumenter state for generating OOL code, by 1.351 + * setting up the frame state to seem as if there are exactly 1.352 + * two pushed frames: a frame for the top-level script, and 1.353 + * a frame for the OOL code being generated. Any 1.354 + * vm-calls from the OOL code will "leave" the OOL frame and 1.355 + * return back to it. 1.356 + */ 1.357 + bool prepareForOOL() { 1.358 + if (!enabled()) 1.359 + return true; 1.360 + JS_ASSERT(!frames.empty()); 1.361 + if (frames.length() >= 2) { 1.362 + frames.shrinkBy(frames.length() - 2); 1.363 + 1.364 + } else { // frames.length() == 1 1.365 + if (!frames.growBy(1)) 1.366 + return false; 1.367 + } 1.368 + frames[0].pc = frames[0].script->code(); 1.369 + frame = &frames[1]; 1.370 + clearFrame(frame); 1.371 + return true; 1.372 + } 1.373 + void finishOOL() { 1.374 + if (!enabled()) 1.375 + return; 1.376 + JS_ASSERT(!frames.empty()); 1.377 + frames.shrinkBy(frames.length() - 1); 1.378 + } 1.379 + 1.380 + /* Number of inline frames currently active (doesn't include original one) */ 1.381 + unsigned inliningDepth() { 1.382 + return frames.length() - 1; 1.383 + } 1.384 + 1.385 + /* 1.386 + * When debugging or with slow assertions, sometimes a C++ method will be 1.387 + * invoked to perform the pop operation from the SPS stack. When we leave 1.388 + * JIT code, we need to record the current PC, but upon reentering JIT 1.389 + * code, no update back to nullptr should happen. This method exists to 1.390 + * flag this behavior. The next leave() will emit instrumentation, but the 1.391 + * following reenter() will be a no-op. 1.392 + */ 1.393 + void skipNextReenter() { 1.394 + /* If we've left the frame, the reenter will be skipped anyway */ 1.395 + if (!enabled() || frame->left != 0) 1.396 + return; 1.397 + JS_ASSERT(frame->script); 1.398 + JS_ASSERT(!frame->skipNext); 1.399 + frame->skipNext = true; 1.400 + } 1.401 + 1.402 + /* 1.403 + * In some cases, a frame needs to be flagged as having been pushed, but no 1.404 + * instrumentation should be emitted. This updates internal state to flag 1.405 + * that further instrumentation should actually be emitted. 1.406 + */ 1.407 + void setPushed(JSScript *script) { 1.408 + if (!enabled()) 1.409 + return; 1.410 + JS_ASSERT(frame->left == 0); 1.411 + frame->script = script; 1.412 + } 1.413 + 1.414 + /* 1.415 + * Flags entry into a JS function for the first time. Before this is called, 1.416 + * no instrumentation is emitted, but after this instrumentation is emitted. 1.417 + */ 1.418 + bool push(JSScript *script, Assembler &masm, Register scratch, bool inlinedFunction = false) { 1.419 + if (!enabled()) 1.420 + return true; 1.421 +#ifdef JS_ION 1.422 + if (!inlinedFunction || jit::js_JitOptions.profileInlineFrames) { 1.423 +#endif 1.424 + const char *string = profiler_->profileString(script, script->functionNonDelazifying()); 1.425 + if (string == nullptr) 1.426 + return false; 1.427 + masm.spsPushFrame(profiler_, string, script, scratch); 1.428 +#ifdef JS_ION 1.429 + } 1.430 +#endif 1.431 + setPushed(script); 1.432 + return true; 1.433 + } 1.434 + 1.435 + /* 1.436 + * Signifies that C++ performed the push() for this function. C++ always 1.437 + * sets the current PC to something non-null, however, so as soon as JIT 1.438 + * code is reentered this updates the current pc to nullptr. 1.439 + */ 1.440 + void pushManual(JSScript *script, Assembler &masm, Register scratch, 1.441 + bool inlinedFunction = false) 1.442 + { 1.443 + if (!enabled()) 1.444 + return; 1.445 + 1.446 +#ifdef JS_ION 1.447 + if (!inlinedFunction || jit::js_JitOptions.profileInlineFrames) 1.448 +#endif 1.449 + masm.spsUpdatePCIdx(profiler_, ProfileEntry::NullPCIndex, scratch); 1.450 + 1.451 + setPushed(script); 1.452 + } 1.453 + 1.454 + /* 1.455 + * Signals that the current function is leaving for a function call. This 1.456 + * can happen both on JS function calls and also calls to C++. This 1.457 + * internally manages how many leave() calls have been seen, and only the 1.458 + * first leave() emits instrumentation. Similarly, only the last 1.459 + * corresponding reenter() actually emits instrumentation. 1.460 + */ 1.461 + void leave(jsbytecode *pc, Assembler &masm, Register scratch, bool inlinedFunction = false) { 1.462 + if (enabled() && frame->script && frame->left++ == 0) { 1.463 + jsbytecode *updatePC = pc; 1.464 + JSScript *script = frame->script; 1.465 +#ifdef JS_ION 1.466 + if (!inlinedFunction) { 1.467 + // We may be leaving an inlined frame for entry into a C++ 1.468 + // frame. If profileInlineFrames is turned off, use the top 1.469 + // script's pc offset instead of the innermost script's. 1.470 + if (!jit::js_JitOptions.profileInlineFrames && inliningDepth() > 0) { 1.471 + JS_ASSERT(frames[0].pc); 1.472 + updatePC = frames[0].pc; 1.473 + script = frames[0].script; 1.474 + } 1.475 + } 1.476 +#endif 1.477 + 1.478 +#ifdef JS_ION 1.479 + if (!inlinedFunction || jit::js_JitOptions.profileInlineFrames) 1.480 +#endif 1.481 + masm.spsUpdatePCIdx(profiler_, script->pcToOffset(updatePC), scratch); 1.482 + } 1.483 + } 1.484 + 1.485 + /* 1.486 + * Flags that the leaving of the current function has returned. This tracks 1.487 + * state with leave() to only emit instrumentation at proper times. 1.488 + */ 1.489 + void reenter(Assembler &masm, Register scratch, bool inlinedFunction = false) { 1.490 + if (!enabled() || !frame->script || frame->left-- != 1) 1.491 + return; 1.492 + if (frame->skipNext) { 1.493 + frame->skipNext = false; 1.494 + } else { 1.495 +#ifdef JS_ION 1.496 + if (!inlinedFunction || jit::js_JitOptions.profileInlineFrames) 1.497 +#endif 1.498 + masm.spsUpdatePCIdx(profiler_, ProfileEntry::NullPCIndex, scratch); 1.499 + } 1.500 + } 1.501 + 1.502 + /* 1.503 + * Signifies exiting a JS frame, popping the SPS entry. Because there can be 1.504 + * multiple return sites of a function, this does not cease instrumentation 1.505 + * emission. 1.506 + */ 1.507 + void pop(Assembler &masm, Register scratch, bool inlinedFunction = false) { 1.508 + if (enabled()) { 1.509 + JS_ASSERT(frame->left == 0); 1.510 + JS_ASSERT(frame->script); 1.511 +#ifdef JS_ION 1.512 + if (!inlinedFunction || jit::js_JitOptions.profileInlineFrames) 1.513 +#endif 1.514 + masm.spsPopFrame(profiler_, scratch); 1.515 + } 1.516 + } 1.517 +}; 1.518 + 1.519 +} /* namespace js */ 1.520 + 1.521 +#endif /* vm_SPSProfiler_h */