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: #ifndef vm_SPSProfiler_h michael@0: #define vm_SPSProfiler_h michael@0: michael@0: #include "mozilla/DebugOnly.h" michael@0: #include "mozilla/GuardObjects.h" michael@0: michael@0: #include michael@0: michael@0: #include "jslock.h" michael@0: #include "jsscript.h" michael@0: michael@0: #include "js/ProfilingStack.h" michael@0: michael@0: /* michael@0: * SPS Profiler integration with the JS Engine michael@0: * https://developer.mozilla.org/en/Performance/Profiling_with_the_Built-in_Profiler michael@0: * michael@0: * The SPS profiler (found in tools/profiler) is an implementation of a profiler michael@0: * which has the ability to walk the C++ stack as well as use instrumentation to michael@0: * gather information. When dealing with JS, however, SPS needs integration michael@0: * with the engine because otherwise it is very difficult to figure out what michael@0: * javascript is executing. michael@0: * michael@0: * The current method of integration with SPS is a form of instrumentation: michael@0: * every time a JS function is entered, a bit of information is pushed onto a michael@0: * stack that SPS owns and maintains. This information is then popped at the end michael@0: * of the JS function. SPS informs the JS engine of this stack at runtime, and michael@0: * it can by turned on/off dynamically. michael@0: * michael@0: * The SPS stack has three parameters: a base pointer, a size, and a maximum michael@0: * size. The stack is the ProfileEntry stack which will have information written michael@0: * to it. The size location is a pointer to an integer which represents the michael@0: * current size of the stack (number of valid frames). This size will be michael@0: * modified when JS functions are called. The maximum specified is the maximum michael@0: * capacity of the ProfileEntry stack. michael@0: * michael@0: * Throughout execution, the size of the stack recorded in memory may exceed the michael@0: * maximum. The JS engine will not write any information past the maximum limit, michael@0: * but it will still maintain the size of the stack. SPS code is aware of this michael@0: * and iterates the stack accordingly. michael@0: * michael@0: * There is some information pushed on the SPS stack for every JS function that michael@0: * is entered. First is a char* pointer of a description of what function was michael@0: * entered. Currently this string is of the form "function (file:line)" if michael@0: * there's a function name, or just "file:line" if there's no function name michael@0: * available. The other bit of information is the relevant C++ (native) stack michael@0: * pointer. This stack pointer is what enables the interleaving of the C++ and michael@0: * the JS stack. Finally, throughout execution of the function, some extra michael@0: * information may be updated on the ProfileEntry structure. michael@0: * michael@0: * = Profile Strings michael@0: * michael@0: * The profile strings' allocations and deallocation must be carefully michael@0: * maintained, and ideally at a very low overhead cost. For this reason, the JS michael@0: * engine maintains a mapping of all known profile strings. These strings are michael@0: * keyed in lookup by a JSScript*, but are serialized with a JSFunction*, michael@0: * JSScript* pair. A JSScript will destroy its corresponding profile string when michael@0: * the script is finalized. michael@0: * michael@0: * For this reason, a char* pointer pushed on the SPS stack is valid only while michael@0: * it is on the SPS stack. SPS uses sampling to read off information from this michael@0: * instrumented stack, and it therefore copies the string byte for byte when a michael@0: * JS function is encountered during sampling. michael@0: * michael@0: * = Native Stack Pointer michael@0: * michael@0: * The actual value pushed as the native pointer is nullptr for most JS michael@0: * functions. The reason for this is that there's actually very little michael@0: * correlation between the JS stack and the C++ stack because many JS functions michael@0: * all run in the same C++ frame, or can even go backwards in C++ when going michael@0: * from the JIT back to the interpreter. michael@0: * michael@0: * To alleviate this problem, all JS functions push nullptr as their "native michael@0: * stack pointer" to indicate that it's a JS function call. The function michael@0: * RunScript(), however, pushes an actual C++ stack pointer onto the SPS stack. michael@0: * This way when interleaving C++ and JS, if SPS sees a nullptr native stack michael@0: * pointer on the SPS stack, it looks backwards for the first non-nullptr michael@0: * pointer and uses that for all subsequent nullptr native stack pointers. michael@0: * michael@0: * = Line Numbers michael@0: * michael@0: * One goal of sampling is to get both a backtrace of the JS stack, but also michael@0: * know where within each function on the stack execution currently is. For michael@0: * this, each ProfileEntry has a 'pc' field to tell where its execution michael@0: * currently is. This field is updated whenever a call is made to another JS michael@0: * function, and for the JIT it is also updated whenever the JIT is left. michael@0: * michael@0: * This field is in a union with a uint32_t 'line' so that C++ can make use of michael@0: * the field as well. It was observed that tracking 'line' via PCToLineNumber in michael@0: * JS was far too expensive, so that is why the pc instead of the translated michael@0: * line number is stored. michael@0: * michael@0: * As an invariant, if the pc is nullptr, then the JIT is currently executing michael@0: * generated code. Otherwise execution is in another JS function or in C++. With michael@0: * this in place, only the top entry of the stack can ever have nullptr as its michael@0: * pc. Additionally with this invariant, it is possible to maintain mappings of michael@0: * JIT code to pc which can be accessed safely because they will only be michael@0: * accessed from a signal handler when the JIT code is executing. michael@0: */ michael@0: michael@0: namespace js { michael@0: michael@0: class ProfileEntry; michael@0: michael@0: typedef HashMap, SystemAllocPolicy> michael@0: ProfileStringMap; michael@0: michael@0: class SPSEntryMarker; michael@0: michael@0: class SPSProfiler michael@0: { michael@0: friend class SPSEntryMarker; michael@0: michael@0: JSRuntime *rt; michael@0: ProfileStringMap strings; michael@0: ProfileEntry *stack_; michael@0: uint32_t *size_; michael@0: uint32_t max_; michael@0: bool slowAssertions; michael@0: uint32_t enabled_; michael@0: PRLock *lock_; michael@0: void (*eventMarker_)(const char *); michael@0: michael@0: const char *allocProfileString(JSScript *script, JSFunction *function); michael@0: void push(const char *string, void *sp, JSScript *script, jsbytecode *pc); michael@0: void pushNoCopy(const char *string, void *sp, michael@0: JSScript *script, jsbytecode *pc) { michael@0: push(string, reinterpret_cast( michael@0: reinterpret_cast(sp) | ProfileEntry::NoCopyBit), michael@0: script, pc); michael@0: } michael@0: void pop(); michael@0: michael@0: public: michael@0: SPSProfiler(JSRuntime *rt); michael@0: ~SPSProfiler(); michael@0: michael@0: bool init(); michael@0: michael@0: uint32_t **addressOfSizePointer() { michael@0: return &size_; michael@0: } michael@0: michael@0: uint32_t *addressOfMaxSize() { michael@0: return &max_; michael@0: } michael@0: michael@0: ProfileEntry **addressOfStack() { michael@0: return &stack_; michael@0: } michael@0: michael@0: uint32_t *sizePointer() { return size_; } michael@0: uint32_t maxSize() { return max_; } michael@0: ProfileEntry *stack() { return stack_; } michael@0: michael@0: /* management of whether instrumentation is on or off */ michael@0: bool enabled() { JS_ASSERT_IF(enabled_, installed()); return enabled_; } michael@0: bool installed() { return stack_ != nullptr && size_ != nullptr; } michael@0: void enable(bool enabled); michael@0: void enableSlowAssertions(bool enabled) { slowAssertions = enabled; } michael@0: bool slowAssertionsEnabled() { return slowAssertions; } michael@0: michael@0: /* michael@0: * Functions which are the actual instrumentation to track run information michael@0: * michael@0: * - enter: a function has started to execute michael@0: * - updatePC: updates the pc information about where a function michael@0: * is currently executing michael@0: * - exit: this function has ceased execution, and no further michael@0: * entries/exits will be made michael@0: */ michael@0: bool enter(JSScript *script, JSFunction *maybeFun); michael@0: void exit(JSScript *script, JSFunction *maybeFun); michael@0: void updatePC(JSScript *script, jsbytecode *pc) { michael@0: if (enabled() && *size_ - 1 < max_) { michael@0: JS_ASSERT(*size_ > 0); michael@0: JS_ASSERT(stack_[*size_ - 1].script() == script); michael@0: stack_[*size_ - 1].setPC(pc); michael@0: } michael@0: } michael@0: michael@0: /* Enter a C++ function. */ michael@0: void enterNative(const char *string, void *sp); michael@0: void exitNative() { pop(); } michael@0: michael@0: jsbytecode *ipToPC(JSScript *script, size_t ip) { return nullptr; } michael@0: michael@0: void setProfilingStack(ProfileEntry *stack, uint32_t *size, uint32_t max); michael@0: void setEventMarker(void (*fn)(const char *)); michael@0: const char *profileString(JSScript *script, JSFunction *maybeFun); michael@0: void onScriptFinalized(JSScript *script); michael@0: michael@0: void markEvent(const char *event); michael@0: michael@0: /* meant to be used for testing, not recommended to call in normal code */ michael@0: size_t stringsCount(); michael@0: void stringsReset(); michael@0: michael@0: uint32_t *addressOfEnabled() { michael@0: return &enabled_; michael@0: } michael@0: }; michael@0: michael@0: /* michael@0: * This class is used to make sure the strings table michael@0: * is only accessed on one thread at a time. michael@0: */ michael@0: class AutoSPSLock michael@0: { michael@0: public: michael@0: #ifdef JS_THREADSAFE michael@0: AutoSPSLock(PRLock *lock) michael@0: { michael@0: MOZ_ASSERT(lock, "Parameter should not be null!"); michael@0: lock_ = lock; michael@0: PR_Lock(lock); michael@0: } michael@0: ~AutoSPSLock() { PR_Unlock(lock_); } michael@0: #else michael@0: AutoSPSLock(PRLock *) {} michael@0: #endif michael@0: michael@0: private: michael@0: PRLock *lock_; michael@0: }; michael@0: michael@0: inline size_t michael@0: SPSProfiler::stringsCount() michael@0: { michael@0: AutoSPSLock lock(lock_); michael@0: return strings.count(); michael@0: } michael@0: michael@0: inline void michael@0: SPSProfiler::stringsReset() michael@0: { michael@0: AutoSPSLock lock(lock_); michael@0: strings.clear(); michael@0: } michael@0: michael@0: /* michael@0: * This class is used in RunScript() to push the marker onto the sampling stack michael@0: * that we're about to enter JS function calls. This is the only time in which a michael@0: * valid stack pointer is pushed to the sampling stack. michael@0: */ michael@0: class SPSEntryMarker michael@0: { michael@0: public: michael@0: SPSEntryMarker(JSRuntime *rt michael@0: MOZ_GUARD_OBJECT_NOTIFIER_PARAM); michael@0: ~SPSEntryMarker(); michael@0: michael@0: private: michael@0: SPSProfiler *profiler; michael@0: mozilla::DebugOnly size_before; michael@0: MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER michael@0: }; michael@0: michael@0: /* michael@0: * SPS is the profiling backend used by the JS engine to enable time profiling. michael@0: * More information can be found in vm/SPSProfiler.{h,cpp}. This class manages michael@0: * the instrumentation portion of the profiling for JIT code. michael@0: * michael@0: * The instrumentation tracks entry into functions, leaving those functions via michael@0: * a function call, reentering the functions from a function call, and exiting michael@0: * the functions from returning. This class also handles inline frames and michael@0: * manages the instrumentation which needs to be attached to them as well. michael@0: * michael@0: * The basic methods which emit instrumentation are at the end of this class, michael@0: * and the management functions are all described in the middle. michael@0: */ michael@0: template michael@0: class SPSInstrumentation michael@0: { michael@0: /* Because of inline frames, this is a nested structure in a vector */ michael@0: struct FrameState { michael@0: JSScript *script; // script for this frame, nullptr if not pushed yet michael@0: jsbytecode *pc; // pc at which this frame was left for entry into a callee michael@0: bool skipNext; // should the next call to reenter be skipped? michael@0: int left; // number of leave() calls made without a matching reenter() michael@0: }; michael@0: michael@0: SPSProfiler *profiler_; // Instrumentation location management michael@0: michael@0: Vector frames; michael@0: FrameState *frame; michael@0: michael@0: static void clearFrame(FrameState *frame) { michael@0: frame->script = nullptr; michael@0: frame->pc = nullptr; michael@0: frame->skipNext = false; michael@0: frame->left = 0; michael@0: } michael@0: michael@0: public: michael@0: /* michael@0: * Creates instrumentation which writes information out the the specified michael@0: * profiler's stack and constituent fields. michael@0: */ michael@0: SPSInstrumentation(SPSProfiler *profiler) michael@0: : profiler_(profiler), frame(nullptr) michael@0: { michael@0: enterInlineFrame(nullptr); michael@0: } michael@0: michael@0: /* Small proxies around SPSProfiler */ michael@0: bool enabled() { return profiler_ && profiler_->enabled(); } michael@0: SPSProfiler *profiler() { JS_ASSERT(enabled()); return profiler_; } michael@0: void disable() { profiler_ = nullptr; } michael@0: michael@0: /* Signals an inline function returned, reverting to the previous state */ michael@0: void leaveInlineFrame() { michael@0: if (!enabled()) michael@0: return; michael@0: JS_ASSERT(frame->left == 0); michael@0: JS_ASSERT(frame->script != nullptr); michael@0: frames.shrinkBy(1); michael@0: JS_ASSERT(frames.length() > 0); michael@0: frame = &frames[frames.length() - 1]; michael@0: } michael@0: michael@0: /* Saves the current state and assumes a fresh one for the inline function */ michael@0: bool enterInlineFrame(jsbytecode *callerPC) { michael@0: if (!enabled()) michael@0: return true; michael@0: JS_ASSERT_IF(frames.empty(), callerPC == nullptr); michael@0: michael@0: JS_ASSERT_IF(frame != nullptr, frame->script != nullptr); michael@0: JS_ASSERT_IF(frame != nullptr, frame->left == 1); michael@0: if (!frames.empty()) { michael@0: JS_ASSERT(frame == &frames[frames.length() - 1]); michael@0: frame->pc = callerPC; michael@0: } michael@0: if (!frames.growBy(1)) michael@0: return false; michael@0: frame = &frames[frames.length() - 1]; michael@0: clearFrame(frame); michael@0: return true; michael@0: } michael@0: michael@0: /* Prepares the instrumenter state for generating OOL code, by michael@0: * setting up the frame state to seem as if there are exactly michael@0: * two pushed frames: a frame for the top-level script, and michael@0: * a frame for the OOL code being generated. Any michael@0: * vm-calls from the OOL code will "leave" the OOL frame and michael@0: * return back to it. michael@0: */ michael@0: bool prepareForOOL() { michael@0: if (!enabled()) michael@0: return true; michael@0: JS_ASSERT(!frames.empty()); michael@0: if (frames.length() >= 2) { michael@0: frames.shrinkBy(frames.length() - 2); michael@0: michael@0: } else { // frames.length() == 1 michael@0: if (!frames.growBy(1)) michael@0: return false; michael@0: } michael@0: frames[0].pc = frames[0].script->code(); michael@0: frame = &frames[1]; michael@0: clearFrame(frame); michael@0: return true; michael@0: } michael@0: void finishOOL() { michael@0: if (!enabled()) michael@0: return; michael@0: JS_ASSERT(!frames.empty()); michael@0: frames.shrinkBy(frames.length() - 1); michael@0: } michael@0: michael@0: /* Number of inline frames currently active (doesn't include original one) */ michael@0: unsigned inliningDepth() { michael@0: return frames.length() - 1; michael@0: } michael@0: michael@0: /* michael@0: * When debugging or with slow assertions, sometimes a C++ method will be michael@0: * invoked to perform the pop operation from the SPS stack. When we leave michael@0: * JIT code, we need to record the current PC, but upon reentering JIT michael@0: * code, no update back to nullptr should happen. This method exists to michael@0: * flag this behavior. The next leave() will emit instrumentation, but the michael@0: * following reenter() will be a no-op. michael@0: */ michael@0: void skipNextReenter() { michael@0: /* If we've left the frame, the reenter will be skipped anyway */ michael@0: if (!enabled() || frame->left != 0) michael@0: return; michael@0: JS_ASSERT(frame->script); michael@0: JS_ASSERT(!frame->skipNext); michael@0: frame->skipNext = true; michael@0: } michael@0: michael@0: /* michael@0: * In some cases, a frame needs to be flagged as having been pushed, but no michael@0: * instrumentation should be emitted. This updates internal state to flag michael@0: * that further instrumentation should actually be emitted. michael@0: */ michael@0: void setPushed(JSScript *script) { michael@0: if (!enabled()) michael@0: return; michael@0: JS_ASSERT(frame->left == 0); michael@0: frame->script = script; michael@0: } michael@0: michael@0: /* michael@0: * Flags entry into a JS function for the first time. Before this is called, michael@0: * no instrumentation is emitted, but after this instrumentation is emitted. michael@0: */ michael@0: bool push(JSScript *script, Assembler &masm, Register scratch, bool inlinedFunction = false) { michael@0: if (!enabled()) michael@0: return true; michael@0: #ifdef JS_ION michael@0: if (!inlinedFunction || jit::js_JitOptions.profileInlineFrames) { michael@0: #endif michael@0: const char *string = profiler_->profileString(script, script->functionNonDelazifying()); michael@0: if (string == nullptr) michael@0: return false; michael@0: masm.spsPushFrame(profiler_, string, script, scratch); michael@0: #ifdef JS_ION michael@0: } michael@0: #endif michael@0: setPushed(script); michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Signifies that C++ performed the push() for this function. C++ always michael@0: * sets the current PC to something non-null, however, so as soon as JIT michael@0: * code is reentered this updates the current pc to nullptr. michael@0: */ michael@0: void pushManual(JSScript *script, Assembler &masm, Register scratch, michael@0: bool inlinedFunction = false) michael@0: { michael@0: if (!enabled()) michael@0: return; michael@0: michael@0: #ifdef JS_ION michael@0: if (!inlinedFunction || jit::js_JitOptions.profileInlineFrames) michael@0: #endif michael@0: masm.spsUpdatePCIdx(profiler_, ProfileEntry::NullPCIndex, scratch); michael@0: michael@0: setPushed(script); michael@0: } michael@0: michael@0: /* michael@0: * Signals that the current function is leaving for a function call. This michael@0: * can happen both on JS function calls and also calls to C++. This michael@0: * internally manages how many leave() calls have been seen, and only the michael@0: * first leave() emits instrumentation. Similarly, only the last michael@0: * corresponding reenter() actually emits instrumentation. michael@0: */ michael@0: void leave(jsbytecode *pc, Assembler &masm, Register scratch, bool inlinedFunction = false) { michael@0: if (enabled() && frame->script && frame->left++ == 0) { michael@0: jsbytecode *updatePC = pc; michael@0: JSScript *script = frame->script; michael@0: #ifdef JS_ION michael@0: if (!inlinedFunction) { michael@0: // We may be leaving an inlined frame for entry into a C++ michael@0: // frame. If profileInlineFrames is turned off, use the top michael@0: // script's pc offset instead of the innermost script's. michael@0: if (!jit::js_JitOptions.profileInlineFrames && inliningDepth() > 0) { michael@0: JS_ASSERT(frames[0].pc); michael@0: updatePC = frames[0].pc; michael@0: script = frames[0].script; michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: #ifdef JS_ION michael@0: if (!inlinedFunction || jit::js_JitOptions.profileInlineFrames) michael@0: #endif michael@0: masm.spsUpdatePCIdx(profiler_, script->pcToOffset(updatePC), scratch); michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Flags that the leaving of the current function has returned. This tracks michael@0: * state with leave() to only emit instrumentation at proper times. michael@0: */ michael@0: void reenter(Assembler &masm, Register scratch, bool inlinedFunction = false) { michael@0: if (!enabled() || !frame->script || frame->left-- != 1) michael@0: return; michael@0: if (frame->skipNext) { michael@0: frame->skipNext = false; michael@0: } else { michael@0: #ifdef JS_ION michael@0: if (!inlinedFunction || jit::js_JitOptions.profileInlineFrames) michael@0: #endif michael@0: masm.spsUpdatePCIdx(profiler_, ProfileEntry::NullPCIndex, scratch); michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Signifies exiting a JS frame, popping the SPS entry. Because there can be michael@0: * multiple return sites of a function, this does not cease instrumentation michael@0: * emission. michael@0: */ michael@0: void pop(Assembler &masm, Register scratch, bool inlinedFunction = false) { michael@0: if (enabled()) { michael@0: JS_ASSERT(frame->left == 0); michael@0: JS_ASSERT(frame->script); michael@0: #ifdef JS_ION michael@0: if (!inlinedFunction || jit::js_JitOptions.profileInlineFrames) michael@0: #endif michael@0: masm.spsPopFrame(profiler_, scratch); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: } /* namespace js */ michael@0: michael@0: #endif /* vm_SPSProfiler_h */